Files
Leeksov 4647310322 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.
2026-04-06 09:48:12 +03:00

1390 lines
53 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
import SGAppGroupIdentifier
import SGLogging
let APP_GROUP_IDENTIFIER = sgAppGroupIdentifier()
/// Lightweight file-backed key-value store (replaces NSUserDefaults for sensitive keys).
private class SGFileStore {
static let shared = SGFileStore()
private var data: [String: Any] = [:]
private let filePath: String
private let lock = NSLock()
private init() {
let docs = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? NSTemporaryDirectory()
filePath = docs + "/sg_private_settings.plist"
if let dict = NSDictionary(contentsOfFile: filePath) as? [String: Any] {
data = dict
}
}
func double(forKey key: String, default defaultValue: Double) -> Double {
lock.lock()
defer { lock.unlock() }
return data[key] as? Double ?? defaultValue
}
func int32(forKey key: String, default defaultValue: Int32) -> Int32 {
lock.lock()
defer { lock.unlock() }
if let v = data[key] as? Int { return Int32(v) }
if let v = data[key] as? Int32 { return v }
return defaultValue
}
func set(_ value: Any, forKey key: String) {
lock.lock()
data[key] = value
(data as NSDictionary).write(toFile: filePath, atomically: true)
lock.unlock()
}
}
public class SGSimpleSettings {
public static let shared = SGSimpleSettings()
private init() {
setDefaultValues()
migrate()
preCacheValues()
}
private func setDefaultValues() {
UserDefaults.standard.register(defaults: SGSimpleSettings.defaultValues)
// Just in case group defaults will be nil
UserDefaults.standard.register(defaults: SGSimpleSettings.groupDefaultValues)
if let groupUserDefaults = UserDefaults(suiteName: APP_GROUP_IDENTIFIER) {
groupUserDefaults.register(defaults: SGSimpleSettings.groupDefaultValues)
}
}
private func migrate() {
let showRepostToStoryMigrationKey = "migrated_\(Keys.showRepostToStory.rawValue)"
if let groupUserDefaults = UserDefaults(suiteName: APP_GROUP_IDENTIFIER) {
if !groupUserDefaults.bool(forKey: showRepostToStoryMigrationKey) {
self.showRepostToStoryV2 = self.showRepostToStory
groupUserDefaults.set(true, forKey: showRepostToStoryMigrationKey)
SGLogger.shared.log("SGSimpleSettings", "Migrated showRepostToStory. \(self.showRepostToStory) -> \(self.showRepostToStoryV2)")
}
} else {
SGLogger.shared.log("SGSimpleSettings", "Unable to migrate showRepostToStory. Shared UserDefaults suite is not available for '\(APP_GROUP_IDENTIFIER)'.")
}
// MARK: AppBadge default migration
// Older builds used an empty value which resulted in the classic badge being shown.
if self.customAppBadge.isEmpty || self.customAppBadge == "Components/AppBadge" {
self.customAppBadge = "SkyAppBadge"
}
let chatListLinesMigrationKey = "migrated_\(Keys.chatListLines.rawValue)"
if !UserDefaults.standard.bool(forKey: chatListLinesMigrationKey) {
let legacyCompactMessagePreviewKey = "compactMessagePreview"
if UserDefaults.standard.object(forKey: legacyCompactMessagePreviewKey) != nil {
if UserDefaults.standard.bool(forKey: legacyCompactMessagePreviewKey) {
self.chatListLines = ChatListLines.one.rawValue
}
UserDefaults.standard.removeObject(forKey: legacyCompactMessagePreviewKey)
SGLogger.shared.log("SGSimpleSettings", "Migrated compactMessagePreview -> chatListLines. \(self.chatListLines)")
}
UserDefaults.standard.set(true, forKey: chatListLinesMigrationKey)
}
}
private func preCacheValues() {
// let dispatchGroup = DispatchGroup()
let tasks = [
// { let _ = self.allChatsFolderPositionOverride },
{ let _ = self.tabBarSearchEnabled },
{ let _ = self.allChatsHidden },
{ let _ = self.hideTabBar },
{ let _ = self.bottomTabStyle },
{ let _ = self.compactChatList },
{ let _ = self.chatListLines },
{ let _ = self.compactFolderNames },
{ let _ = self.disableSwipeToRecordStory },
{ let _ = self.rememberLastFolder },
{ let _ = self.quickTranslateButton },
{ let _ = self.stickerSize },
{ let _ = self.stickerTimestamp },
{ let _ = self.hideReactions },
{ let _ = self.disableGalleryCamera },
{ let _ = self.disableSendAsButton },
{ let _ = self.disableSnapDeletionEffect },
{ let _ = self.startTelescopeWithRearCam },
{ let _ = self.hideRecordingButton },
{ let _ = self.inputToolbar },
{ let _ = self.dismissedSGSuggestions },
{ let _ = self.customAppBadge }
]
tasks.forEach { task in
DispatchQueue.global(qos: .background).async(/*group: dispatchGroup*/) {
task()
}
}
// dispatchGroup.notify(queue: DispatchQueue.main) {}
}
public func synchronizeShared() {
if let groupUserDefaults = UserDefaults(suiteName: APP_GROUP_IDENTIFIER) {
groupUserDefaults.synchronize()
}
}
public enum Keys: String, CaseIterable {
case hidePhoneInSettings
case showTabNames
case startTelescopeWithRearCam
case accountColorsSaturation
case uploadSpeedBoost
case downloadSpeedBoost
case bottomTabStyle
case rememberLastFolder
case lastAccountFolders
case localDNSForProxyHost
case sendLargePhotos
case outgoingPhotoQuality
case storyStealthMode
case canUseStealthMode
case disableSwipeToRecordStory
case quickTranslateButton
case outgoingLanguageTranslation
case showRepostToStory
case showRepostToStoryV2
case contextShowSelectFromUser
case contextShowSaveToCloud
case contextShowRestrict
// case contextShowBan
case contextShowHideForwardName
case contextShowReport
case contextShowReply
case contextShowPin
case contextShowSaveMedia
case contextShowMessageReplies
case contextShowJson
case disableScrollToNextChannel
case disableScrollToNextTopic
case disableChatSwipeOptions
case disableDeleteChatSwipeOption
case disableGalleryCamera
case disableGalleryCameraPreview
case disableSendAsButton
case disableSnapDeletionEffect
case stickerSize
case stickerTimestamp
case hideRecordingButton
case hideTabBar
case showDC
case showCreationDate
case showRegDate
case regDateCache
case compactChatList
case compactFolderNames
case allChatsTitleLengthOverride
// case allChatsFolderPositionOverride
case allChatsHidden
case defaultEmojisFirst
case messageDoubleTapActionOutgoing
case wideChannelPosts
case forceEmojiTab
case forceBuiltInMic
case secondsInMessages
case hideChannelBottomButton
case forceSystemSharing
case confirmCalls
case videoPIPSwipeDirection
case legacyNotificationsFix
case messageFilterKeywords
case inputToolbar
case pinnedMessageNotifications
case mentionsAndRepliesNotifications
case primaryUserId
case status
case dismissedSGSuggestions
case duckyAppIconAvailable
case transcriptionBackend
case translationBackend
case customAppBadge
case canUseNY
case nyStyle
case wideTabBar
case tabBarSearchEnabled
case hideStories
case warnOnStoriesOpen
case showProfileId
case sendWithReturnKey
case chatListLines
case showDeletedMessages
case saveEditHistory
// MARK: Saved Deleted Messages (AyuGram-style)
case saveDeletedMessagesMedia
case saveDeletedMessagesReactions
case saveDeletedMessagesForBots
// Ghost Mode settings
case ghostModeMessageSendDelaySeconds
case disableOnlineStatus
case disableTypingStatus
case disableRecordingVideoStatus
case disableUploadingVideoStatus
case disableVCMessageRecordingStatus
case disableVCMessageUploadingStatus
case disableUploadingPhotoStatus
case disableUploadingFileStatus
case disableChoosingLocationStatus
case disableChoosingContactStatus
case disablePlayingGameStatus
case disableRecordingRoundVideoStatus
case disableUploadingRoundVideoStatus
case disableSpeakingInGroupCallStatus
case disableChoosingStickerStatus
case disableEmojiInteractionStatus
case disableEmojiAcknowledgementStatus
case disableMessageReadReceipt
case disableStoryReadReceipt
case disableAllAds
case hideProxySponsor
case enableSavingProtectedContent
case disableScreenshotDetection
case enableSavingSelfDestructingMessages
case disableSecretChatBlurOnScreenshot
case enableLocalPremium
case scrollToTopButtonEnabled
case fakeLocationEnabled
case keepRemovedChannels
case enableVideoToCircleOrVoice
case enableTelescope
// Font replacement (A-Font style)
case enableFontReplacement
case fontReplacementName
case fontReplacementBoldName
case fontReplacementFilePath
case fontReplacementBoldFilePath
case enableLocalMessageEditing
case disableCompactNumbers
case disableZalgoText
// Оформление
case unlimitedFavoriteStickers
// Запись времени в сети
case enableOnlineStatusRecording
case onlineStatusRecordingIntervalMinutes
case savedOnlineStatusByPeerId
case addMusicFromDeviceToProfile
case hideReactions
case pluginSystemEnabled
case installedPluginsJson
case chatExportEnabled
case profileCoverMediaPath
case profileCoverIsVideo
case emojiDownloaderEnabled
case feelRichEnabled
case feelRichStarsAmount
case giftIdEnabled
case fakeProfileEnabled
case fakeProfileTargetUserId
case fakeProfileFirstName
case fakeProfileLastName
case fakeProfileUsername
case fakeProfilePhone
case fakeProfileId
case fakeProfilePremium
case fakeProfileVerified
case fakeProfileScam
case fakeProfileFake
case fakeProfileSupport
case fakeProfileBot
case currentAccountPeerId
case notificationMutedAccountRecordIds
case gatedFeatureKeys
case unlockedFeatureKeys
}
public enum DownloadSpeedBoostValues: String, CaseIterable {
case none
case medium
case maximum
}
public enum BottomTabStyleValues: String, CaseIterable {
case telegram
case ios
}
public enum AllChatsTitleLengthOverride: String, CaseIterable {
case none
case short
case long
}
public enum AllChatsFolderPositionOverride: String, CaseIterable {
case none
case last
case hidden
}
public enum ChatListLines: String, CaseIterable {
case three = "3"
case two = "2"
case one = "1"
public static let defaultValue: ChatListLines = .three
}
public enum MessageDoubleTapAction: String, CaseIterable {
case `default`
case none
case edit
}
public enum VideoPIPSwipeDirection: String, CaseIterable {
case up
case down
case none
}
public enum TranscriptionBackend: String, CaseIterable {
case `default`
case apple
}
public enum TranslationBackend: String, CaseIterable {
case `default`
case gtranslate
case system
// Make sure to update TranslationConfiguration
}
public enum PinnedMessageNotificationsSettings: String, CaseIterable {
case `default`
case silenced
case disabled
}
public enum MentionsAndRepliesNotificationsSettings: String, CaseIterable {
case `default`
case silenced
case disabled
}
public enum NYStyle: String, CaseIterable {
case `default`
case snow
case lightning
}
public static let defaultValues: [String: Any] = [
Keys.hidePhoneInSettings.rawValue: true,
Keys.showTabNames.rawValue: true,
Keys.startTelescopeWithRearCam.rawValue: false,
Keys.accountColorsSaturation.rawValue: 100,
Keys.uploadSpeedBoost.rawValue: false,
Keys.downloadSpeedBoost.rawValue: DownloadSpeedBoostValues.none.rawValue,
Keys.rememberLastFolder.rawValue: false,
Keys.bottomTabStyle.rawValue: BottomTabStyleValues.telegram.rawValue,
Keys.lastAccountFolders.rawValue: [:],
Keys.localDNSForProxyHost.rawValue: false,
Keys.sendLargePhotos.rawValue: false,
Keys.outgoingPhotoQuality.rawValue: 70,
Keys.storyStealthMode.rawValue: false,
Keys.canUseStealthMode.rawValue: true,
Keys.disableSwipeToRecordStory.rawValue: false,
Keys.quickTranslateButton.rawValue: false,
Keys.outgoingLanguageTranslation.rawValue: [:],
Keys.showRepostToStory.rawValue: true,
Keys.contextShowSelectFromUser.rawValue: true,
Keys.contextShowSaveToCloud.rawValue: true,
Keys.contextShowRestrict.rawValue: true,
// Keys.contextShowBan.rawValue: true,
Keys.contextShowHideForwardName.rawValue: true,
Keys.contextShowReport.rawValue: true,
Keys.contextShowReply.rawValue: true,
Keys.contextShowPin.rawValue: true,
Keys.contextShowSaveMedia.rawValue: true,
Keys.contextShowMessageReplies.rawValue: true,
Keys.contextShowJson.rawValue: false,
Keys.disableScrollToNextChannel.rawValue: false,
Keys.disableScrollToNextTopic.rawValue: false,
Keys.disableChatSwipeOptions.rawValue: false,
Keys.disableDeleteChatSwipeOption.rawValue: false,
Keys.disableGalleryCamera.rawValue: false,
Keys.disableGalleryCameraPreview.rawValue: false,
Keys.disableSendAsButton.rawValue: false,
Keys.disableSnapDeletionEffect.rawValue: false,
Keys.stickerSize.rawValue: 100,
Keys.stickerTimestamp.rawValue: true,
Keys.hideRecordingButton.rawValue: false,
Keys.hideTabBar.rawValue: false,
Keys.showDC.rawValue: false,
Keys.showCreationDate.rawValue: true,
Keys.showRegDate.rawValue: true,
Keys.regDateCache.rawValue: [:],
Keys.compactChatList.rawValue: false,
Keys.compactFolderNames.rawValue: false,
Keys.allChatsTitleLengthOverride.rawValue: AllChatsTitleLengthOverride.none.rawValue,
// Keys.allChatsFolderPositionOverride.rawValue: AllChatsFolderPositionOverride.none.rawValue
Keys.allChatsHidden.rawValue: false,
Keys.defaultEmojisFirst.rawValue: false,
Keys.messageDoubleTapActionOutgoing.rawValue: MessageDoubleTapAction.default.rawValue,
Keys.wideChannelPosts.rawValue: false,
Keys.forceEmojiTab.rawValue: false,
Keys.hideChannelBottomButton.rawValue: false,
Keys.secondsInMessages.rawValue: false,
Keys.forceSystemSharing.rawValue: false,
Keys.confirmCalls.rawValue: true,
Keys.videoPIPSwipeDirection.rawValue: VideoPIPSwipeDirection.up.rawValue,
Keys.messageFilterKeywords.rawValue: [],
Keys.inputToolbar.rawValue: false,
Keys.primaryUserId.rawValue: "",
Keys.dismissedSGSuggestions.rawValue: [],
Keys.duckyAppIconAvailable.rawValue: true,
Keys.transcriptionBackend.rawValue: TranscriptionBackend.default.rawValue,
Keys.translationBackend.rawValue: TranslationBackend.default.rawValue,
// Default app badge (GLEGram Dark Purple)
Keys.customAppBadge.rawValue: "SkyAppBadge",
Keys.canUseNY.rawValue: false,
Keys.nyStyle.rawValue: NYStyle.default.rawValue,
Keys.wideTabBar.rawValue: false,
Keys.tabBarSearchEnabled.rawValue: true,
Keys.hideStories.rawValue: false,
Keys.warnOnStoriesOpen.rawValue: false,
Keys.showProfileId.rawValue: true,
Keys.sendWithReturnKey.rawValue: false,
Keys.chatListLines.rawValue: ChatListLines.defaultValue.rawValue,
Keys.showDeletedMessages.rawValue: true,
Keys.saveEditHistory.rawValue: true,
// Saved Deleted Messages defaults (AyuGram-style)
Keys.saveDeletedMessagesMedia.rawValue: true,
Keys.saveDeletedMessagesReactions.rawValue: true,
Keys.saveDeletedMessagesForBots.rawValue: true,
// Ghost Mode defaults
Keys.ghostModeMessageSendDelaySeconds.rawValue: 0,
Keys.disableOnlineStatus.rawValue: false,
Keys.disableTypingStatus.rawValue: false,
Keys.disableRecordingVideoStatus.rawValue: false,
Keys.disableUploadingVideoStatus.rawValue: false,
Keys.disableVCMessageRecordingStatus.rawValue: false,
Keys.disableVCMessageUploadingStatus.rawValue: false,
Keys.disableUploadingPhotoStatus.rawValue: false,
Keys.disableUploadingFileStatus.rawValue: false,
Keys.disableChoosingLocationStatus.rawValue: false,
Keys.disableChoosingContactStatus.rawValue: false,
Keys.disablePlayingGameStatus.rawValue: false,
Keys.disableRecordingRoundVideoStatus.rawValue: false,
Keys.disableUploadingRoundVideoStatus.rawValue: false,
Keys.disableSpeakingInGroupCallStatus.rawValue: false,
Keys.disableChoosingStickerStatus.rawValue: false,
Keys.disableEmojiInteractionStatus.rawValue: false,
Keys.disableEmojiAcknowledgementStatus.rawValue: false,
Keys.disableMessageReadReceipt.rawValue: false,
Keys.disableStoryReadReceipt.rawValue: false,
Keys.disableAllAds.rawValue: false,
Keys.hideProxySponsor.rawValue: false,
Keys.enableSavingProtectedContent.rawValue: false,
Keys.disableScreenshotDetection.rawValue: false,
Keys.enableSavingSelfDestructingMessages.rawValue: false,
Keys.disableSecretChatBlurOnScreenshot.rawValue: false,
Keys.enableLocalPremium.rawValue: false,
Keys.scrollToTopButtonEnabled.rawValue: true,
Keys.fakeLocationEnabled.rawValue: false,
Keys.keepRemovedChannels.rawValue: false,
Keys.enableVideoToCircleOrVoice.rawValue: false,
Keys.enableTelescope.rawValue: false,
Keys.enableFontReplacement.rawValue: false,
Keys.fontReplacementName.rawValue: "",
Keys.fontReplacementBoldName.rawValue: "",
Keys.fontReplacementFilePath.rawValue: "",
Keys.fontReplacementBoldFilePath.rawValue: "",
Keys.enableLocalMessageEditing.rawValue: false,
Keys.disableCompactNumbers.rawValue: false,
Keys.disableZalgoText.rawValue: false,
Keys.unlimitedFavoriteStickers.rawValue: true,
Keys.enableOnlineStatusRecording.rawValue: false,
Keys.onlineStatusRecordingIntervalMinutes.rawValue: 5,
Keys.savedOnlineStatusByPeerId.rawValue: "{}",
Keys.addMusicFromDeviceToProfile.rawValue: false,
Keys.hideReactions.rawValue: false,
Keys.pluginSystemEnabled.rawValue: false,
Keys.installedPluginsJson.rawValue: "[]",
Keys.chatExportEnabled.rawValue: false,
Keys.profileCoverMediaPath.rawValue: "",
Keys.profileCoverIsVideo.rawValue: false,
Keys.emojiDownloaderEnabled.rawValue: false,
Keys.feelRichEnabled.rawValue: false,
Keys.feelRichStarsAmount.rawValue: "1000",
Keys.giftIdEnabled.rawValue: false,
Keys.fakeProfileEnabled.rawValue: false,
Keys.fakeProfileTargetUserId.rawValue: "",
Keys.fakeProfileFirstName.rawValue: "",
Keys.fakeProfileLastName.rawValue: "",
Keys.fakeProfileUsername.rawValue: "",
Keys.fakeProfilePhone.rawValue: "",
Keys.fakeProfileId.rawValue: "",
Keys.fakeProfilePremium.rawValue: false,
Keys.fakeProfileVerified.rawValue: false,
Keys.fakeProfileScam.rawValue: false,
Keys.fakeProfileFake.rawValue: false,
Keys.fakeProfileSupport.rawValue: false,
Keys.fakeProfileBot.rawValue: false,
Keys.currentAccountPeerId.rawValue: "",
Keys.gatedFeatureKeys.rawValue: [] as [String],
Keys.unlockedFeatureKeys.rawValue: [] as [String]
]
public static let groupDefaultValues: [String: Any] = [
Keys.legacyNotificationsFix.rawValue: false,
Keys.pinnedMessageNotifications.rawValue: PinnedMessageNotificationsSettings.default.rawValue,
Keys.mentionsAndRepliesNotifications.rawValue: MentionsAndRepliesNotificationsSettings.default.rawValue,
Keys.status.rawValue: 1,
Keys.showRepostToStoryV2.rawValue: true,
Keys.notificationMutedAccountRecordIds.rawValue: [] as [String],
]
@UserDefault(key: Keys.hidePhoneInSettings.rawValue)
public var hidePhoneInSettings: Bool
@UserDefault(key: Keys.showTabNames.rawValue)
public var showTabNames: Bool
@UserDefault(key: Keys.startTelescopeWithRearCam.rawValue)
public var startTelescopeWithRearCam: Bool
@UserDefault(key: Keys.accountColorsSaturation.rawValue)
public var accountColorsSaturation: Int32
@UserDefault(key: Keys.uploadSpeedBoost.rawValue)
public var uploadSpeedBoost: Bool
@UserDefault(key: Keys.downloadSpeedBoost.rawValue)
public var downloadSpeedBoost: String
@UserDefault(key: Keys.rememberLastFolder.rawValue)
public var rememberLastFolder: Bool
// Disabled while Telegram is migrating to Glass
// @UserDefault(key: Keys.bottomTabStyle.rawValue)
public var bottomTabStyle: String {
set {}
get {
return BottomTabStyleValues.ios.rawValue
}
}
public var lastAccountFolders = UserDefaultsBackedDictionary<String, Int32>(userDefaultsKey: Keys.lastAccountFolders.rawValue, threadSafe: false)
@UserDefault(key: Keys.localDNSForProxyHost.rawValue)
public var localDNSForProxyHost: Bool
@UserDefault(key: Keys.sendLargePhotos.rawValue)
public var sendLargePhotos: Bool
@UserDefault(key: Keys.outgoingPhotoQuality.rawValue)
public var outgoingPhotoQuality: Int32
@UserDefault(key: Keys.storyStealthMode.rawValue)
public var storyStealthMode: Bool
@UserDefault(key: Keys.canUseStealthMode.rawValue)
public var canUseStealthMode: Bool
@UserDefault(key: Keys.disableSwipeToRecordStory.rawValue)
public var disableSwipeToRecordStory: Bool
@UserDefault(key: Keys.quickTranslateButton.rawValue)
public var quickTranslateButton: Bool
public var outgoingLanguageTranslation = UserDefaultsBackedDictionary<String, String>(userDefaultsKey: Keys.outgoingLanguageTranslation.rawValue, threadSafe: false)
// @available(*, deprecated, message: "Use showRepostToStoryV2 instead")
@UserDefault(key: Keys.showRepostToStory.rawValue)
public var showRepostToStory: Bool
@UserDefault(key: Keys.showRepostToStoryV2.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
public var showRepostToStoryV2: Bool
@UserDefault(key: Keys.contextShowRestrict.rawValue)
public var contextShowRestrict: Bool
/*@UserDefault(key: Keys.contextShowBan.rawValue)
public var contextShowBan: Bool*/
@UserDefault(key: Keys.contextShowSelectFromUser.rawValue)
public var contextShowSelectFromUser: Bool
@UserDefault(key: Keys.contextShowSaveToCloud.rawValue)
public var contextShowSaveToCloud: Bool
@UserDefault(key: Keys.contextShowHideForwardName.rawValue)
public var contextShowHideForwardName: Bool
@UserDefault(key: Keys.contextShowReport.rawValue)
public var contextShowReport: Bool
@UserDefault(key: Keys.contextShowReply.rawValue)
public var contextShowReply: Bool
@UserDefault(key: Keys.contextShowPin.rawValue)
public var contextShowPin: Bool
@UserDefault(key: Keys.contextShowSaveMedia.rawValue)
public var contextShowSaveMedia: Bool
@UserDefault(key: Keys.contextShowMessageReplies.rawValue)
public var contextShowMessageReplies: Bool
@UserDefault(key: Keys.contextShowJson.rawValue)
public var contextShowJson: Bool
@UserDefault(key: Keys.disableScrollToNextChannel.rawValue)
public var disableScrollToNextChannel: Bool
@UserDefault(key: Keys.disableScrollToNextTopic.rawValue)
public var disableScrollToNextTopic: Bool
@UserDefault(key: Keys.disableChatSwipeOptions.rawValue)
public var disableChatSwipeOptions: Bool
@UserDefault(key: Keys.disableDeleteChatSwipeOption.rawValue)
public var disableDeleteChatSwipeOption: Bool
@UserDefault(key: Keys.disableGalleryCamera.rawValue)
public var disableGalleryCamera: Bool
@UserDefault(key: Keys.disableGalleryCameraPreview.rawValue)
public var disableGalleryCameraPreview: Bool
@UserDefault(key: Keys.disableSendAsButton.rawValue)
public var disableSendAsButton: Bool
@UserDefault(key: Keys.disableSnapDeletionEffect.rawValue)
public var disableSnapDeletionEffect: Bool
@UserDefault(key: Keys.stickerSize.rawValue)
public var stickerSize: Int32
@UserDefault(key: Keys.stickerTimestamp.rawValue)
public var stickerTimestamp: Bool
@UserDefault(key: Keys.hideRecordingButton.rawValue)
public var hideRecordingButton: Bool
@UserDefault(key: Keys.hideTabBar.rawValue)
public var hideTabBar: Bool
@UserDefault(key: Keys.showDC.rawValue)
public var showDC: Bool
@UserDefault(key: Keys.showCreationDate.rawValue)
public var showCreationDate: Bool
@UserDefault(key: Keys.showRegDate.rawValue)
public var showRegDate: Bool
public var regDateCache = UserDefaultsBackedDictionary<String, Data>(userDefaultsKey: Keys.regDateCache.rawValue, threadSafe: false)
@UserDefault(key: Keys.compactChatList.rawValue)
public var compactChatList: Bool
@UserDefault(key: Keys.compactFolderNames.rawValue)
public var compactFolderNames: Bool
@UserDefault(key: Keys.allChatsTitleLengthOverride.rawValue)
public var allChatsTitleLengthOverride: String
//
// @UserDefault(key: Keys.allChatsFolderPositionOverride.rawValue)
// public var allChatsFolderPositionOverride: String
@UserDefault(key: Keys.allChatsHidden.rawValue)
public var allChatsHidden: Bool
@UserDefault(key: Keys.defaultEmojisFirst.rawValue)
public var defaultEmojisFirst: Bool
@UserDefault(key: Keys.messageDoubleTapActionOutgoing.rawValue)
public var messageDoubleTapActionOutgoing: String
@UserDefault(key: Keys.wideChannelPosts.rawValue)
public var wideChannelPosts: Bool
@UserDefault(key: Keys.forceEmojiTab.rawValue)
public var forceEmojiTab: Bool
@UserDefault(key: Keys.forceBuiltInMic.rawValue)
public var forceBuiltInMic: Bool
@UserDefault(key: Keys.secondsInMessages.rawValue)
public var secondsInMessages: Bool
@UserDefault(key: Keys.hideChannelBottomButton.rawValue)
public var hideChannelBottomButton: Bool
@UserDefault(key: Keys.forceSystemSharing.rawValue)
public var forceSystemSharing: Bool
@UserDefault(key: Keys.confirmCalls.rawValue)
public var confirmCalls: Bool
@UserDefault(key: Keys.videoPIPSwipeDirection.rawValue)
public var videoPIPSwipeDirection: String
@UserDefault(key: Keys.legacyNotificationsFix.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
public var legacyNotificationsFix: Bool
@UserDefault(key: Keys.status.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
public var status: Int64
public var ephemeralStatus: Int64 = 1
@UserDefault(key: Keys.messageFilterKeywords.rawValue)
public var messageFilterKeywords: [String]
@UserDefault(key: Keys.inputToolbar.rawValue)
public var inputToolbar: Bool
@UserDefault(key: Keys.pinnedMessageNotifications.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
public var pinnedMessageNotifications: String
@UserDefault(key: Keys.mentionsAndRepliesNotifications.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
public var mentionsAndRepliesNotifications: String
/// Account record IDs (as strings) for which push notifications are muted.
@UserDefault(key: Keys.notificationMutedAccountRecordIds.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
public var notificationMutedAccountRecordIds: [String]
public func isAccountNotificationMuted(recordId: Int64) -> Bool {
return notificationMutedAccountRecordIds.contains(String(recordId))
}
public func setAccountNotificationMuted(recordId: Int64, muted: Bool) {
let key = String(recordId)
var current = notificationMutedAccountRecordIds
if muted {
if !current.contains(key) {
current.append(key)
}
} else {
current.removeAll { $0 == key }
}
notificationMutedAccountRecordIds = current
}
// MARK: - Gated Features
@UserDefault(key: Keys.gatedFeatureKeys.rawValue)
public var gatedFeatureKeys: [String]
@UserDefault(key: Keys.unlockedFeatureKeys.rawValue)
public var unlockedFeatureKeys: [String]
/// A gated feature is visible only if it's been unlocked (or if it's not gated at all).
public func isFeatureVisible(_ settingKey: String) -> Bool {
if !gatedFeatureKeys.contains(settingKey) { return true }
return unlockedFeatureKeys.contains(settingKey)
}
public func unlockFeature(_ settingKey: String) {
var current = unlockedFeatureKeys
if !current.contains(settingKey) {
current.append(settingKey)
unlockedFeatureKeys = current
}
}
/// Deeplink path feature key mapping (cached from server)
private static let gatedDeeplinkMapKey = "sg_gatedDeeplinkMap"
public var gatedDeeplinkMap: [String: String] {
get { UserDefaults.standard.dictionary(forKey: Self.gatedDeeplinkMapKey) as? [String: String] ?? [:] }
set { UserDefaults.standard.set(newValue, forKey: Self.gatedDeeplinkMapKey) }
}
/// Update gated features from server response
public func updateGatedFeatures(_ features: [(key: String, deeplinkPath: String)]) {
gatedFeatureKeys = features.map { $0.key }
var map: [String: String] = [:]
for f in features { map[f.deeplinkPath] = f.key }
gatedDeeplinkMap = map
}
/// Handle deeplink path. For single paths, unlocks locally and returns the key.
/// For group paths (not in map), returns nil server will handle and return keys.
public func handleFeatureDeeplink(_ path: String) -> String? {
guard let key = gatedDeeplinkMap[path] else {
// Group path (e.g. "unlock-all", "ghost-mode") not in local map.
// Server will resolve and return unlocked keys.
return nil
}
unlockFeature(key)
return key
}
/// Unlock multiple features at once (used for group deeplinks after server response).
public func unlockFeatures(_ keys: [String]) {
var current = unlockedFeatureKeys
for key in keys {
if !current.contains(key) {
current.append(key)
}
}
unlockedFeatureKeys = current
}
@UserDefault(key: Keys.primaryUserId.rawValue)
public var primaryUserId: String
@UserDefault(key: Keys.dismissedSGSuggestions.rawValue)
public var dismissedSGSuggestions: [String]
@UserDefault(key: Keys.duckyAppIconAvailable.rawValue)
public var duckyAppIconAvailable: Bool
@UserDefault(key: Keys.transcriptionBackend.rawValue)
public var transcriptionBackend: String
@UserDefault(key: Keys.translationBackend.rawValue)
public var translationBackend: String
@UserDefault(key: Keys.customAppBadge.rawValue)
public var customAppBadge: String
@UserDefault(key: Keys.canUseNY.rawValue)
public var canUseNY: Bool
@UserDefault(key: Keys.nyStyle.rawValue)
public var nyStyle: String
@UserDefault(key: Keys.wideTabBar.rawValue)
public var wideTabBar: Bool
@UserDefault(key: Keys.tabBarSearchEnabled.rawValue)
public var tabBarSearchEnabled: Bool
@UserDefault(key: Keys.hideStories.rawValue)
public var hideStories: Bool
@UserDefault(key: Keys.warnOnStoriesOpen.rawValue)
public var warnOnStoriesOpen: Bool
@UserDefault(key: Keys.showProfileId.rawValue)
public var showProfileId: Bool
@UserDefault(key: Keys.chatListLines.rawValue)
public var chatListLines: String
@UserDefault(key: Keys.sendWithReturnKey.rawValue)
public var sendWithReturnKey: Bool
@UserDefault(key: Keys.showDeletedMessages.rawValue)
public var showDeletedMessages: Bool
@UserDefault(key: Keys.saveEditHistory.rawValue)
public var saveEditHistory: Bool
// MARK: Saved Deleted Messages (AyuGram-style)
@UserDefault(key: Keys.saveDeletedMessagesMedia.rawValue)
public var saveDeletedMessagesMedia: Bool
@UserDefault(key: Keys.saveDeletedMessagesReactions.rawValue)
public var saveDeletedMessagesReactions: Bool
@UserDefault(key: Keys.saveDeletedMessagesForBots.rawValue)
public var saveDeletedMessagesForBots: Bool
// Ghost Mode settings
/// 0 = off, 12 / 30 / 45 = delay in seconds
@UserDefault(key: Keys.ghostModeMessageSendDelaySeconds.rawValue)
public var ghostModeMessageSendDelaySeconds: Int32
@UserDefault(key: Keys.disableOnlineStatus.rawValue)
public var disableOnlineStatus: Bool
@UserDefault(key: Keys.disableTypingStatus.rawValue)
public var disableTypingStatus: Bool
@UserDefault(key: Keys.disableRecordingVideoStatus.rawValue)
public var disableRecordingVideoStatus: Bool
@UserDefault(key: Keys.disableUploadingVideoStatus.rawValue)
public var disableUploadingVideoStatus: Bool
@UserDefault(key: Keys.disableVCMessageRecordingStatus.rawValue)
public var disableVCMessageRecordingStatus: Bool
@UserDefault(key: Keys.disableVCMessageUploadingStatus.rawValue)
public var disableVCMessageUploadingStatus: Bool
@UserDefault(key: Keys.disableUploadingPhotoStatus.rawValue)
public var disableUploadingPhotoStatus: Bool
@UserDefault(key: Keys.disableUploadingFileStatus.rawValue)
public var disableUploadingFileStatus: Bool
@UserDefault(key: Keys.disableChoosingLocationStatus.rawValue)
public var disableChoosingLocationStatus: Bool
@UserDefault(key: Keys.disableChoosingContactStatus.rawValue)
public var disableChoosingContactStatus: Bool
@UserDefault(key: Keys.disablePlayingGameStatus.rawValue)
public var disablePlayingGameStatus: Bool
@UserDefault(key: Keys.disableRecordingRoundVideoStatus.rawValue)
public var disableRecordingRoundVideoStatus: Bool
@UserDefault(key: Keys.disableUploadingRoundVideoStatus.rawValue)
public var disableUploadingRoundVideoStatus: Bool
@UserDefault(key: Keys.disableSpeakingInGroupCallStatus.rawValue)
public var disableSpeakingInGroupCallStatus: Bool
@UserDefault(key: Keys.disableChoosingStickerStatus.rawValue)
public var disableChoosingStickerStatus: Bool
@UserDefault(key: Keys.disableEmojiInteractionStatus.rawValue)
public var disableEmojiInteractionStatus: Bool
@UserDefault(key: Keys.disableEmojiAcknowledgementStatus.rawValue)
public var disableEmojiAcknowledgementStatus: Bool
@UserDefault(key: Keys.disableMessageReadReceipt.rawValue)
public var disableMessageReadReceipt: Bool
/// Peer IDs (as "namespace:id") to whom read receipts ARE sent (whitelist). Empty = send to all.
public var messageReadReceiptsSendToPeerIds: Set<String> {
get {
if let data = UserDefaults.standard.data(forKey: "messageReadReceiptsSendToPeerIds"),
let array = try? JSONDecoder().decode([String].self, from: data) {
return Set(array)
}
return []
}
set {
if let data = try? JSONEncoder().encode(Array(newValue)) {
UserDefaults.standard.set(data, forKey: "messageReadReceiptsSendToPeerIds")
synchronizeShared()
}
}
}
/// Check if read receipts should be blocked for this peer (when disableMessageReadReceipt is false).
/// Whitelist: empty list = send to no one; non-empty = send only to peers in list.
public func shouldBlockReadReceiptFor(peerIdNamespace: Int32, peerIdId: Int64) -> Bool {
let list = messageReadReceiptsSendToPeerIds
if list.isEmpty { return true }
let key = "\(peerIdNamespace):\(peerIdId)"
return !list.contains(key)
}
@UserDefault(key: Keys.disableStoryReadReceipt.rawValue)
public var disableStoryReadReceipt: Bool
@UserDefault(key: Keys.disableAllAds.rawValue)
public var disableAllAds: Bool
@UserDefault(key: Keys.hideProxySponsor.rawValue)
public var hideProxySponsor: Bool
@UserDefault(key: Keys.enableSavingProtectedContent.rawValue)
public var enableSavingProtectedContent: Bool
@UserDefault(key: Keys.enableSavingSelfDestructingMessages.rawValue)
public var enableSavingSelfDestructingMessages: Bool
@UserDefault(key: Keys.disableSecretChatBlurOnScreenshot.rawValue)
public var disableSecretChatBlurOnScreenshot: Bool
@UserDefault(key: Keys.enableLocalPremium.rawValue)
public var enableLocalPremium: Bool
@UserDefault(key: Keys.disableScreenshotDetection.rawValue)
public var disableScreenshotDetection: Bool
@UserDefault(key: Keys.scrollToTopButtonEnabled.rawValue)
public var scrollToTopButtonEnabled: Bool
@UserDefault(key: Keys.fakeLocationEnabled.rawValue)
public var fakeLocationEnabled: Bool
public var fakeLatitude: Double {
get { SGFileStore.shared.double(forKey: "fakeLatitude", default: 0.0) }
set { SGFileStore.shared.set(newValue, forKey: "fakeLatitude") }
}
public var fakeLongitude: Double {
get { SGFileStore.shared.double(forKey: "fakeLongitude", default: 0.0) }
set { SGFileStore.shared.set(newValue, forKey: "fakeLongitude") }
}
@UserDefault(key: Keys.keepRemovedChannels.rawValue)
public var keepRemovedChannels: Bool
@UserDefault(key: Keys.enableVideoToCircleOrVoice.rawValue)
public var enableVideoToCircleOrVoice: Bool
@UserDefault(key: Keys.enableTelescope.rawValue)
public var enableTelescope: Bool
/// Font replacement (A-Font style): enable, main font name, bold font name, size multiplier (100 = 1.0)
@UserDefault(key: Keys.enableFontReplacement.rawValue)
public var enableFontReplacement: Bool
@UserDefault(key: Keys.fontReplacementName.rawValue)
public var fontReplacementName: String
@UserDefault(key: Keys.fontReplacementBoldName.rawValue)
public var fontReplacementBoldName: String
/// Persistent path to copied main font file (so it survives app restart)
@UserDefault(key: Keys.fontReplacementFilePath.rawValue)
public var fontReplacementFilePath: String
/// Persistent path to copied bold font file
@UserDefault(key: Keys.fontReplacementBoldFilePath.rawValue)
public var fontReplacementBoldFilePath: String
public var fontReplacementSizeMultiplier: Int32 {
get { SGFileStore.shared.int32(forKey: "fontReplacementSizeMultiplier", default: 100) }
set { SGFileStore.shared.set(Int(newValue), forKey: "fontReplacementSizeMultiplier") }
}
@UserDefault(key: Keys.enableLocalMessageEditing.rawValue)
public var enableLocalMessageEditing: Bool
@UserDefault(key: Keys.disableCompactNumbers.rawValue)
public var disableCompactNumbers: Bool
@UserDefault(key: Keys.disableZalgoText.rawValue)
public var disableZalgoText: Bool
@UserDefault(key: Keys.unlimitedFavoriteStickers.rawValue)
public var unlimitedFavoriteStickers: Bool
@UserDefault(key: Keys.enableOnlineStatusRecording.rawValue)
public var enableOnlineStatusRecording: Bool
@UserDefault(key: Keys.onlineStatusRecordingIntervalMinutes.rawValue)
public var onlineStatusRecordingIntervalMinutes: Int32
@UserDefault(key: Keys.savedOnlineStatusByPeerId.rawValue)
public var savedOnlineStatusByPeerId: String
@UserDefault(key: Keys.addMusicFromDeviceToProfile.rawValue)
public var addMusicFromDeviceToProfile: Bool
@UserDefault(key: Keys.hideReactions.rawValue)
public var hideReactions: Bool
@UserDefault(key: Keys.pluginSystemEnabled.rawValue)
public var pluginSystemEnabled: Bool
@UserDefault(key: Keys.installedPluginsJson.rawValue)
public var installedPluginsJson: String
@UserDefault(key: Keys.chatExportEnabled.rawValue)
public var chatExportEnabled: Bool
@UserDefault(key: Keys.profileCoverMediaPath.rawValue)
public var profileCoverMediaPath: String
@UserDefault(key: Keys.profileCoverIsVideo.rawValue)
public var profileCoverIsVideo: Bool
@UserDefault(key: Keys.emojiDownloaderEnabled.rawValue)
public var emojiDownloaderEnabled: Bool
@UserDefault(key: Keys.feelRichEnabled.rawValue)
public var feelRichEnabled: Bool
@UserDefault(key: Keys.feelRichStarsAmount.rawValue)
public var feelRichStarsAmount: String
@UserDefault(key: Keys.giftIdEnabled.rawValue)
public var giftIdEnabled: Bool
@UserDefault(key: Keys.fakeProfileEnabled.rawValue)
public var fakeProfileEnabled: Bool
@UserDefault(key: Keys.fakeProfileTargetUserId.rawValue)
public var fakeProfileTargetUserId: String
@UserDefault(key: Keys.fakeProfileFirstName.rawValue)
public var fakeProfileFirstName: String
@UserDefault(key: Keys.fakeProfileLastName.rawValue)
public var fakeProfileLastName: String
@UserDefault(key: Keys.fakeProfileUsername.rawValue)
public var fakeProfileUsername: String
@UserDefault(key: Keys.fakeProfilePhone.rawValue)
public var fakeProfilePhone: String
@UserDefault(key: Keys.fakeProfileId.rawValue)
public var fakeProfileId: String
@UserDefault(key: Keys.fakeProfilePremium.rawValue)
public var fakeProfilePremium: Bool
@UserDefault(key: Keys.fakeProfileVerified.rawValue)
public var fakeProfileVerified: Bool
@UserDefault(key: Keys.fakeProfileScam.rawValue)
public var fakeProfileScam: Bool
@UserDefault(key: Keys.fakeProfileFake.rawValue)
public var fakeProfileFake: Bool
@UserDefault(key: Keys.fakeProfileSupport.rawValue)
public var fakeProfileSupport: Bool
@UserDefault(key: Keys.fakeProfileBot.rawValue)
public var fakeProfileBot: Bool
@UserDefault(key: Keys.currentAccountPeerId.rawValue)
public var currentAccountPeerId: String
/// Whether fake profile overlay should apply for this peer id (current account or target user).
public func shouldApplyFakeProfile(peerId: Int64) -> Bool {
guard fakeProfileEnabled else { return false }
let target: String = fakeProfileTargetUserId.isEmpty ? currentAccountPeerId : fakeProfileTargetUserId
guard let targetNum = Int64(target) else { return false }
return peerId == targetNum
}
/// Display value for first name when fake profile is active.
public func displayFirstName(peerId: Int64, real: String?) -> String {
shouldApplyFakeProfile(peerId: peerId) && !fakeProfileFirstName.isEmpty ? fakeProfileFirstName : (real ?? "")
}
/// Display value for last name when fake profile is active.
public func displayLastName(peerId: Int64, real: String?) -> String {
shouldApplyFakeProfile(peerId: peerId) && !fakeProfileLastName.isEmpty ? fakeProfileLastName : (real ?? "")
}
/// Display value for username (without @) when fake profile is active.
public func displayUsername(peerId: Int64, real: String?) -> String {
shouldApplyFakeProfile(peerId: peerId) && !fakeProfileUsername.isEmpty ? fakeProfileUsername : (real ?? "")
}
/// Display value for phone when fake profile is active.
public func displayPhone(peerId: Int64, real: String?) -> String {
shouldApplyFakeProfile(peerId: peerId) && !fakeProfilePhone.isEmpty ? fakeProfilePhone : (real ?? "")
}
/// Display value for user id string when fake profile is active.
public func displayId(peerId: Int64, real: String?) -> String {
shouldApplyFakeProfile(peerId: peerId) && !fakeProfileId.isEmpty ? fakeProfileId : (real ?? "")
}
/// Saved "last seen" timestamps per peer (for online status recording). Key: peerId as Int64, value: timestamp.
public var savedOnlineStatusByPeerIdDict: [Int64: Int32] {
get {
guard let data = savedOnlineStatusByPeerId.data(using: .utf8),
let dict = try? JSONDecoder().decode([String: Int32].self, from: data) else {
return [:]
}
var result: [Int64: Int32] = [:]
for (k, v) in dict where Int64(k) != nil {
result[Int64(k)!] = v
}
return result
}
set {
let dict = Dictionary(uniqueKeysWithValues: newValue.map { ("\($0.key)", $0.value) })
if let data = try? JSONEncoder().encode(dict),
let string = String(data: data, encoding: .utf8) {
savedOnlineStatusByPeerId = string
synchronizeShared()
}
}
}
public func getSavedOnlineStatusTimestamp(peerId: Int64) -> Int32? {
return savedOnlineStatusByPeerIdDict[peerId]
}
public static let onlineStatusTimestampDidChangeNotification = Notification.Name("SGOnlineStatusTimestampDidChange")
public func setSavedOnlineStatusTimestamp(peerId: Int64, timestamp: Int32) {
var dict = savedOnlineStatusByPeerIdDict
dict[peerId] = timestamp
savedOnlineStatusByPeerIdDict = dict
DispatchQueue.main.async {
NotificationCenter.default.post(name: SGSimpleSettings.onlineStatusTimestampDidChangeNotification, object: nil, userInfo: ["peerId": peerId])
}
}
/// Strip Zalgo / combining characters from string (for display when disableZalgoText is on).
public static func stripZalgo(_ string: String) -> String {
return string.filter { char in
!char.unicodeScalars.contains(where: { (scalar: Unicode.Scalar) in
let cat = scalar.properties.generalCategory
return cat == .nonspacingMark || cat == .spacingMark || cat == .enclosingMark
})
}
}
// MARK: Swiftgram - Keep Removed Channels
public var removedChannels: [Int64] {
get {
if let data = UserDefaults.standard.data(forKey: "removedChannels"),
let array = try? JSONDecoder().decode([Int64].self, from: data) {
return array
}
return []
}
set {
if let data = try? JSONEncoder().encode(newValue) {
UserDefaults.standard.set(data, forKey: "removedChannels")
synchronizeShared()
}
}
}
public func isChannelRemoved(_ peerId: Int64) -> Bool {
return keepRemovedChannels && removedChannels.contains(peerId)
}
public func markChannelAsRemoved(_ peerId: Int64) {
if !removedChannels.contains(peerId) {
removedChannels.append(peerId)
}
}
public func unmarkChannelAsRemoved(_ peerId: Int64) {
removedChannels.removeAll { $0 == peerId }
}
// MARK: Swiftgram - Keep Removed User Chats
public var removedUserChats: [Int64] {
get {
if let data = UserDefaults.standard.data(forKey: "removedUserChats"),
let array = try? JSONDecoder().decode([Int64].self, from: data) {
return array
}
return []
}
set {
if let data = try? JSONEncoder().encode(newValue) {
UserDefaults.standard.set(data, forKey: "removedUserChats")
synchronizeShared()
}
}
}
public func isUserChatRemoved(_ peerId: Int64) -> Bool {
return keepRemovedChannels && removedUserChats.contains(peerId)
}
public func markUserChatAsRemoved(_ peerId: Int64) {
if !removedUserChats.contains(peerId) {
removedUserChats.append(peerId)
}
}
public func unmarkUserChatAsRemoved(_ peerId: Int64) {
removedUserChats.removeAll { $0 == peerId }
}
}
extension SGSimpleSettings {
public var isStealthModeEnabled: Bool {
return storyStealthMode && canUseStealthMode
}
public static func makeOutgoingLanguageTranslationKey(accountId: Int64, peerId: Int64) -> String {
return "\(accountId):\(peerId)"
}
}
extension SGSimpleSettings {
public var translationBackendEnum: SGSimpleSettings.TranslationBackend {
return TranslationBackend(rawValue: translationBackend) ?? .default
}
public var transcriptionBackendEnum: SGSimpleSettings.TranscriptionBackend {
return TranscriptionBackend(rawValue: transcriptionBackend) ?? .default
}
}
extension SGSimpleSettings {
public var isNYEnabled: Bool {
return canUseNY && NYStyle(rawValue: nyStyle) != .default
}
/// Check if a peer should be treated as premium, considering local premium setting
/// - Parameters:
/// - peerId: The peer ID to check
/// - accountPeerId: The current account's peer ID
/// - isPremium: The actual premium status from Telegram
/// - Returns: True if the peer should be treated as premium (either has real premium or has local premium enabled for current user)
public func isPremium(peerId: Int64, accountPeerId: Int64, isPremium: Bool) -> Bool {
if isPremium {
return true
}
// Local premium only applies to the current user
if self.enableLocalPremium && peerId == accountPeerId {
return true
}
// Fake profile: show premium badge for the substituted profile when enabled
if self.shouldApplyFakeProfile(peerId: peerId) && self.fakeProfilePremium {
return true
}
return false
}
}
public func getSGDownloadPartSize(_ default: Int64, fileSize: Int64?) -> Int64 {
let currentDownloadSetting = SGSimpleSettings.shared.downloadSpeedBoost
// Increasing chunk size for small files make it worse in terms of overall download performance
let smallFileSizeThreshold = 1 * 1024 * 1024 // 1 MB
switch (currentDownloadSetting) {
case SGSimpleSettings.DownloadSpeedBoostValues.medium.rawValue:
if let fileSize, fileSize <= smallFileSizeThreshold {
return `default`
}
return 512 * 1024
case SGSimpleSettings.DownloadSpeedBoostValues.maximum.rawValue:
if let fileSize, fileSize <= smallFileSizeThreshold {
return `default`
}
return 1024 * 1024
default:
return `default`
}
}
public func getSGMaxPendingParts(_ default: Int) -> Int {
let currentDownloadSetting = SGSimpleSettings.shared.downloadSpeedBoost
switch (currentDownloadSetting) {
case SGSimpleSettings.DownloadSpeedBoostValues.medium.rawValue:
return 8
case SGSimpleSettings.DownloadSpeedBoostValues.maximum.rawValue:
return 12
default:
return `default`
}
}
public func sgUseShortAllChatsTitle(_ default: Bool) -> Bool {
let currentOverride = SGSimpleSettings.shared.allChatsTitleLengthOverride
switch (currentOverride) {
case SGSimpleSettings.AllChatsTitleLengthOverride.short.rawValue:
return true
case SGSimpleSettings.AllChatsTitleLengthOverride.long.rawValue:
return false
default:
return `default`
}
}
public extension Notification.Name {
/// Posted when "Hide Proxy Sponsor" is toggled so the chat list can refresh.
static let sgHideProxySponsorDidChange = Notification.Name("SGHideProxySponsorDidChange")
/// Posted when a badge image finishes downloading so the UI can re-render.
static let sgBadgeImageDidCache = Notification.Name("SGBadgeImageDidCache")
}