@manybot/manybot 5.6.1 → 5.8.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 (71) hide show
  1. package/README.md +20 -3
  2. package/dist/client/banner.js +10 -0
  3. package/dist/client/banner.test.js +31 -0
  4. package/dist/client/store.js +56 -5
  5. package/dist/client/store.test.js +170 -0
  6. package/dist/config.js +28 -44
  7. package/dist/config.test.js +26 -0
  8. package/dist/drivers/baileys/adapter.js +207 -8
  9. package/dist/drivers/baileys/api/index.js +300 -130
  10. package/dist/drivers/baileys/index.js +62 -30
  11. package/dist/drivers/baileys/loginPrompt.js +0 -2
  12. package/dist/drivers/baileys/messageHandler.js +158 -4
  13. package/dist/drivers/baileys/messageHandler.test.js +203 -0
  14. package/dist/drivers/baileysAdapter.test.js +281 -0
  15. package/dist/drivers/jid.test.js +40 -0
  16. package/dist/drivers/types.js +5 -5
  17. package/dist/i18n/index.js +15 -2
  18. package/dist/kernel/activeDriverSend.js +21 -0
  19. package/dist/kernel/activeDriverSend.test.js +89 -0
  20. package/dist/kernel/alerts.js +3 -9
  21. package/dist/kernel/chatSession.js +65 -0
  22. package/dist/kernel/chatSession.test.js +46 -0
  23. package/dist/kernel/commandAccess.js +66 -0
  24. package/dist/kernel/commandAccess.test.js +74 -0
  25. package/dist/kernel/commandDeprecation.js +168 -0
  26. package/dist/kernel/commandDeprecation.test.js +107 -0
  27. package/dist/kernel/commandMenu.js +268 -0
  28. package/dist/kernel/commandMenu.test.js +234 -0
  29. package/dist/kernel/commandPermissions.js +125 -0
  30. package/dist/kernel/commandPermissions.test.js +159 -0
  31. package/dist/kernel/commandRegistry.js +459 -0
  32. package/dist/kernel/commandRegistry.test.js +156 -0
  33. package/dist/kernel/commandsConfig.js +517 -0
  34. package/dist/kernel/commandsConfig.test.js +236 -0
  35. package/dist/kernel/contactAutoSave.test.js +87 -0
  36. package/dist/kernel/driverManager.js +10 -6
  37. package/dist/kernel/driverManager.test.js +90 -0
  38. package/dist/kernel/integrationMode.js +88 -0
  39. package/dist/kernel/integrationMode.test.js +95 -0
  40. package/dist/kernel/loadIntegrationPlugin.test.js +67 -0
  41. package/dist/kernel/pluginApi.test.js +583 -0
  42. package/dist/kernel/pluginGuard.js +15 -12
  43. package/dist/kernel/pluginGuard.test.js +39 -0
  44. package/dist/kernel/pluginLoader.js +96 -1
  45. package/dist/kernel/pluginLoader.test.js +80 -0
  46. package/dist/kernel/runCommand.js +245 -0
  47. package/dist/kernel/runCommand.test.js +235 -0
  48. package/dist/kernel/sendFallbackGuard.js +19 -48
  49. package/dist/kernel/sendFallbackGuard.test.js +80 -0
  50. package/dist/kernel/sendGuard.js +38 -42
  51. package/dist/kernel/sendGuard.test.js +102 -0
  52. package/dist/kernel/settingsDb.js +4 -3
  53. package/dist/kernel/statusServer.js +9 -2
  54. package/dist/kernel/statusServer.test.js +70 -0
  55. package/dist/kernel/testConfig.js +183 -0
  56. package/dist/kernel/testConfig.test.js +181 -0
  57. package/dist/kernel/updateCheck.js +33 -10
  58. package/dist/locales/en.json +64 -13
  59. package/dist/locales/es.json +64 -13
  60. package/dist/locales/pt.json +64 -13
  61. package/dist/logger/logger.js +23 -3
  62. package/dist/logger/logger.test.js +45 -0
  63. package/dist/main.js +5 -76
  64. package/dist/plugins/__manybot_integration__/index.js +167 -0
  65. package/dist/plugins/__manybot_integration__/index.test.js +184 -0
  66. package/package.json +75 -18
  67. package/dist/drivers/whatsmeow/client.js +0 -252
  68. package/dist/drivers/whatsmeow/index.js +0 -79
  69. package/dist/drivers/whatsmeow/installer.js +0 -86
  70. package/dist/drivers/whatsmeow/supervisor.js +0 -328
  71. package/dist/drivers/whatsmeow/whatsmeow.proto +0 -64
