fix(bot): chunk participant pre-key fetches to survive broken JIDs
WhatsApp's pre-key endpoint returns 406 not-acceptable if ANY single JID in the batch is in a broken state (deleted account, deactivated, etc.). With Baileys' default behavior of asking for the whole participant list at once, one stale member poisons the whole group send. Chunk participant JIDs into batches of 5 and tolerate per-chunk failures. The send fan-out then works for the participants whose sessions did land, which covers the vast majority of real-world groups. Also adds explicit pino logging so we can see which chunks failed during diagnosis.
This commit is contained in:
parent
2fdcdb6202
commit
5259f88776
@ -1,4 +1,7 @@
|
||||
import type { WASocket } from "@whiskeysockets/baileys";
|
||||
import pino from "pino";
|
||||
|
||||
const logger = pino({ name: "sender" });
|
||||
|
||||
// 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
|
||||
@ -7,13 +10,53 @@ type SocketWithAssertSessions = WASocket & {
|
||||
assertSessions?: (jids: string[], force: boolean) => Promise<boolean>;
|
||||
};
|
||||
|
||||
async function ensureSessionsForGroup(socket: WASocket, groupJid: string): Promise<void> {
|
||||
const CHUNK_SIZE = 5;
|
||||
|
||||
async function chunked<T>(items: T[], size: number): Promise<T[][]> {
|
||||
const out: T[][] = [];
|
||||
for (let i = 0; i < items.length; i += size) {
|
||||
out.push(items.slice(i, i + size));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Establish per-participant libsignal sessions in small chunks. WhatsApp's
|
||||
* pre-key endpoint returns 406 "not-acceptable" if any single JID in the
|
||||
* batch is in a broken state (deleted account, deactivated, etc.) — so we
|
||||
* chunk the work and tolerate per-chunk failures rather than letting one
|
||||
* bad participant poison the whole send.
|
||||
*/
|
||||
async function ensureSessionsForGroup(
|
||||
socket: WASocket,
|
||||
groupJid: string,
|
||||
): Promise<{ ok: number; failed: number; total: number }> {
|
||||
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);
|
||||
if (typeof internal.assertSessions !== "function") {
|
||||
return { ok: 0, failed: 0, total: participantJids.length };
|
||||
}
|
||||
let ok = 0;
|
||||
let failed = 0;
|
||||
const chunks = await chunked(participantJids, CHUNK_SIZE);
|
||||
for (const chunk of chunks) {
|
||||
try {
|
||||
await internal.assertSessions(chunk, true);
|
||||
ok += chunk.length;
|
||||
} catch (err) {
|
||||
failed += chunk.length;
|
||||
logger.warn(
|
||||
{ groupJid, chunkSize: chunk.length, err: (err as Error).message },
|
||||
"assertSessions chunk failed; continuing",
|
||||
);
|
||||
}
|
||||
}
|
||||
logger.info(
|
||||
{ groupJid, ok, failed, total: participantJids.length },
|
||||
"ensureSessionsForGroup: done",
|
||||
);
|
||||
return { ok, failed, total: participantJids.length };
|
||||
}
|
||||
|
||||
export async function sendTextToGroup(
|
||||
@ -21,29 +64,16 @@ export async function sendTextToGroup(
|
||||
groupJid: string,
|
||||
text: string,
|
||||
): Promise<{ messageId: string | undefined }> {
|
||||
// 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 ensureSessionsForGroup(socket, groupJid);
|
||||
} catch {
|
||||
// Non-fatal: fall through and let sendMessage surface its error
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await socket.sendMessage(groupJid, { text });
|
||||
return { messageId: result?.key?.id ?? undefined };
|
||||
} catch (err) {
|
||||
// libsignal session establishment can race on the very first send. Retry
|
||||
// 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, 2000));
|
||||
try {
|
||||
await ensureSessionsForGroup(socket, groupJid);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
const result = await socket.sendMessage(groupJid, { text });
|
||||
return { messageId: result?.key?.id ?? undefined };
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user