From 9062ba7e7f1597b4dcac48253d33c6cadfd74433 Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sat, 9 May 2026 17:08:11 +0800 Subject: [PATCH] fix(bot): drop removed groups during sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously syncGroupsForAccount only upserted, so groups removed from WhatsApp (deleted, bot was kicked, etc.) lingered in the DB. Now compute the diff: any whatsapp_groups row for this account whose wa_group_jid is not in the live fetch result is deleted. Skip the delete sweep when the fetch returns empty — that's more likely transient than a genuine "every group gone" signal, and we don't want to nuke valid data on a hiccup. Return shape gains a `removed` count alongside `synced`. --- apps/bot/src/whatsapp/group-sync.ts | 37 ++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/apps/bot/src/whatsapp/group-sync.ts b/apps/bot/src/whatsapp/group-sync.ts index 00afcd2..d1d97ed 100644 --- a/apps/bot/src/whatsapp/group-sync.ts +++ b/apps/bot/src/whatsapp/group-sync.ts @@ -1,4 +1,4 @@ -import { sql } from "drizzle-orm"; +import { and, eq, notInArray, sql } from "drizzle-orm"; import type { WASocket } from "@whiskeysockets/baileys"; import { whatsappGroups } from "@cmbot/db"; import { db } from "../db.js"; @@ -7,13 +7,35 @@ import { logger } from "../logger.js"; export async function syncGroupsForAccount( accountId: string, socket: WASocket, -): Promise<{ synced: number }> { +): Promise<{ synced: number; removed: number }> { const meta = await socket.groupFetchAllParticipating(); const entries = Object.values(meta); + const liveJids = entries.map((g) => g.id); + + // Remove DB rows for groups that are no longer in the live participant list + // (group was deleted, bot was removed, etc.). Only run the delete when we + // got at least one live group back — an empty result is more likely a + // transient WA fetch failure than a genuine "all groups gone" signal, and + // we don't want to nuke valid data on a hiccup. + let removed: { id: string }[] = []; + if (liveJids.length > 0) { + removed = await db + .delete(whatsappGroups) + .where( + and( + eq(whatsappGroups.accountId, accountId), + notInArray(whatsappGroups.waGroupJid, liveJids), + ), + ) + .returning({ id: whatsappGroups.id }); + } if (entries.length === 0) { - logger.info({ accountId }, "group-sync: no groups"); - return { synced: 0 }; + logger.info( + { accountId }, + "group-sync: empty fetch — skipping delete sweep (treating as transient)", + ); + return { synced: 0, removed: 0 }; } const rows = entries.map((g) => ({ @@ -37,6 +59,9 @@ export async function syncGroupsForAccount( }, }); - logger.info({ accountId, count: rows.length }, "group-sync: synced"); - return { synced: rows.length }; + logger.info( + { accountId, count: rows.length, removed: removed.length }, + "group-sync: synced", + ); + return { synced: rows.length, removed: removed.length }; }