@@ -22,7 +22,7 @@
22
22
  * Returns/dispatches are pure adapters — error semantics, retries, fallbacks
23
23
  * all live elsewhere (sendFallbackGuard, sendGuard, pluginGuard).
24
24
  */
25
- import { normalizeMessageContent, downloadMediaMessage, jidNormalizedUser, } from "@whiskeysockets/baileys";
25
+ import { normalizeMessageContent, downloadMediaMessage, jidNormalizedUser, decryptPollVote as baileysDecryptPollVote, getAggregateVotesInPollMessage, } from "@whiskeysockets/baileys";
26
26
  import { createHash } from "node:crypto";
27
27
  import { logger } from "#logger";
28
28
  /**
@@ -72,6 +72,22 @@ export function decodeContent(content) {
72
72
  type = "sticker";
73
73
  mimetype = m.stickerMessage.mimetype ?? undefined;
74
74
  }
75
+ else if (m?.templateMessage) {
76
+ type = "text";
77
+ const tpl = m.templateMessage.hydratedTemplate ?? m.templateMessage.hydratedFourRowTemplate;
78
+ const buttonUrls = tpl?.hydratedButtons?.map((b) => b.urlButton?.url).filter(Boolean).join(" ") ?? "";
79
+ body = [tpl?.hydratedContentText ?? "", buttonUrls].filter(Boolean).join(" ");
80
+ }
81
+ else if (m?.interactiveMessage) {
82
+ type = "text";
83
+ const buttonParams = m.interactiveMessage.nativeFlowMessage?.buttons
84
+ ?.map((b) => b.buttonParamsJson).filter(Boolean).join(" ") ?? "";
85
+ body = [m.interactiveMessage.body?.text ?? "", buttonParams].filter(Boolean).join(" ");
86
+ }
87
+ else if (m?.buttonsMessage) {
88
+ type = "text";
89
+ body = [m.buttonsMessage.contentText ?? "", m.buttonsMessage.footerText ?? ""].filter(Boolean).join(" ");
90
+ }
75
91
  return { type, body, mimetype };
76
92
  }
77
93
  export function createBaileysAdapter(initial) {
@@ -115,6 +131,29 @@ export function createBaileysAdapter(initial) {
115
131
  // which throws if `.message` is missing.
116
132
  return { quoted: { key: toFlatKey(quoted), message: inner } };
117
133
  }
134
+ /**
135
+ * Merge `quoted` reply-citation with the chat's current
136
+ * `ephemeralExpiration` so every outgoing send in a chat with a
137
+ * disappearing-message timer inherits it automatically. Returns
138
+ * `undefined` when there's nothing to set, so callers can pass it
139
+ * through as the third argument to `sock.sendMessage` without
140
+ * conditionally building the options object.
141
+ *
142
+ * The timer is read off the in-memory store, which is populated by
143
+ * `chats.upsert` / `chats.update` / `messages.upsert` (via the
144
+ * `ephemeralMessage` envelope) and from `groupMetadata().ephemeralDuration`
145
+ * in this same adapter — i.e. everything WhatsApp tells us about the
146
+ * chat's timer ends up there, and the send path just reads it back.
147
+ */
148
+ function buildSendOpts(jid, quoted) {
149
+ const quotedOpts = buildQuotedOpts(quoted);
150
+ const timer = store.chats.get(jid)?.ephemeralExpiration;
151
+ if (!timer)
152
+ return quotedOpts;
153
+ if (!quotedOpts)
154
+ return { ephemeralExpiration: timer };
155
+ return { ...quotedOpts, ephemeralExpiration: timer };
156
+ }
118
157
  /**
119
158
  * Translate a neutral `BotQuotedRef` into the FLAT key shape Baileys
120
159
  * expects for `react`/`delete`/`edit`/`readMessages` (proto.IMessageKey
@@ -151,6 +190,9 @@ export function createBaileysAdapter(initial) {
151
190
  m?.videoMessage?.contextInfo ??
152
191
  m?.audioMessage?.contextInfo ??
153
192
  m?.documentMessage?.contextInfo ??
193
+ m?.templateMessage?.contextInfo ??
194
+ m?.interactiveMessage?.contextInfo ??
195
+ m?.buttonsMessage?.contextInfo ??
154
196
  undefined;
155
197
  const ciTyped = contextInfo;
156
198
  return {
@@ -201,6 +243,44 @@ export function createBaileysAdapter(initial) {
201
243
  lid: c.lid,
202
244
  };
203
245
  }
246
+ /**
247
+ * Compute every plausible JID candidate for a side of a poll-vote
248
+ * decryption. WhatsApp doesn't consistently pick the same JID shape
249
+ * (LID vs PN) when deriving the poll-vote decryption key — it depends
250
+ * on addressingMode, 1:1 vs group, and which side sent last. Trying
251
+ * to compute "the" correct JID up front causes AES-GCM auth failures
252
+ * whenever WhatsApp actually used the LID; brute-forcing candidates
253
+ * is the only reliable approach (see
254
+ * https://github.com/WhiskeySockets/Baileys/issues/2342 and #1678).
255
+ *
256
+ * For the bot's own side (`self === true`), candidates are the bot's
257
+ * `user.id` and `user.lid`. For an external side, candidates are
258
+ * the participant/remoteJid, the `participantPn` if any, and any
259
+ * LID→PN mapping the store has learned.
260
+ */
261
+ function jidCandidatesFromKey(key, sock, store, self) {
262
+ const cands = [];
263
+ if (self) {
264
+ const selfLid = sock.user?.lid;
265
+ if (selfLid)
266
+ cands.push(jidNormalizedUser(selfLid));
267
+ if (sock.user?.id)
268
+ cands.push(jidNormalizedUser(sock.user.id));
269
+ }
270
+ else {
271
+ const rawParticipant = key.participant ?? key.remoteJid;
272
+ if (rawParticipant)
273
+ cands.push(jidNormalizedUser(rawParticipant));
274
+ if (key.participantPn)
275
+ cands.push(jidNormalizedUser(key.participantPn));
276
+ if (rawParticipant?.endsWith("@lid")) {
277
+ const resolved = store.resolveJid(rawParticipant);
278
+ if (resolved && resolved !== rawParticipant)
279
+ cands.push(jidNormalizedUser(resolved));
280
+ }
281
+ }
282
+ return Array.from(new Set(cands.filter(Boolean)));
283
+ }
204
284
  const subscribers = new Map();
