mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-06-08 19:13:56 +02:00
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:
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
+20
-2
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -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
|
||||
|
||||
+4
-1
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-1
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user