GLEGram 12.5 — Initial public release

Based on Swiftgram 12.5 (Telegram iOS 12.5).
All GLEGram features ported and organized in GLEGram/ folder.

Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass,
Font Replacement, Fake Profile, Chat Export, Plugin System, and more.

See CHANGELOG_12.5.md for full details.
This commit is contained in:
Leeksov
2026-04-06 09:48:12 +03:00
commit 4647310322
39685 changed files with 11052678 additions and 0 deletions
@@ -0,0 +1,436 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import ChatControllerInteraction
import AccountContext
import TelegramCore
import Postbox
import WallpaperBackgroundNode
import ChatMessageItemCommon
import ContextUI
import HierarchyTrackingLayer
public class ChatMessageShareButton: ASDisplayNode {
private let referenceNode: ContextReferenceContentNode
private let containerNode: ContextControllerSourceNode
private var backgroundContent: WallpaperBubbleBackgroundNode?
private var backgroundBlurView: PortalView?
private let topButton: HighlightTrackingButtonNode
private let topIconNode: ASImageNode
private var topIconOffset = CGPoint()
private var bottomButton: HighlightTrackingButtonNode?
private var bottomIconNode: ASImageNode?
private var starsView: StarsView?
private var separatorNode: ASDisplayNode?
private var theme: PresentationTheme?
private var isReplies: Bool = false
private var hasMore: Bool = false
private var isExpand: Bool = false
private var textNode: ImmediateTextNode?
private var absolutePosition: (CGRect, CGSize)?
public var pressed: (() -> Void)?
public var morePressed: (() -> Void)?
public var longPressAction: ((ASDisplayNode, ContextGesture) -> Void)?
override public init() {
self.referenceNode = ContextReferenceContentNode()
self.containerNode = ContextControllerSourceNode()
self.containerNode.animateScale = false
self.topButton = HighlightTrackingButtonNode()
self.topIconNode = ASImageNode()
self.topIconNode.displaysAsynchronously = false
self.topIconNode.isUserInteractionEnabled = false
super.init()
self.allowsGroupOpacity = true
self.containerNode.addSubnode(self.referenceNode)
self.topButton.addSubnode(self.containerNode)
self.addSubnode(self.topIconNode)
self.addSubnode(self.topButton)
self.topButton.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.topButton.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.topIconNode.layer.removeAnimation(forKey: "opacity")
self.topIconNode.alpha = 0.4
self.textNode?.layer.removeAnimation(forKey: "opacity")
self.textNode?.alpha = 0.4
} else {
self.topIconNode.alpha = 1.0
self.topIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
self.textNode?.alpha = 1.0
self.textNode?.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
self.containerNode.shouldBegin = { [weak self] location in
guard let strongSelf = self, let _ = strongSelf.longPressAction else {
return false
}
return true
}
self.containerNode.activated = { [weak self] gesture, _ in
guard let strongSelf = self else {
return
}
strongSelf.longPressAction?(strongSelf.containerNode, gesture)
}
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func buttonPressed() {
self.pressed?()
}
@objc private func moreButtonPressed() {
self.morePressed?()
}
public func update(hasTranslation: Bool? = nil, presentationData: ChatPresentationData, controllerInteraction: ChatControllerInteraction, chatLocation: ChatLocation, subject: ChatControllerSubject?, message: Message, account: Account, disableComments: Bool = false, isSummarize: Bool = false) -> CGSize {
var isReplies = false
var isNavigate = false
var replyCount = 0
if let channel = message.peers[message.id.peerId] as? TelegramChannel {
if case .broadcast = channel.info {
for attribute in message.attributes {
if let attribute = attribute as? ReplyThreadMessageAttribute {
replyCount = Int(attribute.count)
isReplies = true
break
}
}
} else if channel.isMonoForum, case .peer = chatLocation {
isNavigate = true
}
}
if case let .replyThread(replyThreadMessage) = chatLocation, replyThreadMessage.effectiveTopId == message.id {
replyCount = 0
isReplies = false
}
if disableComments {
replyCount = 0
isReplies = false
}
var hasMore = false
if let adAttribute = message.adAttribute, adAttribute.canReport {
hasMore = true
}
var isExpand = false
if controllerInteraction.summarizedMessageIds.contains(message.id) {
isExpand = true
}
if self.theme !== presentationData.theme.theme || self.isReplies != isReplies || self.hasMore != hasMore || self.isExpand != isExpand {
self.theme = presentationData.theme.theme
self.isReplies = isReplies
self.hasMore = hasMore
self.isExpand = isExpand
var updatedIconImage: UIImage?
var updatedBottomIconImage: UIImage?
var updatedIconOffset = CGPoint()
if isSummarize {
if isExpand {
updatedIconImage = PresentationResourcesChat.chatFreeExpandButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
} else {
updatedIconImage = PresentationResourcesChat.chatFreeCollapseButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
}
} else if let _ = message.adAttribute {
updatedIconImage = PresentationResourcesChat.chatFreeCloseButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: UIScreenPixel)
if hasMore {
updatedBottomIconImage = PresentationResourcesChat.chatFreeMoreButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
}
} else if case let .customChatContents(contents) = subject, case .hashTagSearch = contents.kind {
updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0)
} else if let hasTranslation = hasTranslation /* MARK: Swiftgram */ {
updatedIconImage = PresentationResourcesChat.chatTranslateShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, undoTranslate: hasTranslation)
} else if isNavigate {
updatedIconImage = PresentationResourcesChat.chatFreeNavigateToThreadButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: -3.0)
} else if case .pinnedMessages = subject {
updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0)
} else if isReplies {
updatedIconImage = PresentationResourcesChat.chatFreeCommentButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
} else if message.id.peerId.isRepliesOrSavedMessages(accountPeerId: account.peerId) {
updatedIconImage = PresentationResourcesChat.chatFreeNavigateButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
updatedIconOffset = CGPoint(x: UIScreenPixel, y: 1.0)
} else {
updatedIconImage = PresentationResourcesChat.chatFreeShareButtonIcon(presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper)
}
if isSummarize {
if self.topIconNode.image != nil, let snapshotView = self.topIconNode.view.snapshotContentTree() {
self.view.addSubview(snapshotView)
snapshotView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false, completion: { _ in
snapshotView.removeFromSuperview()
})
self.topIconNode.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
}
}
self.topIconNode.image = updatedIconImage
self.topIconOffset = updatedIconOffset
if let updatedBottomIconImage {
let bottomButton: HighlightTrackingButtonNode
let bottomIconNode: ASImageNode
let separatorNode: ASDisplayNode
if let currentButton = self.bottomButton, let currentIcon = self.bottomIconNode, let currentSeparator = self.separatorNode {
bottomButton = currentButton
bottomIconNode = currentIcon
separatorNode = currentSeparator
} else {
bottomButton = HighlightTrackingButtonNode()
bottomButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
self.bottomButton = bottomButton
bottomIconNode = ASImageNode()
bottomIconNode.displaysAsynchronously = false
self.bottomIconNode = bottomIconNode
bottomButton.highligthedChanged = { [weak self] highlighted in
guard let self, let bottomIconNode = self.bottomIconNode else {
return
}
if highlighted {
bottomIconNode.layer.removeAnimation(forKey: "opacity")
bottomIconNode.alpha = 0.4
} else {
bottomIconNode.alpha = 1.0
bottomIconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
separatorNode = ASDisplayNode()
self.separatorNode = separatorNode
self.addSubnode(separatorNode)
self.addSubnode(bottomIconNode)
self.addSubnode(bottomButton)
}
separatorNode.backgroundColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper).withAlphaComponent(0.15)
bottomIconNode.image = updatedBottomIconImage
} else {
self.bottomButton?.removeFromSupernode()
self.bottomButton = nil
self.bottomIconNode?.removeFromSupernode()
self.bottomIconNode = nil
self.separatorNode?.removeFromSupernode()
self.separatorNode = nil
}
}
var size = CGSize(width: 30.0, height: 30.0)
if hasMore {
size.height += 30.0
}
var offsetIcon = false
if isReplies, replyCount > 0 {
offsetIcon = true
let textNode: ImmediateTextNode
if let current = self.textNode {
textNode = current
} else {
textNode = ImmediateTextNode()
self.textNode = textNode
self.addSubnode(textNode)
}
let textColor = bubbleVariableColor(variableColor: presentationData.theme.theme.chat.message.shareButtonForegroundColor, wallpaper: presentationData.theme.wallpaper)
let countString: String
if replyCount >= 1000 * 1000 {
countString = "\(replyCount / 1000_000)M"
} else if replyCount >= 1000 {
countString = "\(replyCount / 1000)K"
} else {
countString = "\(replyCount)"
}
textNode.attributedText = NSAttributedString(string: countString, font: Font.regular(11.0), textColor: textColor)
let textSize = textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
size.height += textSize.height - 1.0
textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: size.height - textSize.height - 4.0), size: textSize)
} else if let textNode = self.textNode {
self.textNode = nil
textNode.removeFromSupernode()
}
if self.backgroundBlurView == nil {
if let backgroundBlurView = controllerInteraction.presentationContext.backgroundNode?.makeFreeBackground() {
self.backgroundBlurView = backgroundBlurView
self.view.insertSubview(backgroundBlurView.view, at: 0)
backgroundBlurView.view.clipsToBounds = true
}
}
if let backgroundBlurView = self.backgroundBlurView {
backgroundBlurView.view.frame = CGRect(origin: CGPoint(), size: size)
backgroundBlurView.view.layer.cornerRadius = min(size.width, size.height) / 2.0
}
if let image = self.topIconNode.image {
self.topIconNode.frame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0) + self.topIconOffset.x, y: floor((size.width - image.size.width) / 2.0) - (offsetIcon ? 1.0 : 0.0) + self.topIconOffset.y), size: image.size)
}
self.topButton.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.width))
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width))
self.referenceNode.frame = self.containerNode.bounds
if let bottomIconNode = self.bottomIconNode, let bottomButton = self.bottomButton, let bottomImage = bottomIconNode.image {
bottomIconNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - bottomImage.size.width) / 2.0), y: size.height - size.width + floorToScreenPixels((size.width - bottomImage.size.height) / 2.0)), size: bottomImage.size)
bottomButton.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - size.width), size: CGSize(width: size.width, height: size.width))
}
self.separatorNode?.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height / 2.0), size: CGSize(width: size.width, height: 1.0 - UIScreenPixel))
if controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
if self.backgroundContent == nil, let backgroundContent = controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
backgroundContent.clipsToBounds = true
self.backgroundContent = backgroundContent
self.insertSubnode(backgroundContent, at: 0)
}
} else {
self.backgroundContent?.removeFromSupernode()
self.backgroundContent = nil
}
if let backgroundContent = self.backgroundContent {
//self.backgroundNode.isHidden = true
self.backgroundBlurView?.view.isHidden = true
backgroundContent.cornerRadius = min(size.width, size.height) / 2.0
backgroundContent.frame = CGRect(origin: CGPoint(), size: size)
if let (rect, containerSize) = self.absolutePosition {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
} else {
//self.backgroundNode.isHidden = false
self.backgroundBlurView?.view.isHidden = false
}
if isSummarize {
let starsView: StarsView
if let current = self.starsView {
starsView = current
} else {
starsView = StarsView()
self.starsView = starsView
self.view.insertSubview(starsView, belowSubview: self.topIconNode.view)
}
starsView.frame = CGRect(origin: .zero, size: size)
starsView.update(size: size, color: .white)
} else if let starsView = self.starsView {
self.starsView = nil
starsView.removeFromSuperview()
}
return size
}
public func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
self.absolutePosition = (rect, containerSize)
if let backgroundContent = self.backgroundContent {
var backgroundFrame = backgroundContent.frame
backgroundFrame.origin.x += rect.minX
backgroundFrame.origin.y += rect.minY
backgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
}
}
}
private final class StarsView: UIView {
private let hierarchyTrackingLayer: HierarchyTrackingLayer
private let topStar = SimpleLayer()
private let bottomStar = SimpleLayer()
override init(frame: CGRect) {
self.hierarchyTrackingLayer = HierarchyTrackingLayer()
super.init(frame: frame)
self.clipsToBounds = true
self.layer.addSublayer(self.hierarchyTrackingLayer)
self.hierarchyTrackingLayer.didEnterHierarchy = { [weak self] in
guard let self else {
return
}
self.updateAnimations()
}
self.layer.addSublayer(self.topStar)
self.layer.addSublayer(self.bottomStar)
let image = UIImage(bundleImageName: "Settings/Storage/ParticleStar")
self.topStar.contents = image?.cgImage
self.bottomStar.contents = image?.cgImage
self.topStar.bounds = CGRect(origin: .zero, size: CGSize(width: 10.0, height: 10.0))
self.bottomStar.bounds = CGRect(origin: .zero, size: CGSize(width: 10.0, height: 10.0))
self.topStar.opacity = 0.5
self.bottomStar.opacity = 0.5
}
required init(coder: NSCoder) {
preconditionFailure()
}
func updateAnimations() {
let topAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
topAnimation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.55 as NSNumber]
topAnimation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 1.0 as NSNumber]
topAnimation.duration = 0.9
topAnimation.autoreverses = true
topAnimation.repeatCount = Float.infinity
topAnimation.beginTime = CACurrentMediaTime()
self.topStar.add(topAnimation, forKey: "blink")
let bottomAnimation = CAKeyframeAnimation(keyPath: "transform.scale")
bottomAnimation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.55 as NSNumber]
bottomAnimation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 1.0 as NSNumber]
bottomAnimation.duration = 0.9
bottomAnimation.autoreverses = true
bottomAnimation.repeatCount = Float.infinity
bottomAnimation.beginTime = CACurrentMediaTime() + 0.9
self.bottomStar.add(bottomAnimation, forKey: "blink")
}
func update(size: CGSize, color: UIColor) {
self.topStar.layerTintColor = color.cgColor
self.bottomStar.layerTintColor = color.cgColor
self.topStar.position = CGPoint(x: 9.0, y: 9.0)
self.bottomStar.position = CGPoint(x: size.width - 9.0, y: size.height - 9.0)
}
}