fix: use loadChats API and load chat folders for complete chat discovery
Some checks failed
continuous-integration/drone/push Build is failing

- Switch from getChats pagination to loadChats (the TDLib-recommended
  API) which properly loads all chats into TDLib's cache and signals
  completion with a 404 error
- Discover and load chat folders via getChatFolders so chats in
  user-created folders are included
- Load from main + archive + all folders in both worker startup and
  getAccountChats channel discovery
- After loading, use getChats with high limit to retrieve all cached IDs
- This ensures private chats, 1-on-1 conversations, Saved Messages,
  basic groups, and archived/folder chats are all discoverable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-23 22:38:49 +01:00
parent aef76828ef
commit 1425db8774
2 changed files with 130 additions and 82 deletions

View File

@@ -34,31 +34,64 @@ export async function getAccountChats(
log.warn("Failed to get current user via getMe"); log.warn("Failed to get current user via getMe");
} }
// Load ALL chats from both main and archive lists by paginating getChats. // First, load all chats into TDLib's cache using loadChats (the proper API).
// TDLib's getChats returns batches — keep calling until it returns // loadChats returns 404 when all chats have been loaded.
// an empty list, which signals all chats have been loaded. // Then use getChats to retrieve the IDs for enrichment.
// Load from main, archive, AND chat folders to cover all chat types.
const folderLists: { _: "chatListFolder"; chat_folder_id: number }[] = [];
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const folders = (await client.invoke({ _: "getChatFolders" })) as any;
if (folders?.chat_folders) {
for (const f of folders.chat_folders) {
folderLists.push({ _: "chatListFolder", chat_folder_id: f.id });
}
}
} catch {
// getChatFolders may not be available in older TDLib versions
}
const chatLists: Record<string, unknown>[] = [
{ _: "chatListMain" },
{ _: "chatListArchive" },
...folderLists,
];
// Phase 1: Load all chats into TDLib's cache
for (const chatList of chatLists) {
try {
for (let page = 0; page < 500; page++) {
await withFloodWait(
() => client.invoke({ _: "loadChats", chat_list: chatList, limit: 100 }),
"loadChats"
);
}
} catch {
// 404 = all chats loaded (expected), or unsupported list type
}
}
// Phase 2: Retrieve chat IDs and enrich with details
const seenChatIds = new Set<number>(); const seenChatIds = new Set<number>();
for (const chatList of [ for (const chatList of chatLists) {
{ _: "chatListMain" as const },
{ _: "chatListArchive" as const },
]) {
const MAX_PAGES = 500; // support up to 50,000 chats per list
for (let page = 0; page < MAX_PAGES; page++) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = (await withFloodWait( let result: { chat_ids: number[] };
try {
result = (await withFloodWait(
() => client.invoke({ () => client.invoke({
_: "getChats", _: "getChats",
chat_list: chatList, chat_list: chatList,
limit: 100, limit: 50000,
}), }),
"getChats" "getChats"
)) as { chat_ids: number[] }; )) as { chat_ids: number[] };
} catch {
if (!result.chat_ids || result.chat_ids.length === 0) { continue;
break;
} }
if (!result.chat_ids || result.chat_ids.length === 0) continue;
for (const chatId of result.chat_ids) { for (const chatId of result.chat_ids) {
if (seenChatIds.has(chatId)) continue; if (seenChatIds.has(chatId)) continue;
seenChatIds.add(chatId); seenChatIds.add(chatId);
@@ -79,7 +112,6 @@ export async function getAccountChats(
let title = chat.title ?? `Chat ${chatId}`; let title = chat.title ?? `Chat ${chatId}`;
if (chatType === "chatTypeSupergroup") { if (chatType === "chatTypeSupergroup") {
// Get supergroup details to check if it's a channel or group
try { try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const sg = (await withFloodWait( const sg = (await withFloodWait(
@@ -99,7 +131,6 @@ export async function getAccountChats(
type = "group"; type = "group";
} else if (chatType === "chatTypePrivate" || chatType === "chatTypeSecret") { } else if (chatType === "chatTypePrivate" || chatType === "chatTypeSecret") {
type = "private"; type = "private";
// Label the self-chat as "Saved Messages"
if (selfUserId !== null && chat.type?.user_id === selfUserId) { if (selfUserId !== null && chat.type?.user_id === selfUserId) {
title = "Saved Messages"; title = "Saved Messages";
} }
@@ -115,14 +146,11 @@ export async function getAccountChats(
log.warn({ chatId, err }, "Failed to get chat details, skipping"); log.warn({ chatId, err }, "Failed to get chat details, skipping");
} }
} }
await sleep(config.apiDelayMs);
}
} }
log.info( log.info(
{ total: chats.length }, { total: chats.length },
"Fetched all chats from Telegram (main + archive)" "Fetched all chats from Telegram (main + archive + folders)"
); );
return chats; return chats;

View File

@@ -335,25 +335,45 @@ export async function runWorkerForAccount(
phone: account.phone, phone: account.phone,
}); });
// Load the full chat list so TDLib knows about all chats. // Load all chats into TDLib's local cache using loadChats (the recommended API).
// Without this, getChat/searchChatMessages fail with "Chat not found". // Without this, getChat/searchChatMessages fail with "Chat not found".
// TDLib returns chats in batches — keep calling until empty. // loadChats returns a 404 when all chats have been loaded — that's the stop signal.
// Load from both main and archive lists to cover older/archived chats. // Load from main, archive, AND chat folders to cover all chat types.
for (const chatList of [ {
{ _: "chatListMain" as const }, // Discover chat folders first
{ _: "chatListArchive" as const }, const folderLists: { _: "chatListFolder"; chat_folder_id: number }[] = [];
]) {
try { try {
for (let page = 0; page < 500; page++) { const folders = await client.invoke({ _: "getChatFolders" }) as {
const chatResult = await client.invoke({ chat_folders?: { id: number }[];
_: "getChats", };
chat_list: chatList, if (folders.chat_folders) {
limit: 100, for (const f of folders.chat_folders) {
}) as { chat_ids?: number[] }; folderLists.push({ _: "chatListFolder", chat_folder_id: f.id });
if (!chatResult.chat_ids || chatResult.chat_ids.length === 0) break; }
} }
} catch { } catch {
// Ignore — chat list may already be loaded // getChatFolders may not be available in older TDLib versions
}
const chatLists: Record<string, unknown>[] = [
{ _: "chatListMain" },
{ _: "chatListArchive" },
...folderLists,
];
for (const chatList of chatLists) {
try {
for (let page = 0; page < 500; page++) {
await client.invoke({
_: "loadChats",
chat_list: chatList,
limit: 100,
});
// loadChats returns ok — keep going until 404
}
} catch {
// 404 = all chats loaded (expected), or unsupported list type
}
} }
} }