From 43882d5a1b68243140d13533f6a727335950c6eb Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sat, 9 May 2026 16:54:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(bot):=20refresh=20groups=20list=20?= =?UTF-8?q?=E2=80=94=20manual=20button=20+=20auto=20event=20listener?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Group sync previously only ran once at pairing time, so groups created in WhatsApp afterwards never showed up. Two complementary fixes: - 🔄 Refresh button in the groups list view triggers syncGroupsForAccount() on demand and re-renders the menu - session.ts now subscribes to Baileys 'groups.upsert' and 'groups.update' events and re-syncs (debounced 1.5s) so new groups appear without manual action --- apps/bot/src/telegram/bot.ts | 4 ++++ apps/bot/src/telegram/callbacks.ts | 33 ++++++++++++++++++++++++++++++ apps/bot/src/telegram/menus.ts | 10 ++++++--- apps/bot/src/whatsapp/session.ts | 17 +++++++++++++++ 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/apps/bot/src/telegram/bot.ts b/apps/bot/src/telegram/bot.ts index 8ddae28..b3bce49 100644 --- a/apps/bot/src/telegram/bot.ts +++ b/apps/bot/src/telegram/bot.ts @@ -19,6 +19,7 @@ import { showGroupDetail, showSendTestPrompt, executeSendTest, + refreshGroupsList, } from "./callbacks.js"; import { consumePendingPairLabel, @@ -82,6 +83,9 @@ export function createTelegramBot(): Bot { bot.callbackQuery(/^st:(.+)$/, async (ctx) => { await showSendTestPrompt(ctx, ctx.match[1]!); }); + bot.callbackQuery(/^rs:(.+)$/, async (ctx) => { + await refreshGroupsList(ctx, ctx.match[1]!); + }); // Plain-text messages: if the operator is in the "pending pair label" state // (because they tapped 📡 Pair New), treat their next non-command message as diff --git a/apps/bot/src/telegram/callbacks.ts b/apps/bot/src/telegram/callbacks.ts index f527a82..5ae9cb7 100644 --- a/apps/bot/src/telegram/callbacks.ts +++ b/apps/bot/src/telegram/callbacks.ts @@ -10,6 +10,7 @@ import { sessionManager } from "../whatsapp/session-manager.js"; import { writeAuditLog } from "../audit.js"; import { setPendingPairLabel, setPendingSendToGroup } from "./state.js"; import { sendTextToGroup } from "../whatsapp/sender.js"; +import { syncGroupsForAccount } from "../whatsapp/group-sync.js"; import { mainMenu, helpMenu, @@ -92,6 +93,38 @@ export async function showGroupsList(ctx: Context, accountId: string): Promise { + const op = await findOperator(ctx); + if (!op) { + await ctx.answerCallbackQuery(); + return; + } + const account = await db.query.whatsappAccounts.findFirst({ + where: (a, { eq, and }) => and(eq(a.id, accountId), eq(a.operatorId, op.id)), + }); + if (!account) { + await ctx.answerCallbackQuery({ text: "Account not found.", show_alert: true }); + return; + } + const session = sessionManager.getSession(accountId); + if (!session) { + await ctx.answerCallbackQuery({ + text: "Account not connected. Re-pair first.", + show_alert: true, + }); + return; + } + await ctx.answerCallbackQuery({ text: "Refreshing…" }); + try { + const result = await syncGroupsForAccount(accountId, session.socket); + logger.info({ accountId, count: result.synced }, "refreshGroupsList: ok"); + } catch (err) { + logger.error({ err, accountId }, "refreshGroupsList: failed"); + } + const view = await groupsListMenu(op.id, accountId); + if (view) await showMenu(ctx, view); +} + export async function showUnpairConfirm(ctx: Context, accountId: string): Promise { await ctx.answerCallbackQuery(); const op = await findOperator(ctx); diff --git a/apps/bot/src/telegram/menus.ts b/apps/bot/src/telegram/menus.ts index bfa43f7..535ad62 100644 --- a/apps/bot/src/telegram/menus.ts +++ b/apps/bot/src/telegram/menus.ts @@ -144,18 +144,22 @@ export async function groupsListMenu( const name = g.name.length > 32 ? `${g.name.slice(0, 31)}…` : g.name; keyboard.text(`👥 ${name}`, `gr:${g.id}`).row(); } - keyboard.text("⬅ Account", `acc:${accountId}`).text("⬅ Main Menu", "m:main"); + keyboard + .text("🔄 Refresh", `rs:${accountId}`) + .row() + .text("⬅ Account", `acc:${accountId}`) + .text("⬅ Main Menu", "m:main"); if (groups.length === 0) { return { - text: `👥 *Groups in ${account.label}*\n\nNo groups synced yet.`, + text: `👥 *Groups in ${account.label}*\n\nNo groups synced yet. Tap *Refresh* to pull the latest list.`, keyboard, }; } const overflow = groups.length > 30 ? `\n\n_…${groups.length - 30} more not shown_` : ""; return { - text: `👥 *Groups in ${account.label}*\n\nTap a group to send a test message.${overflow}`, + text: `👥 *Groups in ${account.label}*\n\nTap a group to send a test message, or *Refresh* to pick up new groups.${overflow}`, keyboard, }; } diff --git a/apps/bot/src/whatsapp/session.ts b/apps/bot/src/whatsapp/session.ts index 52f7314..d79fd1d 100644 --- a/apps/bot/src/whatsapp/session.ts +++ b/apps/bot/src/whatsapp/session.ts @@ -11,6 +11,7 @@ import { } from "@whiskeysockets/baileys"; import { logger } from "../logger.js"; import { env } from "../env.js"; +import { syncGroupsForAccount } from "./group-sync.js"; export type SessionEvent = | { type: "qr"; payload: string } @@ -50,6 +51,22 @@ export async function startSession(params: { socket.ev.on("creds.update", () => void saveCreds()); + // Keep `whatsapp_groups` in sync as Baileys discovers new groups or updates. + // Debounced so a flurry of upsert events from the initial sync collapses + // into a single DB write. + let groupsSyncTimer: NodeJS.Timeout | null = null; + const scheduleGroupsSync = (): void => { + if (groupsSyncTimer) return; + groupsSyncTimer = setTimeout(() => { + groupsSyncTimer = null; + void syncGroupsForAccount(accountId, socket).catch((err) => + logger.warn({ err, accountId }, "auto group sync failed"), + ); + }, 1500); + }; + socket.ev.on("groups.upsert", scheduleGroupsSync); + socket.ev.on("groups.update", scheduleGroupsSync); + socket.ev.on("connection.update", (update: Partial) => { if (update.qr) { void onEvent({ type: "qr", payload: update.qr });