@@ -63,14 +63,109 @@ public struct MessageSavingBridgeSettings: Equatable {
6363 self . saveForBots = saveForBots
6464 }
6565
66+ /// Matches ForkExtrasSettings.defaultSettings so deletes are captured before
67+ /// SharedAccountContext's async sharedData subscription applies persisted prefs.
68+ public static let defaults = MessageSavingBridgeSettings ( saveDeleted: true , saveEdits: true , saveForBots: false )
69+
6670 public static let disabled = MessageSavingBridgeSettings ( saveDeleted: false , saveEdits: false , saveForBots: false )
6771}
6872
6973/// TelegramCore cannot import TelegramUIPreferences; SharedAccountContext pushes settings + append sink here.
7074public enum MessageSavingBridge {
71- public static let settings = Atomic ( value: MessageSavingBridgeSettings . disabled )
75+ public static let settings = Atomic ( value: MessageSavingBridgeSettings . defaults )
7276 public static let append = Atomic < ( ( MessageSavingRecord ) -> Void ) ? > ( value: nil )
7377
78+ /// Prefer a textual body; fall back to a short media placeholder so media-only
79+ /// deletes still appear in View Deleted (AyuGram is text-only, but empty skips
80+ /// looked like "saving is broken" for photo/sticker/voice messages).
81+ private static func displayText( for message: Message ) -> String ? {
82+ let text = message. text. trimmingCharacters ( in: . whitespacesAndNewlines)
83+ if !text. isEmpty {
84+ return text
85+ }
86+ for media in message. media {
87+ if media is TelegramMediaImage {
88+ return " [Photo] "
89+ }
90+ if let file = media as? TelegramMediaFile {
91+ if file. isAnimatedSticker || file. isSticker {
92+ return " [Sticker] "
93+ }
94+ if file. isInstantVideo {
95+ return " [Video message] "
96+ }
97+ if file. isVoice {
98+ return " [Voice message] "
99+ }
100+ if file. isMusic {
101+ return " [Music] "
102+ }
103+ if file. isVideo {
104+ return " [Video] "
105+ }
106+ if file. isAnimated {
107+ return " [GIF] "
108+ }
109+ if let name = file. fileName, !name. isEmpty {
110+ return " [ \( name) ] "
111+ }
112+ return " [File] "
113+ }
114+ if media is TelegramMediaWebpage {
115+ return " [Link] "
116+ }
117+ if media is TelegramMediaMap {
118+ return " [Location] "
119+ }
120+ if media is TelegramMediaContact {
121+ return " [Contact] "
122+ }
123+ if media is TelegramMediaPoll {
124+ return " [Poll] "
125+ }
126+ if media is TelegramMediaDice {
127+ return " [Dice] "
128+ }
129+ if media is TelegramMediaGame {
130+ return " [Game] "
131+ }
132+ if media is TelegramMediaInvoice {
133+ return " [Invoice] "
134+ }
135+ if media is TelegramMediaAction {
136+ return nil
137+ }
138+ }
139+ return nil
140+ }
141+
142+ private static func authorName( for message: Message ) -> String {
143+ if let user = message. author as? TelegramUser {
144+ let name = [ user. firstName, user. lastName] . compactMap { $0 } . filter { !$0. isEmpty } . joined ( separator: " " )
145+ return name. isEmpty ? " Unknown " : name
146+ }
147+ if let peer = message. author {
148+ let title = peer. debugDisplayTitle
149+ return title. isEmpty ? " Unknown " : title
150+ }
151+ return " Unknown "
152+ }
153+
154+ /// Incoming (MTProto `out` unset) or CountedAsIncoming — excludes the local user's
155+ /// own outgoing messages in every chat type, including channels.
156+ private static func shouldSave( message: Message , settings: MessageSavingBridgeSettings ) -> Bool {
157+ if !message. flags. contains ( . Incoming) && !message. flags. contains ( . CountedAsIncoming) {
158+ return false
159+ }
160+ if let author = message. author as? TelegramUser , author. botInfo != nil , !settings. saveForBots {
161+ return false
162+ }
163+ if let peer = message. peers [ message. id. peerId] as? TelegramUser , peer. botInfo != nil , !settings. saveForBots {
164+ return false
165+ }
166+ return true
167+ }
168+
74169 public static func snapshotMessages(
75170 transaction: Transaction ,
76171 accountPeerId: PeerId ,
@@ -89,30 +184,8 @@ public enum MessageSavingBridge {
89184 let now = Int32 ( Date ( ) . timeIntervalSince1970)
90185 for id in messageIds {
91186 guard let message = transaction. getMessage ( id) else { continue }
92- // message.id.peerId is the CHAT the message lives in, not its author — it only
93- // equals accountPeerId for the Saved Messages self-chat. Use the Incoming flag to
94- // correctly exclude the local account's own outgoing messages in every chat.
95- if !message. flags. contains ( . Incoming) {
96- continue
97- }
98- if let author = message. author as? TelegramUser , author. botInfo != nil , !current. saveForBots {
99- continue
100- }
101- if let peer = message. peers [ message. id. peerId] as? TelegramUser , peer. botInfo != nil , !current. saveForBots {
102- continue
103- }
104- let text = message. text. trimmingCharacters ( in: . whitespacesAndNewlines)
105- guard !text. isEmpty else { continue }
106-
107- let author = message. author
108- let authorName : String
109- if let user = author as? TelegramUser {
110- authorName = [ user. firstName, user. lastName] . compactMap { $0 } . filter { !$0. isEmpty } . joined ( separator: " " )
111- } else if let peer = author {
112- authorName = peer. debugDisplayTitle
113- } else {
114- authorName = " "
115- }
187+ guard shouldSave ( message: message, settings: current) else { continue }
188+ guard let text = displayText ( for: message) else { continue }
116189
117190 let record = MessageSavingRecord (
118191 id: UUID ( ) . uuidString,
@@ -121,8 +194,8 @@ public enum MessageSavingBridge {
121194 messageId: message. id. id,
122195 namespace: message. id. namespace,
123196 date: message. timestamp,
124- authorId: author. map { $0. id. toInt64 ( ) } ,
125- authorName: authorName. isEmpty ? " Unknown " : authorName ,
197+ authorId: message . author. map { $0. id. toInt64 ( ) } ,
198+ authorName: authorName ( for : message ) ,
126199 text: text,
127200 kind: kind,
128201 savedAt: now,
@@ -145,29 +218,8 @@ public enum MessageSavingBridge {
145218 guard current. saveEdits else { return }
146219 }
147220 guard let sink = append. with ( { $0 } ) else { return }
148- // Same fix as snapshotMessages: check authorship via the Incoming flag, not whether
149- // the chat itself is the Saved Messages self-chat.
150- if !message. flags. contains ( . Incoming) {
151- return
152- }
153- if let author = message. author as? TelegramUser , author. botInfo != nil , !current. saveForBots {
154- return
155- }
156- if let peer = message. peers [ message. id. peerId] as? TelegramUser , peer. botInfo != nil , !current. saveForBots {
157- return
158- }
159- let text = message. text. trimmingCharacters ( in: . whitespacesAndNewlines)
160- guard !text. isEmpty else { return }
161-
162- let author = message. author
163- let authorName : String
164- if let user = author as? TelegramUser {
165- authorName = [ user. firstName, user. lastName] . compactMap { $0 } . filter { !$0. isEmpty } . joined ( separator: " " )
166- } else if let peer = author {
167- authorName = peer. debugDisplayTitle
168- } else {
169- authorName = " "
170- }
221+ guard shouldSave ( message: message, settings: current) else { return }
222+ guard let text = displayText ( for: message) else { return }
171223
172224 let record = MessageSavingRecord (
173225 id: UUID ( ) . uuidString,
@@ -176,13 +228,31 @@ public enum MessageSavingBridge {
176228 messageId: message. id. id,
177229 namespace: message. id. namespace,
178230 date: message. timestamp,
179- authorId: author. map { $0. id. toInt64 ( ) } ,
180- authorName: authorName. isEmpty ? " Unknown " : authorName ,
231+ authorId: message . author. map { $0. id. toInt64 ( ) } ,
232+ authorName: authorName ( for : message ) ,
181233 text: text,
182234 kind: kind,
183235 savedAt: Int32 ( Date ( ) . timeIntervalSince1970) ,
184236 topicId: message. threadId ?? 0
185237 )
186238 sink ( record)
187239 }
240+
241+ /// Resolve account peer from the postbox and snapshot before a local delete.
242+ /// Used by `_internal_deleteMessages` so TTL / interactive / secret-chat paths
243+ /// are covered even when they never go through `replayFinalState`.
244+ public static func snapshotDeletedMessages(
245+ transaction: Transaction ,
246+ messageIds: [ MessageId ]
247+ ) {
248+ guard let accountPeerId = ( transaction. getState ( ) as? AuthorizedAccountState ) ? . peerId else {
249+ return
250+ }
251+ snapshotMessages (
252+ transaction: transaction,
253+ accountPeerId: accountPeerId,
254+ messageIds: messageIds,
255+ kind: . deleted
256+ )
257+ }
188258}
0 commit comments