831 lines
42 KiB
Swift
831 lines
42 KiB
Swift
import Foundation
|
||
import TelegramCore
|
||
import SwiftSignalKit
|
||
|
||
public enum WinterGramSendWithoutSound: Int32 {
|
||
case never = 0
|
||
case inGhostMode = 1
|
||
case always = 2
|
||
}
|
||
|
||
public enum WinterGramPeerIdDisplay: Int32 {
|
||
case hidden = 0
|
||
case telegramApi = 1
|
||
case botApi = 2
|
||
}
|
||
|
||
public enum WinterGramTranslationProvider: Int32 {
|
||
case telegram = 0
|
||
case google = 1
|
||
case yandex = 2
|
||
case system = 3
|
||
}
|
||
|
||
public enum WinterGramWebviewPlatform: Int32 {
|
||
case auto = 0
|
||
case ios = 1
|
||
case android = 2
|
||
case macos = 3
|
||
case desktop = 4
|
||
}
|
||
|
||
public enum WinterGramIconPack: Int32 {
|
||
case wintergram = 0
|
||
case ayugram = 1
|
||
case exteragram = 2
|
||
case telegram = 3
|
||
}
|
||
|
||
// Style of the persistent top-center branding pill shown around the Dynamic Island / notch.
|
||
public enum WinterGramTopBannerStyle: Int32 {
|
||
case off = 0
|
||
case solid = 1
|
||
case glass = 2
|
||
case gradient = 3
|
||
case outline = 4
|
||
}
|
||
|
||
public struct WinterGramLiquidGlass: Codable, Equatable {
|
||
public var enabled: Bool
|
||
public var transparency: Double
|
||
public var blurRadius: Double
|
||
public var tintColor: Int32?
|
||
public var vibrancy: Bool
|
||
public var applyToChatList: Bool
|
||
public var applyToBubbles: Bool
|
||
public var applyToNavigationBars: Bool
|
||
public var applyToTabBar: Bool
|
||
|
||
public static var defaultSettings: WinterGramLiquidGlass {
|
||
return WinterGramLiquidGlass(
|
||
enabled: true,
|
||
transparency: 0.75,
|
||
blurRadius: 30.0,
|
||
tintColor: nil,
|
||
vibrancy: true,
|
||
applyToChatList: true,
|
||
applyToBubbles: false,
|
||
applyToNavigationBars: true,
|
||
applyToTabBar: true
|
||
)
|
||
}
|
||
|
||
public init(
|
||
enabled: Bool,
|
||
transparency: Double,
|
||
blurRadius: Double,
|
||
tintColor: Int32?,
|
||
vibrancy: Bool,
|
||
applyToChatList: Bool,
|
||
applyToBubbles: Bool,
|
||
applyToNavigationBars: Bool,
|
||
applyToTabBar: Bool
|
||
) {
|
||
self.enabled = enabled
|
||
self.transparency = transparency
|
||
self.blurRadius = blurRadius
|
||
self.tintColor = tintColor
|
||
self.vibrancy = vibrancy
|
||
self.applyToChatList = applyToChatList
|
||
self.applyToBubbles = applyToBubbles
|
||
self.applyToNavigationBars = applyToNavigationBars
|
||
self.applyToTabBar = applyToTabBar
|
||
}
|
||
|
||
public init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||
self.enabled = try container.decodeIfPresent(Bool.self, forKey: "enabled") ?? true
|
||
self.transparency = try container.decodeIfPresent(Double.self, forKey: "transparency") ?? 0.75
|
||
self.blurRadius = try container.decodeIfPresent(Double.self, forKey: "blurRadius") ?? 30.0
|
||
self.tintColor = try container.decodeIfPresent(Int32.self, forKey: "tintColor")
|
||
self.vibrancy = try container.decodeIfPresent(Bool.self, forKey: "vibrancy") ?? true
|
||
self.applyToChatList = try container.decodeIfPresent(Bool.self, forKey: "applyToChatList") ?? true
|
||
self.applyToBubbles = try container.decodeIfPresent(Bool.self, forKey: "applyToBubbles") ?? false
|
||
self.applyToNavigationBars = try container.decodeIfPresent(Bool.self, forKey: "applyToNavigationBars") ?? true
|
||
self.applyToTabBar = try container.decodeIfPresent(Bool.self, forKey: "applyToTabBar") ?? true
|
||
}
|
||
|
||
public func encode(to encoder: Encoder) throws {
|
||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||
try container.encode(self.enabled, forKey: "enabled")
|
||
try container.encode(self.transparency, forKey: "transparency")
|
||
try container.encode(self.blurRadius, forKey: "blurRadius")
|
||
try container.encodeIfPresent(self.tintColor, forKey: "tintColor")
|
||
try container.encode(self.vibrancy, forKey: "vibrancy")
|
||
try container.encode(self.applyToChatList, forKey: "applyToChatList")
|
||
try container.encode(self.applyToBubbles, forKey: "applyToBubbles")
|
||
try container.encode(self.applyToNavigationBars, forKey: "applyToNavigationBars")
|
||
try container.encode(self.applyToTabBar, forKey: "applyToTabBar")
|
||
}
|
||
}
|
||
|
||
public struct WinterGramSpoofPreset: Codable, Equatable {
|
||
public var name: String
|
||
public var deviceModel: String
|
||
public var appVersion: String
|
||
|
||
public init(name: String, deviceModel: String, appVersion: String) {
|
||
self.name = name
|
||
self.deviceModel = deviceModel
|
||
self.appVersion = appVersion
|
||
}
|
||
}
|
||
|
||
public struct WinterGramStashPrivacySettings: Codable, Equatable {
|
||
public var profilePhoto: Bool
|
||
public var phoneNumber: Bool
|
||
public var presence: Bool
|
||
public var forwards: Bool
|
||
public var voiceCalls: Bool
|
||
public var birthday: Bool
|
||
public var giftsAutoSave: Bool
|
||
public var bio: Bool
|
||
public var savedMusic: Bool
|
||
public var groupInvitations: Bool
|
||
|
||
public static var defaultSettings: WinterGramStashPrivacySettings {
|
||
return WinterGramStashPrivacySettings(
|
||
profilePhoto: true,
|
||
phoneNumber: false,
|
||
presence: false,
|
||
forwards: false,
|
||
voiceCalls: false,
|
||
birthday: false,
|
||
giftsAutoSave: false,
|
||
bio: false,
|
||
savedMusic: false,
|
||
groupInvitations: false
|
||
)
|
||
}
|
||
|
||
public init(profilePhoto: Bool, phoneNumber: Bool, presence: Bool, forwards: Bool, voiceCalls: Bool, birthday: Bool, giftsAutoSave: Bool, bio: Bool, savedMusic: Bool, groupInvitations: Bool) {
|
||
self.profilePhoto = profilePhoto
|
||
self.phoneNumber = phoneNumber
|
||
self.presence = presence
|
||
self.forwards = forwards
|
||
self.voiceCalls = voiceCalls
|
||
self.birthday = birthday
|
||
self.giftsAutoSave = giftsAutoSave
|
||
self.bio = bio
|
||
self.savedMusic = savedMusic
|
||
self.groupInvitations = groupInvitations
|
||
}
|
||
|
||
public var hasAny: Bool {
|
||
return self.profilePhoto || self.phoneNumber || self.presence || self.forwards || self.voiceCalls || self.birthday || self.giftsAutoSave || self.bio || self.savedMusic || self.groupInvitations
|
||
}
|
||
}
|
||
|
||
public struct WinterGramVisualGift: Codable, Equatable {
|
||
public var id: String
|
||
public var gift: StarGift
|
||
public var fromPeer: EnginePeer?
|
||
|
||
public init(id: String, gift: StarGift, fromPeer: EnginePeer?) {
|
||
self.id = id
|
||
self.gift = gift
|
||
self.fromPeer = fromPeer
|
||
}
|
||
|
||
private enum CodingKeys: String, CodingKey {
|
||
case id
|
||
case gift
|
||
case fromPeerId
|
||
}
|
||
|
||
public init(from decoder: any Decoder) throws {
|
||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||
self.id = try container.decode(String.self, forKey: .id)
|
||
self.gift = try container.decode(StarGift.self, forKey: .gift)
|
||
// `EnginePeer` isn't `Codable` and can't be rebuilt without a postbox.
|
||
// Like TelegramCore's `StarGift`, only the peer id is persisted and `fromPeer` is left
|
||
// nil on decode, to be resolved lazily by the consumer when a peer is actually needed.
|
||
self.fromPeer = nil
|
||
}
|
||
|
||
public func encode(to encoder: any Encoder) throws {
|
||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||
try container.encode(self.id, forKey: .id)
|
||
try container.encode(self.gift, forKey: .gift)
|
||
try container.encodeIfPresent(self.fromPeer?.id, forKey: .fromPeerId)
|
||
}
|
||
}
|
||
|
||
public struct WinterGramSettings: Codable, Equatable {
|
||
// Ghost mode & privacy
|
||
public var ghostModeEnabled: Bool
|
||
public var sendReadReceipts: Bool
|
||
public var sendReadStories: Bool
|
||
public var sendOnlineStatus: Bool
|
||
public var sendUploadProgress: Bool
|
||
public var sendOfflineAfterOnline: Bool
|
||
public var markReadAfterAction: Bool
|
||
public var useScheduledMessages: Bool
|
||
public var sendWithoutSound: WinterGramSendWithoutSound
|
||
public var suggestGhostBeforeStory: Bool
|
||
|
||
// Local history
|
||
public var saveDeletedMessages: Bool
|
||
public var saveMessagesHistory: Bool
|
||
public var saveForBots: Bool
|
||
public var saveSelfDestructMessages: Bool
|
||
public var hideFromBlocked: Bool
|
||
public var semiTransparentDeletedMessages: Bool
|
||
// Show the deletion time next to the deleted-message marker emoji.
|
||
public var showDeletedTime: Bool
|
||
|
||
// Hidden archive ("ААрхив")
|
||
public var stashedPeerIds: [Int64]
|
||
public var stashMuteNotifications: Bool
|
||
public var stashAutoMarkRead: Bool
|
||
public var stashPrivacy: WinterGramStashPrivacySettings
|
||
// Optional passcode (digits) required to open the hidden stash; empty = no passcode.
|
||
public var stashPasscode: String
|
||
|
||
// Anti-features
|
||
public var disableAds: Bool
|
||
public var localPremium: Bool
|
||
public var shadowBanIds: [Int64]
|
||
public var disableStories: Bool
|
||
public var hidePremiumStatuses: Bool
|
||
public var disableOpenLinkWarning: Bool
|
||
public var disableCopyProtection: Bool
|
||
public var forwardWithoutAuthor: Bool
|
||
public var allowScreenshots: Bool
|
||
|
||
// Confirmations
|
||
public var stickerConfirmation: Bool
|
||
public var gifConfirmation: Bool
|
||
public var voiceConfirmation: Bool
|
||
public var confirmStoryView: Bool
|
||
|
||
// Message decorations
|
||
public var showMessageSeconds: Bool
|
||
public var showPeerId: WinterGramPeerIdDisplay
|
||
public var showRegistrationDate: Bool
|
||
public var hideEditedMark: Bool
|
||
public var deletedMark: String
|
||
public var editedMark: String
|
||
public var recentStickersCount: Int32
|
||
|
||
// Translation
|
||
public var translateMessages: Bool
|
||
public var translationProvider: WinterGramTranslationProvider
|
||
|
||
// Webview / platform spoofing
|
||
public var webviewSpoofPlatform: WinterGramWebviewPlatform
|
||
public var increaseWebviewHeight: Bool
|
||
|
||
// Session device / app-version spoofing (applied at next launch). nil means real value.
|
||
public var spoofDeviceModel: String?
|
||
public var spoofAppVersion: String?
|
||
public var spoofPresets: [WinterGramSpoofPreset]
|
||
public var visualGifts: [WinterGramVisualGift]
|
||
|
||
// Custom Telegram API credentials (applied at next launch). nil means the built-in values.
|
||
public var customApiId: Int32?
|
||
public var customApiHash: String?
|
||
|
||
// Emoji used for the double-tap quick reaction; empty means Telegram's configured one.
|
||
public var customDefaultReaction: String
|
||
|
||
// Log online/offline transitions of peers whose chats you open.
|
||
public var trackOnlineStatus: Bool
|
||
|
||
// Appearance & customization
|
||
public var liquidGlass: WinterGramLiquidGlass
|
||
public var materialDesign: Bool
|
||
public var avatarCornerRadius: Int32
|
||
public var singleCornerRadius: Bool
|
||
public var messageBubbleRadius: Int32
|
||
public var removeMessageTail: Bool
|
||
public var customFont: String?
|
||
public var monoFont: String?
|
||
public var appIcon: String
|
||
public var iconPack: WinterGramIconPack
|
||
public var showOnlyAddedEmojisAndStickers: Bool
|
||
public var useDefaultBranding: Bool
|
||
public var topBannerStyle: WinterGramTopBannerStyle
|
||
public var topBannerName: String
|
||
|
||
public static var defaultSettings: WinterGramSettings {
|
||
return WinterGramSettings(
|
||
ghostModeEnabled: false,
|
||
sendReadReceipts: true,
|
||
sendReadStories: true,
|
||
sendOnlineStatus: true,
|
||
sendUploadProgress: true,
|
||
sendOfflineAfterOnline: false,
|
||
markReadAfterAction: true,
|
||
useScheduledMessages: false,
|
||
sendWithoutSound: .never,
|
||
suggestGhostBeforeStory: true,
|
||
saveDeletedMessages: true,
|
||
saveMessagesHistory: true,
|
||
saveForBots: false,
|
||
saveSelfDestructMessages: false,
|
||
hideFromBlocked: false,
|
||
semiTransparentDeletedMessages: true,
|
||
showDeletedTime: true,
|
||
stashedPeerIds: [],
|
||
stashMuteNotifications: true,
|
||
stashAutoMarkRead: false,
|
||
stashPrivacy: .defaultSettings,
|
||
stashPasscode: "",
|
||
disableAds: true,
|
||
localPremium: false,
|
||
shadowBanIds: [],
|
||
disableStories: false,
|
||
hidePremiumStatuses: false,
|
||
disableOpenLinkWarning: false,
|
||
disableCopyProtection: false,
|
||
forwardWithoutAuthor: false,
|
||
allowScreenshots: false,
|
||
stickerConfirmation: false,
|
||
gifConfirmation: false,
|
||
voiceConfirmation: false,
|
||
confirmStoryView: false,
|
||
showMessageSeconds: false,
|
||
showPeerId: .botApi,
|
||
showRegistrationDate: false,
|
||
hideEditedMark: false,
|
||
deletedMark: "🧹",
|
||
editedMark: "",
|
||
recentStickersCount: 100,
|
||
translateMessages: false,
|
||
translationProvider: .telegram,
|
||
webviewSpoofPlatform: .auto,
|
||
increaseWebviewHeight: false,
|
||
spoofDeviceModel: nil,
|
||
spoofAppVersion: nil,
|
||
spoofPresets: [],
|
||
visualGifts: [],
|
||
customApiId: nil,
|
||
customApiHash: nil,
|
||
customDefaultReaction: "",
|
||
trackOnlineStatus: false,
|
||
liquidGlass: .defaultSettings,
|
||
materialDesign: true,
|
||
avatarCornerRadius: 50,
|
||
singleCornerRadius: false,
|
||
messageBubbleRadius: 16,
|
||
removeMessageTail: false,
|
||
customFont: nil,
|
||
monoFont: nil,
|
||
appIcon: "WinterGramDark",
|
||
iconPack: .wintergram,
|
||
showOnlyAddedEmojisAndStickers: false,
|
||
useDefaultBranding: false,
|
||
topBannerStyle: .solid,
|
||
topBannerName: "WntGramBanner"
|
||
)
|
||
}
|
||
|
||
public init(
|
||
ghostModeEnabled: Bool,
|
||
sendReadReceipts: Bool,
|
||
sendReadStories: Bool,
|
||
sendOnlineStatus: Bool,
|
||
sendUploadProgress: Bool,
|
||
sendOfflineAfterOnline: Bool,
|
||
markReadAfterAction: Bool,
|
||
useScheduledMessages: Bool,
|
||
sendWithoutSound: WinterGramSendWithoutSound,
|
||
suggestGhostBeforeStory: Bool,
|
||
saveDeletedMessages: Bool,
|
||
saveMessagesHistory: Bool,
|
||
saveForBots: Bool,
|
||
saveSelfDestructMessages: Bool,
|
||
hideFromBlocked: Bool,
|
||
semiTransparentDeletedMessages: Bool,
|
||
showDeletedTime: Bool,
|
||
stashedPeerIds: [Int64],
|
||
stashMuteNotifications: Bool,
|
||
stashAutoMarkRead: Bool,
|
||
stashPrivacy: WinterGramStashPrivacySettings,
|
||
stashPasscode: String,
|
||
disableAds: Bool,
|
||
localPremium: Bool,
|
||
shadowBanIds: [Int64],
|
||
disableStories: Bool,
|
||
hidePremiumStatuses: Bool,
|
||
disableOpenLinkWarning: Bool,
|
||
disableCopyProtection: Bool,
|
||
forwardWithoutAuthor: Bool,
|
||
allowScreenshots: Bool,
|
||
stickerConfirmation: Bool,
|
||
gifConfirmation: Bool,
|
||
voiceConfirmation: Bool,
|
||
confirmStoryView: Bool,
|
||
showMessageSeconds: Bool,
|
||
showPeerId: WinterGramPeerIdDisplay,
|
||
showRegistrationDate: Bool,
|
||
hideEditedMark: Bool,
|
||
deletedMark: String,
|
||
editedMark: String,
|
||
recentStickersCount: Int32,
|
||
translateMessages: Bool,
|
||
translationProvider: WinterGramTranslationProvider,
|
||
webviewSpoofPlatform: WinterGramWebviewPlatform,
|
||
increaseWebviewHeight: Bool,
|
||
spoofDeviceModel: String?,
|
||
spoofAppVersion: String?,
|
||
spoofPresets: [WinterGramSpoofPreset],
|
||
visualGifts: [WinterGramVisualGift],
|
||
customApiId: Int32?,
|
||
customApiHash: String?,
|
||
customDefaultReaction: String,
|
||
trackOnlineStatus: Bool,
|
||
liquidGlass: WinterGramLiquidGlass,
|
||
materialDesign: Bool,
|
||
avatarCornerRadius: Int32,
|
||
singleCornerRadius: Bool,
|
||
messageBubbleRadius: Int32,
|
||
removeMessageTail: Bool,
|
||
customFont: String?,
|
||
monoFont: String?,
|
||
appIcon: String,
|
||
iconPack: WinterGramIconPack,
|
||
showOnlyAddedEmojisAndStickers: Bool,
|
||
useDefaultBranding: Bool,
|
||
topBannerStyle: WinterGramTopBannerStyle,
|
||
topBannerName: String
|
||
) {
|
||
self.ghostModeEnabled = ghostModeEnabled
|
||
self.sendReadReceipts = sendReadReceipts
|
||
self.sendReadStories = sendReadStories
|
||
self.sendOnlineStatus = sendOnlineStatus
|
||
self.sendUploadProgress = sendUploadProgress
|
||
self.sendOfflineAfterOnline = sendOfflineAfterOnline
|
||
self.markReadAfterAction = markReadAfterAction
|
||
self.useScheduledMessages = useScheduledMessages
|
||
self.sendWithoutSound = sendWithoutSound
|
||
self.suggestGhostBeforeStory = suggestGhostBeforeStory
|
||
self.saveDeletedMessages = saveDeletedMessages
|
||
self.saveMessagesHistory = saveMessagesHistory
|
||
self.saveForBots = saveForBots
|
||
self.saveSelfDestructMessages = saveSelfDestructMessages
|
||
self.hideFromBlocked = hideFromBlocked
|
||
self.semiTransparentDeletedMessages = semiTransparentDeletedMessages
|
||
self.showDeletedTime = showDeletedTime
|
||
self.stashedPeerIds = stashedPeerIds
|
||
self.stashMuteNotifications = stashMuteNotifications
|
||
self.stashAutoMarkRead = stashAutoMarkRead
|
||
self.stashPrivacy = stashPrivacy
|
||
self.stashPasscode = stashPasscode
|
||
self.disableAds = disableAds
|
||
self.localPremium = localPremium
|
||
self.shadowBanIds = shadowBanIds
|
||
self.disableStories = disableStories
|
||
self.hidePremiumStatuses = hidePremiumStatuses
|
||
self.disableOpenLinkWarning = disableOpenLinkWarning
|
||
self.disableCopyProtection = disableCopyProtection
|
||
self.forwardWithoutAuthor = forwardWithoutAuthor
|
||
self.allowScreenshots = allowScreenshots
|
||
self.stickerConfirmation = stickerConfirmation
|
||
self.gifConfirmation = gifConfirmation
|
||
self.voiceConfirmation = voiceConfirmation
|
||
self.confirmStoryView = confirmStoryView
|
||
self.showMessageSeconds = showMessageSeconds
|
||
self.showPeerId = showPeerId
|
||
self.showRegistrationDate = showRegistrationDate
|
||
self.hideEditedMark = hideEditedMark
|
||
self.deletedMark = deletedMark
|
||
self.editedMark = editedMark
|
||
self.recentStickersCount = recentStickersCount
|
||
self.translateMessages = translateMessages
|
||
self.translationProvider = translationProvider
|
||
self.webviewSpoofPlatform = webviewSpoofPlatform
|
||
self.increaseWebviewHeight = increaseWebviewHeight
|
||
self.spoofDeviceModel = spoofDeviceModel
|
||
self.spoofAppVersion = spoofAppVersion
|
||
self.spoofPresets = spoofPresets
|
||
self.visualGifts = visualGifts
|
||
self.customApiId = customApiId
|
||
self.customApiHash = customApiHash
|
||
self.customDefaultReaction = customDefaultReaction
|
||
self.trackOnlineStatus = trackOnlineStatus
|
||
self.liquidGlass = liquidGlass
|
||
self.materialDesign = materialDesign
|
||
self.avatarCornerRadius = avatarCornerRadius
|
||
self.singleCornerRadius = singleCornerRadius
|
||
self.messageBubbleRadius = messageBubbleRadius
|
||
self.removeMessageTail = removeMessageTail
|
||
self.customFont = customFont
|
||
self.monoFont = monoFont
|
||
self.appIcon = appIcon
|
||
self.iconPack = iconPack
|
||
self.showOnlyAddedEmojisAndStickers = showOnlyAddedEmojisAndStickers
|
||
self.useDefaultBranding = useDefaultBranding
|
||
self.topBannerStyle = topBannerStyle
|
||
self.topBannerName = topBannerName
|
||
}
|
||
|
||
public init(from decoder: Decoder) throws {
|
||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||
let defaults = WinterGramSettings.defaultSettings
|
||
|
||
self.ghostModeEnabled = try container.decodeIfPresent(Bool.self, forKey: "ghostModeEnabled") ?? defaults.ghostModeEnabled
|
||
self.sendReadReceipts = try container.decodeIfPresent(Bool.self, forKey: "sendReadReceipts") ?? defaults.sendReadReceipts
|
||
self.sendReadStories = try container.decodeIfPresent(Bool.self, forKey: "sendReadStories") ?? defaults.sendReadStories
|
||
self.sendOnlineStatus = try container.decodeIfPresent(Bool.self, forKey: "sendOnlineStatus") ?? defaults.sendOnlineStatus
|
||
self.sendUploadProgress = try container.decodeIfPresent(Bool.self, forKey: "sendUploadProgress") ?? defaults.sendUploadProgress
|
||
self.sendOfflineAfterOnline = try container.decodeIfPresent(Bool.self, forKey: "sendOfflineAfterOnline") ?? defaults.sendOfflineAfterOnline
|
||
self.markReadAfterAction = try container.decodeIfPresent(Bool.self, forKey: "markReadAfterAction") ?? defaults.markReadAfterAction
|
||
self.useScheduledMessages = try container.decodeIfPresent(Bool.self, forKey: "useScheduledMessages") ?? defaults.useScheduledMessages
|
||
self.sendWithoutSound = WinterGramSendWithoutSound(rawValue: try container.decodeIfPresent(Int32.self, forKey: "sendWithoutSound") ?? defaults.sendWithoutSound.rawValue) ?? defaults.sendWithoutSound
|
||
self.suggestGhostBeforeStory = try container.decodeIfPresent(Bool.self, forKey: "suggestGhostBeforeStory") ?? defaults.suggestGhostBeforeStory
|
||
self.saveDeletedMessages = try container.decodeIfPresent(Bool.self, forKey: "saveDeletedMessages") ?? defaults.saveDeletedMessages
|
||
self.saveMessagesHistory = try container.decodeIfPresent(Bool.self, forKey: "saveMessagesHistory") ?? defaults.saveMessagesHistory
|
||
self.saveForBots = try container.decodeIfPresent(Bool.self, forKey: "saveForBots") ?? defaults.saveForBots
|
||
self.saveSelfDestructMessages = try container.decodeIfPresent(Bool.self, forKey: "saveSelfDestructMessages") ?? defaults.saveSelfDestructMessages
|
||
self.hideFromBlocked = try container.decodeIfPresent(Bool.self, forKey: "hideFromBlocked") ?? defaults.hideFromBlocked
|
||
self.semiTransparentDeletedMessages = try container.decodeIfPresent(Bool.self, forKey: "semiTransparentDeletedMessages") ?? defaults.semiTransparentDeletedMessages
|
||
self.showDeletedTime = try container.decodeIfPresent(Bool.self, forKey: "showDeletedTime") ?? defaults.showDeletedTime
|
||
self.stashedPeerIds = try container.decodeIfPresent([Int64].self, forKey: "stashedPeerIds") ?? defaults.stashedPeerIds
|
||
self.stashMuteNotifications = try container.decodeIfPresent(Bool.self, forKey: "stashMuteNotifications") ?? defaults.stashMuteNotifications
|
||
self.stashAutoMarkRead = try container.decodeIfPresent(Bool.self, forKey: "stashAutoMarkRead") ?? defaults.stashAutoMarkRead
|
||
if let legacyProfilePhoto = try container.decodeIfPresent(Bool.self, forKey: "stashHideProfilePhotoFromPeer") {
|
||
var migrated = defaults.stashPrivacy
|
||
migrated.profilePhoto = legacyProfilePhoto
|
||
self.stashPrivacy = try container.decodeIfPresent(WinterGramStashPrivacySettings.self, forKey: "stashPrivacy") ?? migrated
|
||
} else {
|
||
self.stashPrivacy = try container.decodeIfPresent(WinterGramStashPrivacySettings.self, forKey: "stashPrivacy") ?? defaults.stashPrivacy
|
||
}
|
||
self.stashPasscode = try container.decodeIfPresent(String.self, forKey: "stashPasscode") ?? defaults.stashPasscode
|
||
self.disableAds = try container.decodeIfPresent(Bool.self, forKey: "disableAds") ?? defaults.disableAds
|
||
self.localPremium = try container.decodeIfPresent(Bool.self, forKey: "localPremium") ?? defaults.localPremium
|
||
self.shadowBanIds = try container.decodeIfPresent([Int64].self, forKey: "shadowBanIds") ?? defaults.shadowBanIds
|
||
self.disableStories = try container.decodeIfPresent(Bool.self, forKey: "disableStories") ?? defaults.disableStories
|
||
self.hidePremiumStatuses = try container.decodeIfPresent(Bool.self, forKey: "hidePremiumStatuses") ?? defaults.hidePremiumStatuses
|
||
self.disableOpenLinkWarning = try container.decodeIfPresent(Bool.self, forKey: "disableOpenLinkWarning") ?? defaults.disableOpenLinkWarning
|
||
self.disableCopyProtection = try container.decodeIfPresent(Bool.self, forKey: "disableCopyProtection") ?? defaults.disableCopyProtection
|
||
self.forwardWithoutAuthor = try container.decodeIfPresent(Bool.self, forKey: "forwardWithoutAuthor") ?? defaults.forwardWithoutAuthor
|
||
self.allowScreenshots = try container.decodeIfPresent(Bool.self, forKey: "allowScreenshots") ?? defaults.allowScreenshots
|
||
self.stickerConfirmation = try container.decodeIfPresent(Bool.self, forKey: "stickerConfirmation") ?? defaults.stickerConfirmation
|
||
self.gifConfirmation = try container.decodeIfPresent(Bool.self, forKey: "gifConfirmation") ?? defaults.gifConfirmation
|
||
self.voiceConfirmation = try container.decodeIfPresent(Bool.self, forKey: "voiceConfirmation") ?? defaults.voiceConfirmation
|
||
self.confirmStoryView = try container.decodeIfPresent(Bool.self, forKey: "confirmStoryView") ?? defaults.confirmStoryView
|
||
self.showMessageSeconds = try container.decodeIfPresent(Bool.self, forKey: "showMessageSeconds") ?? defaults.showMessageSeconds
|
||
self.showPeerId = WinterGramPeerIdDisplay(rawValue: try container.decodeIfPresent(Int32.self, forKey: "showPeerId") ?? defaults.showPeerId.rawValue) ?? defaults.showPeerId
|
||
self.showRegistrationDate = try container.decodeIfPresent(Bool.self, forKey: "showRegistrationDate") ?? defaults.showRegistrationDate
|
||
self.hideEditedMark = try container.decodeIfPresent(Bool.self, forKey: "hideEditedMark") ?? defaults.hideEditedMark
|
||
self.deletedMark = try container.decodeIfPresent(String.self, forKey: "deletedMark") ?? defaults.deletedMark
|
||
self.editedMark = try container.decodeIfPresent(String.self, forKey: "editedMark") ?? defaults.editedMark
|
||
self.recentStickersCount = try container.decodeIfPresent(Int32.self, forKey: "recentStickersCount") ?? defaults.recentStickersCount
|
||
self.translateMessages = try container.decodeIfPresent(Bool.self, forKey: "translateMessages") ?? defaults.translateMessages
|
||
self.translationProvider = WinterGramTranslationProvider(rawValue: try container.decodeIfPresent(Int32.self, forKey: "translationProvider") ?? defaults.translationProvider.rawValue) ?? defaults.translationProvider
|
||
self.webviewSpoofPlatform = WinterGramWebviewPlatform(rawValue: try container.decodeIfPresent(Int32.self, forKey: "webviewSpoofPlatform") ?? defaults.webviewSpoofPlatform.rawValue) ?? defaults.webviewSpoofPlatform
|
||
self.increaseWebviewHeight = try container.decodeIfPresent(Bool.self, forKey: "increaseWebviewHeight") ?? defaults.increaseWebviewHeight
|
||
self.spoofDeviceModel = try container.decodeIfPresent(String.self, forKey: "spoofDeviceModel")
|
||
self.spoofAppVersion = try container.decodeIfPresent(String.self, forKey: "spoofAppVersion")
|
||
self.spoofPresets = try container.decodeIfPresent([WinterGramSpoofPreset].self, forKey: "spoofPresets") ?? defaults.spoofPresets
|
||
self.visualGifts = try container.decodeIfPresent([WinterGramVisualGift].self, forKey: "visualGifts") ?? defaults.visualGifts
|
||
self.customApiId = try container.decodeIfPresent(Int32.self, forKey: "customApiId")
|
||
self.customApiHash = try container.decodeIfPresent(String.self, forKey: "customApiHash")
|
||
self.customDefaultReaction = try container.decodeIfPresent(String.self, forKey: "customDefaultReaction") ?? defaults.customDefaultReaction
|
||
self.trackOnlineStatus = try container.decodeIfPresent(Bool.self, forKey: "trackOnlineStatus") ?? defaults.trackOnlineStatus
|
||
self.liquidGlass = try container.decodeIfPresent(WinterGramLiquidGlass.self, forKey: "liquidGlass") ?? defaults.liquidGlass
|
||
self.materialDesign = try container.decodeIfPresent(Bool.self, forKey: "materialDesign") ?? defaults.materialDesign
|
||
self.avatarCornerRadius = try container.decodeIfPresent(Int32.self, forKey: "avatarCornerRadius") ?? defaults.avatarCornerRadius
|
||
self.singleCornerRadius = try container.decodeIfPresent(Bool.self, forKey: "singleCornerRadius") ?? defaults.singleCornerRadius
|
||
self.messageBubbleRadius = try container.decodeIfPresent(Int32.self, forKey: "messageBubbleRadius") ?? defaults.messageBubbleRadius
|
||
self.removeMessageTail = try container.decodeIfPresent(Bool.self, forKey: "removeMessageTail") ?? defaults.removeMessageTail
|
||
self.customFont = try container.decodeIfPresent(String.self, forKey: "customFont")
|
||
self.monoFont = try container.decodeIfPresent(String.self, forKey: "monoFont")
|
||
self.appIcon = try container.decodeIfPresent(String.self, forKey: "appIcon") ?? defaults.appIcon
|
||
self.iconPack = WinterGramIconPack(rawValue: try container.decodeIfPresent(Int32.self, forKey: "iconPack") ?? defaults.iconPack.rawValue) ?? defaults.iconPack
|
||
self.showOnlyAddedEmojisAndStickers = try container.decodeIfPresent(Bool.self, forKey: "showOnlyAddedEmojisAndStickers") ?? defaults.showOnlyAddedEmojisAndStickers
|
||
self.useDefaultBranding = try container.decodeIfPresent(Bool.self, forKey: "useDefaultBranding") ?? defaults.useDefaultBranding
|
||
self.topBannerStyle = WinterGramTopBannerStyle(rawValue: try container.decodeIfPresent(Int32.self, forKey: "topBannerStyle") ?? defaults.topBannerStyle.rawValue) ?? defaults.topBannerStyle
|
||
self.topBannerName = try container.decodeIfPresent(String.self, forKey: "topBannerName") ?? defaults.topBannerName
|
||
}
|
||
|
||
public func encode(to encoder: Encoder) throws {
|
||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||
try container.encode(self.ghostModeEnabled, forKey: "ghostModeEnabled")
|
||
try container.encode(self.sendReadReceipts, forKey: "sendReadReceipts")
|
||
try container.encode(self.sendReadStories, forKey: "sendReadStories")
|
||
try container.encode(self.sendOnlineStatus, forKey: "sendOnlineStatus")
|
||
try container.encode(self.sendUploadProgress, forKey: "sendUploadProgress")
|
||
try container.encode(self.sendOfflineAfterOnline, forKey: "sendOfflineAfterOnline")
|
||
try container.encode(self.markReadAfterAction, forKey: "markReadAfterAction")
|
||
try container.encode(self.useScheduledMessages, forKey: "useScheduledMessages")
|
||
try container.encode(self.sendWithoutSound.rawValue, forKey: "sendWithoutSound")
|
||
try container.encode(self.suggestGhostBeforeStory, forKey: "suggestGhostBeforeStory")
|
||
try container.encode(self.saveDeletedMessages, forKey: "saveDeletedMessages")
|
||
try container.encode(self.saveMessagesHistory, forKey: "saveMessagesHistory")
|
||
try container.encode(self.saveForBots, forKey: "saveForBots")
|
||
try container.encode(self.saveSelfDestructMessages, forKey: "saveSelfDestructMessages")
|
||
try container.encode(self.hideFromBlocked, forKey: "hideFromBlocked")
|
||
try container.encode(self.semiTransparentDeletedMessages, forKey: "semiTransparentDeletedMessages")
|
||
try container.encode(self.showDeletedTime, forKey: "showDeletedTime")
|
||
try container.encode(self.stashedPeerIds, forKey: "stashedPeerIds")
|
||
try container.encode(self.stashMuteNotifications, forKey: "stashMuteNotifications")
|
||
try container.encode(self.stashAutoMarkRead, forKey: "stashAutoMarkRead")
|
||
try container.encode(self.stashPrivacy, forKey: "stashPrivacy")
|
||
try container.encode(self.stashPasscode, forKey: "stashPasscode")
|
||
try container.encode(self.disableAds, forKey: "disableAds")
|
||
try container.encode(self.localPremium, forKey: "localPremium")
|
||
try container.encode(self.shadowBanIds, forKey: "shadowBanIds")
|
||
try container.encode(self.disableStories, forKey: "disableStories")
|
||
try container.encode(self.hidePremiumStatuses, forKey: "hidePremiumStatuses")
|
||
try container.encode(self.disableOpenLinkWarning, forKey: "disableOpenLinkWarning")
|
||
try container.encode(self.disableCopyProtection, forKey: "disableCopyProtection")
|
||
try container.encode(self.forwardWithoutAuthor, forKey: "forwardWithoutAuthor")
|
||
try container.encode(self.allowScreenshots, forKey: "allowScreenshots")
|
||
try container.encode(self.stickerConfirmation, forKey: "stickerConfirmation")
|
||
try container.encode(self.gifConfirmation, forKey: "gifConfirmation")
|
||
try container.encode(self.voiceConfirmation, forKey: "voiceConfirmation")
|
||
try container.encode(self.confirmStoryView, forKey: "confirmStoryView")
|
||
try container.encode(self.showMessageSeconds, forKey: "showMessageSeconds")
|
||
try container.encode(self.showPeerId.rawValue, forKey: "showPeerId")
|
||
try container.encode(self.showRegistrationDate, forKey: "showRegistrationDate")
|
||
try container.encode(self.hideEditedMark, forKey: "hideEditedMark")
|
||
try container.encode(self.deletedMark, forKey: "deletedMark")
|
||
try container.encode(self.editedMark, forKey: "editedMark")
|
||
try container.encode(self.recentStickersCount, forKey: "recentStickersCount")
|
||
try container.encode(self.translateMessages, forKey: "translateMessages")
|
||
try container.encode(self.translationProvider.rawValue, forKey: "translationProvider")
|
||
try container.encode(self.webviewSpoofPlatform.rawValue, forKey: "webviewSpoofPlatform")
|
||
try container.encode(self.increaseWebviewHeight, forKey: "increaseWebviewHeight")
|
||
try container.encodeIfPresent(self.spoofDeviceModel, forKey: "spoofDeviceModel")
|
||
try container.encodeIfPresent(self.spoofAppVersion, forKey: "spoofAppVersion")
|
||
try container.encode(self.spoofPresets, forKey: "spoofPresets")
|
||
try container.encode(self.visualGifts, forKey: "visualGifts")
|
||
try container.encodeIfPresent(self.customApiId, forKey: "customApiId")
|
||
try container.encodeIfPresent(self.customApiHash, forKey: "customApiHash")
|
||
try container.encode(self.customDefaultReaction, forKey: "customDefaultReaction")
|
||
try container.encode(self.trackOnlineStatus, forKey: "trackOnlineStatus")
|
||
try container.encode(self.liquidGlass, forKey: "liquidGlass")
|
||
try container.encode(self.materialDesign, forKey: "materialDesign")
|
||
try container.encode(self.avatarCornerRadius, forKey: "avatarCornerRadius")
|
||
try container.encode(self.singleCornerRadius, forKey: "singleCornerRadius")
|
||
try container.encode(self.messageBubbleRadius, forKey: "messageBubbleRadius")
|
||
try container.encode(self.removeMessageTail, forKey: "removeMessageTail")
|
||
try container.encodeIfPresent(self.customFont, forKey: "customFont")
|
||
try container.encodeIfPresent(self.monoFont, forKey: "monoFont")
|
||
try container.encode(self.appIcon, forKey: "appIcon")
|
||
try container.encode(self.iconPack.rawValue, forKey: "iconPack")
|
||
try container.encode(self.showOnlyAddedEmojisAndStickers, forKey: "showOnlyAddedEmojisAndStickers")
|
||
try container.encode(self.useDefaultBranding, forKey: "useDefaultBranding")
|
||
try container.encode(self.topBannerStyle.rawValue, forKey: "topBannerStyle")
|
||
try container.encode(self.topBannerName, forKey: "topBannerName")
|
||
}
|
||
|
||
|
||
|
||
public func isShadowBanned(_ peerId: Int64) -> Bool {
|
||
return self.shadowBanIds.contains(peerId)
|
||
}
|
||
|
||
public func isStashed(_ peerId: Int64) -> Bool {
|
||
return self.stashedPeerIds.contains(peerId)
|
||
}
|
||
|
||
// MARK: - Ghost-mode decision helpers (pure, unit-testable)
|
||
|
||
// These forward to WinterGramGhostLogic (a pure, standalone-testable file).
|
||
|
||
/// Read receipts must be withheld (the other side won't see "read").
|
||
public var suppressesReadReceipts: Bool {
|
||
return WinterGramGhostLogic.suppressesReadReceipts(ghostModeEnabled: self.ghostModeEnabled, sendReadReceipts: self.sendReadReceipts)
|
||
}
|
||
|
||
/// Online presence must be withheld (appear offline).
|
||
public var suppressesOnlinePresence: Bool {
|
||
return WinterGramGhostLogic.suppressesOnlinePresence(ghostModeEnabled: self.ghostModeEnabled, sendOnlineStatus: self.sendOnlineStatus)
|
||
}
|
||
|
||
/// Typing / upload activity must be withheld.
|
||
public var suppressesTypingStatus: Bool {
|
||
return WinterGramGhostLogic.suppressesTypingStatus(ghostModeEnabled: self.ghostModeEnabled, sendUploadProgress: self.sendUploadProgress)
|
||
}
|
||
|
||
/// Story views must be withheld (don't mark stories seen).
|
||
public var suppressesStoryViews: Bool {
|
||
return WinterGramGhostLogic.suppressesStoryViews(ghostModeEnabled: self.ghostModeEnabled, sendReadStories: self.sendReadStories)
|
||
}
|
||
|
||
/// When reads are suppressed but the user took an explicit action (sent a message),
|
||
/// the active chat should still be marked read.
|
||
public var shouldMarkReadAfterAction: Bool {
|
||
return WinterGramGhostLogic.shouldMarkReadAfterAction(ghostModeEnabled: self.ghostModeEnabled, sendReadReceipts: self.sendReadReceipts, markReadAfterAction: self.markReadAfterAction)
|
||
}
|
||
|
||
/// After an action forces the client online, immediately drop back to offline.
|
||
public var shouldGoOfflineAfterAction: Bool {
|
||
return WinterGramGhostLogic.shouldGoOfflineAfterAction(ghostModeEnabled: self.ghostModeEnabled, sendOfflineAfterOnline: self.sendOfflineAfterOnline)
|
||
}
|
||
|
||
public var shouldSendWithoutSound: Bool {
|
||
switch self.sendWithoutSound {
|
||
case .never:
|
||
return false
|
||
case .inGhostMode:
|
||
return self.ghostModeEnabled
|
||
case .always:
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
|
||
public func updateWinterGramSettingsInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (WinterGramSettings) -> WinterGramSettings) -> Signal<Void, NoError> {
|
||
return accountManager.transaction { transaction -> Void in
|
||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.winterGramSettings, { entry in
|
||
let currentSettings: WinterGramSettings
|
||
if let entry = entry?.get(WinterGramSettings.self) {
|
||
currentSettings = entry
|
||
} else {
|
||
currentSettings = .defaultSettings
|
||
}
|
||
return SharedPreferencesEntry(f(currentSettings))
|
||
})
|
||
}
|
||
}
|
||
|
||
public func winterGramSettings(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<WinterGramSettings, NoError> {
|
||
return accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.winterGramSettings])
|
||
|> map { sharedData -> WinterGramSettings in
|
||
return sharedData.entries[ApplicationSpecificSharedDataKeys.winterGramSettings]?.get(WinterGramSettings.self) ?? .defaultSettings
|
||
}
|
||
}
|
||
|
||
private let currentWinterGramSettingsValue = Atomic<WinterGramSettings>(value: .defaultSettings)
|
||
|
||
// Synchronous snapshot of the latest settings, for hook points that cannot subscribe to a Signal.
|
||
// Kept up to date by observeWinterGramSettings(accountManager:) at app startup.
|
||
public var currentWinterGramSettings: WinterGramSettings {
|
||
return currentWinterGramSettingsValue.with { $0 }
|
||
}
|
||
|
||
public func setCurrentWinterGramSettings(_ settings: WinterGramSettings) {
|
||
let _ = currentWinterGramSettingsValue.swap(settings)
|
||
}
|
||
|
||
public func observeWinterGramSettings(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Disposable {
|
||
return (winterGramSettings(accountManager: accountManager)
|
||
|> deliverOnMainQueue).start(next: { settings in
|
||
setCurrentWinterGramSettings(settings)
|
||
// Bridge branding to standard UserDefaults so the early-launch intro (Obj-C,
|
||
// runs before the settings store is up) can pick the title synchronously.
|
||
UserDefaults.standard.set(settings.useDefaultBranding, forKey: "wnt_useDefaultBranding")
|
||
// Bridge session spoof values to standard UserDefaults so the network stack, which is
|
||
// initialized at launch before the settings store is up, can read them synchronously.
|
||
// These take effect on the next launch.
|
||
if let spoofDeviceModel = settings.spoofDeviceModel, !spoofDeviceModel.isEmpty {
|
||
UserDefaults.standard.set(spoofDeviceModel, forKey: "wnt_spoofDeviceModel")
|
||
} else {
|
||
UserDefaults.standard.removeObject(forKey: "wnt_spoofDeviceModel")
|
||
}
|
||
if let spoofAppVersion = settings.spoofAppVersion, !spoofAppVersion.isEmpty {
|
||
UserDefaults.standard.set(spoofAppVersion, forKey: "wnt_spoofAppVersion")
|
||
} else {
|
||
UserDefaults.standard.removeObject(forKey: "wnt_spoofAppVersion")
|
||
}
|
||
// Custom API credentials (applied at next launch). Both must be set to take effect.
|
||
if let customApiId = settings.customApiId, customApiId != 0, let customApiHash = settings.customApiHash, !customApiHash.isEmpty {
|
||
UserDefaults.standard.set(Int(customApiId), forKey: "wnt_customApiId")
|
||
UserDefaults.standard.set(customApiHash, forKey: "wnt_customApiHash")
|
||
} else {
|
||
UserDefaults.standard.removeObject(forKey: "wnt_customApiId")
|
||
UserDefaults.standard.removeObject(forKey: "wnt_customApiHash")
|
||
}
|
||
let webviewPlatform: String?
|
||
switch settings.webviewSpoofPlatform {
|
||
case .auto:
|
||
webviewPlatform = nil
|
||
case .ios:
|
||
webviewPlatform = "ios"
|
||
case .android:
|
||
webviewPlatform = "android"
|
||
case .macos:
|
||
webviewPlatform = "macos"
|
||
case .desktop:
|
||
webviewPlatform = "tdesktop"
|
||
}
|
||
setCurrentWinterGramCoreSettings(WinterGramCoreSettings(
|
||
saveDeletedMessages: settings.saveDeletedMessages,
|
||
saveMessageEditHistory: settings.saveMessagesHistory,
|
||
saveForBots: settings.saveForBots,
|
||
saveSelfDestructMessages: settings.saveSelfDestructMessages,
|
||
allowScreenshots: settings.allowScreenshots,
|
||
webviewPlatform: webviewPlatform
|
||
))
|
||
})
|
||
}
|
||
|
||
public func isWinterGramOfficialPeer(_ peer: EnginePeer) -> Bool {
|
||
return WinterGramBadgeManager.shared.badge(for: peer) != nil
|
||
}
|
||
|
||
public func isWinterGramDeveloperPeer(_ peer: EnginePeer) -> Bool {
|
||
return WinterGramBadgeManager.shared.badge(for: peer)?.id == "developer"
|
||
}
|
||
|
||
public func winterGramBadgeImageName(for peer: EnginePeer) -> String? {
|
||
guard let badge = WinterGramBadgeManager.shared.badge(for: peer) else {
|
||
return nil
|
||
}
|
||
return badge.id == "developer" ? "WntGramDeveloperBadge" : "WinterGramSnowflake"
|
||
}
|