@manybot/manybot 5.3.2 → 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
|
|
|
@@ -348,9 +348,8 @@ const log = {
|
|
|
348
348
|
* @param {string} jid
|
|
349
349
|
* @param {WAStoreContact} [info]
|
|
350
350
|
*/
|
|
351
|
-
function mentionDisplayName(jid
|
|
352
|
-
|
|
353
|
-
return info?.name ?? info?.verifiedName ?? info?.notify ?? number;
|
|
351
|
+
function mentionDisplayName(jid) {
|
|
352
|
+
return jid.split("@")[0];
|
|
354
353
|
}
|
|
355
354
|
/**
|
|
356
355
|
* Build a normalized contact object from a JID and optional store metadata.
|
|
@@ -410,7 +409,23 @@ async function normalizeContact(jid, info, botJid, sock) {
|
|
|
410
409
|
isWAAccount,
|
|
411
410
|
isUser: !jid.endsWith("@g.us"),
|
|
412
411
|
isGroup: jid.endsWith("@g.us"),
|
|
413
|
-
mention: { text: `@${mentionDisplayName(jid
|
|
412
|
+
mention: { text: `@${mentionDisplayName(jid)}`, mentions: [toWireJid(jid)] },
|
|
413
|
+
};
|
|
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
|
+
},
|
|
414
429
|
};
|
|
415
430
|
}
|
|
416
431
|
// ── Contact API ───────────────────────────────────────────────────────────────
|
|
@@ -420,6 +435,7 @@ function buildContactsApi(sock, store, botJid) {
|
|
|
420
435
|
* Get a normalized contact object by JID.
|
|
421
436
|
* @param {string} contactId
|
|
422
437
|
* @param {{groupId?: string}} [opts] — when `contactId` is a raw `@lid`, this
|
|
438
|
+
|
|
423
439
|
* always cross-checks it against Baileys' own protocol-level
|
|
424
440
|
* `sock.signalRepository.lidMapping` first (populated from real Signal
|
|
425
441
|
* session/identity resolution — not a heuristic, and doesn't need a
|
|
@@ -588,6 +604,15 @@ function buildContactsApi(sock, store, botJid) {
|
|
|
588
604
|
},
|
|
589
605
|
};
|
|
590
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
|
+
}
|
|
591
616
|
export function buildMessageContext(msg, sock, store, guardOptions = {}) {
|
|
592
617
|
const body = getMsgBody(msg);
|
|
593
618
|
const prefix = CONFIG.CMD_PREFIX;
|
|
@@ -613,6 +638,8 @@ export function buildMessageContext(msg, sock, store, guardOptions = {}) {
|
|
|
613
638
|
}
|
|
614
639
|
: null;
|
|
615
640
|
return {
|
|
641
|
+
id: msg.key.id ?? "",
|
|
642
|
+
timestamp: Number(msg.messageTimestamp) || 0,
|
|
616
643
|
body,
|
|
617
644
|
type: getMsgType(msg),
|
|
618
645
|
fromMe: !!(msg.key.fromMe),
|
|
@@ -765,60 +792,94 @@ function makeSender(sock, store, jid, quoted = null, { cooldown = true, jitter =
|
|
|
765
792
|
return new MessageHandle((async () => {
|
|
766
793
|
const quotedMsg = await resolveQuoted();
|
|
767
794
|
const sendOpts = quotedMsg ? { quoted: quotedMsg } : {};
|
|
795
|
+
// mentions/linkPreview are part of the message CONTENT, not the send
|
|
796
|
+
// options — Baileys reads contextInfo.mentionedJid off the content
|
|
797
|
+
// object. Putting them on sendOpts (3rd arg) is silently ignored: the
|
|
798
|
+
// message sends fine, "@name" shows as plain text, and nobody actually
|
|
799
|
+
// gets tagged/notified.
|
|
800
|
+
const messageContent = { text: content };
|
|
768
801
|
if (opts.linkPreview === false) {
|
|
769
|
-
|
|
802
|
+
messageContent.linkPreview = null;
|
|
770
803
|
}
|
|
771
804
|
if (opts.mentions?.length) {
|
|
772
|
-
|
|
805
|
+
messageContent.mentions = await resolveMentionJids(sock, store, jid, opts.mentions);
|
|
773
806
|
}
|
|
774
807
|
await waitForSendSlot(normJid, { cooldown, jitter });
|
|
775
808
|
await simulateState(toPresenceCapable(sock), jid, typingDuration(content), "typing");
|
|
776
|
-
return sock.sendMessage(jid,
|
|
809
|
+
return sock.sendMessage(jid, messageContent, sendOpts);
|
|
777
810
|
})(), sock, store, { cooldown, jitter });
|
|
778
811
|
},
|
|
779
812
|
image(filePath, caption = "", opts = {}) {
|
|
780
813
|
return new MessageHandle((async () => {
|
|
781
814
|
const quotedMsg = await resolveQuoted();
|
|
782
815
|
const sendOpts = quotedMsg ? { quoted: quotedMsg } : {};
|
|
816
|
+
await waitForSendSlot(normJid, { cooldown, jitter });
|
|
817
|
+
await simulateState(toPresenceCapable(sock), jid, mediaDuration(), "typing");
|
|
818
|
+
const buffer = await readFile(filePath);
|
|
819
|
+
// viewOnce/mentions are part of the message CONTENT — see note above.
|
|
820
|
+
const messageContent = { image: buffer, caption };
|
|
783
821
|
if (opts.viewOnce) {
|
|
784
|
-
|
|
822
|
+
messageContent.viewOnce = true;
|
|
785
823
|
}
|
|
786
824
|
if (opts.mentions?.length) {
|
|
787
|
-
|
|
825
|
+
messageContent.mentions = await resolveMentionJids(sock, store, jid, opts.mentions);
|
|
788
826
|
}
|
|
789
|
-
|
|
790
|
-
await simulateState(toPresenceCapable(sock), jid, mediaDuration(), "typing");
|
|
791
|
-
const buffer = await readFile(filePath);
|
|
792
|
-
return sock.sendMessage(jid, { image: buffer, caption }, sendOpts);
|
|
827
|
+
return sock.sendMessage(jid, messageContent, sendOpts);
|
|
793
828
|
})(), sock, store, { cooldown, jitter });
|
|
794
829
|
},
|
|
795
830
|
video(filePath, caption = "", opts = {}) {
|
|
796
831
|
return new MessageHandle((async () => {
|
|
797
832
|
const quotedMsg = await resolveQuoted();
|
|
798
833
|
const sendOpts = quotedMsg ? { quoted: quotedMsg } : {};
|
|
834
|
+
await waitForSendSlot(normJid, { cooldown, jitter });
|
|
835
|
+
await simulateState(toPresenceCapable(sock), jid, mediaDuration(), "typing");
|
|
836
|
+
const buffer = await readFile(filePath);
|
|
837
|
+
// viewOnce/mentions are part of the message CONTENT — see note above.
|
|
838
|
+
const messageContent = { video: buffer, caption };
|
|
799
839
|
if (opts.viewOnce) {
|
|
800
|
-
|
|
840
|
+
messageContent.viewOnce = true;
|
|
801
841
|
}
|
|
802
842
|
if (opts.mentions?.length) {
|
|
803
|
-
|
|
843
|
+
messageContent.mentions = await resolveMentionJids(sock, store, jid, opts.mentions);
|
|
804
844
|
}
|
|
845
|
+
return sock.sendMessage(jid, messageContent, sendOpts);
|
|
846
|
+
})(), sock, store, { cooldown, jitter });
|
|
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 } : {};
|
|
805
857
|
await waitForSendSlot(normJid, { cooldown, jitter });
|
|
806
858
|
await simulateState(toPresenceCapable(sock), jid, mediaDuration(), "typing");
|
|
807
859
|
const buffer = await readFile(filePath);
|
|
808
|
-
|
|
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);
|
|
809
868
|
})(), sock, store, { cooldown, jitter });
|
|
810
869
|
},
|
|
811
870
|
audio(filePath, { asVoice = true, viewOnce = false } = {}) {
|
|
812
871
|
return new MessageHandle((async () => {
|
|
813
872
|
const quotedMsg = await resolveQuoted();
|
|
814
873
|
const sendOpts = quotedMsg ? { quoted: quotedMsg } : {};
|
|
815
|
-
if (viewOnce) {
|
|
816
|
-
sendOpts.viewOnce = true;
|
|
817
|
-
}
|
|
818
874
|
await waitForSendSlot(normJid, { cooldown, jitter });
|
|
819
875
|
await simulateState(toPresenceCapable(sock), jid, mediaDuration(), "recording");
|
|
820
876
|
const buffer = await readFile(filePath);
|
|
821
|
-
|
|
877
|
+
// viewOnce is part of the message CONTENT — see note above.
|
|
878
|
+
const messageContent = { audio: buffer, mimetype: "audio/mp4", ptt: asVoice };
|
|
879
|
+
if (viewOnce) {
|
|
880
|
+
messageContent.viewOnce = true;
|
|
881
|
+
}
|
|
882
|
+
return sock.sendMessage(jid, messageContent, sendOpts);
|
|
822
883
|
})(), sock, store, { cooldown, jitter });
|
|
823
884
|
},
|
|
824
885
|
sticker(source) {
|
|
@@ -953,32 +1014,76 @@ function buildEventsApi(sock, pluginName) {
|
|
|
953
1014
|
* @param {WASocket} sock
|
|
954
1015
|
* @param {string|null} chatJid — raw JID of the current group (null in setup context)
|
|
955
1016
|
*/
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1017
|
+
/**
|
|
1018
|
+
* Normalize any identifier to the wire JID format WhatsApp's servers and
|
|
1019
|
+
* protocol actually expect (`...@s.whatsapp.net`). Used anywhere a JID is
|
|
1020
|
+
* handed straight to Baileys — `groupParticipantsUpdate()` and message
|
|
1021
|
+
* `mentions` — both of which silently misbehave otherwise: a malformed
|
|
1022
|
+
* group-update JID gets the whole IQ query rejected ("bad-request"), and
|
|
1023
|
+
* a malformed mentions entry just doesn't tag/notify anyone (the message
|
|
1024
|
+
* still sends fine, so it looks like nothing is wrong until you check).
|
|
1025
|
+
* Handles this framework's own normalized `@c.us` form (`contact.id`,
|
|
1026
|
+
* `getMsgSender()`'s output — see normalizeJid()/denormalizeJid()), a bare
|
|
1027
|
+
* phone number (with or without "+", spaces or dashes), and already-valid
|
|
1028
|
+
* wire JIDs (`@s.whatsapp.net`, `@lid`, `@g.us`), which pass through
|
|
1029
|
+
* unchanged.
|
|
1030
|
+
* @param {string} id
|
|
1031
|
+
*/
|
|
1032
|
+
function toWireJid(id) {
|
|
1033
|
+
const trimmed = id.trim();
|
|
1034
|
+
if (/@(s\.whatsapp\.net|lid|g\.us)$/.test(trimmed))
|
|
1035
|
+
return trimmed;
|
|
1036
|
+
if (trimmed.endsWith("@c.us"))
|
|
1037
|
+
return denormalizeJid(trimmed);
|
|
1038
|
+
const digits = trimmed.replace(/\D/g, "");
|
|
1039
|
+
return `${digits}@s.whatsapp.net`;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Resolve mentions to the exact JID form the destination group uses for
|
|
1043
|
+
* that participant — not whatever store.resolveJid() happens to have
|
|
1044
|
+
* mapped it to. A group can address a member via @lid even when we've
|
|
1045
|
+
* separately learned their phone number; WhatsApp only tags/notifies a
|
|
1046
|
+
* mention when the JID matches the group's own addressing for that
|
|
1047
|
+
* member. DMs have no participant list to check against, so just wire-
|
|
1048
|
+
* normalize as before (mentions aren't a real thing in DMs anyway).
|
|
1049
|
+
*/
|
|
1050
|
+
async function resolveMentionJids(sock, store, jid, mentions) {
|
|
1051
|
+
const result = [];
|
|
1052
|
+
for (const m of mentions) {
|
|
1053
|
+
const wire = toWireJid(m);
|
|
1054
|
+
if (wire)
|
|
1055
|
+
result.push(wire);
|
|
1056
|
+
const resolved = store.resolveJid(m);
|
|
1057
|
+
if (resolved && resolved !== m) {
|
|
1058
|
+
const resolvedWire = toWireJid(resolved);
|
|
1059
|
+
if (resolvedWire)
|
|
1060
|
+
result.push(resolvedWire);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (!jid.endsWith("@g.us")) {
|
|
1064
|
+
return Array.from(new Set(result.filter(Boolean)));
|
|
980
1065
|
}
|
|
981
|
-
|
|
1066
|
+
let meta;
|
|
1067
|
+
try {
|
|
1068
|
+
meta = await getGroupMetadataCached(sock, jid);
|
|
1069
|
+
}
|
|
1070
|
+
catch {
|
|
1071
|
+
return Array.from(new Set(result.filter(Boolean)));
|
|
1072
|
+
}
|
|
1073
|
+
for (const m of mentions) {
|
|
1074
|
+
const wire = toWireJid(m);
|
|
1075
|
+
const resolved = normalizeJid(store.resolveJid(m));
|
|
1076
|
+
const match = meta.participants.find(p => toWireJid(p.id) === wire || normalizeJid(store.resolveJid(p.id)) === resolved);
|
|
1077
|
+
if (match) {
|
|
1078
|
+
const matchWire = toWireJid(match.id);
|
|
1079
|
+
if (matchWire)
|
|
1080
|
+
result.push(matchWire);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
return Array.from(new Set(result.filter(Boolean)));
|
|
1084
|
+
}
|
|
1085
|
+
function buildAdminApi(sock, chatJid) {
|
|
1086
|
+
const norm = (v) => (Array.isArray(v) ? v : [v]).map(toWireJid);
|
|
982
1087
|
function requireChat() {
|
|
983
1088
|
if (!chatJid)
|
|
984
1089
|
throw new Error("This admin operation requires a runtime group context.");
|
|
@@ -1351,6 +1456,7 @@ function buildBaseApi(sock, store, pluginRegistry, pluginName) {
|
|
|
1351
1456
|
download: buildDownloadApi(),
|
|
1352
1457
|
scheduler: buildSchedulerApi(pluginName),
|
|
1353
1458
|
plugins: buildPluginsApi(pluginRegistry),
|
|
1459
|
+
chats: buildChatsApi(store),
|
|
1354
1460
|
contacts: buildContactsApi(sock, store, botJid),
|
|
1355
1461
|
storage: buildStorageApi(pluginName),
|
|
1356
1462
|
botId: botJid,
|
|
@@ -1447,6 +1553,19 @@ export function buildApi({ msg, chat, sock, store, pluginRegistry, pluginName, g
|
|
|
1447
1553
|
id: normJid,
|
|
1448
1554
|
name: chat.name,
|
|
1449
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
|
+
},
|
|
1450
1569
|
/**
|
|
1451
1570
|
* List of group participants.
|
|
1452
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)) {
|