205
285
  // Tracks the handlers each socket has registered so we can remove them on a
206
286
  // fresh socket after reconnect. Declared up here because the rebind helpers
@@ -257,7 +337,7 @@ export function createBaileysAdapter(initial) {
257
337
  async sendText(jid, text, opts) {
258
338
  const content = { text };
259
339
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
260
- const sendOpts = buildQuotedOpts(opts?.quoted);
340
+ const sendOpts = buildSendOpts(jid, opts?.quoted);
261
341
  if (opts?.mentions?.length)
262
342
  content.mentions = opts.mentions;
263
343
  const ref = await sock.sendMessage(jid, content, sendOpts);
@@ -271,7 +351,7 @@ export function createBaileysAdapter(initial) {
271
351
  content.viewOnce = true;
272
352
  if (opts?.mentions?.length)
273
353
  content.mentions = opts.mentions;
274
- const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
354
+ const ref = await sock.sendMessage(jid, content, buildSendOpts(jid, opts?.quoted));
275
355
  return toSentRef(ref, jid);
276
356
  },
277
357
  async sendVideo(jid, buffer, opts) {
@@ -284,7 +364,7 @@ export function createBaileysAdapter(initial) {
284
364
  content.gifPlayback = true;
285
365
  if (opts?.mentions?.length)
286
366
  content.mentions = opts.mentions;
287
- const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
367
+ const ref = await sock.sendMessage(jid, content, buildSendOpts(jid, opts?.quoted));
288
368
  return toSentRef(ref, jid);
289
369
  },
290
370
  async sendAudio(jid, buffer, opts) {
@@ -294,20 +374,20 @@ export function createBaileysAdapter(initial) {
294
374
  content.ptt = true;
295
375
  if (opts?.viewOnce)
296
376
  content.viewOnce = true;
297
- const ref = await sock.sendMessage(jid, content, buildQuotedOpts(opts?.quoted));
377
+ const ref = await sock.sendMessage(jid, content, buildSendOpts(jid, opts?.quoted));
298
378
  return toSentRef(ref, jid);
299
379
  },
300
380
  async sendSticker(jid, buffer, opts) {
301
- const ref = await sock.sendMessage(jid, { sticker: buffer }, buildQuotedOpts(opts?.quoted));
381
+ const ref = await sock.sendMessage(jid, { sticker: buffer }, buildSendOpts(jid, opts?.quoted));
302
382
  return toSentRef(ref, jid);
303
383
  },
304
384
  async sendDocument(jid, buffer, filename, mimetype, opts) {
305
- const ref = await sock.sendMessage(jid, { document: buffer, mimetype, fileName: filename }, buildQuotedOpts(opts?.quoted));
385
+ const ref = await sock.sendMessage(jid, { document: buffer, mimetype, fileName: filename }, buildSendOpts(jid, opts?.quoted));
306
386
  return toSentRef(ref, jid);
307
387
  },
308
388
  async sendPoll(jid, opts) {
309
389
  const poll = { name: opts.name, values: opts.values, selectableCount: opts.selectableCount ?? 1 };
310
- const ref = await sock.sendMessage(jid, { poll }, buildQuotedOpts(opts.quoted));
390
+ const ref = await sock.sendMessage(jid, { poll }, buildSendOpts(jid, opts.quoted));
311
391
  return toSentRef(ref, jid);
312
392
  },
313
393
  async react(jid, target, emoji) {
@@ -389,6 +469,15 @@ export function createBaileysAdapter(initial) {
389
469
  // ── groups ─────────────────────────────────────────────────────────────
390
470
  async groupMetadata(jid) {
391
471
  const meta = await sock.groupMetadata(jid);
472
+ // WhatsApp reports the group's current disappearing-message
473
+ // timer as `ephemeralDuration` on the metadata payload — promote
474
+ // it to the in-memory store so the next send to this group
475
+ // picks it up automatically (and so the timer survives a
476
+ // snapshot round-trip without us re-fetching metadata).
477
+ const rawDuration = meta.ephemeralDuration;
478
+ if (rawDuration !== undefined) {
479
+ store.setChatEphemeralExpiration(jid, Number(rawDuration) || 0);
480
+ }
392
481
  return {
393
482
  subject: meta.subject,
394
483
  participants: meta.participants.map(p => ({
@@ -505,6 +594,72 @@ export function createBaileysAdapter(initial) {
505
594
  return null;
506
595
  }
507
596
  },
597
+ // ── poll decryption (Baileys-only) ───────────────────────────────────
598
+ //
599
+ // These are the only two methods on the contract that are explicitly
600
+ // Baileys-specific. whatsmeow (and any future driver) may leave them
601
+ // undefined; the only consumer today is `buildPollApi` in
602
+ // drivers/baileys/api/index.ts, which already tolerates the absence.
603
+ //
604
+ // Both live on the contract (not as a separate file-level helper)
605
+ // because the Baileys-side knowledge they encode — picking the
606
+ // correct LID-vs-PN JID on each side, knowing the bot's own
607
+ // `sock.user.id/lid`, knowing the WAMessage shape for the encrypted
608
+ // payload — would otherwise leak out of the adapter.
609
+ async decryptPollVote(opts) {
610
+ const voteRaw = store.messages.get(opts.voteKey.remoteJid ?? "")?.get(opts.voteKey.id ?? "");
611
+ const pum = voteRaw?.message?.pollUpdateMessage;
612
+ const vote = pum?.vote;
613
+ if (!vote)
614
+ return null;
615
+ const encKey = Buffer.isBuffer(opts.pollEncKey)
616
+ ? opts.pollEncKey
617
+ : Buffer.from(opts.pollEncKey, "base64");
618
+ // WhatsApp doesn't consistently use the same JID shape (LID vs PN) for
619
+ // pollCreatorJid/voterJid — it depends on addressingMode, 1:1 vs group,
620
+ // and which side sent last. Compute every plausible candidate and
621
+ // brute-force combinations until one decrypts successfully (see
622
+ // https://github.com/WhiskeySockets/Baileys/issues/2342 and #1678).
623
+ const creatorCandidates = jidCandidatesFromKey(opts.pollKey, sock, store, /*self*/ false);
624
+ const voterCandidates = jidCandidatesFromKey(opts.voteKey, sock, store, !!opts.voteKey.fromMe);
625
+ for (const pollCreatorJid of creatorCandidates) {
626
+ for (const voterJid of voterCandidates) {
627
+ try {
628
+ const decrypted = baileysDecryptPollVote(vote, {
629
+ pollCreatorJid,
630
+ pollMsgId: opts.pollKey.id ?? "",
631
+ pollEncKey: encKey,
632
+ voterJid,
633
+ });
634
+ // PollVoteMessage.selectedOptions is a list of { optionHash: Buffer | null }.
635
+ // Map to plain hex strings for the contract surface.
636
+ const selectedOptions = (decrypted.selectedOptions ?? [])
637
+ .map((o) => {
638
+ const h = o?.optionHash;
639
+ if (!h)
640
+ return null;
641
+ return Buffer.isBuffer(h) ? h.toString("hex") : Buffer.from(h).toString("hex");
642
+ })
643
+ .filter((x) => !!x);
644
+ return { selectedOptions, raw: decrypted };
645
+ }
646
+ catch {
647
+ // try next JID combination
648
+ }
649
+ }
650
+ }
651
+ return null;
652
+ },
653
+ aggregatePollVotes(opts) {
654
+ const pollRaw = store.messages.get(opts.pollKey.remoteJid ?? "")?.get(opts.pollKey.id ?? "");
655
+ if (!pollRaw?.message)
656
+ return [];
657
+ const meId = opts.selfJid ?? (sock.user?.id ? jidNormalizedUser(sock.user.id) : undefined);
658
+ const aggregated = getAggregateVotesInPollMessage(
659
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
660
+ { message: pollRaw.message, pollUpdates: opts.votes }, meId);
661
+ return aggregated;
662
+ },
508
663
  };
509
664
  // ── Rebind helpers (post-reconnect in drivers/baileys/index.ts) ────────
510
665
  //
@@ -528,6 +683,26 @@ export function createBaileysAdapter(initial) {
528
683
  const updates = arg;
529
684
  emit("messages.update", { updates });
530
685
  });
686
+ // Baileys emits one of two shapes for `messages.delete`: a `keys`
687
+ // array (per-message revoke) or `{ jid, all: true }` (chat clear).
688
+ // Normalize to a single payload here so the contract's consumer
689
+ // never has to special-case the variant.
690
+ register("messages.delete", (arg) => {
691
+ const a = arg;
692
+ if ("all" in a) {
693
+ emit("messages.delete", { keys: [], all: { jid: a.jid } });
694
+ }
695
+ else {
696
+ emit("messages.delete", {
697
+ keys: a.keys.map((k) => ({
698
+ id: k.id ?? null,
699
+ remoteJid: k.remoteJid ?? null,
700
+ fromMe: k.fromMe ?? null,
701
+ participant: k.participant ?? null,
702
+ })),
703
+ });
704
+ }
705
+ });
531
706
  register("messaging-history.set", (arg) => {
532
707
  const { chats, contacts, messages } = arg;
533
708
  emit("messaging-history.set", {
@@ -542,6 +717,9 @@ export function createBaileysAdapter(initial) {
542
717
  register("chats.update", (arg) => {
543
718
  emit("chats.update", { updates: arg });
544
719
  });
720
+ register("chats.delete", (arg) => {
721
+ emit("chats.delete", { ids: arg });
722
+ });
545
723
  register("contacts.upsert", (arg) => {
546
724
  emit("contacts.upsert", { contacts: arg.map(contactSummary) });
547
725
  });
@@ -552,9 +730,30 @@ export function createBaileysAdapter(initial) {
552
730
  const { id, participants } = arg;
553
731
  emit("group-participants.update", { id, participants });
554
732
  });
733
+ register("groups.upsert", (arg) => {
734
+ const groups = arg;
735
+ emit("groups.upsert", { groups: groups.map((g) => ({ id: g.id, subject: g.subject })) });
736
+ });
555
737
  register("groups.update", (arg) => {
556
738
  emit("groups.update", { updates: arg });
557
739
  });
740
+ register("group.join-request", (arg) => {
741
+ const a = arg;
742
+ emit("group.join-request", {
743
+ id: a.id,
744
+ author: a.author,
745
+ participant: a.participant,
746
+ action: a.action,
747
+ method: a.method ?? "unknown",
748
+ });
749
+ });
750
+ register("blocklist.set", (arg) => {
751
+ emit("blocklist.set", { blocklist: arg.blocklist });
752
+ });
753
+ register("blocklist.update", (arg) => {
754
+ const a = arg;
755
+ emit("blocklist.update", { blocklist: a.blocklist, type: a.type });
756
+ });
558
757
  register("connection.update", (arg) => {
559
758
  const { connection, lastDisconnect } = arg;
560
759
  emit("connection.update", {