@manybot/manybot 5.3.3 → 5.4.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.
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|

|
|
@@ -46,9 +46,9 @@ Browse available plugins at **[manybot.org/plugins](https://manybot.org/plugins/
|
|
|
46
46
|
All kinds of contributions are welcome:
|
|
47
47
|
|
|
48
48
|
- **Bug reports and feature requests**: open an issue on GitHub or Codeberg
|
|
49
|
-
- **Code**: pull requests are welcome on [GitHub](https://github.com/many-bot/manybot) or [Codeberg](https://codeberg.org/many-bot/manybot); patches by email (`
|
|
49
|
+
- **Code**: pull requests are welcome on [GitHub](https://github.com/many-bot/manybot) or [Codeberg](https://codeberg.org/many-bot/manybot); patches by email (`manybot@pm.me`) are also accepted.
|
|
50
50
|
- **Plugins**: submit your plugin to [manyplug-repo](https://github.com/many-bot/manyplug-repo), which has instructions on how to do it
|
|
51
|
-
- **Anything else**: suggestions, translations, documentation fixes - reach out by email or open an issue
|
|
51
|
+
- **Anything else**: suggestions, translations, documentation fixes, art - reach out by email or open an issue
|
|
52
52
|
|
|
53
53
|
## License
|
|
54
54
|
|
|
@@ -412,6 +412,22 @@ async function normalizeContact(jid, info, botJid, sock) {
|
|
|
412
412
|
mention: { text: `@${mentionDisplayName(jid)}`, mentions: [toWireJid(jid)] },
|
|
413
413
|
};
|
|
414
414
|
}
|
|
415
|
+
// ── Chats API ─────────────────────────────────────────────────────────────────
|
|
416
|
+
function buildChatsApi(store) {
|
|
417
|
+
return {
|
|
418
|
+
/**
|
|
419
|
+
* Chats currently known from the in-memory cache (populated from
|
|
420
|
+
* Baileys' chats.upsert / messaging-history.set events) — no network call.
|
|
421
|
+
* @returns {Array<{ id: string, name: string, isGroup: boolean }>}
|
|
422
|
+
*/
|
|
423
|
+
all() {
|
|
424
|
+
return store.chats.all().map((c) => {
|
|
425
|
+
const id = normalizeJid(c.id);
|
|
426
|
+
return { id, name: c.name, isGroup: id.endsWith("@g.us") };
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
415
431
|
// ── Contact API ───────────────────────────────────────────────────────────────
|
|
416
432
|
function buildContactsApi(sock, store, botJid) {
|
|
417
433
|
return {
|
|
@@ -419,6 +435,7 @@ function buildContactsApi(sock, store, botJid) {
|
|
|
419
435
|
* Get a normalized contact object by JID.
|
|
420
436
|
* @param {string} contactId
|
|
421
437
|
* @param {{groupId?: string}} [opts] — when `contactId` is a raw `@lid`, this
|
|
438
|
+
|
|
422
439
|
* always cross-checks it against Baileys' own protocol-level
|
|
423
440
|
* `sock.signalRepository.lidMapping` first (populated from real Signal
|
|
424
441
|
* session/identity resolution — not a heuristic, and doesn't need a
|
|
@@ -587,6 +604,15 @@ function buildContactsApi(sock, store, botJid) {
|
|
|
587
604
|
},
|
|
588
605
|
};
|
|
589
606
|
}
|
|
607
|
+
function makeHistoryArray(entries, store) {
|
|
608
|
+
const arr = entries;
|
|
609
|
+
arr.last = (n) => makeHistoryArray(typeof n === "number" ? entries.slice(-n) : entries.slice(), store);
|
|
610
|
+
arr.from = (senderId) => {
|
|
611
|
+
const target = normalizeJid(store.resolveJid(normalizeJid(senderId)));
|
|
612
|
+
return makeHistoryArray(entries.filter((e) => e.sender === target), store);
|
|
613
|
+
};
|
|
614
|
+
return arr;
|
|
615
|
+
}
|
|
590
616
|
export function buildMessageContext(msg, sock, store, guardOptions = {}) {
|
|
591
617
|
const body = getMsgBody(msg);
|
|
592
618
|
const prefix = CONFIG.CMD_PREFIX;
|
|
@@ -612,6 +638,8 @@ export function buildMessageContext(msg, sock, store, guardOptions = {}) {
|
|
|
612
638
|
}
|
|
613
639
|
: null;
|
|
614
640
|
return {
|
|
641
|
+
id: msg.key.id ?? "",
|
|
642
|
+
timestamp: Number(msg.messageTimestamp) || 0,
|
|
615
643
|
body,
|
|
616
644
|
type: getMsgType(msg),
|
|
617
645
|
fromMe: !!(msg.key.fromMe),
|
|
@@ -817,6 +845,28 @@ function makeSender(sock, store, jid, quoted = null, { cooldown = true, jitter =
|
|
|
817
845
|
return sock.sendMessage(jid, messageContent, sendOpts);
|
|
818
846
|
})(), sock, store, { cooldown, jitter });
|
|
819
847
|
},
|
|
848
|
+
/**
|
|
849
|
+
* Send a GIF. WhatsApp has no native GIF format — this sends an mp4
|
|
850
|
+
* with the `gifPlayback` flag, which the client auto-loops, muted.
|
|
851
|
+
* `filePath` must already be mp4-encoded (no .gif input, no conversion).
|
|
852
|
+
*/
|
|
853
|
+
gif(filePath, caption = "", opts = {}) {
|
|
854
|
+
return new MessageHandle((async () => {
|
|
855
|
+
const quotedMsg = await resolveQuoted();
|
|
856
|
+
const sendOpts = quotedMsg ? { quoted: quotedMsg } : {};
|
|
857
|
+
await waitForSendSlot(normJid, { cooldown, jitter });
|
|
858
|
+
await simulateState(toPresenceCapable(sock), jid, mediaDuration(), "typing");
|
|
859
|
+
const buffer = await readFile(filePath);
|
|
860
|
+
const messageContent = { video: buffer, caption, gifPlayback: true };
|
|
861
|
+
if (opts.viewOnce) {
|
|
862
|
+
messageContent.viewOnce = true;
|
|
863
|
+
}
|
|
864
|
+
if (opts.mentions?.length) {
|
|
865
|
+
messageContent.mentions = await resolveMentionJids(sock, store, jid, opts.mentions);
|
|
866
|
+
}
|
|
867
|
+
return sock.sendMessage(jid, messageContent, sendOpts);
|
|
868
|
+
})(), sock, store, { cooldown, jitter });
|
|
869
|
+
},
|
|
820
870
|
audio(filePath, { asVoice = true, viewOnce = false } = {}) {
|
|
821
871
|
return new MessageHandle((async () => {
|
|
822
872
|
const quotedMsg = await resolveQuoted();
|
|
@@ -1406,6 +1456,7 @@ function buildBaseApi(sock, store, pluginRegistry, pluginName) {
|
|
|
1406
1456
|
download: buildDownloadApi(),
|
|
1407
1457
|
scheduler: buildSchedulerApi(pluginName),
|
|
1408
1458
|
plugins: buildPluginsApi(pluginRegistry),
|
|
1459
|
+
chats: buildChatsApi(store),
|
|
1409
1460
|
contacts: buildContactsApi(sock, store, botJid),
|
|
1410
1461
|
storage: buildStorageApi(pluginName),
|
|
1411
1462
|
botId: botJid,
|
|
@@ -1502,6 +1553,19 @@ export function buildApi({ msg, chat, sock, store, pluginRegistry, pluginName, g
|
|
|
1502
1553
|
id: normJid,
|
|
1503
1554
|
name: chat.name,
|
|
1504
1555
|
isGroup: chat.isGroup,
|
|
1556
|
+
/**
|
|
1557
|
+
* Cached message history for this chat (oldest → newest), capped at
|
|
1558
|
+
* the store's per-chat limit. Supports index access (`history[10]`)
|
|
1559
|
+
* and two chainable filters: `.last(n)` and `.from(senderId)`.
|
|
1560
|
+
* @returns {WAHistoryArray}
|
|
1561
|
+
*/
|
|
1562
|
+
get history() {
|
|
1563
|
+
const chatMsgs = store.messages.get(rawJid);
|
|
1564
|
+
const entries = chatMsgs
|
|
1565
|
+
? [...chatMsgs.values()].map((m) => buildMessageContext(m, sock, store, { cooldown: false, jitter: false }))
|
|
1566
|
+
: [];
|
|
1567
|
+
return makeHistoryArray(entries, store);
|
|
1568
|
+
},
|
|
1505
1569
|
/**
|
|
1506
1570
|
* List of group participants.
|
|
1507
1571
|
* Returns [] for non-group chats.
|
|
@@ -31,6 +31,39 @@ let connecting = false;
|
|
|
31
31
|
let reconnectAttempts = 0;
|
|
32
32
|
let cacheHydrated = false;
|
|
33
33
|
let cacheSaveTimer = null;
|
|
34
|
+
// ── Per-chat message queue ──────────────────────────────────────────────────
|
|
35
|
+
// Messages from the same chat are processed one at a time (in order), but
|
|
36
|
+
// different chats run concurrently — a slow plugin in one chat (e.g. sticker
|
|
37
|
+
// generation) no longer blocks replies in every other chat.
|
|
38
|
+
const chatQueues = new Map();
|
|
39
|
+
function enqueueForChat(jid, task) {
|
|
40
|
+
const prev = chatQueues.get(jid) ?? Promise.resolve();
|
|
41
|
+
const settled = prev.catch(() => { }).then(task).catch((e) => {
|
|
42
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
43
|
+
logger.error(`${err.message}\n${err.stack}`);
|
|
44
|
+
});
|
|
45
|
+
chatQueues.set(jid, settled);
|
|
46
|
+
settled.finally(() => {
|
|
47
|
+
if (chatQueues.get(jid) === settled)
|
|
48
|
+
chatQueues.delete(jid);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Messages older than this (WhatsApp's own delivery delay — e.g. backlog
|
|
52
|
+
// dumped after the bot reconnects) are skipped. Checked at arrival time,
|
|
53
|
+
// so time spent waiting in chatQueues never counts against a message.
|
|
54
|
+
const MAX_MESSAGE_AGE_SECONDS = 60;
|
|
55
|
+
function isMessageStale(msg) {
|
|
56
|
+
const msgTimestamp = Number(msg.messageTimestamp);
|
|
57
|
+
if (!msgTimestamp)
|
|
58
|
+
return false;
|
|
59
|
+
const nowInSeconds = Math.floor(Date.now() / 1000);
|
|
60
|
+
const age = nowInSeconds - msgTimestamp;
|
|
61
|
+
if (age > MAX_MESSAGE_AGE_SECONDS) {
|
|
62
|
+
logger.debug(`[whatsapp] Skipping stale message (age: ${age}s, id: ${msg.key.id})`);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
34
67
|
const RECONNECT_BASE_MS = 1000;
|
|
35
68
|
const RECONNECT_MAX_MS = 60000;
|
|
36
69
|
const CACHE_SAVE_INTERVAL_MS = 5 * 60 * 1000; // 5min
|
|
@@ -156,13 +189,10 @@ async function startBot() {
|
|
|
156
189
|
const body = getBodyQuick(m);
|
|
157
190
|
if (!body && !msgHasMediaQuick(m))
|
|
158
191
|
continue;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const err = e instanceof Error ? e : new Error(String(e));
|
|
164
|
-
logger.error(`${err.message}\n${err.stack}`);
|
|
165
|
-
}
|
|
192
|
+
if (isMessageStale(m))
|
|
193
|
+
continue;
|
|
194
|
+
const jid = normalizeJid(m.key.remoteJid ?? "");
|
|
195
|
+
enqueueForChat(jid, () => handleMessage(m, sock, store));
|
|
166
196
|
}
|
|
167
197
|
});
|
|
168
198
|
}
|
|
@@ -45,14 +45,6 @@ function alreadyProcessed(id) {
|
|
|
45
45
|
* @param {WAStore} store
|
|
46
46
|
*/
|
|
47
47
|
export async function handleMessage(msg, sock, store) {
|
|
48
|
-
const msgTimestamp = Number(msg.messageTimestamp);
|
|
49
|
-
if (msgTimestamp) {
|
|
50
|
-
const nowInSeconds = Math.floor(Date.now() / 1000);
|
|
51
|
-
const messageAge = nowInSeconds - msgTimestamp;
|
|
52
|
-
if (messageAge > 60) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
48
|
const rawJid = msg.key.remoteJid ?? "";
|
|
57
49
|
const jid = normalizeJid(rawJid);
|
|
58
50
|
if (CHATS.length > 0 && !CHATS.includes(jid)) {
|
package/dist/main.js
CHANGED
|
File without changes
|