fix: use searchChatMessages instead of getChatHistory for channel scanning

getChatHistory fails silently in supergroups with hidden history for new
members, returning only system messages. searchChatMessages with document
and photo filters works regardless of history visibility settings.

Also adds getChats call after TDLib client creation to populate the chat
list, preventing 'Chat not found' errors.
This commit is contained in:
admin
2026-03-21 20:15:18 +01:00
parent fe7a548fef
commit ba3d3a6040
2 changed files with 83 additions and 87 deletions

View File

@@ -154,31 +154,25 @@ export async function getChannelMessages(
const photos: TelegramPhoto[] = []; const photos: TelegramPhoto[] = [];
const boundary = lastProcessedMessageId ? Number(lastProcessedMessageId) : null; const boundary = lastProcessedMessageId ? Number(lastProcessedMessageId) : null;
// Open the chat so TDLib loads remote messages, then load chat history // Open the chat so TDLib can access it
// from the server. getChat forces TDLib to fetch chat metadata and
// getChatHistory with from_message_id=0 triggers a server-side fetch.
await invokeWithTimeout(client, {
_: "openChat",
chat_id: Number(chatId),
});
// Force TDLib to load the full chat list which populates chat state
try { try {
await invokeWithTimeout(client, { await invokeWithTimeout(client, { _: "openChat", chat_id: Number(chatId) });
_: "getChat",
chat_id: Number(chatId),
});
} catch { } catch {
// Ignore - chat may already be loaded // Ignore may already be open
} }
// Give TDLib time to sync chat data from the server
await sleep(2000);
let currentFromId = 0;
let totalScanned = 0; let totalScanned = 0;
let pageCount = 0; let pageCount = 0;
// Use searchChatMessages with document filter — this works even when
// getChatHistory is restricted (e.g. hidden history for new members).
// We search for documents first, then photos separately.
for (const filter of [
{ _: "searchMessagesFilterDocument" as const, kind: "document" },
{ _: "searchMessagesFilterPhoto" as const, kind: "photo" },
]) {
let fromMessageId = 0;
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
while (true) { while (true) {
if (pageCount >= MAX_SCAN_PAGES) { if (pageCount >= MAX_SCAN_PAGES) {
@@ -190,15 +184,15 @@ export async function getChannelMessages(
} }
pageCount++; pageCount++;
const previousFromId = currentFromId; const result = await invokeWithTimeout<{ messages: TdMessage[]; total_count?: number }>(client, {
_: "searchChatMessages",
const result = await invokeWithTimeout<{ messages: TdMessage[] }>(client, {
_: "getChatHistory",
chat_id: Number(chatId), chat_id: Number(chatId),
from_message_id: currentFromId, query: "",
from_message_id: fromMessageId,
offset: 0, offset: 0,
limit: Math.min(limit, 100), limit: Math.min(limit, 100),
only_local: false, filter,
message_thread_id: 0,
}); });
if (!result.messages || result.messages.length === 0) break; if (!result.messages || result.messages.length === 0) break;
@@ -209,6 +203,8 @@ export async function getChannelMessages(
// Check for archive documents // Check for archive documents
const doc = msg.content?.document; const doc = msg.content?.document;
if (doc?.file_name && doc.document && isArchiveAttachment(doc.file_name)) { if (doc?.file_name && doc.document && isArchiveAttachment(doc.file_name)) {
// Skip if we've already processed past this message
if (boundary && msg.id <= boundary) continue;
archives.push({ archives.push({
id: BigInt(msg.id), id: BigInt(msg.id),
fileName: doc.file_name, fileName: doc.file_name,
@@ -223,6 +219,7 @@ export async function getChannelMessages(
const photo = msg.content?.photo; const photo = msg.content?.photo;
const caption = msg.content?.caption?.text ?? ""; const caption = msg.content?.caption?.text ?? "";
if (photo?.sizes && photo.sizes.length > 0) { if (photo?.sizes && photo.sizes.length > 0) {
if (boundary && msg.id <= boundary) continue;
const smallest = photo.sizes[0]; const smallest = photo.sizes[0];
photos.push({ photos.push({
id: BigInt(msg.id), id: BigInt(msg.id),
@@ -234,34 +231,21 @@ export async function getChannelMessages(
} }
} }
// Report scanning progress after each page
onProgress?.(totalScanned); onProgress?.(totalScanned);
currentFromId = result.messages[result.messages.length - 1].id; // Advance pagination
fromMessageId = result.messages[result.messages.length - 1].id;
// Stuck detection: if from_message_id didn't advance, break to prevent infinite loop
if (currentFromId === previousFromId) {
log.warn(
{ chatId: chatId.toString(), currentFromId, totalScanned },
"Pagination stuck (from_message_id not advancing), breaking"
);
break;
}
// Stop scanning once we've gone past the boundary (this page is the lookback)
if (boundary && currentFromId < boundary) break;
if (result.messages.length < Math.min(limit, 100)) break; if (result.messages.length < Math.min(limit, 100)) break;
// Rate limit delay
await sleep(config.apiDelayMs); await sleep(config.apiDelayMs);
} }
}
// Close the chat after scanning // Close the chat after scanning
await invokeWithTimeout(client, { await invokeWithTimeout(client, {
_: "closeChat", _: "closeChat",
chat_id: Number(chatId), chat_id: Number(chatId),
}).catch(() => {}); // Ignore close errors }).catch(() => {});
log.info( log.info(
{ chatId: chatId.toString(), archives: archives.length, photos: photos.length, totalScanned, pages: pageCount }, { chatId: chatId.toString(), archives: archives.length, photos: photos.length, totalScanned, pages: pageCount },

View File

@@ -333,6 +333,18 @@ export async function runWorkerForAccount(
phone: account.phone, phone: account.phone,
}); });
// Load the chat list so TDLib knows about all chats
// Without this, getChat/getChatHistory fail with "Chat not found"
try {
await client.invoke({
_: "getChats",
chat_list: { _: "chatListMain" },
limit: 1000,
});
} catch {
// Ignore — chat list may already be loaded
}
const counters = { const counters = {
messagesScanned: 0, messagesScanned: 0,
zipsFound: 0, zipsFound: 0,