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,35 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatMessagePaymentAlertController",
module_name = "ChatMessagePaymentAlertController",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/Postbox:Postbox",
"//submodules/TelegramCore:TelegramCore",
"//submodules/AccountContext:AccountContext",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TextFormat",
"//submodules/AvatarNode",
"//submodules/CheckNode",
"//submodules/TelegramUIPreferences",
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
"//submodules/TelegramUI/Components/AlertComponent",
"//submodules/TelegramUI/Components/AlertComponent/AlertCheckComponent",
"//submodules/Markdown",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,354 @@
import Foundation
import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import ComponentFlow
import ComponentDisplayAdapters
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import AppBundle
import AvatarNode
import CheckNode
import Markdown
import TextFormat
import StarsBalanceOverlayComponent
import AlertComponent
import AlertCheckComponent
public class ChatMessagePaymentAlertController: AlertScreen {
private let context: AccountContext?
private let presentationData: PresentationData
private weak var parentNavigationController: NavigationController?
private let chatPeerId: EnginePeer.Id
private let showBalance: Bool
private let animateBalanceOverlay: Bool
private var didUpdateCurrency = false
private var initialCurrency: CurrencyAmount.Currency?
public var currency: CurrencyAmount.Currency?
private var currencyDisposable: Disposable?
private let balance = ComponentView<Empty>()
private var didAppear = false
public init(
context: AccountContext?,
presentationData: PresentationData,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
configuration: Configuration = AlertScreen.Configuration(),
contentSignal: Signal<[AnyComponentWithIdentity<AlertComponentEnvironment>], NoError>,
actionsSignal: Signal<[AlertScreen.Action], NoError>,
navigationController: NavigationController?,
chatPeerId: EnginePeer.Id,
showBalance: Bool = true,
currencySignal: Signal<CurrencyAmount.Currency, NoError> = .single(.stars),
animateBalanceOverlay: Bool = true
) {
self.context = context
self.presentationData = presentationData
self.parentNavigationController = navigationController
self.chatPeerId = chatPeerId
self.showBalance = showBalance
self.animateBalanceOverlay = animateBalanceOverlay
var effectiveUpdatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)
if let updatedPresentationData {
effectiveUpdatedPresentationData = updatedPresentationData
} else {
effectiveUpdatedPresentationData = (initial: presentationData, signal: .single(presentationData))
}
super.init(
configuration: configuration,
contentSignal: contentSignal,
actionsSignal: actionsSignal,
updatedPresentationData: effectiveUpdatedPresentationData
)
self.currencyDisposable = (currencySignal
|> distinctUntilChanged
|> deliverOnMainQueue).start(next: { [weak self] currency in
guard let self else {
return
}
if self.currency == nil {
self.initialCurrency = currency
}
self.currency = currency
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.25, curve: .easeInOut))
}
})
}
public convenience init(
context: AccountContext?,
presentationData: PresentationData,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
configuration: Configuration = AlertScreen.Configuration(),
content: [AnyComponentWithIdentity<AlertComponentEnvironment>],
actions: [AlertScreen.Action],
navigationController: NavigationController?,
chatPeerId: EnginePeer.Id,
showBalance: Bool = true,
currency: CurrencyAmount.Currency = .stars,
animateBalanceOverlay: Bool = true
) {
self.init(
context: context,
presentationData: presentationData,
updatedPresentationData: updatedPresentationData,
configuration: configuration,
contentSignal: .single(content),
actionsSignal: .single(actions),
navigationController: navigationController,
chatPeerId: chatPeerId,
showBalance: showBalance,
currencySignal: .single(currency),
animateBalanceOverlay: animateBalanceOverlay
)
}
required public init(coder aDecoder: NSCoder) {
preconditionFailure()
}
override public func dismiss(completion: (() -> Void)? = nil) {
super.dismiss(completion: completion)
self.animateOut()
}
private func animateOut() {
if !self.animateBalanceOverlay {
if case .ton = self.currency, let initialCurrency, initialCurrency != self.currency {
self.currency = .stars
if let layout = self.validLayout {
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.25, curve: .easeInOut))
}
}
} else {
if let view = self.balance.view {
view.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false)
view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
}
}
}
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
if !self.didAppear {
self.didAppear = true
if !layout.metrics.isTablet && layout.size.width > layout.size.height {
Queue.mainQueue().after(0.1) {
self.view.window?.endEditing(true)
}
}
}
if let context = self.context, let _ = self.parentNavigationController, self.showBalance, let currency = self.currency {
let insets = layout.insets(options: .statusBar)
var balanceTransition = ComponentTransition(transition)
if self.balance.view == nil {
balanceTransition = .immediate
}
let balanceSize = self.balance.update(
transition: balanceTransition,
component: AnyComponent(
StarsBalanceOverlayComponent(
context: context,
peerId: self.chatPeerId.namespace == Namespaces.Peer.CloudChannel ? self.chatPeerId : context.account.peerId,
theme: self.presentationData.theme,
currency: currency,
action: { [weak self] in
guard let self, let starsContext = context.starsContext, let navigationController = self.parentNavigationController, let currency = self.currency else {
return
}
switch currency {
case .stars:
let _ = (context.engine.payments.starsTopUpOptions()
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { options in
let controller = context.sharedContext.makeStarsPurchaseScreen(
context: context,
starsContext: starsContext,
options: options,
purpose: .generic,
targetPeerId: nil,
customTheme: nil,
completion: { _ in }
)
navigationController.pushViewController(controller)
})
case .ton:
var fragmentUrl = "https://fragment.com/ads/topup"
if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String {
fragmentUrl = value
}
context.sharedContext.applicationBindings.openUrl(fragmentUrl)
}
self.dismiss(completion: nil)
}
)
),
environment: {},
containerSize: layout.size
)
if let view = self.balance.view {
if view.superview == nil {
self.view.addSubview(view)
if self.animateBalanceOverlay {
view.layer.animatePosition(from: CGPoint(x: 0.0, y: -64.0), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
view.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
}
}
balanceTransition.setFrame(view: view, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - balanceSize.width) / 2.0), y: insets.top + 5.0), size: balanceSize))
}
}
}
}
public func chatMessagePaymentAlertController(
context: AccountContext?,
presentationData: PresentationData,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
peers: [EngineRenderedPeer],
count: Int32,
amount: StarsAmount,
totalAmount: StarsAmount?,
hasCheck: Bool = true,
navigationController: NavigationController?,
completion: @escaping (Bool) -> Void
) -> ViewController {
let strings = presentationData.strings
let messagesString = strings.Chat_PaidMessage_Confirm_Text_Messages(count)
let text: String
if peers.count == 1, let peer = peers.first {
let amountString = strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value))
let totalString = strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value * Int64(count)))
if case let .channel(channel) = peer.chatOrMonoforumMainPeer, case .broadcast = channel.info {
text = strings.Chat_PaidMessage_Confirm_SingleComment_Text(EnginePeer(channel).compactDisplayTitle, amountString, totalString, messagesString).string
} else {
text = strings.Chat_PaidMessage_Confirm_Single_Text(peer.chatOrMonoforumMainPeer?.compactDisplayTitle ?? " ", amountString, totalString, messagesString).string
}
} else {
let amount = totalAmount ?? amount
let usersString = strings.Chat_PaidMessage_Confirm_Text_Users(Int32(peers.count))
let totalString = strings.Chat_PaidMessage_Confirm_Text_Stars(Int32(clamping: amount.value * Int64(count)))
text = strings.Chat_PaidMessage_Confirm_Multiple_Text(usersString, totalString, messagesString).string
}
let checkState = AlertCheckComponent.ExternalState()
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(title: strings.Chat_PaidMessage_Confirm_Title)
)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .plain(text))
)
))
if hasCheck {
content.append(AnyComponentWithIdentity(
id: "check",
component: AnyComponent(
AlertCheckComponent(title: strings.Chat_PaidMessage_Confirm_DontAskAgain, initialValue: false, externalState: checkState)
)
))
}
let alertController = ChatMessagePaymentAlertController(
context: context,
presentationData: presentationData,
updatedPresentationData: updatedPresentationData,
configuration: AlertScreen.Configuration(actionAlignment: .vertical, allowInputInset: true),
content: content,
actions: [
.init(title: strings.Chat_PaidMessage_Confirm_PayForMessage(count), type: .default, action: {
completion(checkState.value)
}),
.init(title: strings.Common_Cancel)
],
navigationController: navigationController,
chatPeerId: context?.account.peerId ?? peers[0].peerId
)
return alertController
}
public func chatMessageRemovePaymentAlertController(
context: AccountContext? = nil,
presentationData: PresentationData,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
peer: EnginePeer,
chatPeer: EnginePeer,
amount: StarsAmount?,
navigationController: NavigationController?,
completion: @escaping (Bool) -> Void
) -> ViewController {
let strings = presentationData.strings
let text: String
if case .user = chatPeer {
text = strings.Chat_PaidMessage_RemoveFee_Text(peer.compactDisplayTitle).string
} else if let context, chatPeer.id != context.account.peerId {
text = strings.Channel_RemoveFeeAlert_Text(peer.compactDisplayTitle).string
} else {
text = strings.Chat_PaidMessage_RemoveFee_Text(peer.compactDisplayTitle).string
}
let checkState = AlertCheckComponent.ExternalState()
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(title: strings.Chat_PaidMessage_RemoveFee_Title)
)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .plain(text))
)
))
if let amount {
content.append(AnyComponentWithIdentity(
id: "check",
component: AnyComponent(
AlertCheckComponent(title: strings.Chat_PaidMessage_RemoveFee_Refund(strings.Chat_PaidMessage_RemoveFee_Refund_Stars(Int32(clamping: amount.value))).string, initialValue: false, externalState: checkState)
)
))
}
let alertController = ChatMessagePaymentAlertController(
context: context,
presentationData: presentationData,
updatedPresentationData: updatedPresentationData,
content: content,
actions: [
.init(title: strings.Common_Cancel),
.init(title: strings.Chat_PaidMessage_RemoveFee_Yes, type: .default, action: {
completion(checkState.value)
})
],
navigationController: navigationController,
chatPeerId: chatPeer.id
)
return alertController
}