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");
}
// Load ALL chats from both main and archive lists by paginating getChats.
// TDLib's getChats returns batches — keep calling until it returns
// an empty list, which signals all chats have been loaded.
// First, load all chats into TDLib's cache using loadChats (the proper API).
// loadChats returns 404 when 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>();
for (const chatList of [
{ _: "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++) {
for (const chatList of chatLists) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result = (await withFloodWait(
let result: { chat_ids: number[] };
try {
result = (await withFloodWait(
() => client.invoke({
_: "getChats",
chat_list: chatList,
limit: 100,
limit: 50000,
}),
"getChats"
)) as { chat_ids: number[] };
if (!result.chat_ids || result.chat_ids.length === 0) {
break;
} catch {
continue;
}
if (!result.chat_ids || result.chat_ids.length === 0) continue;
for (const chatId of result.chat_ids) {
if (seenChatIds.has(chatId)) continue;
seenChatIds.add(chatId);
@@ -79,7 +112,6 @@ export async function getAccountChats(
let title = chat.title ?? `Chat ${chatId}`;
if (chatType === "chatTypeSupergroup") {
// Get supergroup details to check if it's a channel or group
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sg = (await withFloodWait(
@@ -99,7 +131,6 @@ export async function getAccountChats(
type = "group";
} else if (chatType === "chatTypePrivate" || chatType === "chatTypeSecret") {
type = "private";
// Label the self-chat as "Saved Messages"
if (selfUserId !== null && chat.type?.user_id === selfUserId) {
title = "Saved Messages";
}
@@ -115,14 +146,11 @@ export async function getAccountChats(
log.warn({ chatId, err }, "Failed to get chat details, skipping");
}
}
await sleep(config.apiDelayMs);
}
}
log.info(
{ total: chats.length },
"Fetched all chats from Telegram (main + archive)"
"Fetched all chats from Telegram (main + archive + folders)"
);
return chats;

View File

@@ -335,25 +335,45 @@ export async function runWorkerForAccount(
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".
// TDLib returns chats in batches — keep calling until empty.
// Load from both main and archive lists to cover older/archived chats.
for (const chatList of [
{ _: "chatListMain" as const },
{ _: "chatListArchive" as const },
]) {
// loadChats returns a 404 when all chats have been loaded — that's the stop signal.
// Load from main, archive, AND chat folders to cover all chat types.
{
// Discover chat folders first
const folderLists: { _: "chatListFolder"; chat_folder_id: number }[] = [];
try {
for (let page = 0; page < 500; page++) {
const chatResult = await client.invoke({
_: "getChats",
chat_list: chatList,
limit: 100,
}) as { chat_ids?: number[] };
if (!chatResult.chat_ids || chatResult.chat_ids.length === 0) break;
const folders = await client.invoke({ _: "getChatFolders" }) as {
chat_folders?: { id: number }[];
};
if (folders.chat_folders) {
for (const f of folders.chat_folders) {
folderLists.push({ _: "chatListFolder", chat_folder_id: f.id });
}
}
} 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
}
}
}