From e8daabd28dcbc6e8d872f28ec358719333740737 Mon Sep 17 00:00:00 2001 From: xCyanGrizzly Date: Mon, 25 May 2026 16:45:06 +0200 Subject: [PATCH] fix(tdlib): handle 1.8.64 renames in searchChatMessages + message reply_to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit of every TDLib call site against the live 1.8.64 schema in node_modules/@prebuilt-tdlib/types/tdlib-types.d.ts surfaced three additional silent breakages beyond the getForumTopics fix in 106700b. 1. searchChatMessages parameter restructure The top-level `message_thread_id` and `saved_messages_topic_id` request fields were collapsed into a single tagged-union `topic_id: MessageTopic$Input`. Three call sites affected: - topics.ts getTopicMessages — was passing message_thread_id, now sends topic_id with the messageTopicForum variant carrying forum_topic_id. Without this the topic scan returns the whole channel (or nothing) instead of just the topic. - download.ts getChannelMessages — used to pass message_thread_id: 0; just omit the topic_id field entirely for a flat scan. - rebuild.ts — same treatment. 2. message.reply_to_message_id replaced with reply_to tagged union On incoming messages, the flat `reply_to_message_id` field was replaced with `reply_to: MessageReplyTo` (messageReplyToMessage or messageReplyToStory). Our reply-chain grouping needs the message-ID case. Added extractReplyToMessageId() that reads both old and new shapes so a transition build or future downgrade still works. Co-Authored-By: Claude Opus 4.7 (1M context) --- worker/src/rebuild.ts | 5 +++-- worker/src/tdlib/download.ts | 32 ++++++++++++++++++++++++++++++-- worker/src/tdlib/topics.ts | 10 ++++++++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/worker/src/rebuild.ts b/worker/src/rebuild.ts index 924285b..484d1b4 100644 --- a/worker/src/rebuild.ts +++ b/worker/src/rebuild.ts @@ -308,14 +308,15 @@ async function scanDestinationChannel( }>(client, { _: "searchChatMessages", chat_id: Number(chatId), + // No topic context for a flat destination scan. TDLib 1.8.64+ replaced + // `message_thread_id` / `saved_messages_topic_id` with a single + // optional `topic_id`; for a flat scan we just omit it. query: "", from_message_id: currentFromId, offset: 0, limit: 100, filter: { _: "searchMessagesFilterDocument" }, sender_id: null, - message_thread_id: 0, - saved_messages_topic_id: 0, }); if (!result.messages || result.messages.length === 0) break; diff --git a/worker/src/tdlib/download.ts b/worker/src/tdlib/download.ts index 8751154..7e155b7 100644 --- a/worker/src/tdlib/download.ts +++ b/worker/src/tdlib/download.ts @@ -39,7 +39,15 @@ interface TdMessage { id: number; date: number; media_album_id?: string; + // TDLib 1.8.50 exposed `reply_to_message_id` directly on the message. + // 1.8.64+ replaced it with a tagged-union `reply_to: MessageReplyTo`. + // Read both for resilience across versions. reply_to_message_id?: number; + reply_to?: { + _: string; + chat_id?: number; + message_id?: number; + }; content: { _: string; document?: { @@ -66,6 +74,24 @@ interface TdMessage { }; } +/** + * Pick the right "the message I'm replying to" ID across TDLib versions. + * - 1.8.50 and earlier expose it directly as `reply_to_message_id`. + * - 1.8.64+ expose `reply_to: MessageReplyTo` (tagged union); a reply to + * a regular message has `_: "messageReplyToMessage"` with `message_id`. + * - Story replies (`_: "messageReplyToStory"`) intentionally return null + * here — they aren't useful for our reply-chain grouping. + */ +function extractReplyToMessageId(msg: TdMessage): bigint | undefined { + if (msg.reply_to_message_id) { + return BigInt(msg.reply_to_message_id); + } + if (msg.reply_to && msg.reply_to._ === "messageReplyToMessage" && msg.reply_to.message_id) { + return BigInt(msg.reply_to.message_id); + } + return undefined; +} + interface TdFile { id: number; size: number; @@ -202,12 +228,14 @@ export async function getChannelMessages( const result = await invokeWithTimeout<{ messages: TdMessage[]; total_count?: number }>(client, { _: "searchChatMessages", chat_id: Number(chatId), + // No topic_id for a flat (non-forum) channel scan. TDLib 1.8.64+ + // dropped the top-level `message_thread_id: 0` we used to pass; the + // type-narrow now is "omit the field entirely if not in a topic". query: "", from_message_id: fromMessageId, offset: 0, limit: Math.min(limit, 100), filter, - message_thread_id: 0, }); if (!result.messages || result.messages.length === 0) break; @@ -233,7 +261,7 @@ export async function getChannelMessages( fileSize: BigInt(doc.document.size), date: new Date(msg.date * 1000), mediaAlbumId: msg.media_album_id && msg.media_album_id !== "0" ? msg.media_album_id : undefined, - replyToMessageId: msg.reply_to_message_id ? BigInt(msg.reply_to_message_id) : undefined, + replyToMessageId: extractReplyToMessageId(msg), caption: msg.content?.caption?.text || undefined, remoteUniqueId: doc.document.remote?.unique_id || undefined, }); diff --git a/worker/src/tdlib/topics.ts b/worker/src/tdlib/topics.ts index f69dd4f..6579388 100644 --- a/worker/src/tdlib/topics.ts +++ b/worker/src/tdlib/topics.ts @@ -239,14 +239,20 @@ export async function getTopicMessages( }>(client, { _: "searchChatMessages", chat_id: Number(chatId), + // TDLib 1.8.64+ replaced the top-level `message_thread_id` and + // `saved_messages_topic_id` parameters with a single tagged-union + // `topic_id: MessageTopic$Input`. For a forum topic, use the + // messageTopicForum variant carrying the forum_topic_id. + topic_id: { + _: "messageTopicForum", + forum_topic_id: Number(topicId), + }, query: "", - message_thread_id: Number(topicId), from_message_id: currentFromId, offset: 0, limit: Math.min(limit, 100), filter: null, sender_id: null, - saved_messages_topic_id: 0, }); if (!result.messages || result.messages.length === 0) break;