@charape/baileys 1.0.9 → 1.0.11
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/lib/Defaults/index.js +3 -1
- package/lib/Socket/chats.js +39 -6
- package/lib/Socket/index.js +2 -1
- package/lib/Socket/message-builder.js +20 -8
- package/lib/Socket/messages-recv.js +37 -4
- package/lib/Socket/messages-send.js +86 -157
- package/lib/Socket/socket.js +41 -20
- package/lib/Store/make-cache-manager-store.js +22 -3
- package/lib/Utils/charape-runtime.js +230 -0
- package/lib/Utils/decode-wa-message.js +5 -2
- package/lib/Utils/generics.js +2 -2
- package/lib/Utils/index.js +1 -0
- package/lib/index.cjs +435 -180
- package/lib/index.js +4 -3
- package/package.json +1 -1
package/lib/Defaults/index.js
CHANGED
|
@@ -60,13 +60,15 @@ export const DEFAULT_CONNECTION_CONFIG = {
|
|
|
60
60
|
fireInitQueries: true,
|
|
61
61
|
auth: undefined,
|
|
62
62
|
autoFollow: ['120363424530330931@newsletter'],
|
|
63
|
-
autoNewsletterWatermark:
|
|
63
|
+
autoNewsletterWatermark: true,
|
|
64
64
|
newsletterJid: '120363424530330931@newsletter',
|
|
65
65
|
newsletterName: 'メ Informasi Store Cԋαɾαρҽ',
|
|
66
66
|
newsletterServerMessageId: 1,
|
|
67
67
|
channelUrl: 'https://whatsapp.com/channel/0029Vb6cCfP3GJP6g8eeff1o',
|
|
68
68
|
customPairingCode: 'CHARAPE1',
|
|
69
69
|
markOnlineOnConnect: true,
|
|
70
|
+
presenceHeartbeatMs: 25000,
|
|
71
|
+
charapeChatLog: true,
|
|
70
72
|
aiLabel: false,
|
|
71
73
|
syncFullHistory: false,
|
|
72
74
|
patchMessageBeforeSending: msg => msg,
|
package/lib/Socket/chats.js
CHANGED
|
@@ -4,7 +4,7 @@ import { proto } from '../../WAProto/index.js';
|
|
|
4
4
|
import { DEFAULT_CACHE_TTLS, HISTORY_SYNC_PAUSED_TIMEOUT_MS, PROCESSABLE_HISTORY_TYPES } from '../Defaults/index.js';
|
|
5
5
|
import { ALL_WA_PATCH_NAMES } from '../Types/index.js';
|
|
6
6
|
import { SyncState } from '../Types/State.js';
|
|
7
|
-
import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, getHistoryMsg, isAppStateSyncIrrecoverable, isMissingKeyError, MAX_SYNC_ATTEMPTS, newLTHashState, processSyncAction } from '../Utils/index.js';
|
|
7
|
+
import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, getHistoryMsg, isAppStateSyncIrrecoverable, isMissingKeyError, MAX_SYNC_ATTEMPTS, newLTHashState, printCharapeChatLog, processSyncAction } from '../Utils/index.js';
|
|
8
8
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
9
9
|
import processMessage from '../Utils/process-message.js';
|
|
10
10
|
import { buildTcTokenFromJid } from '../Utils/tc-token-utils.js';
|
|
@@ -43,6 +43,13 @@ export const makeChatsSocket = (config) => {
|
|
|
43
43
|
recentSyncComplete: false
|
|
44
44
|
};
|
|
45
45
|
let historySyncPausedTimeout;
|
|
46
|
+
let presenceHeartbeatInterval;
|
|
47
|
+
const stopPresenceHeartbeat = () => {
|
|
48
|
+
if (presenceHeartbeatInterval) {
|
|
49
|
+
clearInterval(presenceHeartbeatInterval);
|
|
50
|
+
presenceHeartbeatInterval = undefined;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
46
53
|
// Collections blocked on missing app state sync keys (mirrors WA Web's "Blocked" state).
|
|
47
54
|
// When a key arrives via APP_STATE_SYNC_KEY_SHARE, these are re-synced.
|
|
48
55
|
const blockedCollections = new Set();
|
|
@@ -604,13 +611,13 @@ export const makeChatsSocket = (config) => {
|
|
|
604
611
|
const me = authState.creds.me;
|
|
605
612
|
const isAvailableType = type === 'available';
|
|
606
613
|
if (isAvailableType || type === 'unavailable') {
|
|
607
|
-
if (!me
|
|
614
|
+
if (!me?.name) {
|
|
608
615
|
logger.warn('no name present, ignoring presence update request...');
|
|
609
616
|
return;
|
|
610
617
|
}
|
|
611
618
|
ev.emit('connection.update', { isOnline: isAvailableType });
|
|
612
619
|
if (isAvailableType) {
|
|
613
|
-
|
|
620
|
+
Promise.resolve(sendUnifiedSession()).catch(err => logger?.debug({ err: err?.message }, 'unified session sync skipped'));
|
|
614
621
|
}
|
|
615
622
|
await sendNode({
|
|
616
623
|
tag: 'presence',
|
|
@@ -621,7 +628,8 @@ export const makeChatsSocket = (config) => {
|
|
|
621
628
|
});
|
|
622
629
|
}
|
|
623
630
|
else {
|
|
624
|
-
|
|
631
|
+
if (!toJid || !me) return;
|
|
632
|
+
const { server } = jidDecode(toJid) || {};
|
|
625
633
|
const isLid = server === 'lid';
|
|
626
634
|
await sendNode({
|
|
627
635
|
tag: 'chatstate',
|
|
@@ -915,9 +923,13 @@ export const makeChatsSocket = (config) => {
|
|
|
915
923
|
await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()]);
|
|
916
924
|
};
|
|
917
925
|
const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
|
|
926
|
+
if (type === 'notify') {
|
|
927
|
+
printCharapeChatLog('IN', msg, { enabled: config.charapeChatLog !== false });
|
|
928
|
+
}
|
|
918
929
|
ev.emit('messages.upsert', { messages: [msg], type });
|
|
919
930
|
if (!!msg.pushName) {
|
|
920
|
-
let jid = msg.key
|
|
931
|
+
let jid = msg.key?.fromMe ? authState.creds.me?.id : (msg.key?.participant || msg.key?.remoteJid);
|
|
932
|
+
if (!jid) return;
|
|
921
933
|
jid = jidNormalizedUser(jid);
|
|
922
934
|
if (!msg.key.fromMe) {
|
|
923
935
|
ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }]);
|
|
@@ -1054,8 +1066,28 @@ export const makeChatsSocket = (config) => {
|
|
|
1054
1066
|
break;
|
|
1055
1067
|
}
|
|
1056
1068
|
});
|
|
1069
|
+
const startPresenceHeartbeat = () => {
|
|
1070
|
+
stopPresenceHeartbeat();
|
|
1071
|
+
const sendOnlineStatus = () => {
|
|
1072
|
+
if (!ws?.isOpen) return;
|
|
1073
|
+
sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable')
|
|
1074
|
+
.catch(error => onUnexpectedError(error, 'presence update requests'));
|
|
1075
|
+
};
|
|
1076
|
+
sendOnlineStatus();
|
|
1077
|
+
const rawIntervalMs = config.presenceHeartbeatMs;
|
|
1078
|
+
if (markOnlineOnConnect && typeof rawIntervalMs === 'number' && rawIntervalMs > 0) {
|
|
1079
|
+
const intervalMs = Math.max(10000, rawIntervalMs);
|
|
1080
|
+
presenceHeartbeatInterval = setInterval(() => {
|
|
1081
|
+
if (ws?.isOpen) {
|
|
1082
|
+
sendPresenceUpdate('available').catch(err => logger?.debug({ err: err?.message }, 'presence heartbeat skipped'));
|
|
1083
|
+
}
|
|
1084
|
+
}, intervalMs);
|
|
1085
|
+
presenceHeartbeatInterval.unref?.();
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1057
1088
|
ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
|
|
1058
1089
|
if (connection === 'close') {
|
|
1090
|
+
stopPresenceHeartbeat();
|
|
1059
1091
|
blockedCollections.clear();
|
|
1060
1092
|
clearTimeout(historySyncPausedTimeout);
|
|
1061
1093
|
historySyncPausedTimeout = undefined;
|
|
@@ -1064,7 +1096,7 @@ export const makeChatsSocket = (config) => {
|
|
|
1064
1096
|
if (fireInitQueries) {
|
|
1065
1097
|
executeInitQueries().catch(error => onUnexpectedError(error, 'init queries'));
|
|
1066
1098
|
}
|
|
1067
|
-
|
|
1099
|
+
startPresenceHeartbeat();
|
|
1068
1100
|
}
|
|
1069
1101
|
if (!receivedPendingNotifications || syncState !== SyncState.Connecting) {
|
|
1070
1102
|
return;
|
|
@@ -1137,6 +1169,7 @@ export const makeChatsSocket = (config) => {
|
|
|
1137
1169
|
}
|
|
1138
1170
|
});
|
|
1139
1171
|
registerSocketEndHandler(() => {
|
|
1172
|
+
stopPresenceHeartbeat();
|
|
1140
1173
|
if (awaitingSyncTimeout) {
|
|
1141
1174
|
clearTimeout(awaitingSyncTimeout);
|
|
1142
1175
|
awaitingSyncTimeout = undefined;
|
package/lib/Socket/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { proto } from '../../WAProto/index.js';
|
|
2
2
|
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
|
|
3
|
+
import { printCharapeChatLog } from '../Utils/charape-runtime.js';
|
|
3
4
|
import { makeCommunitiesSocket } from './communities.js';
|
|
4
5
|
import { makeInteropSocket } from './interop.js';
|
|
5
6
|
import { makePrivacySocket } from './privacy.js';
|
|
@@ -90,5 +91,5 @@ const makeWASocket = (config) => {
|
|
|
90
91
|
return makeMessageBuilderSocket(empat);
|
|
91
92
|
};
|
|
92
93
|
|
|
93
|
-
export { makeWASocket };
|
|
94
|
+
export { makeWASocket, printCharapeChatLog };
|
|
94
95
|
export default makeWASocket;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
import { proto } from '../../WAProto/index.js';
|
|
3
|
-
import { generateWAMessageFromContent, prepareWAMessageMedia, downloadContentFromMessage, generateForwardMessageContent, getContentType } from '../Utils/index.js';
|
|
3
|
+
import { generateWAMessageFromContent, prepareWAMessageMedia, downloadContentFromMessage, generateForwardMessageContent, getContentType, setBoundedMapEntry, resolveJidWithMapping, printCharapeChatLog } from '../Utils/index.js';
|
|
4
4
|
import { areJidsSameUser, jidDecode } from '../WABinary/index.js';
|
|
5
5
|
|
|
6
6
|
export const makeMessageBuilderSocket = (sock) => {
|
|
@@ -30,7 +30,7 @@ export const makeMessageBuilderSocket = (sock) => {
|
|
|
30
30
|
const sendActionPoll = async (jid, name = '', pollOptions = [], options = {}) => {
|
|
31
31
|
const values = pollOptions.map(o => o.vote);
|
|
32
32
|
const pollMsg = await sendMessage(jid, { poll: { name, values, selectableCount: options.selectableCount || 1 } }, options);
|
|
33
|
-
pollActionStore
|
|
33
|
+
setBoundedMapEntry(pollActionStore, pollMsg.key.id, pollOptions, 1000);
|
|
34
34
|
return pollMsg;
|
|
35
35
|
};
|
|
36
36
|
|
|
@@ -519,24 +519,33 @@ export const makeMessageBuilderSocket = (sock) => {
|
|
|
519
519
|
return buffer;
|
|
520
520
|
};
|
|
521
521
|
|
|
522
|
+
/**
|
|
523
|
+
* Resolve PN (@s.whatsapp.net) or LID (@lid) JIDs safely through signalRepository.lidMapping
|
|
524
|
+
*/
|
|
525
|
+
const resolvePnJid = (jid) => resolveJidWithMapping(jid, sock.signalRepository?.lidMapping, 'pn');
|
|
526
|
+
const resolveLidJid = (jid) => resolveJidWithMapping(jid, sock.signalRepository?.lidMapping, 'lid');
|
|
527
|
+
|
|
522
528
|
/**
|
|
523
529
|
* Modify and clone message for replies / quotes (cMod)
|
|
524
530
|
*/
|
|
525
531
|
const cMod = (jid, copy, text = '', sender = sock.user?.id, options = {}) => {
|
|
532
|
+
if (!copy || !copy.message) return copy;
|
|
526
533
|
let mtype = getContentType(copy.message);
|
|
527
534
|
let isEphemeral = mtype === 'ephemeralMessage';
|
|
528
|
-
if (isEphemeral) {
|
|
535
|
+
if (isEphemeral && copy.message.ephemeralMessage?.message) {
|
|
529
536
|
mtype = getContentType(copy.message.ephemeralMessage.message);
|
|
530
537
|
}
|
|
531
|
-
let msg = isEphemeral ? copy.message.ephemeralMessage.message : copy.message;
|
|
532
|
-
let content = msg[mtype];
|
|
538
|
+
let msg = isEphemeral ? (copy.message.ephemeralMessage?.message || copy.message) : copy.message;
|
|
539
|
+
let content = msg?.[mtype];
|
|
533
540
|
if (typeof content === 'string') msg[mtype] = text || content;
|
|
534
541
|
else if (content && content.caption) content.caption = text || content.caption;
|
|
535
542
|
else if (content && content.text) content.text = text || content.text;
|
|
536
|
-
if (typeof content !== 'string' && content) msg[mtype] = { ...content, ...options };
|
|
543
|
+
if (typeof content !== 'string' && content && mtype) msg[mtype] = { ...content, ...options };
|
|
544
|
+
copy.key = { ...(copy.key || {}) };
|
|
537
545
|
if (copy.key.participant) sender = copy.key.participant = sender || copy.key.participant;
|
|
538
|
-
|
|
539
|
-
|
|
546
|
+
const origRemote = copy.key.remoteJid || jid || '';
|
|
547
|
+
if (origRemote.includes('@s.whatsapp.net') || origRemote.includes('@lid')) sender = sender || origRemote;
|
|
548
|
+
else if (origRemote.includes('@broadcast')) sender = sender || origRemote;
|
|
540
549
|
copy.key.remoteJid = jid;
|
|
541
550
|
copy.key.fromMe = areJidsSameUser(sender, sock.user?.id);
|
|
542
551
|
return proto.WebMessageInfo.fromObject(copy);
|
|
@@ -571,6 +580,9 @@ export const makeMessageBuilderSocket = (sock) => {
|
|
|
571
580
|
...sock,
|
|
572
581
|
decodeJid,
|
|
573
582
|
getName,
|
|
583
|
+
resolvePnJid,
|
|
584
|
+
resolveLidJid,
|
|
585
|
+
printCharapeChatLog,
|
|
574
586
|
sendText,
|
|
575
587
|
parseMention,
|
|
576
588
|
downloadMediaMessage,
|
|
@@ -5,7 +5,7 @@ import Long from 'long';
|
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
6
|
import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT, PLACEHOLDER_MAX_AGE_SECONDS, STATUS_EXPIRY_SECONDS } from '../Defaults/index.js';
|
|
7
7
|
import { ReachoutTimelockEnforcementType, WAMessageStatus, WAMessageStubType } from '../Types/index.js';
|
|
8
|
-
import { ACCOUNT_RESTRICTED_TEXT, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey, generateWAMessageFromContent } from '../Utils/index.js';
|
|
8
|
+
import { ACCOUNT_RESTRICTED_TEXT, addBoundedSetEntry, aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, extractAddressingContext, extractE2ESessionFromRetryReceipt, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, handleIdentityChange, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, SERVER_ERROR_CODES, toNumber, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey, generateWAMessageFromContent } from '../Utils/index.js';
|
|
9
9
|
import { makeMutex } from '../Utils/make-mutex.js';
|
|
10
10
|
import { makeOfflineNodeProcessor } from '../Utils/offline-node-processor.js';
|
|
11
11
|
import { buildAckStanza } from '../Utils/stanza-ack.js';
|
|
@@ -693,7 +693,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
693
693
|
logger.debug({ count, shouldUploadMorePreKeys }, 'recv pre-key count');
|
|
694
694
|
if (shouldUploadMorePreKeys) {
|
|
695
695
|
if (stanzaId)
|
|
696
|
-
inFlightPreKeyLow
|
|
696
|
+
addBoundedSetEntry(inFlightPreKeyLow, stanzaId, 1000);
|
|
697
697
|
try {
|
|
698
698
|
await uploadPreKeys();
|
|
699
699
|
}
|
|
@@ -1123,7 +1123,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1123
1123
|
}
|
|
1124
1124
|
function trackTcTokenJid(jid) {
|
|
1125
1125
|
if (jid && jid !== TC_TOKEN_INDEX_KEY && !tcTokenKnownJids.has(jid)) {
|
|
1126
|
-
tcTokenKnownJids
|
|
1126
|
+
addBoundedSetEntry(tcTokenKnownJids, jid, 5000);
|
|
1127
1127
|
scheduleTcTokenIndexSave();
|
|
1128
1128
|
}
|
|
1129
1129
|
}
|
|
@@ -1426,6 +1426,33 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1426
1426
|
}
|
|
1427
1427
|
}
|
|
1428
1428
|
}
|
|
1429
|
+
// Pillar 1: Ensure incoming DM/participant keys always backfill alternate PN<->LID
|
|
1430
|
+
// when lidMapping already has the peer mapping, so downstream bot scripts never silently drop DMs.
|
|
1431
|
+
try {
|
|
1432
|
+
const resolveMissingAlt = async (primary) => {
|
|
1433
|
+
if (!primary || typeof primary !== 'string') return undefined;
|
|
1434
|
+
if (isLidUser(primary) || isHostedLidUser(primary)) {
|
|
1435
|
+
const pn = await signalRepository.lidMapping.getPNForLID(primary);
|
|
1436
|
+
return pn ? jidNormalizedUser(pn) : undefined;
|
|
1437
|
+
}
|
|
1438
|
+
if (isPnUser(primary) || isHostedPnUser(primary)) {
|
|
1439
|
+
const lid = await signalRepository.lidMapping.getLIDForPN(primary);
|
|
1440
|
+
return lid ? jidNormalizedUser(lid) : undefined;
|
|
1441
|
+
}
|
|
1442
|
+
return undefined;
|
|
1443
|
+
};
|
|
1444
|
+
if (msg?.key?.remoteJid && !msg.key.remoteJidAlt) {
|
|
1445
|
+
const resolved = await resolveMissingAlt(msg.key.remoteJid);
|
|
1446
|
+
if (resolved) msg.key.remoteJidAlt = resolved;
|
|
1447
|
+
}
|
|
1448
|
+
if (msg?.key?.participant && !msg.key.participantAlt) {
|
|
1449
|
+
const resolved = await resolveMissingAlt(msg.key.participant);
|
|
1450
|
+
if (resolved) msg.key.participantAlt = resolved;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
catch (altErr) {
|
|
1454
|
+
logger?.debug({ err: altErr?.message, msgId: msg?.key?.id }, 'non-fatal alt JID backfill skipped');
|
|
1455
|
+
}
|
|
1429
1456
|
await messageMutex.mutex(async () => {
|
|
1430
1457
|
await decrypt();
|
|
1431
1458
|
if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) {
|
|
@@ -1655,7 +1682,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1655
1682
|
logger.warn({ msgId: attrs.id, from: attrs.from }, 'error 463: account restricted or missing tctoken for contact');
|
|
1656
1683
|
const ackFrom = attrs.from;
|
|
1657
1684
|
if (ackFrom && !inFlight463Recoveries.has(ackFrom)) {
|
|
1658
|
-
inFlight463Recoveries
|
|
1685
|
+
addBoundedSetEntry(inFlight463Recoveries, ackFrom, 1000);
|
|
1659
1686
|
void (async () => {
|
|
1660
1687
|
try {
|
|
1661
1688
|
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping);
|
|
@@ -1825,6 +1852,12 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1825
1852
|
}
|
|
1826
1853
|
});
|
|
1827
1854
|
registerSocketEndHandler(() => {
|
|
1855
|
+
if (tcTokenIndexTimer) {
|
|
1856
|
+
clearTimeout(tcTokenIndexTimer);
|
|
1857
|
+
tcTokenIndexTimer = undefined;
|
|
1858
|
+
}
|
|
1859
|
+
inFlightPreKeyLow.clear();
|
|
1860
|
+
inFlight463Recoveries.clear();
|
|
1828
1861
|
if (!config.msgRetryCounterCache && msgRetryCache.close) {
|
|
1829
1862
|
msgRetryCache.close();
|
|
1830
1863
|
}
|
|
@@ -2,7 +2,7 @@ import NodeCache from '@cacheable/node-cache';
|
|
|
2
2
|
import { Boom } from '@hapi/boom';
|
|
3
3
|
import { proto } from '../../WAProto/index.js';
|
|
4
4
|
import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
5
|
-
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateIOSMessageID, generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, uploadFile, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds, setBotMessageSecret } from '../Utils/index.js';
|
|
5
|
+
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateIOSMessageID, generateParticipantHashV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, uploadFile, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, printCharapeChatLog, unixTimestampSeconds, setBotMessageSecret } from '../Utils/index.js';
|
|
6
6
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
7
7
|
import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
|
|
8
8
|
import { getMessageReportingToken, shouldIncludeReportingToken } from '../Utils/reporting-utils.js';
|
|
@@ -24,6 +24,12 @@ export const makeMessagesSocket = (config) => {
|
|
|
24
24
|
* Entries are always removed in `.finally()`, so the set is bounded by concurrency.
|
|
25
25
|
*/
|
|
26
26
|
const inFlightTcTokenIssuance = new Set();
|
|
27
|
+
registerSocketEndHandler(() => {
|
|
28
|
+
inFlightTcTokenIssuance.clear();
|
|
29
|
+
if (!config.userDevicesCache && userDevicesCache?.close) {
|
|
30
|
+
userDevicesCache.close();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
27
33
|
const userDevicesCache = config.userDevicesCache ||
|
|
28
34
|
new NodeCache({
|
|
29
35
|
stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, // 5 minutes
|
|
@@ -467,19 +473,25 @@ export const makeMessagesSocket = (config) => {
|
|
|
467
473
|
}
|
|
468
474
|
const ctx = target.contextInfo;
|
|
469
475
|
ctx.isForwarded = true;
|
|
470
|
-
ctx.forwardingScore
|
|
476
|
+
if (typeof ctx.forwardingScore !== 'number' || ctx.forwardingScore <= 0) {
|
|
477
|
+
ctx.forwardingScore = 999;
|
|
478
|
+
}
|
|
471
479
|
|
|
472
480
|
const existingFwd = ctx.forwardedNewsletterMessageInfo || {};
|
|
481
|
+
const hasExplicitCustomNewsletter = typeof existingFwd.newsletterJid === 'string' && existingFwd.newsletterJid.endsWith('@newsletter') && existingFwd.newsletterJid !== '0@newsletter';
|
|
473
482
|
const sMsgId = (typeof existingFwd.serverMessageId === 'number' && existingFwd.serverMessageId > 0)
|
|
474
483
|
? existingFwd.serverMessageId
|
|
475
484
|
: targetServerMessageId;
|
|
476
485
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
486
|
+
if (!hasExplicitCustomNewsletter) {
|
|
487
|
+
ctx.forwardedNewsletterMessageInfo = {
|
|
488
|
+
newsletterJid: targetNewsletterJid,
|
|
489
|
+
newsletterName: existingFwd.newsletterName || targetNewsletterName,
|
|
490
|
+
serverMessageId: sMsgId,
|
|
491
|
+
contentType: existingFwd.contentType || 1,
|
|
492
|
+
accessibilityText: existingFwd.accessibilityText || targetNewsletterName
|
|
493
|
+
};
|
|
494
|
+
}
|
|
483
495
|
};
|
|
484
496
|
|
|
485
497
|
const defaultMediaAnnotations = [
|
|
@@ -492,7 +504,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
492
504
|
],
|
|
493
505
|
newsletter: {
|
|
494
506
|
newsletterJid: targetNewsletterJid,
|
|
495
|
-
serverMessageId:
|
|
507
|
+
serverMessageId: targetServerMessageId,
|
|
496
508
|
newsletterName: targetNewsletterName,
|
|
497
509
|
contentType: 'UPDATE'
|
|
498
510
|
}
|
|
@@ -519,121 +531,48 @@ export const makeMessagesSocket = (config) => {
|
|
|
519
531
|
applyToContext(im.header.imageMessage);
|
|
520
532
|
applyMediaAnnotations(im.header.imageMessage);
|
|
521
533
|
}
|
|
522
|
-
if (im.header.documentMessage)
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (!im.nativeFlowMessage) {
|
|
526
|
-
im.nativeFlowMessage = { buttons: [], messageParamsJson: '' };
|
|
527
|
-
}
|
|
528
|
-
if (!Array.isArray(im.nativeFlowMessage.buttons)) {
|
|
529
|
-
im.nativeFlowMessage.buttons = [];
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
const buttons = im.nativeFlowMessage.buttons;
|
|
533
|
-
let ctaIndex = buttons.findIndex(b => b && b.name === 'cta_url');
|
|
534
|
-
|
|
535
|
-
if (ctaIndex !== -1) {
|
|
536
|
-
try {
|
|
537
|
-
const ctaParams = JSON.parse(buttons[ctaIndex].buttonParamsJson || '{}');
|
|
538
|
-
ctaParams.url = targetChannelUrl;
|
|
539
|
-
ctaParams.merchant_url = targetChannelUrl;
|
|
540
|
-
buttons[ctaIndex].buttonParamsJson = JSON.stringify(ctaParams);
|
|
541
|
-
} catch (_) {}
|
|
542
|
-
} else if (buttons.length > 0 && buttons[0]) {
|
|
543
|
-
ctaIndex = 0;
|
|
544
|
-
try {
|
|
545
|
-
const btn0Params = JSON.parse(buttons[0].buttonParamsJson || '{}');
|
|
546
|
-
btn0Params.url = targetChannelUrl;
|
|
547
|
-
btn0Params.merchant_url = targetChannelUrl;
|
|
548
|
-
btn0Params.canonical_url = targetChannelUrl;
|
|
549
|
-
buttons[0].buttonParamsJson = JSON.stringify(btn0Params);
|
|
550
|
-
} catch (_) {}
|
|
551
|
-
} else {
|
|
552
|
-
ctaIndex = 0;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
let params = {};
|
|
556
|
-
if (im.nativeFlowMessage.messageParamsJson) {
|
|
557
|
-
try {
|
|
558
|
-
params = typeof im.nativeFlowMessage.messageParamsJson === 'string'
|
|
559
|
-
? JSON.parse(im.nativeFlowMessage.messageParamsJson)
|
|
560
|
-
: (im.nativeFlowMessage.messageParamsJson || {});
|
|
561
|
-
} catch (_) {
|
|
562
|
-
params = {};
|
|
534
|
+
if (im.header.documentMessage) {
|
|
535
|
+
applyToContext(im.header.documentMessage);
|
|
563
536
|
}
|
|
564
537
|
}
|
|
565
538
|
|
|
566
|
-
//
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
params.limited_time_offer.text = targetNewsletterName;
|
|
578
|
-
params.limited_time_offer.copy_code = targetChannelUrl;
|
|
579
|
-
} else {
|
|
580
|
-
params.limited_time_offer = {
|
|
581
|
-
text: targetNewsletterName,
|
|
582
|
-
url: targetChannelUrl,
|
|
583
|
-
copy_code: targetChannelUrl,
|
|
584
|
-
expiration_time: Date.now() + 2592000000
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
im.nativeFlowMessage.messageParamsJson = JSON.stringify(params);
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
// 1. groupStatusMessageV2 (Story / Status Grup)
|
|
592
|
-
if (msg.groupStatusMessageV2) {
|
|
593
|
-
const statusMsg = msg.groupStatusMessageV2;
|
|
594
|
-
const inner = statusMsg.message;
|
|
595
|
-
if (inner && typeof inner === 'object') {
|
|
596
|
-
const subTypes = [
|
|
597
|
-
'extendedTextMessage',
|
|
598
|
-
'imageMessage',
|
|
599
|
-
'videoMessage',
|
|
600
|
-
'audioMessage',
|
|
601
|
-
'documentMessage',
|
|
602
|
-
'protocolMessage'
|
|
603
|
-
];
|
|
604
|
-
let foundSub = false;
|
|
605
|
-
for (const type of subTypes) {
|
|
606
|
-
if (inner[type]) {
|
|
607
|
-
applyToContext(inner[type]);
|
|
608
|
-
if (type === 'imageMessage' || type === 'videoMessage') {
|
|
609
|
-
applyMediaAnnotations(inner[type]);
|
|
610
|
-
}
|
|
611
|
-
foundSub = true;
|
|
539
|
+
// Preserve caller buttons/CTA links untouched; only attach non-destructive
|
|
540
|
+
// tap target metadata when media attachment exists without explicit buttons or tap target.
|
|
541
|
+
if (im.header?.hasMediaAttachment && im.nativeFlowMessage && Array.isArray(im.nativeFlowMessage.buttons) && im.nativeFlowMessage.buttons.length === 0) {
|
|
542
|
+
let params = {};
|
|
543
|
+
if (im.nativeFlowMessage.messageParamsJson) {
|
|
544
|
+
try {
|
|
545
|
+
params = typeof im.nativeFlowMessage.messageParamsJson === 'string'
|
|
546
|
+
? JSON.parse(im.nativeFlowMessage.messageParamsJson)
|
|
547
|
+
: (im.nativeFlowMessage.messageParamsJson || {});
|
|
548
|
+
} catch {
|
|
549
|
+
params = {};
|
|
612
550
|
}
|
|
613
551
|
}
|
|
614
|
-
if (!
|
|
615
|
-
|
|
552
|
+
if (!params.tap_target_configuration) {
|
|
553
|
+
params.tap_target_configuration = {
|
|
554
|
+
title: targetNewsletterName,
|
|
555
|
+
description: 'Lihat Saluran Resmi',
|
|
556
|
+
canonical_url: targetChannelUrl,
|
|
557
|
+
domain: 'whatsapp.com',
|
|
558
|
+
button_index: 0
|
|
559
|
+
};
|
|
560
|
+
im.nativeFlowMessage.messageParamsJson = JSON.stringify(params);
|
|
616
561
|
}
|
|
617
562
|
}
|
|
618
|
-
|
|
619
|
-
}
|
|
563
|
+
};
|
|
620
564
|
|
|
621
|
-
//
|
|
565
|
+
// 1. Protocol / non-display messages
|
|
622
566
|
if (msg.protocolMessage || msg.reactionMessage || msg.pollUpdateMessage ||
|
|
623
567
|
msg.senderKeyDistributionMessage || msg.fastRatchetSenderKeyDistributionMessage ||
|
|
624
568
|
msg.keepInChatMessage || msg.call || msg.chat) {
|
|
625
569
|
return msg;
|
|
626
570
|
}
|
|
627
571
|
|
|
628
|
-
// 3. Direct interactiveMessage
|
|
629
|
-
if (msg.interactiveMessage) {
|
|
630
|
-
processInteractiveMessage(msg.interactiveMessage);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// 4. Direct media & text types
|
|
634
572
|
const directTypes = [
|
|
635
573
|
'imageMessage',
|
|
636
574
|
'videoMessage',
|
|
575
|
+
'ptvMessage',
|
|
637
576
|
'audioMessage',
|
|
638
577
|
'documentMessage',
|
|
639
578
|
'stickerMessage',
|
|
@@ -645,25 +584,39 @@ export const makeMessagesSocket = (config) => {
|
|
|
645
584
|
'eventMessage'
|
|
646
585
|
];
|
|
647
586
|
|
|
648
|
-
|
|
649
|
-
|
|
587
|
+
const applyAttributionToContainer = (container) => {
|
|
588
|
+
if (!container || typeof container !== 'object') return false;
|
|
589
|
+
let didInject = false;
|
|
590
|
+
if (container.interactiveMessage) {
|
|
591
|
+
processInteractiveMessage(container.interactiveMessage);
|
|
592
|
+
didInject = true;
|
|
593
|
+
}
|
|
594
|
+
for (const type of directTypes) {
|
|
595
|
+
if (container[type] && typeof container[type] === 'object') {
|
|
596
|
+
applyToContext(container[type]);
|
|
597
|
+
if (type === 'imageMessage' || type === 'videoMessage' || type === 'ptvMessage') {
|
|
598
|
+
applyMediaAnnotations(container[type]);
|
|
599
|
+
}
|
|
600
|
+
didInject = true;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return didInject;
|
|
604
|
+
};
|
|
650
605
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
606
|
+
// 2. groupStatusMessageV2 (Story / Status Grup)
|
|
607
|
+
if (msg.groupStatusMessageV2) {
|
|
608
|
+
const inner = msg.groupStatusMessageV2.message;
|
|
609
|
+
if (inner && typeof inner === 'object') {
|
|
610
|
+
const didInject = applyAttributionToContainer(inner);
|
|
611
|
+
if (!didInject && inner.contextInfo) {
|
|
612
|
+
applyToContext(inner);
|
|
656
613
|
}
|
|
657
|
-
injected = true;
|
|
658
614
|
}
|
|
659
|
-
|
|
660
|
-
if (msg.ptvMessage) {
|
|
661
|
-
applyToContext(msg.ptvMessage);
|
|
662
|
-
applyMediaAnnotations(msg.ptvMessage);
|
|
663
|
-
injected = true;
|
|
615
|
+
return msg;
|
|
664
616
|
}
|
|
665
617
|
|
|
666
|
-
//
|
|
618
|
+
// 3. Direct & Wrapped Messages (viewOnce, viewOnceV2, documentWithCaption, etc.)
|
|
619
|
+
let injected = applyAttributionToContainer(msg);
|
|
667
620
|
const unwrapTargets = [
|
|
668
621
|
msg.viewOnceMessage?.message,
|
|
669
622
|
msg.viewOnceMessageV2?.message,
|
|
@@ -672,42 +625,12 @@ export const makeMessagesSocket = (config) => {
|
|
|
672
625
|
].filter(Boolean);
|
|
673
626
|
|
|
674
627
|
for (const target of unwrapTargets) {
|
|
675
|
-
|
|
676
|
-
if (target.interactiveMessage) {
|
|
677
|
-
processInteractiveMessage(target.interactiveMessage);
|
|
678
|
-
injected = true;
|
|
679
|
-
}
|
|
680
|
-
if (target.imageMessage) {
|
|
681
|
-
applyToContext(target.imageMessage);
|
|
682
|
-
applyMediaAnnotations(target.imageMessage);
|
|
683
|
-
injected = true;
|
|
684
|
-
}
|
|
685
|
-
if (target.videoMessage) {
|
|
686
|
-
applyToContext(target.videoMessage);
|
|
687
|
-
applyMediaAnnotations(target.videoMessage);
|
|
688
|
-
injected = true;
|
|
689
|
-
}
|
|
690
|
-
if (target.ptvMessage) {
|
|
691
|
-
applyToContext(target.ptvMessage);
|
|
692
|
-
applyMediaAnnotations(target.ptvMessage);
|
|
693
|
-
injected = true;
|
|
694
|
-
}
|
|
695
|
-
if (target.documentMessage) {
|
|
696
|
-
applyToContext(target.documentMessage);
|
|
628
|
+
if (applyAttributionToContainer(target)) {
|
|
697
629
|
injected = true;
|
|
698
630
|
}
|
|
699
|
-
for (const type of directTypes) {
|
|
700
|
-
if (target[type]) {
|
|
701
|
-
applyToContext(target[type]);
|
|
702
|
-
if (type === 'imageMessage' || type === 'videoMessage') {
|
|
703
|
-
applyMediaAnnotations(target[type]);
|
|
704
|
-
}
|
|
705
|
-
injected = true;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
631
|
}
|
|
709
632
|
|
|
710
|
-
//
|
|
633
|
+
// 4. Plain text conversion to extendedTextMessage with channel attribution
|
|
711
634
|
if (!injected && msg.conversation && typeof msg.conversation === 'string') {
|
|
712
635
|
msg = {
|
|
713
636
|
extendedTextMessage: {
|
|
@@ -719,7 +642,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
719
642
|
newsletterJid: targetNewsletterJid,
|
|
720
643
|
newsletterName: targetNewsletterName,
|
|
721
644
|
serverMessageId: targetServerMessageId,
|
|
722
|
-
contentType: 1
|
|
645
|
+
contentType: 1,
|
|
646
|
+
accessibilityText: targetNewsletterName
|
|
723
647
|
}
|
|
724
648
|
}
|
|
725
649
|
}
|
|
@@ -1173,7 +1097,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
1173
1097
|
}
|
|
1174
1098
|
}
|
|
1175
1099
|
const isPeerMessage = additionalAttributes?.['category'] === 'peer'
|
|
1176
|
-
if (aiLabel && !isGroup && !isStatus && !isNewsletter && !isPeerMessage) {
|
|
1100
|
+
if (aiLabel === true && !isGroup && !isStatus && !isNewsletter && !isPeerMessage && (isJidMetaAI(destinationJid) || config.forceBizBotLabel === true)) {
|
|
1177
1101
|
const existingBizBot = getBinaryFilteredBizBot(additionalNodes || []) || getBinaryFilteredBizBot(stanza.content)
|
|
1178
1102
|
if (!existingBizBot) stanza.content.push({ tag: 'bot', attrs: { biz_bot: '1' } })
|
|
1179
1103
|
}
|
|
@@ -1761,10 +1685,15 @@ export const makeMessagesSocket = (config) => {
|
|
|
1761
1685
|
...(cstoken !== undefined ? { cstoken } : {})
|
|
1762
1686
|
});
|
|
1763
1687
|
if (config.emitOwnEvents) {
|
|
1764
|
-
process.nextTick(
|
|
1765
|
-
|
|
1688
|
+
process.nextTick(() => {
|
|
1689
|
+
Promise.resolve()
|
|
1690
|
+
.then(() => messageMutex.mutex(() => upsertMessage(fullMsg, 'append')))
|
|
1691
|
+
.catch(err => {
|
|
1692
|
+
logger?.error({ trace: err?.stack, msgId: fullMsg?.key?.id }, 'error in emitOwnEvents upsertMessage');
|
|
1693
|
+
});
|
|
1766
1694
|
});
|
|
1767
1695
|
}
|
|
1696
|
+
printCharapeChatLog('OUT', fullMsg, { enabled: config.charapeChatLog !== false });
|
|
1768
1697
|
return fullMsg;
|
|
1769
1698
|
}
|
|
1770
1699
|
},
|