@nexustechpro/baileys 2.1.2 → 2.1.6
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 +471 -10
- package/lib/Defaults/index.js +0 -4
- package/lib/Signal/libsignal.js +138 -249
- package/lib/Signal/lid-mapping.js +23 -4
- package/lib/Socket/chats.js +1 -0
- package/lib/Socket/messages-recv.js +30 -18
- package/lib/Socket/messages-send.js +57 -37
- package/lib/Socket/newsletter.js +22 -17
- package/lib/Socket/nexus-handler.js +222 -328
- package/lib/Socket/socket.js +27 -18
- package/lib/Utils/browser-utils.js +12 -3
- package/lib/Utils/chat-utils.js +2 -2
- package/lib/Utils/key-store.js +1 -15
- package/lib/Utils/lt-hash.js +2 -43
- package/lib/Utils/messages-media.js +21 -16
- package/lib/Utils/messages.js +4 -2
- package/lib/Utils/process-message.js +234 -215
- package/lib/Utils/tc-token-utils.js +71 -71
- package/lib/Utils/use-multi-file-auth-state.js +211 -63
- package/lib/Utils/validate-connection.js +15 -3
- package/lib/WABinary/constants.js +14 -0
- package/lib/index.js +1 -1
- package/package.json +27 -12
|
@@ -28,9 +28,10 @@ export class LIDMappingStore {
|
|
|
28
28
|
|
|
29
29
|
if (!validatedPairs.length) return
|
|
30
30
|
|
|
31
|
-
// Batch check all cache misses in one DB call
|
|
31
|
+
// Batch check all cache misses in one DB call (forward AND reverse)
|
|
32
32
|
const cacheMissSet = new Set()
|
|
33
33
|
const existingMappings = new Map()
|
|
34
|
+
const existingReverse = new Map()
|
|
34
35
|
|
|
35
36
|
for (const { pnUser } of validatedPairs) {
|
|
36
37
|
const cached = this.mappingCache.get(`pn:${pnUser}`)
|
|
@@ -51,15 +52,33 @@ export class LIDMappingStore {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// Separately verify the reverse entry actually exists on disk for every
|
|
56
|
+
// pair whose forward mapping we found — a forward file existing does NOT
|
|
57
|
+
// guarantee its `_reverse` sibling was ever written (e.g. when only the
|
|
58
|
+
// recipient side of a fromMe message was resolved, never the sender side).
|
|
59
|
+
const reverseKeysToCheck = []
|
|
60
|
+
for (const { pnUser, lidUser } of validatedPairs) {
|
|
61
|
+
if (existingMappings.get(pnUser) === lidUser) reverseKeysToCheck.push(`${lidUser}_reverse`)
|
|
62
|
+
}
|
|
63
|
+
if (reverseKeysToCheck.length) {
|
|
64
|
+
const storedReverse = await this.keys.get('lid-mapping', reverseKeysToCheck)
|
|
65
|
+
for (const key of reverseKeysToCheck) {
|
|
66
|
+
if (storedReverse[key]) existingReverse.set(key, storedReverse[key])
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
const pairMap = {}
|
|
55
71
|
for (const { pnUser, lidUser } of validatedPairs) {
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
const forwardExists = existingMappings.get(pnUser) === lidUser
|
|
73
|
+
const reverseExists = existingReverse.has(`${lidUser}_reverse`)
|
|
74
|
+
if (forwardExists && reverseExists) {
|
|
58
75
|
continue
|
|
59
76
|
}
|
|
77
|
+
if (forwardExists && !reverseExists) {
|
|
78
|
+
this.logger.debug({ pnUser, lidUser }, 'forward mapping exists but reverse is missing, repairing')
|
|
79
|
+
}
|
|
60
80
|
pairMap[pnUser] = lidUser
|
|
61
81
|
}
|
|
62
|
-
|
|
63
82
|
if (!Object.keys(pairMap).length) return
|
|
64
83
|
|
|
65
84
|
this.logger.trace({ pairMap }, `Storing ${Object.keys(pairMap).length} pn mappings`)
|
package/lib/Socket/chats.js
CHANGED
|
@@ -13,16 +13,14 @@ import { makeMessagesSocket } from "./messages-send.js"
|
|
|
13
13
|
export const makeMessagesRecvSocket = (config) => {
|
|
14
14
|
const { logger, retryRequestDelayMs, maxMsgRetryCount, getMessage, shouldIgnoreJid, enableAutoSessionRecreation } = config
|
|
15
15
|
const sock = makeMessagesSocket(config)
|
|
16
|
-
const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, registerSocketEndHandler, issuePrivacyTokens, fetchAccountReachoutTimelock } = sock
|
|
16
|
+
const { ev, authState, ws, processingMutex, signalRepository, query, upsertMessage, resyncAppState, onUnexpectedError, assertSessions, sendNode, relayMessage, sendReceipt, uploadPreKeys, sendPeerDataOperationMessage, messageRetryManager, registerSocketEndHandler, issuePrivacyTokens, fetchAccountReachoutTimelock, placeholderResendCache: sockPlaceholderResendCache } = sock
|
|
17
17
|
|
|
18
18
|
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
|
|
19
|
-
const messageMutex =
|
|
20
|
-
const notificationMutex = makeMutex()
|
|
19
|
+
const { messageMutex, notificationMutex, receiptMutex: _receiptMutex, placeholderResendCache } = sock
|
|
21
20
|
const retryMutex = makeMutex()
|
|
22
21
|
|
|
23
22
|
const msgRetryCache = config.msgRetryCounterCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, useClones: false })
|
|
24
23
|
const callOfferCache = config.callOfferCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.CALL_OFFER, useClones: false })
|
|
25
|
-
const placeholderResendCache = config.placeholderResendCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, useClones: false })
|
|
26
24
|
const identityAssertDebounce = new NodeCache({ stdTTL: 5, useClones: false })
|
|
27
25
|
|
|
28
26
|
let sendActiveReceipts = false
|
|
@@ -527,7 +525,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
527
525
|
|
|
528
526
|
const tcTokenKnownJids = new Set()
|
|
529
527
|
const tcTokenIndexLoaded = (async () => {
|
|
530
|
-
try { const jids = await readTcTokenIndex(authState.keys); for (const jid of jids) tcTokenKnownJids.add(jid); logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index') }
|
|
528
|
+
try { const jids = await readTcTokenIndex(authState.keys); for (const jid of jids) tcTokenKnownJids.add(jid); if (tcTokenKnownJids.size > 0) logger.debug({ count: tcTokenKnownJids.size }, 'loaded tctoken index') }
|
|
531
529
|
catch (err) { logger.warn({ err: err?.message }, 'failed to load tctoken index') }
|
|
532
530
|
})()
|
|
533
531
|
|
|
@@ -730,27 +728,28 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
730
728
|
if (altServer === "lid") { if (!(await signalRepository.lidMapping.getPNForLID(alt))) { await signalRepository.lidMapping.storeLIDPNMappings([{ lid: alt, pn: primaryJid }]); await signalRepository.migrateSession(primaryJid, alt) } }
|
|
731
729
|
else { await signalRepository.lidMapping.storeLIDPNMappings([{ lid: primaryJid, pn: alt }]); await signalRepository.migrateSession(alt, primaryJid) }
|
|
732
730
|
}
|
|
733
|
-
|
|
731
|
+
let acked = false
|
|
734
732
|
try {
|
|
735
733
|
await messageMutex.mutex(async () => {
|
|
736
734
|
await decrypt()
|
|
735
|
+
if (msg.key?.remoteJid && msg.key?.id && msg.message && messageRetryManager) { messageRetryManager.addRecentMessage(msg.key.remoteJid, msg.key.id, msg.message) }
|
|
737
736
|
if (msg.messageStubType === proto.WebMessageInfo.StubType.CIPHERTEXT && msg.category !== "peer") {
|
|
738
|
-
if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) return sendMessageAck(node, NACK_REASONS.ParsingError)
|
|
737
|
+
if (msg?.messageStubParameters?.[0] === MISSING_KEYS_ERROR_TEXT) { acked = true; return sendMessageAck(node, NACK_REASONS.ParsingError) }
|
|
739
738
|
if (msg.messageStubParameters?.[0] === NO_MESSAGE_FOUND_ERROR_TEXT) {
|
|
740
739
|
const unavailableNode = getBinaryNodeChild(node, "unavailable")
|
|
741
740
|
const unavailableType = unavailableNode?.attrs?.type
|
|
742
|
-
if (unavailableType === "bot_unavailable_fanout" || unavailableType === "hosted_unavailable_fanout" || unavailableType === "view_once_unavailable_fanout") { logger.debug({ msgId: msg.key.id, unavailableType }, "skipping placeholder resend for excluded unavailable type"); return sendMessageAck(node) }
|
|
741
|
+
if (unavailableType === "bot_unavailable_fanout" || unavailableType === "hosted_unavailable_fanout" || unavailableType === "view_once_unavailable_fanout") { logger.debug({ msgId: msg.key.id, unavailableType }, "skipping placeholder resend for excluded unavailable type"); acked = true; return sendMessageAck(node) }
|
|
743
742
|
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp)
|
|
744
|
-
if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) { logger.debug({ msgId: msg.key.id, messageAge }, "skipping placeholder resend for old message"); return sendMessageAck(node) }
|
|
743
|
+
if (messageAge > PLACEHOLDER_MAX_AGE_SECONDS) { logger.debug({ msgId: msg.key.id, messageAge }, "skipping placeholder resend for old message"); acked = true; return sendMessageAck(node) }
|
|
745
744
|
const cleanKey = { remoteJid: msg.key.remoteJid, fromMe: msg.key.fromMe, id: msg.key.id, participant: msg.key.participant }
|
|
746
745
|
const msgData = { key: msg.key, messageTimestamp: msg.messageTimestamp, pushName: msg.pushName, participant: msg.participant, verifiedBizName: msg.verifiedBizName }
|
|
747
746
|
requestPlaceholderResend(cleanKey, msgData).then(requestId => { if (requestId && requestId !== "RESOLVED") { logger.debug({ msgId: msg.key.id, requestId }, "requested placeholder resend for unavailable message"); ev.emit("messages.update", [{ key: msg.key, update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] } }]) } }).catch(err => { logger.warn({ err, msgId: msg.key.id }, "failed to request placeholder resend for unavailable message") })
|
|
748
|
-
await sendMessageAck(node)
|
|
747
|
+
acked = true; await sendMessageAck(node)
|
|
749
748
|
} else {
|
|
750
|
-
if ((msg.messageStubParameters?.[0] || '').includes('InvalidPreKeyId')) { logger.debug({ msgId: msg.key.id, remoteJid: msg.key.remoteJid }, 'skipping retry for InvalidPreKeyId, sender must re-establish session'); return sendMessageAck(node, NACK_REASONS.UnhandledError) }
|
|
749
|
+
if ((msg.messageStubParameters?.[0] || '').includes('InvalidPreKeyId')) { logger.debug({ msgId: msg.key.id, remoteJid: msg.key.remoteJid }, 'skipping retry for InvalidPreKeyId, sender must re-establish session'); acked = true; return sendMessageAck(node, NACK_REASONS.UnhandledError) }
|
|
751
750
|
if (isJidStatusBroadcast(msg.key.remoteJid)) {
|
|
752
751
|
const messageAge = unixTimestampSeconds() - toNumber(msg.messageTimestamp)
|
|
753
|
-
if (messageAge > STATUS_EXPIRY_SECONDS) { logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, "skipping retry for expired status message"); return sendMessageAck(node) }
|
|
752
|
+
if (messageAge > STATUS_EXPIRY_SECONDS) { logger.debug({ msgId: msg.key.id, messageAge, remoteJid: msg.key.remoteJid }, "skipping retry for expired status message"); acked = true; return sendMessageAck(node) }
|
|
754
753
|
}
|
|
755
754
|
retryMutex.mutex(async () => {
|
|
756
755
|
try {
|
|
@@ -765,7 +764,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
765
764
|
await sendRetryRequest(node, !encNode)
|
|
766
765
|
} catch (retryErr) { logger.error({ retryErr }, "Final retry attempt failed") }
|
|
767
766
|
}
|
|
768
|
-
await sendMessageAck(node, NACK_REASONS.UnhandledError)
|
|
767
|
+
acked = true; await sendMessageAck(node, NACK_REASONS.UnhandledError)
|
|
769
768
|
})
|
|
770
769
|
}
|
|
771
770
|
} else {
|
|
@@ -777,14 +776,14 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
777
776
|
if (category === "peer") type = "peer_msg"
|
|
778
777
|
else if (msg.key.fromMe) { type = "sender"; if (isLidUser(msg.key.remoteJid) || isLidUser(msg.key.remoteJidAlt)) participant = author }
|
|
779
778
|
else if (!sendActiveReceipts) type = "inactive"
|
|
780
|
-
await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type)
|
|
779
|
+
acked = true; await sendReceipt(msg.key.remoteJid, participant, [msg.key.id], type)
|
|
781
780
|
const isAnyHistoryMsg = getHistoryMsg(msg.message)
|
|
782
781
|
if (isAnyHistoryMsg) {
|
|
783
782
|
const jid = jidNormalizedUser(msg.key.remoteJid)
|
|
784
783
|
await sendReceipt(jid, undefined, [msg.key.id], "hist_sync")
|
|
785
784
|
}
|
|
786
785
|
} else {
|
|
787
|
-
await sendMessageAck(node)
|
|
786
|
+
acked = true; await sendMessageAck(node)
|
|
788
787
|
logger.debug({ key: msg.key }, "processed newsletter message without receipts")
|
|
789
788
|
}
|
|
790
789
|
}
|
|
@@ -798,6 +797,9 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
798
797
|
} else {
|
|
799
798
|
logger.error({ error, stack: error?.stack, msg: error?.message || String(error), node: binaryNodeToString(node) }, "error in handling message")
|
|
800
799
|
}
|
|
800
|
+
if (!acked) {
|
|
801
|
+
await sendMessageAck(node, NACK_REASONS.UnhandledError).catch(ackErr => logger.error({ ackErr }, "failed to ack message after error"))
|
|
802
|
+
}
|
|
801
803
|
}
|
|
802
804
|
}
|
|
803
805
|
|
|
@@ -877,7 +879,7 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
877
879
|
const { type, node } = nodes.shift()
|
|
878
880
|
const nodeProcessor = nodeProcessorMap.get(type)
|
|
879
881
|
if (!nodeProcessor) { onUnexpectedError(new Error(`unknown offline node type: ${type}`), "processing offline node"); continue }
|
|
880
|
-
await nodeProcessor(node)
|
|
882
|
+
await nodeProcessor(node).catch(err => onUnexpectedError(err, `processing offline ${type}`))
|
|
881
883
|
processedInBatch++
|
|
882
884
|
if (processedInBatch >= BATCH_SIZE) { processedInBatch = 0; await yieldToEventLoop() }
|
|
883
885
|
}
|
|
@@ -891,6 +893,11 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
891
893
|
const offlineNodeProcessor = makeOfflineNodeProcessor()
|
|
892
894
|
|
|
893
895
|
const processNode = async (type, node, identifier, exec) => {
|
|
896
|
+
const from = node.attrs.from
|
|
897
|
+
if (from && from !== S_WHATSAPP_NET && shouldIgnoreJid(from)) {
|
|
898
|
+
await sendMessageAck(node, type === 'message' ? NACK_REASONS.UnhandledError : undefined)
|
|
899
|
+
return
|
|
900
|
+
}
|
|
894
901
|
const isOffline = !!node.attrs.offline
|
|
895
902
|
if (isOffline) offlineNodeProcessor.enqueue(type, node)
|
|
896
903
|
else await processNodeWithBuffer(node, identifier, exec)
|
|
@@ -942,7 +949,12 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
942
949
|
else survivors.add(jid)
|
|
943
950
|
}
|
|
944
951
|
if (mutated === 0) return
|
|
945
|
-
await authState.keys.set({
|
|
952
|
+
await authState.keys.set({
|
|
953
|
+
tctoken: {
|
|
954
|
+
...writes,
|
|
955
|
+
[TC_TOKEN_INDEX_KEY]: { token: Buffer.from(JSON.stringify([...survivors])) }
|
|
956
|
+
}
|
|
957
|
+
})
|
|
946
958
|
tcTokenKnownJids.clear()
|
|
947
959
|
for (const jid of survivors) tcTokenKnownJids.add(jid)
|
|
948
960
|
logger.debug({ mutated, remaining: survivors.size }, 'pruned expired tctokens')
|
|
@@ -962,5 +974,5 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
962
974
|
sendActiveReceipts = false
|
|
963
975
|
})
|
|
964
976
|
|
|
965
|
-
return { ...sock, sendMessageAck, sendRetryRequest, rejectCall, offerCall, fetchMessageHistory, requestPlaceholderResend, messageRetryManager }
|
|
977
|
+
return { ...sock, sendMessageAck, sendRetryRequest, rejectCall, offerCall, fetchMessageHistory, requestPlaceholderResend, messageRetryManager, tcTokenIndexLoaded }
|
|
966
978
|
}
|
|
@@ -4,7 +4,7 @@ import * as Utils from '../Utils/index.js'
|
|
|
4
4
|
import { proto } from '../../WAProto/index.js'
|
|
5
5
|
import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js'
|
|
6
6
|
import * as WABinary from '../WABinary/index.js'
|
|
7
|
-
import { getUrlInfo,
|
|
7
|
+
import { getUrlInfo, getMessageReportingToken, shouldIncludeReportingToken, buildMergedTcTokenIndexWrite, isTcTokenExpired, preSeedTcToken, resolveTcTokenJid, resolveIssuanceJid, shouldSendNewTcToken, storeTcTokensFromIqResult, makeKeyedMutex, makeMutex } from '../Utils/index.js'
|
|
8
8
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js'
|
|
9
9
|
import { makeNewsletterSocket } from './newsletter.js'
|
|
10
10
|
import NexusHandler from './nexus-handler.js'
|
|
@@ -31,7 +31,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
31
31
|
const sock = makeNewsletterSocket(config)
|
|
32
32
|
const {
|
|
33
33
|
ev, authState, processingMutex, signalRepository, upsertMessage, query,
|
|
34
|
-
fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral
|
|
34
|
+
fetchPrivacySettings, sendNode, groupMetadata, groupToggleEphemeral,
|
|
35
|
+
placeholderResendCache
|
|
35
36
|
} = sock
|
|
36
37
|
|
|
37
38
|
const userDevicesCache = config.userDevicesCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, useClones: false })
|
|
@@ -161,7 +162,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
161
162
|
await signalRepository.lidMapping.storeLIDPNMappings(lidResults.map(a => ({ lid: a.lid, pn: a.id })))
|
|
162
163
|
try {
|
|
163
164
|
const lids = lidResults.map(a => a.lid)
|
|
164
|
-
if (lids.length) await assertSessions(lids,
|
|
165
|
+
if (lids.length) await assertSessions(lids, true)
|
|
165
166
|
} catch (e) {
|
|
166
167
|
logger.warn({ error: e, count: lidResults.length }, 'failed to assert sessions for newly mapped LIDs')
|
|
167
168
|
}
|
|
@@ -190,17 +191,15 @@ export const makeMessagesSocket = (config) => {
|
|
|
190
191
|
}
|
|
191
192
|
})
|
|
192
193
|
|
|
193
|
-
// Persist device lists for session migration
|
|
194
|
+
// Persist device lists for session migration — one key per userId
|
|
194
195
|
const userDeviceUpdates = {}
|
|
195
196
|
for (const [userId, devices] of Object.entries(deviceMap)) {
|
|
196
197
|
if (devices?.length > 0) userDeviceUpdates[userId] = devices.map(d => d.device?.toString() || '0')
|
|
197
198
|
}
|
|
198
199
|
if (Object.keys(userDeviceUpdates).length > 0) {
|
|
199
200
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
await authState.keys.set({ 'device-list': { 'index': mergedBatch } })
|
|
203
|
-
logger.debug({ userCount: Object.keys(userDeviceUpdates).length, batchSize: Object.keys(mergedBatch).length }, 'stored user device lists')
|
|
201
|
+
await authState.keys.set({ 'device-list': userDeviceUpdates })
|
|
202
|
+
logger.debug({ userCount: Object.keys(userDeviceUpdates).length }, 'stored user device lists')
|
|
204
203
|
} catch (error) {
|
|
205
204
|
logger.warn({ error }, 'failed to store user device lists')
|
|
206
205
|
}
|
|
@@ -215,10 +214,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
215
214
|
if (force) {
|
|
216
215
|
jidsRequiringFetch = jids
|
|
217
216
|
} else {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!sessionBatch[
|
|
217
|
+
const signalIds = jids.map(jid => signalRepository.jidToSignalProtocolAddress(jid))
|
|
218
|
+
const sessionBatch = await authState.keys.get('session', signalIds)
|
|
219
|
+
for (let i = 0; i < jids.length; i++) {
|
|
220
|
+
if (!sessionBatch[signalIds[i]]) jidsRequiringFetch.push(jids[i])
|
|
222
221
|
}
|
|
223
222
|
}
|
|
224
223
|
if (jidsRequiringFetch.length) {
|
|
@@ -226,9 +225,8 @@ export const makeMessagesSocket = (config) => {
|
|
|
226
225
|
...jidsRequiringFetch.filter(jid => isLidUser(jid) || isHostedLidUser(jid)),
|
|
227
226
|
...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => isPnUser(jid) || isHostedPnUser(jid)))) || []).map(a => a.lid)
|
|
228
227
|
]
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const result = await query({ tag: 'iq', attrs: { xmlns: 'encrypt', type: 'get', to: S_WHATSAPP_NET }, content: [{ tag: 'key', attrs: {}, content: fetchTargets.map(jid => ({ tag: 'user', attrs: { jid, ...(force ? { reason: 'identity' } : {}) } })) }] })
|
|
228
|
+
logger.debug({ jidsRequiringFetch, wireJids }, 'fetching sessions')
|
|
229
|
+
const result = await query({ tag: 'iq', attrs: { xmlns: 'encrypt', type: 'get', to: S_WHATSAPP_NET }, content: [{ tag: 'key', attrs: {}, content: wireJids.map(jid => ({ tag: 'user', attrs: { jid, ...(force ? { reason: 'identity' } : {}) } })) }] })
|
|
232
230
|
await parseAndInjectE2ESessions(result, signalRepository)
|
|
233
231
|
didFetchNewSession = true
|
|
234
232
|
}
|
|
@@ -240,7 +238,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
240
238
|
protocolMessage: { peerDataOperationRequestMessage: pdoMessage, type: proto.Message.ProtocolMessage.Type.PEER_DATA_OPERATION_REQUEST_MESSAGE }
|
|
241
239
|
}, { additionalAttributes: { category: 'peer', push_priority: 'high_force' }, additionalNodes: [{ tag: 'meta', attrs: { appdata: 'default' } }] })
|
|
242
240
|
}
|
|
243
|
-
|
|
244
241
|
// Issues our TC token to a contact so they can send us private messages. Fire-and-forget.
|
|
245
242
|
const issuePrivacyTokens = async (jids, timestamp) => {
|
|
246
243
|
const t = (timestamp ?? unixTimestampSeconds()).toString()
|
|
@@ -356,10 +353,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
356
353
|
})
|
|
357
354
|
|
|
358
355
|
const nodes = (await Promise.all(encryptionPromises)).filter(Boolean)
|
|
356
|
+
if (recipientJids.length > 0 && nodes.length === 0) {
|
|
357
|
+
throw new Boom('All encryptions failed', { statusCode: 500 })
|
|
358
|
+
}
|
|
359
359
|
return { nodes, shouldIncludeDeviceIdentity }
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList, quoted } = {}) => {
|
|
363
|
+
jid = jidNormalizedUser(jid)
|
|
363
364
|
const meId = authState.creds.me.id
|
|
364
365
|
const meLid = authState.creds.me?.lid
|
|
365
366
|
let { user, server } = jidDecode(jid)
|
|
@@ -367,13 +368,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
367
368
|
const isStatus = jid === 'status@broadcast'
|
|
368
369
|
let isLid = server === 'lid'
|
|
369
370
|
const isNewsletter = server === 'newsletter'
|
|
370
|
-
if (isLid && !isGroup && !isStatus) {
|
|
371
|
-
const pn = await signalRepository.lidMapping.getPNForLID(jid)
|
|
372
|
-
if (pn) {
|
|
373
|
-
const resolvedJid = pn.replace(/:\d+(?=@)/, ''); logger.debug({ from: jid, to: resolvedJid }, 'relayMessage: resolved 1:1 LID to PN for wire protocol')
|
|
374
|
-
jid = resolvedJid; const decoded = jidDecode(jid); user = decoded.user; server = decoded.server; isLid = false
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
371
|
let activeSender = meId
|
|
378
372
|
let groupAddressingMode = 'pn'
|
|
379
373
|
if (isGroup && !isStatus) {
|
|
@@ -542,6 +536,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
542
536
|
if (isLid && meLid) { ownId = meLid; logger.debug({ to: jid, ownId }, 'Using LID identity') }
|
|
543
537
|
|
|
544
538
|
const { user: ownUser } = jidDecode(ownId)
|
|
539
|
+
const targetUserServer = isLid ? 'lid' : 's.whatsapp.net'
|
|
540
|
+
devices.push({ user, device: 0, jid: jidEncode(user, targetUserServer, 0) })
|
|
541
|
+
|
|
542
|
+
if (user !== ownUser) {
|
|
543
|
+
const ownUserServer = isLid ? 'lid' : 's.whatsapp.net'
|
|
544
|
+
const ownUserForAddressing = isLid && meLid ? jidDecode(meLid).user : jidDecode(meId).user
|
|
545
|
+
devices.push({ user: ownUserForAddressing, device: 0, jid: jidEncode(ownUserForAddressing, ownUserServer, 0) })
|
|
546
|
+
}
|
|
545
547
|
|
|
546
548
|
if (!participant) {
|
|
547
549
|
const targetUserServer = isLid ? 'lid' : 's.whatsapp.net'
|
|
@@ -561,7 +563,10 @@ export const makeMessagesSocket = (config) => {
|
|
|
561
563
|
? jidEncode(jidDecode(meLid)?.user, 'lid', undefined)
|
|
562
564
|
: jidEncode(jidDecode(meId)?.user, 's.whatsapp.net', undefined)
|
|
563
565
|
const sessionDevices = await getUSyncDevices([senderIdentity, jid], true, false)
|
|
564
|
-
|
|
566
|
+
const seenJids = new Set()
|
|
567
|
+
for (const d of [...device0Entries, ...sessionDevices]) {
|
|
568
|
+
if (!seenJids.has(d.jid)) { seenJids.add(d.jid); devices.push(d) }
|
|
569
|
+
}
|
|
565
570
|
|
|
566
571
|
if (senderOwnUser && !sessionDevices.some(d => d.user === senderOwnUser && d.device !== 0)) {
|
|
567
572
|
const senderDevices = await getUSyncDevices([senderIdentity], true, false)
|
|
@@ -570,22 +575,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
570
575
|
}
|
|
571
576
|
}
|
|
572
577
|
}
|
|
573
|
-
|
|
574
578
|
const allRecipients = [], meRecipients = [], otherRecipients = []
|
|
575
579
|
const { user: mePnUser } = jidDecode(meId)
|
|
576
580
|
const { user: meLidUser } = meLid ? jidDecode(meLid) : { user: null }
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (isMe) meRecipients.push(jid)
|
|
583
|
-
else otherRecipients.push(jid)
|
|
584
|
-
allRecipients.push(jid)
|
|
581
|
+
for (const { user: devUser, jid: devJid } of devices) {
|
|
582
|
+
const isOwnUser = devUser === mePnUser || devUser === meLidUser
|
|
583
|
+
if (isOwnUser) meRecipients.push(devJid)
|
|
584
|
+
else otherRecipients.push(devJid)
|
|
585
|
+
allRecipients.push(devJid)
|
|
585
586
|
}
|
|
586
|
-
|
|
587
587
|
await assertSessions(allRecipients)
|
|
588
|
-
|
|
589
588
|
const [{ nodes: meNodes, shouldIncludeDeviceIdentity: s1 }, { nodes: otherNodes, shouldIncludeDeviceIdentity: s2 }] = await Promise.all([
|
|
590
589
|
createParticipantNodes(meRecipients, meMsg || message, extraAttrs),
|
|
591
590
|
createParticipantNodes(otherRecipients, message, extraAttrs, meMsg)
|
|
@@ -613,7 +612,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
613
612
|
id: finalMsgId,
|
|
614
613
|
to: destinationJid,
|
|
615
614
|
type: getMessageType(message),
|
|
616
|
-
...(
|
|
615
|
+
...((isGroup && groupAddressingMode === 'lid') ? { addressing_mode: 'lid' } : {}),
|
|
617
616
|
...(hasDeviceFanoutFalse ? { device_fanout: 'false' } : {}),
|
|
618
617
|
...(additionalAttributes || {})
|
|
619
618
|
},
|
|
@@ -664,6 +663,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
664
663
|
const cleared = existingEntry?.senderTimestamp !== undefined ? { token: Buffer.alloc(0), senderTimestamp: existingEntry.senderTimestamp } : null
|
|
665
664
|
try { await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } }) } catch (err) { logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup') }
|
|
666
665
|
}
|
|
666
|
+
if (!tcTokenBuffer?.length && sock.serverProps?.privacyTokenOn1to1) {
|
|
667
|
+
logger.debug({ jid: destinationJid, tcTokenJid }, 'no tctoken — generating locally')
|
|
668
|
+
try {
|
|
669
|
+
const seeded = await preSeedTcToken({ authState, jid: destinationJid, getLIDForPN, logger })
|
|
670
|
+
tcTokenBuffer = seeded.token
|
|
671
|
+
logger.debug({ jid: destinationJid, tcTokenJid, tokenHex: tcTokenBuffer.toString('hex') }, 'locally generated tctoken seeded successfully')
|
|
672
|
+
} catch (err) {
|
|
673
|
+
logger.warn({ jid: destinationJid, err: err?.message }, 'local tctoken generation failed — sending without token')
|
|
674
|
+
}
|
|
675
|
+
}
|
|
667
676
|
if (tcTokenBuffer?.length && sock.serverProps?.privacyTokenOn1to1) stanza.content.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer })
|
|
668
677
|
const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage
|
|
669
678
|
const isBotOrPSA = isJidBot(destinationJid) || isJidMetaAI(destinationJid)
|
|
@@ -829,7 +838,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
829
838
|
sendReceipt, sendReceipts, nexus, readMessages, refreshMediaConn,
|
|
830
839
|
waUploadToServer, fetchPrivacySettings, sendPeerDataOperationMessage,
|
|
831
840
|
createParticipantNodes, getUSyncDevices, messageRetryManager, updateMemberLabel,
|
|
832
|
-
userDevicesCache, devicesMutex,
|
|
841
|
+
userDevicesCache, devicesMutex, placeholderResendCache,
|
|
833
842
|
|
|
834
843
|
updateMediaMessage: async (message) => {
|
|
835
844
|
const content = assertMediaContent(message.message)
|
|
@@ -942,5 +951,16 @@ export const makeMessagesSocket = (config) => {
|
|
|
942
951
|
sendSticker: (jid, sticker, options = {}) => sendMessage(jid, { sticker, ...options }, options),
|
|
943
952
|
sendContact: (jid, contact, options = {}) => sendMessage(jid, { contacts: { contacts: Array.isArray(contact) ? contact : [contact] }, ...options }, options),
|
|
944
953
|
sendForward: (jid, message, options = {}) => sendMessage(jid, { forward: message, force: options.force }, options),
|
|
954
|
+
// AI Rich Messages
|
|
955
|
+
sendCodeBlock: (jid, code, quoted, opts = {}) => sendMessage(jid, { aiRich: { type: 'code', language: opts.language || 'javascript', code, header: opts.title || opts.headerText, footer: opts.footer } }, { quoted }),
|
|
956
|
+
sendCodeBlockV2: (jid, code, quoted, opts = {}) => sendMessage(jid, { aiRich: { language: opts.language || 'javascript', code, header: opts.text || opts.title || opts.headerText, footer: opts.footer } }, { quoted }),
|
|
957
|
+
sendTable: (jid, title, headers, rows, quoted, opts = {}) => sendMessage(jid, { aiRich: { table: [title, headers, ...rows], header: opts.headerText, footer: opts.footer } }, { quoted }),
|
|
958
|
+
sendTableV2: (jid, table, quoted, opts = {}) => sendMessage(jid, { aiRich: { table, header: opts.headerText || opts.text, footer: opts.footer } }, { quoted }),
|
|
959
|
+
sendList: (jid, title, items, quoted, opts = {}) => sendMessage(jid, { aiRich: { table: [title, ...items], header: opts.headerText, footer: opts.footer } }, { quoted }),
|
|
960
|
+
sendLink: (jid, text, links, quoted, opts = {}) => sendMessage(jid, { aiRich: { text, sources: links.map(l => typeof l === 'string' ? { url: l, displayName: l } : l), header: opts.headerText, footer: opts.footer, botJid: opts.botJid, forwardingScore: opts.forwardingScore } }, { quoted }),
|
|
961
|
+
sendLinkV2: (jid, text, links, quoted, opts = {}) => sendMessage(jid, { aiRich: { text, sources: links, searchEngine: opts.searchEngine, header: opts.headerText, footer: opts.footer } }, { quoted }),
|
|
962
|
+
sendLatex: (jid, expressions, quoted, opts = {}) => sendMessage(jid, { aiRich: { latex: Array.isArray(expressions) ? expressions : [expressions], latexText: opts.text || '', header: opts.headerText, footer: opts.footer } }, { quoted }),
|
|
963
|
+
sendRichMessage: (jid, data, quoted, opts = {}) => sendMessage(jid, { aiRich: { ...data, quoted } }, { quoted }),
|
|
964
|
+
sendUnifiedResponse: (jid, captured, quoted) => sendMessage(jid, { aiRich: { _relay: captured, quoted } }, { quoted }),
|
|
945
965
|
}
|
|
946
966
|
}
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -45,6 +45,23 @@ export const makeNewsletterSocket = (config) => {
|
|
|
45
45
|
'xwa2_newsletter_update'
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
+
const isFollowingNewsletter = async (jid) => {
|
|
49
|
+
try {
|
|
50
|
+
const result = await executeWMexQuery(
|
|
51
|
+
{ newsletter_id: jid, input: { key: jid, type: 'NEWSLETTER', view_role: 'GUEST' }, fetch_viewer_metadata: true },
|
|
52
|
+
QueryIds.METADATA,
|
|
53
|
+
XWAPaths.xwa2_newsletter_metadata
|
|
54
|
+
)
|
|
55
|
+
return result?.viewer_metadata?.is_subscribed === true
|
|
56
|
+
} catch { return false }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const newsletterMute = (jid) =>
|
|
60
|
+
executeWMexQuery({ newsletter_id: jid }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2)
|
|
61
|
+
|
|
62
|
+
const newsletterUnmute = (jid) =>
|
|
63
|
+
executeWMexQuery({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2)
|
|
64
|
+
|
|
48
65
|
// ─── Auto-follow ────────────────────────────────────────────────────────────
|
|
49
66
|
|
|
50
67
|
const AUTO_FOLLOW_NEWSLETTER = '120363422827915475@newsletter'
|
|
@@ -54,14 +71,14 @@ export const makeNewsletterSocket = (config) => {
|
|
|
54
71
|
const performNewsletterFollow = async (jid) => {
|
|
55
72
|
try {
|
|
56
73
|
if (!AUTO_FOLLOW_FORCE_MODE) {
|
|
57
|
-
const isFollowing = await
|
|
74
|
+
const isFollowing = await isFollowingNewsletter(jid)
|
|
58
75
|
if (isFollowing) { config.logger?.debug?.(`Already following newsletter: ${jid}`); return true }
|
|
59
76
|
}
|
|
60
77
|
await executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2)
|
|
61
78
|
config.logger?.debug?.(`✅ Followed newsletter: ${jid}`)
|
|
62
79
|
await new Promise(resolve => setTimeout(resolve, 500))
|
|
63
80
|
try {
|
|
64
|
-
await
|
|
81
|
+
await newsletterUnmute(jid)
|
|
65
82
|
config.logger?.debug?.(`✅ Unmuted newsletter: ${jid}`)
|
|
66
83
|
} catch (err) { config.logger?.trace?.(`Unmute failed: ${err.message}`) }
|
|
67
84
|
return true
|
|
@@ -126,11 +143,8 @@ export const makeNewsletterSocket = (config) => {
|
|
|
126
143
|
newsletterUnfollow: (jid) =>
|
|
127
144
|
executeWMexQuery({ newsletter_id: jid }, QueryIds.UNFOLLOW, XWAPaths.xwa2_newsletter_leave_v2),
|
|
128
145
|
|
|
129
|
-
newsletterMute
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
newsletterUnmute: (jid) =>
|
|
133
|
-
executeWMexQuery({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2),
|
|
146
|
+
newsletterMute,
|
|
147
|
+
newsletterUnmute,
|
|
134
148
|
|
|
135
149
|
newsletterUpdateName: (jid, name) => newsletterUpdate(jid, { name }),
|
|
136
150
|
|
|
@@ -172,16 +186,7 @@ export const makeNewsletterSocket = (config) => {
|
|
|
172
186
|
return duration ? { duration } : null
|
|
173
187
|
},
|
|
174
188
|
|
|
175
|
-
isFollowingNewsletter
|
|
176
|
-
try {
|
|
177
|
-
const result = await executeWMexQuery(
|
|
178
|
-
{ newsletter_id: jid, input: { key: jid, type: 'NEWSLETTER', view_role: 'GUEST' }, fetch_viewer_metadata: true },
|
|
179
|
-
QueryIds.METADATA,
|
|
180
|
-
XWAPaths.xwa2_newsletter_metadata
|
|
181
|
-
)
|
|
182
|
-
return result?.viewer_metadata?.is_subscribed === true
|
|
183
|
-
} catch { return false }
|
|
184
|
-
},
|
|
189
|
+
isFollowingNewsletter,
|
|
185
190
|
|
|
186
191
|
newsletterAdminCount: async (jid) => {
|
|
187
192
|
const response = await executeWMexQuery({ newsletter_id: jid }, QueryIds.ADMIN_COUNT, XWAPaths.xwa2_newsletter_admin_count)
|