From 2fdcdb6202baa67503dfbae3712e6a3d186bb92f Mon Sep 17 00:00:00 2001 From: yiekheng Date: Sat, 9 May 2026 16:50:51 +0800 Subject: [PATCH] fix(bot): explicit assertSessions before group send MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit groupMetadata alone wasn't enough — Baileys won't establish individual libsignal sessions lazily during sendMessage, so the first send to a freshly-paired group fails per-participant. Cast to the internal assertSessions(jids, force=true) and call it on every participant before attempting to send. --- apps/bot/src/whatsapp/sender.ts | 35 +++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/apps/bot/src/whatsapp/sender.ts b/apps/bot/src/whatsapp/sender.ts index d1db645..229fbd7 100644 --- a/apps/bot/src/whatsapp/sender.ts +++ b/apps/bot/src/whatsapp/sender.ts @@ -1,19 +1,33 @@ import type { WASocket } from "@whiskeysockets/baileys"; +// Internal Baileys method used to fetch pre-key bundles and establish individual +// libsignal sessions for a list of JIDs. Not part of the public type, but it's +// the only way to avoid "No sessions" on the first group send after pairing. +type SocketWithAssertSessions = WASocket & { + assertSessions?: (jids: string[], force: boolean) => Promise; +}; + +async function ensureSessionsForGroup(socket: WASocket, groupJid: string): Promise { + const metadata = await socket.groupMetadata(groupJid); + const participantJids = metadata.participants.map((p) => p.id); + const internal = socket as SocketWithAssertSessions; + if (typeof internal.assertSessions === "function") { + await internal.assertSessions(participantJids, true); + } +} + export async function sendTextToGroup( socket: WASocket, groupJid: string, text: string, ): Promise<{ messageId: string | undefined }> { - // Force-fetch group metadata so Baileys populates its internal participant - // map and triggers libsignal session establishment for any unknown member. - // Without this, the first send to a freshly-paired group fails with - // "No sessions" from libsignal-node. + // Establish individual signal sessions with every participant before sending + // — group sends fan out per participant and need a per-participant session, + // but Baileys won't establish them lazily inside sendMessage. try { - await socket.groupMetadata(groupJid); + await ensureSessionsForGroup(socket, groupJid); } catch { - // If metadata fetch itself fails we still try the send — sendMessage will - // surface a clearer error than the metadata layer. + // Non-fatal: fall through and let sendMessage surface its error } try { @@ -24,7 +38,12 @@ export async function sendTextToGroup( // once after a brief delay before giving up. const message = (err as Error)?.message ?? ""; if (message.includes("No sessions")) { - await new Promise((resolve) => setTimeout(resolve, 1500)); + await new Promise((resolve) => setTimeout(resolve, 2000)); + try { + await ensureSessionsForGroup(socket, groupJid); + } catch { + // ignore + } const result = await socket.sendMessage(groupJid, { text }); return { messageId: result?.key?.id ?? undefined }; }