feat(bot): refresh groups list — manual button + auto event listener

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
This commit is contained in:
yiekheng 2026-05-09 16:54:55 +08:00
parent 5259f88776
commit 43882d5a1b
4 changed files with 61 additions and 3 deletions

View File

@ -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

View File

@ -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<v
await showMenu(ctx, view);
}
export async function refreshGroupsList(ctx: Context, accountId: string): Promise<void> {
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<void> {
await ctx.answerCallbackQuery();
const op = await findOperator(ctx);

View File

@ -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,
};
}

View File

@ -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<ConnectionState>) => {
if (update.qr) {
void onEvent({ type: "qr", payload: update.qr });