@manybot/manybot 5.5.3 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +15 -1
  2. package/dist/client/cache.js +1 -1
  3. package/dist/client/store.js +11 -0
  4. package/dist/config.js +230 -12
  5. package/dist/drivers/baileys/adapter.js +556 -0
  6. package/dist/drivers/{whatsapp → baileys}/api/index.js +533 -386
  7. package/dist/drivers/baileys/index.js +560 -0
  8. package/dist/drivers/{whatsapp → baileys}/loginPrompt.js +2 -0
  9. package/dist/drivers/{whatsapp → baileys}/messageHandler.js +46 -16
  10. package/dist/drivers/{whatsapp → baileys}/sdk/baileysSock.js +11 -31
  11. package/dist/drivers/jid.js +31 -0
  12. package/dist/drivers/types.js +14 -0
  13. package/dist/drivers/whatsmeow/client.js +203 -0
  14. package/dist/drivers/whatsmeow/index.js +79 -0
  15. package/dist/drivers/whatsmeow/installer.js +70 -0
  16. package/dist/drivers/whatsmeow/supervisor.js +309 -0
  17. package/dist/drivers/whatsmeow/whatsmeow.proto +64 -0
  18. package/dist/i18n/index.js +8 -12
  19. package/dist/kernel/alerts.js +190 -0
  20. package/dist/kernel/contactAutoSave.js +200 -0
  21. package/dist/kernel/driverManager.js +117 -0
  22. package/dist/kernel/pluginApi.js +25 -7
  23. package/dist/kernel/pluginLoader.js +12 -12
  24. package/dist/kernel/sendFallbackGuard.js +173 -0
  25. package/dist/kernel/sendGuard.js +143 -33
  26. package/dist/kernel/statusServer.js +39 -0
  27. package/dist/kernel/updateCheck.js +88 -0
  28. package/dist/kernel/waContract.js +16 -0
  29. package/dist/locales/en.json +15 -1
  30. package/dist/locales/es.json +15 -1
  31. package/dist/locales/pt.json +15 -1
  32. package/dist/main.js +77 -5
  33. package/dist/types.js +18 -11
  34. package/package.json +7 -9
  35. package/dist/core/adapter.js +0 -12
  36. package/dist/core/capabilities.js +0 -16
  37. package/dist/core/types.js +0 -6
  38. package/dist/drivers/index.js +0 -14
  39. package/dist/drivers/whatsapp/adapter.js +0 -7
  40. package/dist/drivers/whatsapp/index.js +0 -382
