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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user