101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
import { readFile, stat } from "node:fs/promises";
|
|
import type { WASocket, AnyMessageContent } from "@whiskeysockets/baileys";
|
|
import pino from "pino";
|
|
|
|
const logger = pino({ name: "sender" });
|
|
|
|
type SocketWithAssertSessions = WASocket & {
|
|
assertSessions?: (jids: string[], force: boolean) => Promise<boolean>;
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
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") {
|
|
return { ok: 0, failed: 0, total: participantJids.length };
|
|
}
|
|
let ok = 0;
|
|
let failed = 0;
|
|
for (const chunk of await chunked(participantJids, CHUNK_SIZE)) {
|
|
try {
|
|
await internal.assertSessions(chunk, true);
|
|
ok += chunk.length;
|
|
} catch (err) {
|
|
failed += chunk.length;
|
|
logger.warn({ groupJid, err: (err as Error).message }, "assertSessions chunk failed");
|
|
}
|
|
}
|
|
return { ok, failed, total: participantJids.length };
|
|
}
|
|
|
|
async function sendWithRetry(
|
|
socket: WASocket,
|
|
groupJid: string,
|
|
content: AnyMessageContent,
|
|
): Promise<{ messageId: string | undefined }> {
|
|
await ensureSessionsForGroup(socket, groupJid);
|
|
try {
|
|
const result = await socket.sendMessage(groupJid, content);
|
|
return { messageId: result?.key?.id ?? undefined };
|
|
} catch (err) {
|
|
const message = (err as Error)?.message ?? "";
|
|
if (message.includes("No sessions")) {
|
|
await new Promise((r) => setTimeout(r, 2000));
|
|
await ensureSessionsForGroup(socket, groupJid);
|
|
const result = await socket.sendMessage(groupJid, content);
|
|
return { messageId: result?.key?.id ?? undefined };
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
export async function sendTextToGroup(
|
|
socket: WASocket,
|
|
groupJid: string,
|
|
text: string,
|
|
): Promise<{ messageId: string | undefined }> {
|
|
return sendWithRetry(socket, groupJid, { text });
|
|
}
|
|
|
|
export type MediaKind = "image" | "video" | "document";
|
|
|
|
export async function sendMediaToGroup(
|
|
socket: WASocket,
|
|
groupJid: string,
|
|
kind: MediaKind,
|
|
filePath: string,
|
|
options: { caption?: string; mimeType?: string; filename?: string } = {},
|
|
): Promise<{ messageId: string | undefined }> {
|
|
// Validate the file exists and read into a buffer. For very large files
|
|
// (>50MB) Baileys also accepts a stream, but for our reminder use case
|
|
// files are typically <30MB which fits comfortably in memory.
|
|
await stat(filePath);
|
|
const buffer = await readFile(filePath);
|
|
|
|
const content: AnyMessageContent =
|
|
kind === "image"
|
|
? { image: buffer, caption: options.caption, mimetype: options.mimeType }
|
|
: kind === "video"
|
|
? { video: buffer, caption: options.caption, mimetype: options.mimeType }
|
|
: {
|
|
document: buffer,
|
|
caption: options.caption,
|
|
fileName: options.filename ?? "file",
|
|
mimetype: options.mimeType ?? "application/octet-stream",
|
|
};
|
|
|
|
return sendWithRetry(socket, groupJid, content);
|
|
}
|