@@ -0,0 +1,560 @@
1
+ /**
2
+ * drivers/baileys/index.ts
3
+ *
4
+ * Main WhatsApp driver entry point (Baileys implementation).
5
+ *
6
+ * Owns the socket state machine (connect / disconnect / reconnect /
7
+ * circuit breaker) and exposes the live `WaContract` adapter to the
8
+ * rest of the kernel. Plugins, sendFallbackGuard, messageHandler,
9
+ * pluginLoader, contactAutoSave and sendGuard all consume the
10
+ * `WaContract` returned here — they never touch the raw Baileys socket.
11
+ *
12
+ * The contract's per-call methods (sendText, react, presence, etc.)
13
+ * come from `./adapter.ts` (createBaileysAdapter). The lifecycle
14
+ * methods (connect / disconnect / isReady) live HERE because they
15
+ * also drive the chat-cache persistence, plugin reload, alert
16
+ * registration, and the reconnect circuit breaker — none of which
17
+ * the adapter should know about.
18
+ *
19
+ * For the verification-required path (sendFallbackGuard), the contract
20
+ * also exposes `getHistory?` — which the adapter populates from the
21
+ * Baileys store.messages map on demand.
22
+ */
23
+ import { createSocket, AUTH_DIR, store as sharedStore } from "./sdk/baileysSock.js";
24
+ import { createBaileysAdapter } from "./adapter.js";
25
+ import { handleMessage } from "./messageHandler.js";
26
+ import { normalizeJid } from "#drivers/jid.js";
27
+ import { loadPlugins, setupPlugins } from "#kernel/pluginLoader.js";
28
+ import { runContactRefreshSweep } from "#kernel/contactAutoSave.js";
29
+ import { registerAlertSockProvider, sendAlert } from "#kernel/alerts.js";
30
+ import { startUpdateCheckSchedule, stopUpdateCheckSchedule } from "#kernel/updateCheck.js";
31
+ import { setStatus } from "#kernel/statusServer.js";
32
+ import { logger } from "#logger";
33
+ import { PLUGINS, CLIENT_ID } from "#config";
34
+ import { t } from "#i18n";
35
+ import { printBanner } from "#client/banner.js";
36
+ import { loadChatCache, saveChatCache, isCacheFresh } from "#client/cache.js";
37
+ import { DisconnectReason } from "@whiskeysockets/baileys";
38
+ import fs from "fs/promises";
39
+ import * as clack from "@clack/prompts";
40
+ import { copyToClipboard } from "#utils/clipboard.js";
41
+ import { applyPatches } from "../patches/index.js";
42
+ applyPatches();
43
+ let state = "BOOT";
44
+ let shuttingDown = false;
45
+ let currentSock = null;
46
+ let currentStore = null;
47
+ let currentAdapter = null;
48
+ let reconnectTimer = null;
49
+ let connecting = false;
50
+ let reconnectAttempts = 0;
51
+ let halted = false;
52
+ let cacheHydrated = false;
53
+ let cacheSaveTimer = null;
54
+ let contactRefreshTimer = null;
55
+ registerAlertSockProvider(() => currentSock);
56
+ // ── Per-chat message queue ──────────────────────────────────────────────────
57
+ // Messages from the same chat are processed one at a time (in order), but
58
+ // different chats run concurrently — a slow plugin in one chat (e.g. sticker
59
+ // generation) no longer blocks replies in every other chat.
60
+ const chatQueues = new Map();
61
+ function enqueueForChat(jid, task) {
62
+ const prev = chatQueues.get(jid) ?? Promise.resolve();
63
+ const settled = prev.catch(() => { }).then(task).catch((e) => {
64
+ const err = e instanceof Error ? e : new Error(String(e));
65
+ logger.error(`${err.message}\n${err.stack}`);
66
+ });
67
+ chatQueues.set(jid, settled);
68
+ settled.finally(() => {
69
+ if (chatQueues.get(jid) === settled)
70
+ chatQueues.delete(jid);
71
+ });
72
+ }
73
+ // Messages older than this (WhatsApp's own delivery delay — e.g. backlog
74
+ // dumped after the bot reconnects) are skipped. Checked at arrival time,
75
+ // so time spent waiting in chatQueues never counts against a message.
76
+ const MAX_MESSAGE_AGE_SECONDS = 60;
77
+ function isMessageStale(timestamp) {
78
+ if (!timestamp)
79
+ return false;
80
+ const nowInSeconds = Math.floor(Date.now() / 1000);
81
+ const age = nowInSeconds - timestamp;
82
+ if (age > MAX_MESSAGE_AGE_SECONDS)
83
+ return true;
84
+ return false;
85
+ }
86
+ const RECONNECT_BASE_MS = 1000;
87
+ const RECONNECT_MAX_MS = 60000;
88
+ // Circuit breaker: after this many consecutive failed reconnects, stop
89
+ // retrying automatically instead of hammering the connection forever.
90
+ // Rapid repeated attempts are themselves a ban signal (WhatsApp's abuse
91
+ // detection treats connect/disconnect loops as suspicious automation and
92
+ // each failed re-pairing attempt during a cooldown resets its timer), so
93
+ // silently retrying past this point can make a restriction last longer.
94
+ const MAX_RECONNECT_ATTEMPTS = 6;
95
+ const CACHE_SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5min
96
+ /**
97
+ * Loads the on-disk cache and merges it into `store` (union, never
98
+ * overwrite — see client/cache.ts). Runs once per process: the shared
99
+ * store singleton already accumulates across reconnects, so re-hydrating
100
+ * later would just redo a no-op merge.
101
+ */
102
+ async function hydrateFromCache(store) {
103
+ if (cacheHydrated)
104
+ return;
105
+ cacheHydrated = true;
106
+ const snapshot = await loadChatCache();
107
+ if (!snapshot)
108
+ return;
109
+ store.hydrate(snapshot);
110
+ const fresh = await isCacheFresh();
111
+ const count = snapshot.chats.length;
112
+ const key = fresh ? "system.cacheLoaded" : "system.cacheLoadedStale";
113
+ logger.info(`[cache] ${t(key, { count })}`);
114
+ }
115
+ function startCacheAutosave(store) {
116
+ if (cacheSaveTimer)
117
+ return;
118
+ cacheSaveTimer = setInterval(() => { saveChatCache(store); }, CACHE_SAVE_INTERVAL_MS);
119
+ }
120
+ function stopCacheAutosave() {
121
+ if (!cacheSaveTimer)
122
+ return;
123
+ clearInterval(cacheSaveTimer);
124
+ cacheSaveTimer = null;
125
+ }
126
+ // Runs a few times a day, each time touching only a couple of stale
127
+ // contacts (see REFRESH_SWEEP_SAMPLE) — deliberately slow and staggered,
128
+ // same anti-detection reasoning as everything else in sendGuard.
129
+ const CONTACT_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
130
+ function startContactRefreshSweep(contract) {
131
+ if (contactRefreshTimer)
132
+ return;
133
+ contactRefreshTimer = setInterval(() => {
134
+ runContactRefreshSweep(contract).catch(() => { });
135
+ }, CONTACT_REFRESH_INTERVAL_MS);
136
+ }
137
+ function stopContactRefreshSweep() {
138
+ if (!contactRefreshTimer)
139
+ return;
140
+ clearInterval(contactRefreshTimer);
141
+ contactRefreshTimer = null;
142
+ }
143
+ function nextBackoffMs() {
144
+ const delay = RECONNECT_BASE_MS * 2 ** reconnectAttempts;
145
+ reconnectAttempts++;
146
+ return Math.min(delay, RECONNECT_MAX_MS);
147
+ }
148
+ function teardownSock(sock) {
149
+ if (!sock)
150
+ return;
151
+ try {
152
+ sock.ev.removeAllListeners();
153
+ }
154
+ catch { }
155
+ try {
156
+ sock.end(undefined);
157
+ }
158
+ catch { }
159
+ }
160
+ function scheduleReconnect(delayMs) {
161
+ if (shuttingDown)
162
+ return;
163
+ if (reconnectTimer)
164
+ clearTimeout(reconnectTimer);
165
+ reconnectTimer = setTimeout(() => {
166
+ reconnectTimer = null;
167
+ startBot();
168
+ }, delayMs);
169
+ }
170
+ async function startBot() {
171
+ if (connecting)
172
+ return;
173
+ connecting = true;
174
+ await hydrateFromCache(sharedStore);
175
+ const previousSock = currentSock;
176
+ const { sock, store } = await createSocket();
177
+ teardownSock(previousSock);
178
+ currentSock = sock;
179
+ currentStore = store;
180
+ // Build the driver-neutral WaContract adapter on top of this socket.
181
+ // Everything in the kernel (setupPlugins, handleMessage, sendGuard,
182
+ // contactAutoSave, ...) talks to the contract, never to the raw sock
183
+ // directly — that's how the whatsmeow driver plugs in as a second
184
+ // adapter without any of them knowing which one is active.
185
+ const adapter = createBaileysAdapter({ sock, store });
186
+ currentAdapter = adapter;
187
+ const contract = adapter.contract;
188
+ connecting = false;
189
+ let pluginsReady = false;
190
+ // ── Normal bot mode ─────────────────────────────────────────────────────────
191
+ sock.ev.on("connection.update", async (update) => {
192
+ const { connection, lastDisconnect } = update;
193
+ if (connection === "open") {
194
+ state = "READY_INIT";
195
+ reconnectAttempts = 0;
196
+ setStatus(true);
197
+ logger.success(t("system.connected"));
198
+ logger.info(t("system.clientId", { id: CLIENT_ID }));
199
+ printBanner();
200
+ if (!pluginsReady) {
201
+ pluginsReady = true;
202
+ await loadPlugins(PLUGINS);
203
+ await setupPlugins(contract, store);
204
+ }
205
+ startCacheAutosave(store);
206
+ startContactRefreshSweep(contract);
207
+ startUpdateCheckSchedule();
208
+ // buffer anti-replay / sync ghost messages
209
+ setTimeout(() => { state = "READY"; }, 2000);
210
+ }
211
+ if (connection === "close") {
212
+ const code = lastDisconnect?.error?.output?.statusCode;
213
+ const loggedOut = code === DisconnectReason.loggedOut;
214
+ state = "BOOT";
215
+ setStatus(false, String(code));
216
+ logger.warn(t("system.disconnected", { reason: String(code) }));
217
+ if (loggedOut) {
218
+ logger.warn(t("system.sessionExpired"));
219
+ try {
220
+ await fs.rm(AUTH_DIR, { recursive: true, force: true });
221
+ }
222
+ catch (e) {
223
+ logger.error(`[whatsapp] Failed to remove session dir: ${e.message}`);
224
+ }
225
+ scheduleReconnect(1000);
226
+ }
227
+ else if (!shuttingDown) {
228
+ if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
229
+ halted = true;
230
+ logger.error(t("system.reconnectHalted", { attempts: reconnectAttempts }));
231
+ sendAlert({
232
+ level: "critical",
233
+ title: "manybot parou de tentar reconectar",
234
+ message: `Desisti após ${reconnectAttempts} tentativas — possível restrição de conta. Rode connect() manualmente pra tentar de novo.`,
235
+ }).catch(() => { });
236
+ return;
237
+ }
238
+ const delay = nextBackoffMs();
239
+ logger.info(t("system.reconnecting", { secs: Math.round(delay / 1000) }));
240
+ scheduleReconnect(delay);
241
+ }
242
+ }
243
+ });
244
+ // Subscribe to the contract's translated messages.upsert events so
245
+ // there's a single placeholder where the WAMessage → BotMessage
246
+ // conversion lives (the adapter). The adapter's rebind() handles
247
+ // re-subscribing on the fresh socket on reconnect — we registered
248
+ // once here, that's it.
249
+ contract.on("messages.upsert", ({ messages, type }) => {
250
+ if (state !== "READY")
251
+ return;
252
+ if (type !== "notify" && type !== "append")
253
+ return;
254
+ for (const msg of messages) {
255
+ const tsSec = Math.floor(msg.timestamp / 1000);
256
+ if (type === "append" && !msg.fromMe)
257
+ continue;
258
+ if (isMessageStale(tsSec))
259
+ continue;
260
+ const jid = normalizeJid(msg.chatId);
261
+ enqueueForChat(jid, () => handleMessage(msg, contract, store));
262
+ }
263
+ });
264
+ }
265
+ /**
266
+ * isReady() mirrors the "open" event from Baileys — once connect() resolves
267
+ * we don't yet have a session, so we wait for the connection.update === "open"
268
+ * path to flip the flag. isReady() requires true ONLY after both.
269
+ */
270
+ function sockIsOpen() {
271
+ return state === "READY" || state === "READY_INIT";
272
+ }
273
+ // ── Public driver: WaContract ────────────────────────────────────────────────
274
+ //
275
+ // Drivers expose the full WaContract surface: the kernel plugs in this
276
+ // object, and the lifecycle methods (connect / disconnect / isReady) wrap
277
+ // the startBot state machine. Until connect() resolves, the contract's
278
+ // per-call methods throw — plugins are only loaded after `connection.update
279
+ // === "open"`, so they never see the pre-connect state.
280
+ function requireReady() {
281
+ if (!currentAdapter) {
282
+ throw new Error("[baileys] driver not connected — call connect() first");
283
+ }
284
+ return currentAdapter.contract;
285
+ }
286
+ export const baileysContract = {
287
+ name: "baileys",
288
+ async connect() {
289
+ shuttingDown = false;
290
+ halted = false;
291
+ reconnectAttempts = 0;
292
+ await startBot();
293
+ },
294
+ async disconnect() {
295
+ shuttingDown = true;
296
+ reconnectAttempts = 0;
297
+ setStatus(false);
298
+ if (reconnectTimer) {
299
+ clearTimeout(reconnectTimer);
300
+ reconnectTimer = null;
301
+ }
302
+ stopCacheAutosave();
303
+ stopContactRefreshSweep();
304
+ stopUpdateCheckSchedule();
305
+ if (currentStore)
306
+ await saveChatCache(currentStore);
307
+ teardownSock(currentSock);
308
+ currentSock = null;
309
+ currentStore = null;
310
+ currentAdapter = null;
311
+ },
312
+ isReady() {
313
+ return sockIsOpen();
314
+ },
315
+ // ── event passthrough ───────────────────────────────────────────────────
316
+ on: (...args) => requireReady().on(...args),
317
+ resolveLid: (...args) => {
318
+ const c = requireReady();
319
+ return c.resolveLid ? c.resolveLid(...args) : Promise.resolve(null);
320
+ },
321
+ // ── send ────────────────────────────────────────────────────────────────
322
+ sendText: (...args) => requireReady().sendText(...args),
323
+ sendImage: (...args) => requireReady().sendImage(...args),
324
+ sendVideo: (...args) => requireReady().sendVideo(...args),
325
+ sendAudio: (...args) => requireReady().sendAudio(...args),
326
+ sendSticker: (...args) => requireReady().sendSticker(...args),
327
+ sendDocument: (...args) => requireReady().sendDocument(...args),
328
+ sendPoll: (...args) => requireReady().sendPoll(...args),
329
+ // ── react / edit / delete ───────────────────────────────────────────────
330
+ react: (...args) => requireReady().react(...args),
331
+ deleteMessage: (...args) => requireReady().deleteMessage(...args),
332
+ editMessage: (...args) => requireReady().editMessage(...args),
333
+ // ── presence + read ─────────────────────────────────────────────────────
334
+ sendPresenceUpdate: (...args) => requireReady().sendPresenceUpdate(...args),
335
+ readMessages: (...args) => requireReady().readMessages(...args),
336
+ // ── contacts ────────────────────────────────────────────────────────────
337
+ onWhatsApp: (...args) => requireReady().onWhatsApp(...args),
338
+ getBusinessProfile: (...args) => requireReady().getBusinessProfile(...args),
339
+ profilePictureUrl: (...args) => requireReady().profilePictureUrl(...args),
340
+ fetchStatus: (...args) => requireReady().fetchStatus(...args),
341
+ updateBlockStatus: (...args) => requireReady().updateBlockStatus(...args),
342
+ addOrEditContact: (...args) => requireReady().addOrEditContact(...args),
343
+ removeContact: (...args) => requireReady().removeContact(...args),
344
+ // ── groups ──────────────────────────────────────────────────────────────
345
+ groupMetadata: (...args) => requireReady().groupMetadata(...args),
346
+ groupParticipantsUpdate: (...args) => requireReady().groupParticipantsUpdate(...args),
347
+ groupUpdateSubject: (...args) => requireReady().groupUpdateSubject(...args),
348
+ groupUpdateDescription: (...args) => requireReady().groupUpdateDescription(...args),
349
+ groupInviteCode: (...args) => requireReady().groupInviteCode(...args),
350
+ groupRevokeInvite: (...args) => requireReady().groupRevokeInvite(...args),
351
+ // ── profile ────────────────────────────────────────────────────────────
352
+ updateProfilePicture: (...args) => requireReady().updateProfilePicture(...args),
353
+ updateProfileName: (...args) => requireReady().updateProfileName(...args),
354
+ updateProfileStatus: (...args) => requireReady().updateProfileStatus(...args),
355
+ // ── me ──────────────────────────────────────────────────────────────────
356
+ me: () => requireReady().me(),
357
+ // ── media (download) ────────────────────────────────────────────────────
358
+ downloadMedia: (...args) => requireReady().downloadMedia(...args),
359
+ // ── verification primitive ─────────────────────────────────────────────
360
+ // Delegates to the adapter, which reads from the in-memory Baileys
361
+ // store (store.messages). The adapter guarantees getHistory is defined
362
+ // (the Baileys adapter guarantees getHistory is defined), so the defensive fallback is gone.
363
+ getHistory: (jid, opts) => requireReady().getHistory(jid, opts),
364
+ /**
365
+ * Diagnostic mode: connects on its own session (separate from the
366
+ * running bot's, so it doesn't compete for the same WhatsApp Web
367
+ * slot), waits for the initial chat sync, then shows an interactive
368
+ * list — arrow keys to navigate, Enter to pick. The selected chat's
369
+ * id is normalized, resolved from `@lid` to the real phone-based JID
370
+ * when known, copied to the clipboard, and printed.
371
+ */
372
+ async getId() {
373
+ logger.info(`[getid] ${t("getid.connecting")}`);
374
+ await hydrateFromCache(sharedStore);
375
+ const CONNECT_TIMEOUT_MS = 25000;
376
+ const MAX_ATTEMPTS = 3;
377
+ const MAX_ROUNDS = 2;
378
+ const getidAuthDir = `${CLIENT_ID}-getid`;
379
+ let sock = null;
380
+ let store = null;
381
+ for (let round = 1; round <= MAX_ROUNDS && !sock; round++) {
382
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS && !sock; attempt++) {
383
+ const created = await createSocket(getidAuthDir);
384
+ // Not a spinner here on purpose: if this session isn't paired yet,
385
+ // Baileys prints the QR/pairing code through the normal logger
386
+ // right after this — a spinner redrawing the line would bury it,
387
+ // so the person never gets a chance to approve it on their phone
388
+ // and the connection hangs forever waiting for "open".
389
+ const opened = await new Promise((resolve) => {
390
+ const timer = setTimeout(() => resolve(false), CONNECT_TIMEOUT_MS);
391
+ created.sock.ev.on("connection.update", (u) => {
392
+ if (u.connection === "open") {
393
+ clearTimeout(timer);
394
+ resolve(true);
395
+ }
396
+ });
397
+ });
398
+ if (!opened) {
399
+ logger.warn(`[getid] round ${round} attempt ${attempt} timed out waiting for "open"`);
400
+ teardownSock(created.sock);
401
+ continue;
402
+ }
403
+ sock = created.sock;
404
+ store = created.store;
405
+ }
406
+ }
407
+ if (!sock || !store) {
408
+ logger.error(`[getid] failed to open a session after ${MAX_ROUNDS * MAX_ATTEMPTS} attempts`);
409
+ process.exit(1);
410
+ }
411
+ // Wait for the initial chat sync to land in the store.
412
+ await new Promise((resolve) => {
413
+ const timer = setTimeout(resolve, 5000);
414
+ sock.ev.on("messaging-history.set", () => {
415
+ clearTimeout(timer);
416
+ resolve();
417
+ });
418
+ });
419
+ const chats = store.chats.all();
420
+ if (chats.length === 0) {
421
+ logger.warn(`[getid] no chats synced yet — try again in a few seconds`);
422
+ teardownSock(sock);
423
+ process.exit(1);
424
+ }
425
+ const sorted = chats
426
+ .map((c) => ({ id: c.id, name: c.name ?? "" }))
427
+ .sort((a, b) => a.name.localeCompare(b.name));
428
+ const picked = await clack.select({
429
+ message: t("getid.pickChat"),
430
+ options: sorted.map((c) => ({ label: c.name || c.id, value: c.id })),
431
+ });
432
+ if (clack.isCancel(picked) || typeof picked !== "string") {
433
+ teardownSock(sock);
434
+ process.exit(0);
435
+ }
436
+ const resolved = await resolveLidForJid(sock, picked);
437
+ const finalJid = resolved ?? picked;
438
+ try {
439
+ await copyToClipboard(finalJid);
440
+ logger.success(`[getid] ${t("getid.copied", { id: finalJid })}`);
441
+ }
442
+ catch {
443
+ logger.info(`[getid] ${finalJid}`);
444
+ }
445
+ teardownSock(sock);
446
+ },
447
+ };
448
+ /**
449
+ * Try to resolve a `@lid` JID to its real `@s.whatsapp.net` form via the
450
+ * Baileys signal repository. Returns null if the adapter doesn't expose
451
+ * one (very old sessions) or the lookup fails.
452
+ */
453
+ async function resolveLidForJid(sock, jid) {
454
+ try {
455
+ const repo = sock.signalRepository;
456
+ const fn = repo?.lidMapping?.getPNForLID;
457
+ if (typeof fn === "function") {
458
+ const pn = await fn(jid);
459
+ return pn ?? null;
460
+ }
461
+ }
462
+ catch { }
463
+ return null;
464
+ }
465
+ /**
466
+ * Backwards-compat: callers that still import `baileysDriver` get the
467
+ * new contract object. The old `WaDriver` surface is gone — kernel
468
+ * code that needs the driver only ever sees the `WaContract` now.
469
+ *
470
+ * @deprecated Use `baileysContract` directly.
471
+ */
472
+ export const baileysDriver = baileysContract;
473
+ // ── Baileys-only helpers used by the plugin-context builder ─────────────────
474
+ // The plugin-context layer (drivers/baileys/api/index.ts) still works
475
+ // against Baileys' raw WAMessage shape for things that don't yet have a
476
+ // driver-neutral equivalent (poll decryption, gif detection, history
477
+ // reconstruction from store.messages). These helpers stay here so the
478
+ // api file can convert a raw WAMessage into a driver-neutral BotMessage
479
+ // without re-importing the adapter's private internals.
480
+ import { normalizeMessageContent } from "@whiskeysockets/baileys";
481
+ import { createHash } from "node:crypto";
482
+ /**
483
+ * Map a Baileys WAMessage into the driver-neutral BotMessage envelope.
484
+ * Mirrors the adapter's internal conversion (the two diverged once —
485
+ * keep them in sync if you change one).
486
+ */
487
+ export function toBotMessage(msg) {
488
+ const m = normalizeMessageContent(msg.message);
489
+ let type = "other";
490
+ let text = "";
491
+ let mimetype;
492
+ if (m?.conversation) {
493
+ type = "text";
494
+ text = m.conversation;
495
+ }
496
+ else if (m?.extendedTextMessage?.text) {
497
+ type = "text";
498
+ text = m.extendedTextMessage.text;
499
+ }
500
+ else if (m?.imageMessage) {
501
+ type = "image";
502
+ text = m.imageMessage.caption ?? "";
503
+ mimetype = m.imageMessage.mimetype ?? undefined;
504
+ }
505
+ else if (m?.videoMessage) {
506
+ type = "video";
507
+ text = m.videoMessage.caption ?? "";
508
+ mimetype = m.videoMessage.mimetype ?? undefined;
509
+ }
510
+ else if (m?.audioMessage) {
511
+ type = "audio";
512
+ text = "";
513
+ mimetype = m.audioMessage.mimetype ?? undefined;
514
+ }
515
+ else if (m?.documentMessage) {
516
+ type = "document";
517
+ text = m.documentMessage.caption ?? "";
518
+ mimetype = m.documentMessage.mimetype ?? undefined;
519
+ }
520
+ else if (m?.stickerMessage) {
521
+ type = "sticker";
522
+ text = "";
523
+ mimetype = m.stickerMessage.mimetype ?? undefined;
524
+ }
525
+ const key = msg.key;
526
+ const contextInfo = m?.extendedTextMessage?.contextInfo ??
527
+ m?.imageMessage?.contextInfo ??
528
+ m?.videoMessage?.contextInfo ??
529
+ m?.audioMessage?.contextInfo ??
530
+ m?.documentMessage?.contextInfo ??
531
+ undefined;
532
+ return {
533
+ id: msg.key?.id ?? "",
534
+ chatId: normalizeJid(msg.key?.remoteJid ?? ""),
535
+ fromMe: !!msg.key?.fromMe,
536
+ type,
537
+ contentHash: hashText(text),
538
+ timestamp: Number(msg.messageTimestamp ?? 0) * 1000,
539
+ body: text || undefined,
540
+ mimetype,
541
+ pushName: msg.pushName ?? undefined,
542
+ mentionedJid: contextInfo?.mentionedJid ?? undefined,
543
+ quotedKey: contextInfo?.stanzaId ? {
544
+ id: contextInfo.stanzaId,
545
+ remoteJid: msg.key?.remoteJid ?? undefined,
546
+ fromMe: false,
547
+ participant: contextInfo.participant ?? undefined,
548
+ } : undefined,
549
+ fromLid: key.participantAlt,
550
+ fromPn: key.participant,
551
+ participantAlt: key.participantAlt,
552
+ remoteJidAlt: key.remoteJidAlt,
553
+ _raw: {
554
+ pollEncKeyRaw: m?.messageContextInfo?.messageSecret ?? undefined,
555
+ },
556
+ };
557
+ }
558
+ function hashText(text) {
559
+ return createHash("sha1").update(text.trim(), "utf8").digest("hex");
560
+ }
@@ -11,6 +11,7 @@
11
11
  import * as clack from "@clack/prompts";
12
12
  import { CONFIG, persistConfigValue } from "#config";
13
13
  import { t } from "#i18n";
14
+ import { promptWhatsmeowInstall } from "#drivers/whatsmeow/installer.js";
14
15
  function cancelAndExit() {
15
16
  clack.cancel(t("onboarding.cancelled"));
16
17
  process.exit(1);
@@ -72,6 +73,7 @@ export async function resolveLoginMethod() {
72
73
  clack.intro(t("onboarding.intro"));
73
74
  if (needsMethod) {
74
75
  method = await promptLoginMethod();
76
+ await promptWhatsmeowInstall();
75
77
  }
76
78
  if (method === "phone" && !phone) {
77
79
  phone = await promptPhoneNumber();