chore: migrate to new version + fixed several critical bugs

- Migrated project to latest Telegram iOS base (v12.3.2+)
- Fixed circular dependency between GhostModeManager and MiscSettingsManager
- Fixed multiple Bazel build configuration errors (select() default conditions)
- Fixed duplicate type definitions in PeerInfoScreen
- Fixed swiftmodule directory resolution in build scripts
- Added Ghostgram Settings tab in main Settings menu with all 5 features
- Cleared sensitive credentials from config.json (template-only now)
- Excluded bazel-cache from version control
This commit is contained in:
ichmagmaus 812
2026-02-23 23:04:32 +01:00
parent 703e291bcb
commit db53826061
1017 changed files with 62337 additions and 40559 deletions
@@ -139,6 +139,7 @@ enum AccountStateMutationOperation {
case UpdateMonoForumNoPaidException(peerId: PeerId, threadId: Int64, isFree: Bool)
case UpdateStarGiftAuctionState(giftId: Int64, state: GiftAuctionContext.State.AuctionState)
case UpdateStarGiftAuctionMyState(giftId: Int64, state: GiftAuctionContext.State.MyState)
case UpdateEmojiGameInfo(info: EmojiGameInfo)
}
struct HoleFromPreviousState {
@@ -737,9 +738,13 @@ struct AccountMutableState {
self.addOperation(.UpdateStarGiftAuctionMyState(giftId: giftId, state: state))
}
mutating func updateEmojiGameInfo(info: EmojiGameInfo) {
self.addOperation(.UpdateEmojiGameInfo(info: info))
}
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo:
break
case let .AddMessages(messages, location):
for message in messages {
@@ -892,6 +897,7 @@ struct AccountReplayedFinalState {
let addedConferenceInvitationMessagesIds: [MessageId]
let updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState]
let updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState]
let updatedEmojiGameInfo: EmojiGameInfo?
}
struct AccountFinalStateEvents {
@@ -927,12 +933,13 @@ struct AccountFinalStateEvents {
let addedConferenceInvitationMessagesIds: [MessageId]
let updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState]
let updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState]
let updatedEmojiGameInfo: EmojiGameInfo?
var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty && self.updatedEmojiGameInfo == nil
}
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:]) {
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:], updatedEmojiGameInfo: EmojiGameInfo? = nil) {
self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds
@@ -965,6 +972,7 @@ struct AccountFinalStateEvents {
self.addedConferenceInvitationMessagesIds = addedConferenceInvitationMessagesIds
self.updatedStarGiftAuctionState = updatedStarGiftAuctionState
self.updatedStarGiftAuctionMyState = updatedStarGiftAuctionMyState
self.updatedEmojiGameInfo = updatedEmojiGameInfo
}
init(state: AccountReplayedFinalState) {
@@ -1000,6 +1008,7 @@ struct AccountFinalStateEvents {
self.addedConferenceInvitationMessagesIds = state.addedConferenceInvitationMessagesIds
self.updatedStarGiftAuctionState = state.updatedStarGiftAuctionState
self.updatedStarGiftAuctionMyState = state.updatedStarGiftAuctionMyState
self.updatedEmojiGameInfo = state.updatedEmojiGameInfo
}
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
@@ -1068,7 +1077,8 @@ struct AccountFinalStateEvents {
reportMessageDelivery: reportMessageDelivery,
addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds,
updatedStarGiftAuctionState: self.updatedStarGiftAuctionState.merging(other.updatedStarGiftAuctionState, uniquingKeysWith: { lhs, _ in lhs }),
updatedStarGiftAuctionMyState: self.updatedStarGiftAuctionMyState.merging(other.updatedStarGiftAuctionMyState, uniquingKeysWith: { lhs, _ in lhs })
updatedStarGiftAuctionMyState: self.updatedStarGiftAuctionMyState.merging(other.updatedStarGiftAuctionMyState, uniquingKeysWith: { lhs, _ in lhs }),
updatedEmojiGameInfo: self.updatedEmojiGameInfo
)
}
}
@@ -240,6 +240,7 @@ private var declaredEncodables: Void = {
declareEncodable(PublishedSuggestedPostMessageAttribute.self, f: { PublishedSuggestedPostMessageAttribute(decoder: $0) })
declareEncodable(TelegramMediaLiveStream.self, f: { TelegramMediaLiveStream(decoder: $0) })
declareEncodable(ScheduledRepeatAttribute.self, f: { ScheduledRepeatAttribute(decoder: $0) })
declareEncodable(SummarizationMessageAttribute.self, f: { SummarizationMessageAttribute(decoder: $0) })
return
}()
@@ -0,0 +1,85 @@
import Foundation
/// Менеджер для локального редактирования сообщений (только на стороне клиента)
/// Хранит редактирования в памяти - сбрасываются при перезапуске приложения
public final class LocalEditManager {
public static let shared = LocalEditManager()
// MARK: - Storage
/// Хранилище редактирований: "peerId_messageId" -> новый текст
private var edits: [String: String] = [:]
private let lock = NSLock()
private init() {}
// MARK: - Public API
/// Установить локальное редактирование для сообщения
/// - Parameters:
/// - peerId: ID чата
/// - messageId: ID сообщения
/// - newText: Новый текст сообщения
public func setLocalEdit(peerId: Int64, messageId: Int32, newText: String) {
let key = makeKey(peerId: peerId, messageId: messageId)
lock.lock()
defer { lock.unlock() }
edits[key] = newText
}
/// Получить локальное редактирование для сообщения
/// - Parameters:
/// - peerId: ID чата
/// - messageId: ID сообщения
/// - Returns: Отредактированный текст или nil если редактирования нет
public func getLocalEdit(peerId: Int64, messageId: Int32) -> String? {
let key = makeKey(peerId: peerId, messageId: messageId)
lock.lock()
defer { lock.unlock() }
return edits[key]
}
/// Удалить локальное редактирование для сообщения
/// - Parameters:
/// - peerId: ID чата
/// - messageId: ID сообщения
public func removeLocalEdit(peerId: Int64, messageId: Int32) {
let key = makeKey(peerId: peerId, messageId: messageId)
lock.lock()
defer { lock.unlock() }
edits.removeValue(forKey: key)
}
/// Проверить наличие локального редактирования
/// - Parameters:
/// - peerId: ID чата
/// - messageId: ID сообщения
/// - Returns: true если есть редактирование
public func hasLocalEdit(peerId: Int64, messageId: Int32) -> Bool {
let key = makeKey(peerId: peerId, messageId: messageId)
lock.lock()
defer { lock.unlock() }
return edits[key] != nil
}
/// Очистить все локальные редактирования
public func clearAllEdits() {
lock.lock()
defer { lock.unlock() }
edits.removeAll()
}
/// Количество активных редактирований
public var editCount: Int {
lock.lock()
defer { lock.unlock() }
return edits.count
}
// MARK: - Private
private func makeKey(peerId: Int64, messageId: Int32) -> String {
return "\(peerId)_\(messageId)"
}
}
@@ -128,7 +128,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
switch messsage {
case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
let chatPeerId = messagePeerId
return chatPeerId.peerId
case let .messageEmpty(_, _, peerId):
@@ -144,7 +144,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? {
func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
switch message {
case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
let peerId: PeerId = chatPeerId.peerId
var result = [peerId]
@@ -279,7 +279,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? {
switch message {
case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
if let replyTo = replyTo {
let peerId: PeerId = chatPeerId.peerId
@@ -457,8 +457,17 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
}
return (TelegramMediaTodo(flags: flags, text: todoText, textEntities: todoEntities, items: list.map(TelegramMediaTodo.Item.init(apiItem:)), completions: todoCompletions), nil, nil, nil, nil, nil)
}
case let .messageMediaDice(value, emoticon):
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil, nil, nil)
case let .messageMediaDice(_, value, emoticon, apiGameOutcome):
var gameOutcome: TelegramMediaDice.GameOutcome?
var tonAmount: Int64?
switch apiGameOutcome {
case let .emojiGameOutcome(seed, stakeTonAmount, outcomeTonAmount):
gameOutcome = TelegramMediaDice.GameOutcome(seed: seed.makeData(), tonAmount: outcomeTonAmount)
tonAmount = stakeTonAmount
default:
break
}
return (TelegramMediaDice(emoji: emoticon, tonAmount: tonAmount, value: value, gameOutcome: gameOutcome), nil, nil, nil, nil, nil)
case let .messageMediaStory(flags, peerId, id, _):
let isMention = (flags & (1 << 1)) != 0
return (TelegramMediaStory(storyId: StoryId(peerId: peerId.peerId, id: id), isMention: isMention), nil, nil, nil, nil, nil)
@@ -703,7 +712,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes
extension StoreMessage {
convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) {
switch apiMessage {
case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod):
case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, reportDeliveryUntilDate, paidMessageStars, suggestedPost, scheduledRepeatPeriod, summaryFromLanguage):
var attributes: [MessageAttribute] = []
if (flags2 & (1 << 4)) != 0 {
@@ -964,6 +973,10 @@ extension StoreMessage {
attributes.append(ScheduledRepeatAttribute(repeatPeriod: scheduledRepeatPeriod))
}
if let summaryFromLanguage {
attributes.append(SummarizationMessageAttribute(fromLang: summaryFromLanguage))
}
var entitiesAttribute: TextEntitiesMessageAttribute?
if let entities = entities, !entities.isEmpty {
let attribute = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities))
@@ -988,7 +1001,7 @@ extension StoreMessage {
}
}
if (flags & (1 << 17)) != 0 {
if (flags & (1 << 19)) != 0 {
attributes.append(ContentRequiresValidationMessageAttribute())
}
@@ -1174,7 +1187,7 @@ extension StoreMessage {
threadId = 1
}
if (flags & (1 << 17)) != 0 {
if (flags & (1 << 19)) != 0 {
attributes.append(ContentRequiresValidationMessageAttribute())
}
@@ -343,8 +343,26 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
let inputTodo = Api.InputMedia.inputMediaTodo(todo: .todoList(flags: flags, title: .textWithEntities(text: todo.text, entities: apiEntitiesFromMessageTextEntities(todo.textEntities, associatedPeers: SimpleDictionary())), list: todo.items.map { $0.apiItem }))
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputTodo, text), reuploadInfo: nil, cacheReferenceKey: nil)))
} else if let dice = media as? TelegramMediaDice {
let inputDice = Api.InputMedia.inputMediaDice(emoticon: dice.emoji)
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
if let tonAmount = dice.tonAmount {
let seedBytes = malloc(32)!
let _ = SecRandomCopyBytes(nil, 32, seedBytes.assumingMemoryBound(to: UInt8.self))
let clientSeed = MemoryBuffer(memory: seedBytes, capacity: 32, length: 32, freeWhenDone: true)
return postbox.transaction { transaction -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
let gameInfo = currentEmojiGameInfo(transaction: transaction)
if case let .available(info) = gameInfo {
let inputStakeDice = Api.InputMedia.inputMediaStakeDice(gameHash: info.gameHash, tonAmount: tonAmount, clientSeed: Buffer(buffer: clientSeed))
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputStakeDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
} else {
return .fail(.generic)
}
}
|> castError(PendingMessageUploadError.self)
|> switchToLatest
} else {
let inputDice = Api.InputMedia.inputMediaDice(emoticon: dice.emoji)
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
}
} else if let webPage = media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content {
var flags: Int32 = 0
flags |= 1 << 2
@@ -1020,6 +1020,8 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
let peerId = PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(channelId))
updatedState.updateMinAvailableMessage(MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: minId))
case let .updateDeleteMessages(messages, _, _):
// Note: Actual archiving happens in DeleteMessagesWithGlobalIds handler
// where we have access to transaction and can get full message content
updatedState.deleteMessagesWithGlobalIds(messages)
case let .updatePinnedMessages(flags, peer, messages, _, _):
let peerId = peer.peerId
@@ -1906,6 +1908,8 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
}
case let .updateStarGiftAuctionUserState(giftId, userState):
updatedState.updateStarGiftAuctionMyState(giftId: giftId, state: GiftAuctionContext.State.MyState(apiAuctionUserState: userState))
case let .updateEmojiGameInfo(info):
updatedState.updateEmojiGameInfo(info: EmojiGameInfo(apiEmojiGameInfo: info))
default:
break
}
@@ -3629,7 +3633,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddQuickReplyMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@@ -3772,6 +3776,7 @@ func replayFinalState(
var reportMessageDelivery = Set<MessageId>()
var updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:]
var updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:]
var updatedEmojiGameInfo: EmojiGameInfo?
var holesFromPreviousStateMessageIds: [MessageId] = []
var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:]
@@ -4224,18 +4229,166 @@ func replayFinalState(
}
}
case let .DeleteMessagesWithGlobalIds(ids):
var resourceIds: [MediaResourceId] = []
transaction.deleteMessagesWithGlobalIds(ids, forEachMedia: { media in
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
})
if !resourceIds.isEmpty {
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start()
// ANTI-DELETE: Archive messages with full content before deletion
if AntiDeleteManager.shared.isEnabled {
let messageIds = transaction.messageIdsForGlobalIds(ids)
for (index, messageId) in messageIds.enumerated() {
if let message = transaction.getMessage(messageId) {
let globalId = index < ids.count ? ids[index] : 0
// Extract text content
let textContent = message.text
// Extract media description
var mediaDesc: String? = nil
for media in message.media {
switch media {
case let image as TelegramMediaImage:
mediaDesc = "📷 Photo"
if let largest = image.representations.last {
mediaDesc = "📷 Photo \(largest.dimensions.width)x\(largest.dimensions.height)"
}
case let file as TelegramMediaFile:
if file.isVideo {
mediaDesc = "🎬 Video"
} else if file.isVoice {
mediaDesc = "🎤 Voice Message"
} else if file.isInstantVideo {
mediaDesc = "📹 Video Message"
} else if file.isSticker {
mediaDesc = "🎭 Sticker"
} else {
mediaDesc = "📎 \(file.fileName ?? "File")"
}
case is TelegramMediaContact:
mediaDesc = "👤 Contact"
case is TelegramMediaMap:
mediaDesc = "📍 Location"
case let poll as TelegramMediaPoll:
mediaDesc = "📊 Poll: \(poll.text)"
default:
break
}
}
AntiDeleteManager.shared.archiveMessage(
globalId: globalId,
peerId: messageId.peerId.toInt64(),
messageId: messageId.id,
timestamp: message.timestamp,
authorId: message.author?.id.toInt64(),
text: textContent,
forwardAuthorId: message.forwardInfo?.author?.id.toInt64(),
mediaDescription: mediaDesc
)
}
}
}
// ANTI-DELETE: Mark messages as deleted instead of removing them
if AntiDeleteManager.shared.isEnabled {
let messageIds = transaction.messageIdsForGlobalIds(ids)
for messageId in messageIds {
// Mark as deleted for icon display
AntiDeleteManager.shared.markAsDeleted(peerId: messageId.peerId.toInt64(), messageId: messageId.id)
transaction.updateMessage(messageId, update: { currentMessage in
var attributes = currentMessage.attributes
// Don't add duplicate DeletedMessageAttribute
if !attributes.contains(where: { $0 is DeletedMessageAttribute }) {
attributes.append(DeletedMessageAttribute(deletedAt: Int32(Date().timeIntervalSince1970)))
}
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
// Keep original text, no prefix needed - icon will show deleted status
return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
} else {
var resourceIds: [MediaResourceId] = []
transaction.deleteMessagesWithGlobalIds(ids, forEachMedia: { media in
addMessageMediaResourceIdsToRemove(media: media, resourceIds: &resourceIds)
})
if !resourceIds.isEmpty {
let _ = mediaBox.removeCachedResources(Array(Set(resourceIds)), force: true).start()
}
}
deletedMessageIds.append(contentsOf: ids.map { .global($0) })
case let .DeleteMessages(ids):
_internal_deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in
addMessageThreadStatsDifference(threadKey: id, remove: remove, addedMessagePeer: nil, addedMessageId: nil, isOutgoing: false)
})
// ANTI-DELETE: Archive channel messages with full content before deletion
if AntiDeleteManager.shared.isEnabled {
for messageId in ids {
if let message = transaction.getMessage(messageId) {
// Extract text content
let textContent = message.text
// Extract media description
var mediaDesc: String? = nil
for media in message.media {
switch media {
case let image as TelegramMediaImage:
mediaDesc = "📷 Photo"
if let largest = image.representations.last {
mediaDesc = "📷 Photo \(largest.dimensions.width)x\(largest.dimensions.height)"
}
case let file as TelegramMediaFile:
if file.isVideo {
mediaDesc = "🎬 Video"
} else if file.isVoice {
mediaDesc = "🎤 Voice Message"
} else if file.isInstantVideo {
mediaDesc = "📹 Video Message"
} else if file.isSticker {
mediaDesc = "🎭 Sticker"
} else {
mediaDesc = "📎 \(file.fileName ?? "File")"
}
case is TelegramMediaContact:
mediaDesc = "👤 Contact"
case is TelegramMediaMap:
mediaDesc = "📍 Location"
case let poll as TelegramMediaPoll:
mediaDesc = "📊 Poll: \(poll.text)"
default:
break
}
}
AntiDeleteManager.shared.archiveMessage(
globalId: messageId.id, // Use message id as globalId for channel messages
peerId: messageId.peerId.toInt64(),
messageId: messageId.id,
timestamp: message.timestamp,
authorId: message.author?.id.toInt64(),
text: textContent,
forwardAuthorId: message.forwardInfo?.author?.id.toInt64(),
mediaDescription: mediaDesc
)
}
}
}
// ANTI-DELETE: Mark messages as deleted instead of removing them
if AntiDeleteManager.shared.isEnabled {
for messageId in ids {
// Mark as deleted for icon display
AntiDeleteManager.shared.markAsDeleted(peerId: messageId.peerId.toInt64(), messageId: messageId.id)
transaction.updateMessage(messageId, update: { currentMessage in
var attributes = currentMessage.attributes
// Don't add duplicate DeletedMessageAttribute
if !attributes.contains(where: { $0 is DeletedMessageAttribute }) {
attributes.append(DeletedMessageAttribute(deletedAt: Int32(Date().timeIntervalSince1970)))
}
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
// Keep original text, no prefix needed - icon will show deleted status
return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
}
} else {
_internal_deleteMessages(transaction: transaction, mediaBox: mediaBox, ids: ids, manualAddMessageThreadStatsDifference: { id, add, remove in
addMessageThreadStatsDifference(threadKey: id, remove: remove, addedMessagePeer: nil, addedMessageId: nil, isOutgoing: false)
})
}
deletedMessageIds.append(contentsOf: ids.map { .messageId($0) })
case let .UpdateMinAvailableMessage(id):
if let message = transaction.getMessage(id) {
@@ -4294,6 +4447,14 @@ func replayFinalState(
updatedAttributes.append(translation)
}
}
} else {
// GHOSTGRAM: Save original text before edit
EditHistoryManager.shared.saveOriginalText(
peerId: id.peerId.toInt64(),
messageId: id.id,
originalText: previousMessage.text,
editDate: Int32(Date().timeIntervalSince1970)
)
}
if let previousFactCheckAttribute = previousMessage.attributes.first(where: { $0 is FactCheckMessageAttribute }) as? FactCheckMessageAttribute, let updatedFactCheckAttribute = message.attributes.first(where: { $0 is FactCheckMessageAttribute }) as? FactCheckMessageAttribute {
@@ -5339,6 +5500,8 @@ func replayFinalState(
updatedStarGiftAuctionState[giftId] = state
case let .UpdateStarGiftAuctionMyState(giftId, state):
updatedStarGiftAuctionMyState[giftId] = state
case let .UpdateEmojiGameInfo(info):
updatedEmojiGameInfo = info
}
}
@@ -5889,6 +6052,7 @@ func replayFinalState(
reportMessageDelivery: reportMessageDelivery,
addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds,
updatedStarGiftAuctionState: updatedStarGiftAuctionState,
updatedStarGiftAuctionMyState: updatedStarGiftAuctionMyState
updatedStarGiftAuctionMyState: updatedStarGiftAuctionMyState,
updatedEmojiGameInfo: updatedEmojiGameInfo
)
}
@@ -374,6 +374,7 @@ public final class AccountStateManager {
private let appliedQtsPromise = Promise<Int32?>(nil)
private let appliedQtsDisposable = MetaDisposable()
private let reportMessageDeliveryDisposable = DisposableSet()
private let updateEmojiGameInfoDisposable = MetaDisposable()
let updateConfigRequested: (() -> Void)?
let isPremiumUpdated: (() -> Void)?
@@ -414,6 +415,7 @@ public final class AccountStateManager {
self.appliedMaxMessageIdDisposable.dispose()
self.appliedQtsDisposable.dispose()
self.reportMessageDeliveryDisposable.dispose()
self.updateEmojiGameInfoDisposable.dispose()
}
public func reset() {
@@ -1137,6 +1139,11 @@ public final class AccountStateManager {
if !events.updatedStarGiftAuctionMyState.isEmpty {
strongSelf.notifyUpdatedStarGiftAuctionMyState(events.updatedStarGiftAuctionMyState)
}
if let updatedEmojiGameInfo = events.updatedEmojiGameInfo {
strongSelf.updateEmojiGameInfoDisposable.set(strongSelf.postbox.transaction({ transaction in
updateEmojiGameInfo(transaction: transaction, { _ in return updatedEmojiGameInfo })
}).start())
}
if !events.updatedCalls.isEmpty {
for call in events.updatedCalls {
strongSelf.callSessionManager?.updateSession(call, completion: { _ in })
@@ -125,6 +125,7 @@ final class AccountTaskManager {
tasks.add(managedPeerColorUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
tasks.add(managedSavedMusicIdsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
tasks.add(managedEmojiGameUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
@@ -2173,7 +2173,7 @@ public final class AccountViewTracker {
fixedCombinedReadStates: .peer([peerId: CombinedPeerReadState(states: [
(Namespaces.Message.Cloud, PeerReadState.idBased(maxIncomingReadId: Int32.max - 1, maxOutgoingReadId: Int32.max - 1, maxKnownId: Int32.max - 1, count: 0, markedUnread: false))
])]),
topTaggedMessageIdNamespaces: [],
topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud],
tag: tag,
appendMessagesFromTheSameGroup: false,
namespaces: .not(Namespaces.Message.allNonRegular),
@@ -2201,7 +2201,7 @@ public final class AccountViewTracker {
count: count,
trackHoles: trackHoles,
fixedCombinedReadStates: nil,
topTaggedMessageIdNamespaces: [],
topTaggedMessageIdNamespaces: [Namespaces.Message.Cloud],
tag: tag,
appendMessagesFromTheSameGroup: false,
namespaces: .not(Namespaces.Message.allNonRegular),
@@ -104,7 +104,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
var updatedTimestamp: Int32?
if let apiMessage = apiMessage {
switch apiMessage {
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
updatedTimestamp = date
case .messageEmpty:
break
@@ -400,7 +400,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
} else if let message = messages.first, let apiMessage = result.messages.first {
if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
namespace = Namespaces.Message.ScheduledCloud
} else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 {
} else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 {
namespace = Namespaces.Message.ScheduledCloud
}
}
@@ -43,8 +43,18 @@ private final class AccountPresenceManagerImpl {
}
private func updatePresence(_ isOnline: Bool) {
// GHOST MODE: Completely block status updates to freeze "last seen" time
if GhostModeManager.shared.shouldHideOnlineStatus {
self.onlineTimer?.invalidate()
self.onlineTimer = nil
return
}
// ALWAYS ONLINE: Force online status when enabled
let effectiveOnline = MiscSettingsManager.shared.shouldAlwaysBeOnline ? true : isOnline
let request: Signal<Api.Bool, MTRpcError>
if isOnline {
if effectiveOnline {
let timer = SignalKitTimer(timeout: 30.0, repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
@@ -479,6 +479,26 @@ private func _internal_clearHistory(transaction: Transaction, postbox: Postbox,
|> `catch` { _ -> Signal<Void, NoError> in
return .complete()
}
} else if let threadId = operation.threadId {
guard let inputPeer = apiInputPeer(peer) else {
return .complete()
}
return network.request(Api.functions.messages.deleteTopicHistory(peer: inputPeer, topMsgId: Int32(clamping: threadId)))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.AffectedHistory?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
if let result = result {
switch result {
case let .affectedHistory(pts, ptsCount, _):
stateManager.addUpdateGroups([.updatePts(pts: pts, ptsCount: ptsCount)])
return .complete()
}
} else {
return .complete()
}
}
} else {
return requestClearHistory(postbox: postbox, network: network, stateManager: stateManager, inputPeer: inputPeer, maxId: operation.topMessageId.id, justClear: true, minTimestamp: operation.minTimestamp, maxTimestamp: operation.maxTimestamp, type: operation.type)
}
@@ -0,0 +1,104 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum EmojiGameInfo: Codable, Equatable {
private enum CodingKeys: String, CodingKey {
case type
case info
}
public struct Info: Codable, Equatable {
public let gameHash: String
public let previousStake: Int64
public let currentStreak: Int32
public let parameters: [Int32]
public let playsLeft: Int32?
}
case available(Info)
case unavailable
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(Int32.self, forKey: .type)
switch type {
case 1:
self = .available(try container.decode(Info.self, forKey: .info))
default:
self = .unavailable
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .available(info):
try container.encode(Int32(1), forKey: .type)
try container.encode(info, forKey: .info)
case .unavailable:
try container.encode(Int32(0), forKey: .type)
}
}
}
extension EmojiGameInfo {
init(apiEmojiGameInfo: Api.messages.EmojiGameInfo) {
switch apiEmojiGameInfo {
case let .emojiGameDiceInfo(_, gameHash, prevStake, currentStreak, params, playsLeft):
self = .available(Info(gameHash: gameHash, previousStake: prevStake, currentStreak: currentStreak, parameters: params, playsLeft: playsLeft))
case .emojiGameUnavailable:
self = .unavailable
}
}
}
public func currentEmojiGameInfo(transaction: Transaction) -> EmojiGameInfo {
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.emojiGameInfo())?.get(EmojiGameInfo.self) {
return entry
} else {
return .unavailable
}
}
func updateEmojiGameInfo(transaction: Transaction, _ f: (EmojiGameInfo) -> EmojiGameInfo) {
let current = currentEmojiGameInfo(transaction: transaction)
let updated = f(current)
if updated != current {
transaction.setPreferencesEntry(key: PreferencesKeys.emojiGameInfo(), value: PreferencesEntry(updated))
}
}
func updateEmojiGameInfoOnce(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
return network.request(Api.functions.messages.getEmojiGameInfo())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.EmojiGameInfo?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
guard let result else {
return .complete()
}
return postbox.transaction { transaction -> Void in
let info = EmojiGameInfo(apiEmojiGameInfo: result)
updateEmojiGameInfo(transaction: transaction) { _ in
return info
}
}
}
}
func managedEmojiGameUpdates(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = Signal<Void, NoError> { subscriber in
return updateEmojiGameInfoOnce(postbox: postbox, network: network).start(completed: {
subscriber.putCompletion()
})
}
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
@@ -142,6 +142,11 @@ private func actionFromActivity(_ activity: PeerInputActivity?) -> Api.SendMessa
}
private func requestActivity(postbox: Postbox, network: Network, accountPeerId: PeerId, peerId: PeerId, threadId: Int64?, activity: PeerInputActivity?) -> Signal<Void, NoError> {
// GHOST MODE: Block typing indicator
if GhostModeManager.shared.shouldHideTypingIndicator {
return .complete()
}
return postbox.transaction { transaction -> Signal<Void, NoError> in
if let peer = transaction.getPeer(peerId) {
if peerId == accountPeerId {
@@ -119,6 +119,11 @@ func managedSynchronizeViewStoriesOperations(postbox: Postbox, network: Network,
}
private func pushStoriesAreSeen(postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: SynchronizeViewStoriesOperation) -> Signal<Void, NoError> {
// GHOST MODE: Don't send story view notifications
if GhostModeManager.shared.shouldHideStoryViews {
return .complete()
}
guard let inputPeer = apiInputPeer(peer) else {
return .complete()
}
@@ -2076,7 +2076,7 @@ public final class PendingMessageManager {
if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
isScheduled = true
}
if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage {
if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage {
if (flags2 & (1 << 4)) != 0 {
isScheduled = true
}
@@ -2120,7 +2120,7 @@ public final class PendingMessageManager {
namespace = Namespaces.Message.QuickReplyCloud
} else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
namespace = Namespaces.Message.ScheduledCloud
} else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 {
} else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 {
namespace = Namespaces.Message.ScheduledCloud
}
}
@@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 220
return 221
}
public func parseMessage(_ data: Data!) -> Any! {
@@ -219,6 +219,11 @@ private func validatePeerReadState(network: Network, postbox: Postbox, stateMana
}
private func pushPeerReadState(network: Network, postbox: Postbox, stateManager: AccountStateManager, peerId: PeerId, readState: PeerReadState) -> Signal<PeerReadState, PeerReadStateValidationError> {
// GHOST MODE: Block read receipts (blue checkmarks) for non-secret chats
if peerId.namespace != Namespaces.Peer.SecretChat && GhostModeManager.shared.shouldHideReadReceipts {
return .single(readState)
}
if peerId.namespace == Namespaces.Peer.SecretChat {
return inputSecretChat(postbox: postbox, peerId: peerId)
|> mapToSignal { inputPeer -> Signal<PeerReadState, PeerReadStateValidationError> in
@@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService {
self.putNext(groups)
}
case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod):
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil)
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 {
@@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService {
let generatedPeerId = Api.Peer.peerUser(userId: userId)
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil)
let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil, paidMessageStars: nil, suggestedPost: nil, scheduleRepeatPeriod: nil, summaryFromLanguage: nil)
let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount)
let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil)
if groups.count != 0 {
@@ -104,7 +104,7 @@ extension Api.MessageMedia {
extension Api.Message {
var rawId: Int32 {
switch self {
case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return id
case let .messageEmpty(_, id, _):
return id
@@ -115,7 +115,7 @@ extension Api.Message {
func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? {
switch self {
case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
var namespace = namespace
if (flags2 & (1 << 4)) != 0 {
namespace = Namespaces.Message.ScheduledCloud
@@ -136,7 +136,7 @@ extension Api.Message {
var peerId: PeerId? {
switch self {
case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
let peerId: PeerId = messagePeerId.peerId
return peerId
case let .messageEmpty(_, _, peerId):
@@ -149,7 +149,7 @@ extension Api.Message {
var timestamp: Int32? {
switch self {
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return date
case let .messageService(_, _, _, _, _, _, date, _, _, _):
return date
@@ -160,7 +160,7 @@ extension Api.Message {
var preCachedResources: [(MediaResource, Data)]? {
switch self {
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return media?.preCachedResources
default:
return nil
@@ -169,7 +169,7 @@ extension Api.Message {
var preCachedStories: [StoryId: Api.StoryItem]? {
switch self {
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
return media?.preCachedStories
default:
return nil
@@ -32,9 +32,10 @@ public struct UserLimitsConfiguration: Equatable {
public static var defaultValue: UserLimitsConfiguration {
return UserLimitsConfiguration(
maxPinnedChatCount: 5,
maxPinnedSavedChatCount: 5,
maxArchivedPinnedChatCount: 100,
// GHOSTGRAM: Unlimited pinned chats bypass
maxPinnedChatCount: 99,
maxPinnedSavedChatCount: 99,
maxArchivedPinnedChatCount: 99,
maxChannelsCount: 500,
maxPublicLinksCount: 10,
maxSavedGifCount: 200,
@@ -145,9 +146,10 @@ extension UserLimitsConfiguration {
}
}
self.maxPinnedChatCount = getValue("dialogs_pinned_limit", orElse: defaultValue.maxPinnedChatCount)
self.maxPinnedSavedChatCount = getValue("saved_dialogs_pinned_limit", orElse: defaultValue.maxPinnedSavedChatCount)
self.maxArchivedPinnedChatCount = getValue("dialogs_folder_pinned_limit", orElse: defaultValue.maxArchivedPinnedChatCount)
// GHOSTGRAM: Force unlimited pinned chats (ignore server limits)
self.maxPinnedChatCount = 99
self.maxPinnedSavedChatCount = 99
self.maxArchivedPinnedChatCount = 99
self.maxChannelsCount = getValue("channels_limit", orElse: defaultValue.maxChannelsCount)
self.maxPublicLinksCount = getValue("channels_public_limit", orElse: defaultValue.maxPublicLinksCount)
self.maxSavedGifCount = getValue("saved_gifs_limit", orElse: defaultValue.maxSavedGifCount)
@@ -0,0 +1,87 @@
import Foundation
import Postbox
import TelegramApi
public final class SummarizationMessageAttribute: Equatable, MessageAttribute {
public struct Summary: Equatable, Codable, PostboxCoding {
public let text: String
public let entities: [MessageTextEntity]
public init(
text: String,
entities: [MessageTextEntity]
) {
self.text = text
self.entities = entities
}
public init(decoder: PostboxDecoder) {
self.text = decoder.decodeStringForKey("text", orElse: "")
self.entities = decoder.decodeObjectArrayWithDecoderForKey("entities")
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.text, forKey: "text")
encoder.encodeObjectArray(self.entities, forKey: "entities")
}
}
public let fromLang: String
public let summary: Summary?
public let translated: [String: Summary]
public init(
fromLang: String,
summary: Summary? = nil,
translated: [String: Summary] = [:]
) {
self.fromLang = fromLang
self.summary = summary
self.translated = translated
}
required public init(decoder: PostboxDecoder) {
self.fromLang = decoder.decodeStringForKey("fl", orElse: "")
self.summary = decoder.decodeObjectForKey("s", decoder: { Summary(decoder: $0) }) as? Summary
self.translated = decoder.decodeObjectDictionaryForKey("t", keyDecoder: { decoder in
return decoder.decodeStringForKey("k", orElse: "")
}, valueDecoder: { decoder in
return Summary(decoder: decoder)
})
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.fromLang, forKey: "fl")
if let summary = self.summary {
encoder.encodeObject(summary, forKey: "s")
} else {
encoder.encodeNil(forKey: "s")
}
encoder.encodeObjectDictionary(self.translated, forKey: "t", keyEncoder: { k, e in
e.encodeString(k, forKey: "k")
})
}
public static func ==(lhs: SummarizationMessageAttribute, rhs: SummarizationMessageAttribute) -> Bool {
if lhs.fromLang != rhs.fromLang {
return false
}
if lhs.summary != rhs.summary {
return false
}
if lhs.translated != rhs.translated {
return false
}
return true
}
}
public extension SummarizationMessageAttribute {
func summaryForLang(_ lang: String?) -> Summary? {
if let lang {
return self.translated[lang]
} else {
return self.summary
}
}
}
@@ -111,6 +111,11 @@ public extension Message {
}
var minAutoremoveOrClearTimeout: Int32? {
// MISC: Bypass if view-once setting is enabled
if MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete {
return nil
}
var timeout: Int32?
for attribute in self.attributes {
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
@@ -137,6 +142,11 @@ public extension Message {
}
var containsSecretMedia: Bool {
// MISC: Bypass if view-once setting is enabled
if MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete {
return false
}
guard let timeout = self.minAutoremoveOrClearTimeout else {
return false
}
@@ -323,6 +323,7 @@ private enum PreferencesKeyValues: Int32 {
case persistentChatInterfaceData = 45
case globalPostSearchState = 46
case savedMusicIds = 47
case emojiGameInfo = 48
}
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
@@ -591,6 +592,12 @@ public struct PreferencesKeys {
key.setInt32(0, value: PreferencesKeyValues.savedMusicIds.rawValue)
return key
}
public static func emojiGameInfo() -> ValueBoxKey {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: PreferencesKeyValues.emojiGameInfo.rawValue)
return key
}
}
private enum SharedDataKeyValues: Int32 {
@@ -62,7 +62,8 @@ public struct TelegramChatAdminRightsFlags: OptionSet, Hashable {
.canPostStories,
.canEditStories,
.canDeleteStories,
.canManageDirect
.canManageDirect,
.canBanUsers
]
public static func peerSpecific(peer: EnginePeer) -> TelegramChatAdminRightsFlags {
@@ -1,29 +1,57 @@
import Foundation
import Postbox
public final class TelegramMediaDice: Media, Equatable {
public struct GameOutcome: Equatable {
let seed: Data
public let tonAmount: Int64
}
public let emoji: String
public let tonAmount: Int64?
public let value: Int32?
public let gameOutcome: GameOutcome?
public let id: MediaId? = nil
public let peerIds: [PeerId] = []
public init(emoji: String, value: Int32? = nil) {
public init(emoji: String, tonAmount: Int64? = nil, value: Int32? = nil, gameOutcome: GameOutcome? = nil) {
self.emoji = emoji
self.tonAmount = tonAmount
self.value = value
self.gameOutcome = gameOutcome
}
public init(decoder: PostboxDecoder) {
self.emoji = decoder.decodeStringForKey("e", orElse: "🎲")
self.tonAmount = decoder.decodeOptionalInt64ForKey("ta")
self.value = decoder.decodeOptionalInt32ForKey("v")
if let seed = decoder.decodeDataForKey("gos"), let tonAmount = decoder.decodeOptionalInt64ForKey("goa") {
self.gameOutcome = GameOutcome(seed: seed, tonAmount: tonAmount)
} else {
self.gameOutcome = nil
}
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.emoji, forKey: "e")
if let tonAmount = self.tonAmount {
encoder.encodeInt64(tonAmount, forKey: "ta")
} else {
encoder.encodeNil(forKey: "ta")
}
if let value = self.value {
encoder.encodeInt32(value, forKey: "v")
} else {
encoder.encodeNil(forKey: "v")
}
if let gameOutcome = self.gameOutcome {
encoder.encodeData(gameOutcome.seed, forKey: "gos")
encoder.encodeInt64(gameOutcome.tonAmount, forKey: "goa")
} else {
encoder.encodeNil(forKey: "gos")
encoder.encodeNil(forKey: "goa")
}
}
public static func ==(lhs: TelegramMediaDice, rhs: TelegramMediaDice) -> Bool {
@@ -35,9 +63,15 @@ public final class TelegramMediaDice: Media, Equatable {
if self.emoji != other.emoji {
return false
}
if self.tonAmount != other.tonAmount {
return false
}
if self.value != other.value {
return false
}
if self.gameOutcome != other.gameOutcome {
return false
}
return true
}
return false
@@ -571,5 +571,26 @@ public extension TelegramEngine.EngineData.Item {
return value
}
}
public struct EmojiGame: TelegramEngineDataItem, PostboxViewDataItem {
public typealias Result = EmojiGameInfo
public init() {
}
var key: PostboxViewKey {
return .preferences(keys: Set([PreferencesKeys.emojiGameInfo()]))
}
func extract(view: PostboxView) -> Result {
guard let view = view as? PreferencesView else {
preconditionFailure()
}
guard let emojiGameInfo = view.values[PreferencesKeys.emojiGameInfo()]?.get(EmojiGameInfo.self) else {
return .unavailable
}
return emojiGameInfo
}
}
}
}
@@ -482,6 +482,12 @@ private class AdMessagesHistoryContextImpl {
}
self.isActivated = true
// MISC: Block ads if setting enabled
if MiscSettingsManager.shared.shouldBlockAds {
self.stateValue = State(interPostInterval: nil, startDelay: nil, betweenDelay: nil, messages: [])
return
}
let peerId = self.peerId
let accountPeerId = self.account.peerId
let account = self.account
@@ -64,7 +64,10 @@ func _internal_applyMaxReadIndexInteractively(transaction: Transaction, stateMan
}
}
} else if index.id.peerId.namespace == Namespaces.Peer.CloudUser || index.id.peerId.namespace == Namespaces.Peer.CloudGroup || index.id.peerId.namespace == Namespaces.Peer.CloudChannel {
stateManager.notifyAppliedIncomingReadMessages([index.id])
// GHOST MODE: Don't send read receipts (blue checkmarks)
if !GhostModeManager.shared.shouldHideReadReceipts {
stateManager.notifyAppliedIncomingReadMessages([index.id])
}
}
}
@@ -51,6 +51,11 @@ func _internal_markMessageContentAsConsumedInteractively(postbox: Postbox, messa
for i in 0 ..< updatedAttributes.count {
if let attribute = updatedAttributes[i] as? AutoremoveTimeoutMessageAttribute {
if attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0 {
// MISC: Don't start countdown for view-once if bypass enabled
if attribute.timeout == viewOnceTimeout && MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete {
continue
}
var timeout = attribute.timeout
if let duration = message.secretMediaDuration {
timeout = max(timeout, Int32(duration))
@@ -194,7 +199,9 @@ func markMessageContentAsConsumedRemotely(transaction: Transaction, messageId: M
if message.id.peerId.namespace == Namespaces.Peer.SecretChat {
} else {
if attribute.timeout == viewOnceTimeout || timestamp >= countdownBeginTime + attribute.timeout {
// MISC: Don't expire view-once media if bypass enabled
let shouldExpire = !(attribute.timeout == viewOnceTimeout && MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete)
if shouldExpire && (attribute.timeout == viewOnceTimeout || timestamp >= countdownBeginTime + attribute.timeout) {
for i in 0 ..< updatedMedia.count {
if let _ = updatedMedia[i] as? TelegramMediaImage {
updatedMedia[i] = TelegramMediaExpiredContent(data: .image)
@@ -0,0 +1,83 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum SummarizeError {
case generic
case invalidMessageId
case limitExceeded
case invalidLanguage
case limitExceededPremium
}
func _internal_summarizeMessage(account: Account, messageId: EngineMessage.Id, translateToLang: String?) -> Signal<Never, SummarizeError> {
return account.postbox.transaction { transaction -> Api.InputPeer? in
return transaction.getPeer(messageId.peerId).flatMap(apiInputPeer)
}
|> castError(SummarizeError.self)
|> mapToSignal { inputPeer -> Signal<Never, SummarizeError> in
guard let inputPeer else {
return .never()
}
var flags: Int32 = 0
if let _ = translateToLang {
flags |= (1 << 0)
}
return account.network.request(Api.functions.messages.summarizeText(flags: flags, peer: inputPeer, id: messageId.id, toLang: translateToLang))
|> map(Optional.init)
|> mapError { error -> SummarizeError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "MSG_ID_INVALID" {
return .invalidMessageId
} else if error.errorDescription == "TO_LANG_INVALID" {
return .invalidLanguage
} else if error.errorDescription == "SUMMARY_FLOOD_PREMIUM" {
return .limitExceededPremium
} else {
return .generic
}
}
|> mapToSignal { result -> Signal<Void, SummarizeError> in
return account.postbox.transaction { transaction in
switch result {
case let .textWithEntities(text, entities):
transaction.updateMessage(messageId, update: { currentMessage in
let storeForwardInfo = currentMessage.forwardInfo.flatMap(StoreMessageForwardInfo.init)
var attributes = currentMessage.attributes
let currentAttribute = attributes.first(where: { $0 is SummarizationMessageAttribute }) as? SummarizationMessageAttribute
let updatedAttribute: SummarizationMessageAttribute
if let translateToLang {
var translated = currentAttribute?.translated ?? [:]
translated[translateToLang] = SummarizationMessageAttribute.Summary(text: text, entities: messageTextEntitiesFromApiEntities(entities))
updatedAttribute = SummarizationMessageAttribute(
fromLang: currentAttribute?.fromLang ?? "",
summary: currentAttribute?.summary,
translated: translated
)
} else {
updatedAttribute = SummarizationMessageAttribute(
fromLang: currentAttribute?.fromLang ?? "",
summary: .init(text: text, entities: messageTextEntitiesFromApiEntities(entities)),
translated: currentAttribute?.translated ?? [:]
)
}
attributes = attributes.filter { !($0 is SummarizationMessageAttribute) }
attributes.append(updatedAttribute)
return .update(StoreMessage(id: currentMessage.id, customStableId: nil, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, threadId: currentMessage.threadId, timestamp: currentMessage.timestamp, flags: StoreMessageFlags(currentMessage.flags), tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: attributes, media: currentMessage.media))
})
default:
break
}
}
|> castError(SummarizeError.self)
}
|> ignoreValues
}
}
@@ -604,6 +604,10 @@ public extension TelegramEngine {
return _internal_togglePeerMessagesTranslationHidden(account: self.account, peerId: peerId, hidden: hidden)
}
public func summarizeMessage(messageId: EngineMessage.Id, translateToLang: String?) -> Signal<Never, SummarizeError> {
return _internal_summarizeMessage(account: self.account, messageId: messageId, translateToLang: translateToLang)
}
public func transcribeAudio(messageId: MessageId) -> Signal<EngineAudioTranscriptionResult, NoError> {
return _internal_transcribeAudio(postbox: self.account.postbox, network: self.account.network, messageId: messageId)
}
@@ -1578,7 +1578,8 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star
prepaidUpgradeHash: nil,
upgradeSeparate: false,
dropOriginalDetailsStars: dropOriginalDetailsStars,
number: nil
number: nil,
isRefunded: false
))
}
}
@@ -2533,6 +2534,7 @@ public final class ProfileGiftsContext {
case upgradeSeparate
case dropOriginalDetailsStars
case number
case isRefunded
}
public let gift: TelegramCore.StarGift
@@ -2556,7 +2558,8 @@ public final class ProfileGiftsContext {
public let upgradeSeparate: Bool
public let dropOriginalDetailsStars: Int64?
public let number: Int32?
public let isRefunded: Bool
fileprivate let _fromPeerId: EnginePeer.Id?
public enum DecodingError: Error {
@@ -2584,7 +2587,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: String?,
upgradeSeparate: Bool,
dropOriginalDetailsStars: Int64?,
number: Int32?
number: Int32?,
isRefunded: Bool
) {
self.gift = gift
self.reference = reference
@@ -2608,6 +2612,7 @@ public final class ProfileGiftsContext {
self.upgradeSeparate = upgradeSeparate
self.dropOriginalDetailsStars = dropOriginalDetailsStars
self.number = number
self.isRefunded = isRefunded
}
public init(from decoder: Decoder) throws {
@@ -2641,6 +2646,7 @@ public final class ProfileGiftsContext {
self.upgradeSeparate = try container.decodeIfPresent(Bool.self, forKey: .upgradeSeparate) ?? false
self.dropOriginalDetailsStars = try container.decodeIfPresent(Int64.self, forKey: .dropOriginalDetailsStars)
self.number = try container.decodeIfPresent(Int32.self, forKey: .number)
self.isRefunded = try container.decodeIfPresent(Bool.self, forKey: .isRefunded) ?? false
}
public func encode(to encoder: Encoder) throws {
@@ -2667,6 +2673,8 @@ public final class ProfileGiftsContext {
try container.encode(self.upgradeSeparate, forKey: .upgradeSeparate)
try container.encodeIfPresent(self.dropOriginalDetailsStars, forKey: .dropOriginalDetailsStars)
try container.encodeIfPresent(self.number, forKey: .number)
try container.encode(self.isRefunded, forKey: .isRefunded)
}
public func withGift(_ gift: TelegramCore.StarGift) -> StarGift {
@@ -2691,7 +2699,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
@@ -2717,7 +2726,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
@@ -2743,7 +2753,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
fileprivate func withFromPeer(_ fromPeer: EnginePeer?) -> StarGift {
@@ -2768,7 +2779,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
@@ -2794,7 +2806,8 @@ public final class ProfileGiftsContext {
prepaidUpgradeHash: self.prepaidUpgradeHash,
upgradeSeparate: self.upgradeSeparate,
dropOriginalDetailsStars: self.dropOriginalDetailsStars,
number: self.number
number: self.number,
isRefunded: self.isRefunded
)
}
}
@@ -3063,6 +3076,7 @@ extension ProfileGiftsContext.State.StarGift {
self.upgradeSeparate = (flags & (1 << 17)) != 0
self.dropOriginalDetailsStars = dropOriginalDetailsStars
self.number = number
self.isRefunded = (flags & (1 << 9)) != 0
}
}
}
@@ -580,7 +580,7 @@ public class GiftAuctionsManager {
}
public extension GiftAuctionContext.State {
func getPlace(myBid: Int64?, myBidDate: Int32?) -> Int32? {
func getPlace(myBid: Int64?, myBidDate: Int32?) -> (place: Int32, isApproximate: Bool)? {
guard case let .ongoing(_, _, _, _, bidLevels, _, _, _, _, _, _, _) = self.auctionState else {
return nil
}
@@ -592,7 +592,7 @@ public extension GiftAuctionContext.State {
let levels = bidLevels
guard !levels.isEmpty else {
return 1
return (1, false)
}
func isWorse(than level: GiftAuctionContext.State.BidLevel) -> Bool {
@@ -614,7 +614,7 @@ public extension GiftAuctionContext.State {
}
}
if lowerIndex == -1 {
return 1
return (1, false)
}
let lowerPosition = levels[lowerIndex].position
@@ -626,14 +626,14 @@ public extension GiftAuctionContext.State {
nextPosition = lowerPosition
}
if nextPosition == lowerPosition + 1 {
return lowerPosition + 1
return (lowerPosition + 1, false)
} else {
return nextPosition
return (lowerPosition, true)
}
}
var place: Int32? {
return self.getPlace(myBid: nil, myBidDate: nil)
return self.getPlace(myBid: nil, myBidDate: nil)?.place
}
var startDate: Int32 {
@@ -1644,7 +1644,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid:
receiptMessageId = nil
}
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _) = action.action, case let .Id(messageId) = message.id {
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _) = action.action, case let .Id(messageId) = message.id {
let reference: StarGiftReference
if let peerId, let savedId {
reference = .peer(peerId: peerId, id: savedId)
@@ -1672,7 +1672,8 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
prepaidUpgradeHash: nil,
upgradeSeparate: false,
dropOriginalDetailsStars: dropOriginalDetailsStars,
number: nil
number: nil,
isRefunded: isRefunded
)
}
}
@@ -380,6 +380,11 @@ public extension Message {
}
func isCopyProtected() -> Bool {
// MISC: Bypass copy protection if enabled
if MiscSettingsManager.shared.shouldBypassCopyProtection {
return false
}
if self.flags.contains(.CopyProtected) {
return true
} else if let group = self.peers[self.id.peerId] as? TelegramGroup, group.flags.contains(.copyProtectionEnabled) {