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
+299
View File
@@ -0,0 +1,299 @@
// MARK: Swiftgram Plugin runner (PythonKit-based execution of .plugin files)
//
// Sets SGPluginHooks.messageHookRunner so that outgoing messages are passed to Python plugins.
// Requires PythonKit (https://github.com/pvieito/PythonKit). Add via SPM or embed for macOS/simulator.
// On iOS device, embed a Python framework (e.g. BeeWare) for full support.
import Foundation
import SGSimpleSettings
#if canImport(PythonKit)
import PythonKit
#endif
private let basePluginSource = """
from enum import Enum
from typing import Any, Optional
class HookStrategy(str, Enum):
PASS = "PASS"
MODIFY = "MODIFY"
CANCEL = "CANCEL"
class HookResult:
def __init__(self, strategy=None, params=None):
self.strategy = strategy if strategy is not None else HookStrategy.PASS
self.params = params
class BasePlugin:
def __init__(self):
self._hooks = set()
def on_plugin_load(self):
pass
def add_on_send_message_hook(self):
self._hooks.add("on_send_message_hook")
def add_hook(self, name):
self._hooks.add(name)
def on_update_hook(self, update_name, account, update):
pass
def get_setting(self, key, default=None):
try:
return _get_setting(key, default)
except NameError:
return default
def set_setting(self, key, value):
try:
_set_setting(key, value)
except NameError:
pass
def _has_hook(self, name):
return name in self._hooks
"""
/// Call once at app startup to install the Python-based message hook runner (when PythonKit is available).
public enum PluginRunner {
private static var incomingMessageObserver: NSObjectProtocol?
public static func install() {
#if canImport(PythonKit)
SGPluginHooks.messageHookRunner = { accountPeerId, peerId, text, replyToMessageId, replyMessageInfo in
runPluginsSendMessageHook(accountPeerId: accountPeerId, peerId: peerId, text: text, replyToMessageId: replyToMessageId, replyMessageInfo: replyMessageInfo)
}
SGPluginHooks.incomingMessageHookRunner = { accountId, peerId, messageId, text, outgoing in
runPluginsIncomingMessageHook(accountId: accountId, peerId: peerId, messageId: messageId, text: text, outgoing: outgoing)
}
#else
SGPluginHooks.messageHookRunner = nil
SGPluginHooks.incomingMessageHookRunner = nil
#endif
SGPluginHooks.userDisplayRunner = applyUserDisplayFromPlugins
PluginRunner.incomingMessageObserver = NotificationCenter.default.addObserver(forName: SGPluginIncomingMessageNotificationName, object: nil, queue: .main) { note in
guard let u = note.userInfo,
let accountId = u["accountId"] as? Int64,
let peerId = u["peerId"] as? Int64,
let messageId = u["messageId"] as? Int64,
let outgoing = u["outgoing"] as? Bool else { return }
let text = u["text"] as? String
SGPluginHooks.incomingMessageHookRunner?(accountId, peerId, messageId, text, outgoing)
}
}
}
// MARK: - User display plugins (__user_display__ = True) generic runner, no app code changes per plugin
private func applyUserDisplayFromPlugins(accountId: Int64, user: PluginDisplayUser) -> PluginDisplayUser? {
guard SGSimpleSettings.shared.pluginSystemEnabled,
let data = SGSimpleSettings.shared.installedPluginsJson.data(using: .utf8),
let plugins = try? JSONDecoder().decode([PluginInfo].self, from: data) else {
return nil
}
let host = PluginHost.shared
for plugin in plugins where plugin.enabled && plugin.metadata.hasUserDisplay {
let pluginId = plugin.metadata.id
guard host.getPluginSettingBool(pluginId: pluginId, key: "enabled", default: false) else { continue }
let targetIdStr = host.getPluginSetting(pluginId: pluginId, key: "target_user_id")?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let targetUserId: Int64
if targetIdStr.isEmpty {
targetUserId = accountId
} else if let parsed = Int64(targetIdStr) {
targetUserId = parsed
} else {
continue
}
if user.id != targetUserId { continue }
func s(_ key: String) -> String? {
host.getPluginSetting(pluginId: pluginId, key: key)?.trimmingCharacters(in: .whitespacesAndNewlines)
}
let firstName = s("fake_first_name").flatMap { $0.isEmpty ? nil : $0 } ?? user.firstName
let lastName = s("fake_last_name").flatMap { $0.isEmpty ? nil : $0 } ?? user.lastName
let username = s("fake_username").flatMap { $0.isEmpty ? nil : $0 } ?? user.username
let phone = s("fake_phone").flatMap { $0.isEmpty ? nil : $0 } ?? user.phone
let id: Int64 = s("fake_id").flatMap { $0.isEmpty ? nil : Int64($0) } ?? user.id
let isPremium = host.getPluginSettingBool(pluginId: pluginId, key: "fake_premium", default: user.isPremium)
let isVerified = host.getPluginSettingBool(pluginId: pluginId, key: "fake_verified", default: user.isVerified)
let isScam = host.getPluginSettingBool(pluginId: pluginId, key: "fake_scam", default: user.isScam)
let isFake = host.getPluginSettingBool(pluginId: pluginId, key: "fake_fake", default: user.isFake)
let isSupport = host.getPluginSettingBool(pluginId: pluginId, key: "fake_support", default: user.isSupport)
let isBot = host.getPluginSettingBool(pluginId: pluginId, key: "fake_bot", default: user.isBot)
return PluginDisplayUser(
firstName: firstName,
lastName: lastName,
username: username,
phone: phone,
id: id,
isPremium: isPremium,
isVerified: isVerified,
isScam: isScam,
isFake: isFake,
isSupport: isSupport,
isBot: isBot
)
}
return nil
}
#if canImport(PythonKit)
private func runPluginsSendMessageHook(accountPeerId: Int64, peerId: Int64, text: String, replyToMessageId: Int64?, replyMessageInfo: ReplyMessageInfo?) -> SGPluginHookResult? {
guard let data = SGSimpleSettings.shared.installedPluginsJson.data(using: .utf8),
let plugins = try? JSONDecoder().decode([PluginInfo].self, from: data) else {
return nil
}
let enabled = plugins.filter { $0.enabled }
guard !enabled.isEmpty else { return nil }
let replyId = Int(replyToMessageId ?? 0)
let accountId = Int(accountPeerId)
let peerIdInt = Int(peerId)
for pluginInfo in enabled {
let pluginId = pluginInfo.metadata.id
guard let content = try? String(contentsOfFile: pluginInfo.path, encoding: .utf8) else { continue }
let builtins = Python.import("builtins")
let globals = Python.dict()
do {
try builtins.exec.thunk.call(PythonObject(basePluginSource), globals, globals)
try builtins.exec.thunk.call(PythonObject(content), globals, globals)
} catch {
continue
}
guard let bp = globals["BasePlugin"], bp.isNone == false else { continue }
let findClassCode = """
_plugin_cls = None
for _n, _o in list(globals().items()):
if _n != 'BasePlugin' and isinstance(_o, type) and issubclass(_o, BasePlugin):
_plugin_cls = _o
break
"""
try? builtins.exec.thunk.call(PythonObject(findClassCode), globals, globals)
guard let cls = globals["_plugin_cls"], cls.isNone == false else { continue }
let instance: PythonObject
do {
instance = try cls.call()
} catch {
continue
}
_ = try? instance.on_plugin_load.call()
let hasHook = instance._has_hook.call("on_send_message_hook")
guard hasHook.bool == true else { continue }
// Build params object in Python (message, peer, replyToMsg; reply message document info for FileViewer-style plugins)
globals["_msg_text"] = PythonObject(text)
globals["_msg_peer"] = PythonObject(peerIdInt)
globals["_msg_reply"] = PythonObject(replyId)
globals["_msg_reply_id"] = PythonObject(Int(replyMessageInfo?.messageId ?? 0))
globals["_msg_reply_is_doc"] = PythonObject(replyMessageInfo?.isDocument ?? false)
globals["_msg_reply_file_path"] = replyMessageInfo?.filePath.map { PythonObject($0) } ?? Python.None
globals["_msg_reply_file_name"] = replyMessageInfo?.fileName.map { PythonObject($0) } ?? Python.None
globals["_msg_reply_mime"] = replyMessageInfo?.mimeType.map { PythonObject($0) } ?? Python.None
let paramsCode = """
class _Params:
pass
class _ReplyMsg:
pass
_params_obj = _Params()
_params_obj.message = _msg_text
_params_obj.peer = _msg_peer
_params_obj.replyToMsgId = _msg_reply
_params_obj.replyToMsg = _ReplyMsg()
_params_obj.replyToMsg.id = _msg_reply_id
_params_obj.replyToMsg.messageOwner = _ReplyMsg()
_params_obj.replyToMsg.messageOwner.id = _msg_reply_id
_params_obj.replyToMsg.isDocument = _msg_reply_is_doc
_params_obj.replyToMsg.filePath = _msg_reply_file_path
_params_obj.replyToMsg.fileName = _msg_reply_file_name
_params_obj.replyToMsg.mimeType = _msg_reply_mime
"""
try? builtins.exec.thunk.call(PythonObject(paramsCode), globals, globals)
guard let paramsObj = globals["_params_obj"], paramsObj.isNone == false else { continue }
let result: PythonObject
do {
result = try instance.on_send_message_hook.call(accountId, paramsObj)
} catch {
continue
}
guard let strategyObj = result.strategy, strategyObj.isNone == false else { continue }
let strategyStr = String(strategyObj) ?? "PASS"
if strategyStr == "CANCEL" {
return SGPluginHookResult(strategy: .cancel, message: nil)
}
if strategyStr == "MODIFY" {
var newMessage = text
if let p = result.params, p.isNone == false, let msg = p.message, msg.isNone == false {
newMessage = String(msg) ?? text
}
return SGPluginHookResult(strategy: .modify, message: newMessage)
}
}
return nil
}
private func runPluginsIncomingMessageHook(accountId: Int64, peerId: Int64, messageId: Int64, text: String?, outgoing: Bool) {
guard SGSimpleSettings.shared.pluginSystemEnabled,
let data = SGSimpleSettings.shared.installedPluginsJson.data(using: .utf8),
let plugins = try? JSONDecoder().decode([PluginInfo].self, from: data) else { return }
let enabled = plugins.filter { $0.enabled }
guard !enabled.isEmpty else { return }
let accountIdInt = Int(accountId)
let peerIdInt = Int(peerId)
let messageIdInt = Int(messageId)
let textPy = text.map { PythonObject($0) } ?? Python.None
let outgoingPy = PythonObject(outgoing)
for pluginInfo in enabled {
guard let content = try? String(contentsOfFile: pluginInfo.path, encoding: .utf8) else { continue }
let builtins = Python.import("builtins")
let globals = Python.dict()
do {
try builtins.exec.thunk.call(PythonObject(basePluginSource), globals, globals)
try builtins.exec.thunk.call(PythonObject(content), globals, globals)
} catch { continue }
guard let bp = globals["BasePlugin"], bp.isNone == false else { continue }
let findClassCode = """
_plugin_cls = None
for _n, _o in list(globals().items()):
if _n != 'BasePlugin' and isinstance(_o, type) and issubclass(_o, BasePlugin):
_plugin_cls = _o
break
"""
try? builtins.exec.thunk.call(PythonObject(findClassCode), globals, globals)
guard let cls = globals["_plugin_cls"], cls.isNone == false else { continue }
let instance: PythonObject
do { instance = try cls.call() } catch { continue }
_ = try? instance.on_plugin_load.call()
let hasNewMessage = instance._has_hook.call("updateNewMessage").bool == true
let hasChannelMessage = instance._has_hook.call("updateNewChannelMessage").bool == true
guard hasNewMessage || hasChannelMessage else { continue }
let updateName = hasChannelMessage ? "updateNewChannelMessage" : "updateNewMessage"
globals["_upd_message"] = textPy
globals["_upd_peer"] = PythonObject(peerIdInt)
globals["_upd_msg_id"] = PythonObject(messageIdInt)
globals["_upd_outgoing"] = outgoingPy
let paramsCode = """
class _UpdateObj:
pass
_update_obj = _UpdateObj()
_update_obj.message = _upd_message
_update_obj.peer = _upd_peer
_update_obj.message_id = _upd_msg_id
_update_obj.outgoing = _upd_outgoing
"""
try? builtins.exec.thunk.call(PythonObject(paramsCode), globals, globals)
guard let updateObj = globals["_update_obj"], updateObj.isNone == false else { continue }
_ = try? instance.on_update_hook.call(updateName, accountIdInt, updateObj)
}
}
#endif