@nexustechpro/baileys 2.0.5 → 2.1.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 +71 -0
- package/WAProto/index.js +22 -18
- package/lib/Defaults/baileys-version.json +1 -1
- package/lib/Defaults/index.js +9 -8
- package/lib/Signal/libsignal.js +551 -342
- package/lib/Socket/chats.js +83 -65
- package/lib/Socket/index.js +2 -3
- package/lib/Socket/messages-recv.js +227 -41
- package/lib/Socket/messages-send.js +97 -117
- package/lib/Socket/newsletter.js +87 -36
- package/lib/Socket/nexus-handler.js +326 -89
- package/lib/Socket/registration.js +50 -33
- package/lib/Socket/socket.js +245 -69
- package/lib/Store/make-in-memory-store.js +29 -20
- package/lib/Types/Newsletter.js +37 -29
- package/lib/Types/State.js +43 -0
- package/lib/Utils/auth-utils.js +2 -2
- package/lib/Utils/chat-utils.js +48 -16
- package/lib/Utils/companion-reg-client-utils.js +34 -0
- package/lib/Utils/decode-wa-message.js +39 -22
- package/lib/Utils/generics.js +5 -7
- package/lib/Utils/index.js +4 -0
- package/lib/Utils/key-store.js +1 -1
- package/lib/Utils/link-preview.js +134 -61
- package/lib/Utils/messages-media.js +496 -381
- package/lib/Utils/messages.js +699 -706
- package/lib/Utils/process-message.js +53 -35
- package/lib/Utils/reporting-utils.js +155 -0
- package/lib/Utils/signal.js +134 -104
- package/lib/Utils/sync-action-utils.js +33 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/Utils/use-multi-file-auth-state.js +174 -91
- package/lib/WABinary/constants.js +6 -0
- package/lib/WABinary/index.js +1 -0
- package/lib/index.js +2 -3
- package/package.json +13 -9
|
@@ -4,8 +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, migrateIndexKey } from '../Utils/index.js'
|
|
8
|
-
import { makeKeyedMutex } from '../Utils/make-mutex.js'
|
|
7
|
+
import { getUrlInfo, migrateIndexKey, getMessageReportingToken, shouldIncludeReportingToken, buildMergedTcTokenIndexWrite, isTcTokenExpired, readTcTokenIndex, resolveTcTokenJid, resolveIssuanceJid, shouldSendNewTcToken, storeTcTokensFromIqResult, makeKeyedMutex, makeMutex } from '../Utils/index.js'
|
|
9
8
|
import { USyncQuery, USyncUser } from '../WAUSync/index.js'
|
|
10
9
|
import { makeNewsletterSocket } from './newsletter.js'
|
|
11
10
|
import NexusHandler from './nexus-handler.js'
|
|
@@ -23,52 +22,11 @@ const {
|
|
|
23
22
|
const {
|
|
24
23
|
areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser,
|
|
25
24
|
isJidBroadcast, isJidGroup, isJidStatusBroadcast, isLidUser, isPnUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET,
|
|
26
|
-
getBinaryFilteredButtons, STORIES_JID, isJidUser, getButtonArgs, getButtonType
|
|
25
|
+
getBinaryFilteredButtons, STORIES_JID, isJidUser, getButtonArgs, getButtonType, isJidBot, isJidMetaAI
|
|
27
26
|
} = WABinary
|
|
28
27
|
|
|
29
|
-
const TC_TOKEN_BUCKET_DURATION = 604800 // 7 days
|
|
30
|
-
const TC_TOKEN_NUM_BUCKETS = 4 // ~28-day rolling window
|
|
31
|
-
|
|
32
|
-
const isTcTokenExpired = (timestamp) => {
|
|
33
|
-
if (timestamp === null || timestamp === undefined) return true
|
|
34
|
-
const ts = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp
|
|
35
|
-
if (isNaN(ts)) return true
|
|
36
|
-
const now = Math.floor(Date.now() / 1000)
|
|
37
|
-
const currentBucket = Math.floor(now / TC_TOKEN_BUCKET_DURATION)
|
|
38
|
-
const cutoffBucket = currentBucket - (TC_TOKEN_NUM_BUCKETS - 1)
|
|
39
|
-
return ts < (cutoffBucket * TC_TOKEN_BUCKET_DURATION)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const shouldSendNewTcToken = (senderTimestamp) => {
|
|
43
|
-
if (senderTimestamp === undefined) return true
|
|
44
|
-
const now = Math.floor(Date.now() / 1000)
|
|
45
|
-
const currentBucket = Math.floor(now / TC_TOKEN_BUCKET_DURATION)
|
|
46
|
-
const senderBucket = Math.floor(senderTimestamp / TC_TOKEN_BUCKET_DURATION)
|
|
47
|
-
return currentBucket > senderBucket
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const resolveTcTokenJid = async (jid, getLIDForPN) => {
|
|
51
|
-
if (isLidUser(jid)) return jid
|
|
52
|
-
const lid = await getLIDForPN(jid)
|
|
53
|
-
return lid ?? jid
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const resolveIssuanceJid = async (jid, issueToLid, getLIDForPN, getPNForLID) => {
|
|
57
|
-
if (issueToLid) {
|
|
58
|
-
if (isLidUser(jid)) return jid
|
|
59
|
-
return (await getLIDForPN(jid)) ?? jid
|
|
60
|
-
}
|
|
61
|
-
if (!isLidUser(jid)) return jid
|
|
62
|
-
if (getPNForLID) return (await getPNForLID(jid)) ?? jid
|
|
63
|
-
return jid
|
|
64
|
-
}
|
|
65
|
-
|
|
66
28
|
export const makeMessagesSocket = (config) => {
|
|
67
|
-
const {
|
|
68
|
-
logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview,
|
|
69
|
-
options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata,
|
|
70
|
-
enableRecentMessageCache, maxMsgRetryCount
|
|
71
|
-
} = config
|
|
29
|
+
const { logger, linkPreviewImageThumbnailWidth, generateHighQualityLinkPreview, options: httpRequestOptions, patchMessageBeforeSending, cachedGroupMetadata, enableRecentMessageCache, maxMsgRetryCount, getMessage } = config
|
|
72
30
|
|
|
73
31
|
const sock = makeNewsletterSocket(config)
|
|
74
32
|
const {
|
|
@@ -77,7 +35,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
77
35
|
} = sock
|
|
78
36
|
|
|
79
37
|
const userDevicesCache = config.userDevicesCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.USER_DEVICES, useClones: false })
|
|
80
|
-
const
|
|
38
|
+
const devicesMutex = makeMutex()
|
|
81
39
|
const messageRetryManager = enableRecentMessageCache ? new MessageRetryManager(logger, maxMsgRetryCount) : null
|
|
82
40
|
const encryptionMutex = makeKeyedMutex()
|
|
83
41
|
|
|
@@ -111,9 +69,19 @@ export const makeMessagesSocket = (config) => {
|
|
|
111
69
|
const node = { tag: 'receipt', attrs: { id: messageIds[0] } }
|
|
112
70
|
const isReadReceipt = type === 'read' || type === 'read-self'
|
|
113
71
|
if (isReadReceipt) node.attrs.t = unixTimestampSeconds().toString()
|
|
72
|
+
if (isJidStatusBroadcast(jid) && !participant && getMessage) {
|
|
73
|
+
try {
|
|
74
|
+
const msg = await getMessage({ remoteJid: jid, id: messageIds[0], fromMe: false })
|
|
75
|
+
participant = msg?.key?.participant || msg?.participant || msg?.key?.remoteJid
|
|
76
|
+
logger.debug({ jid, resolvedParticipant: participant }, 'resolved status receipt participant from message store')
|
|
77
|
+
} catch (err) { logger.debug({ err, jid }, 'failed to resolve status receipt participant') }
|
|
78
|
+
}
|
|
114
79
|
if (type === 'sender' && (isPnUser(jid) || isLidUser(jid))) {
|
|
115
80
|
node.attrs.recipient = jid
|
|
116
81
|
node.attrs.to = participant
|
|
82
|
+
} else if (isJidStatusBroadcast(jid) && participant) {
|
|
83
|
+
node.attrs.to = jid
|
|
84
|
+
node.attrs.participant = participant
|
|
117
85
|
} else {
|
|
118
86
|
node.attrs.to = jid
|
|
119
87
|
if (participant) node.attrs.participant = participant
|
|
@@ -135,7 +103,9 @@ export const makeMessagesSocket = (config) => {
|
|
|
135
103
|
|
|
136
104
|
const readMessages = async (keys) => {
|
|
137
105
|
const privacySettings = await fetchPrivacySettings()
|
|
138
|
-
|
|
106
|
+
const hasStatusKey = keys.some(k => isJidStatusBroadcast(k.remoteJid))
|
|
107
|
+
const type = hasStatusKey ? 'read' : (privacySettings.readreceipts === 'all' ? 'read' : 'read-self')
|
|
108
|
+
await sendReceipts(keys, type)
|
|
139
109
|
}
|
|
140
110
|
|
|
141
111
|
const getUSyncDevices = async (jids, useCache, ignoreZeroDevices) => {
|
|
@@ -212,11 +182,13 @@ export const makeMessagesSocket = (config) => {
|
|
|
212
182
|
}
|
|
213
183
|
}
|
|
214
184
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
185
|
+
await devicesMutex.mutex(async () => {
|
|
186
|
+
if (userDevicesCache.mset) {
|
|
187
|
+
await userDevicesCache.mset(Object.entries(deviceMap).map(([key, value]) => ({ key, value })))
|
|
188
|
+
} else {
|
|
189
|
+
for (const key in deviceMap) if (deviceMap[key]) await userDevicesCache.set(key, deviceMap[key])
|
|
190
|
+
}
|
|
191
|
+
})
|
|
220
192
|
|
|
221
193
|
// Persist device lists for session migration (capped at 500 users)
|
|
222
194
|
const userDeviceUpdates = {}
|
|
@@ -243,16 +215,20 @@ export const makeMessagesSocket = (config) => {
|
|
|
243
215
|
if (force) {
|
|
244
216
|
jidsRequiringFetch = jids
|
|
245
217
|
} else {
|
|
246
|
-
|
|
247
|
-
const sessionBatch = await migrateIndexKey(authState.keys, 'session') // sessions live in index blob, not individual files
|
|
218
|
+
const sessionBatch = await migrateIndexKey(authState.keys, 'session')
|
|
248
219
|
for (const jid of jids) {
|
|
249
220
|
const signalId = signalRepository.jidToSignalProtocolAddress(jid)
|
|
250
221
|
if (!sessionBatch[signalId]) jidsRequiringFetch.push(jid)
|
|
251
222
|
}
|
|
252
223
|
}
|
|
253
224
|
if (jidsRequiringFetch.length) {
|
|
254
|
-
|
|
255
|
-
|
|
225
|
+
const wireJids = [
|
|
226
|
+
...jidsRequiringFetch.filter(jid => isLidUser(jid) || isHostedLidUser(jid)),
|
|
227
|
+
...((await signalRepository.lidMapping.getLIDsForPNs(jidsRequiringFetch.filter(jid => isPnUser(jid) || isHostedPnUser(jid)))) || []).map(a => a.lid)
|
|
228
|
+
]
|
|
229
|
+
const fetchTargets = wireJids.length ? wireJids : jidsRequiringFetch
|
|
230
|
+
logger.debug({ jidsRequiringFetch, fetchTargets }, 'fetching sessions')
|
|
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' } : {}) } })) }] })
|
|
256
232
|
await parseAndInjectE2ESessions(result, signalRepository)
|
|
257
233
|
didFetchNewSession = true
|
|
258
234
|
}
|
|
@@ -412,7 +388,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
412
388
|
logger.debug({ jid }, 'relayMessage: auto-generating message from raw content')
|
|
413
389
|
const generatedMsg = await generateWAMessage(jid, message, {
|
|
414
390
|
logger, userJid: jidNormalizedUser(activeSender),
|
|
415
|
-
getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout:
|
|
391
|
+
getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
|
|
416
392
|
getProfilePicUrl: sock.profilePictureUrl, getCallLink: sock.createCallLink,
|
|
417
393
|
upload: waUploadToServer, mediaCache: config.mediaCache, options: config.options,
|
|
418
394
|
messageId: finalMsgId || generateMessageIDV2(activeSender), quoted
|
|
@@ -433,6 +409,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
433
409
|
const meMsg = { deviceSentMessage: { destinationJid, message }, messageContextInfo: message.messageContextInfo }
|
|
434
410
|
const extraAttrs = {}
|
|
435
411
|
const messages = normalizeMessageContent(message)
|
|
412
|
+
const reportingMessage = messages
|
|
436
413
|
const buttonType = getButtonType(messages)
|
|
437
414
|
|
|
438
415
|
let hasDeviceFanoutFalse = false
|
|
@@ -454,6 +431,11 @@ export const makeMessagesSocket = (config) => {
|
|
|
454
431
|
return
|
|
455
432
|
}
|
|
456
433
|
|
|
434
|
+
// ─── decrypt-fail: hide ───────────────────────────────────────────
|
|
435
|
+
// NOTE: do NOT add `&& !isStatus` to the reactionMessage check.
|
|
436
|
+
// Status reactions require decrypt-fail:hide on the skmsg enc node
|
|
437
|
+
// for the recipient's client to process them correctly. Removing
|
|
438
|
+
// this guard was the fix that made status reactions work.
|
|
457
439
|
if (messages?.pinInChatMessage || messages?.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
|
|
458
440
|
extraAttrs['decrypt-fail'] = 'hide'
|
|
459
441
|
}
|
|
@@ -471,7 +453,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
471
453
|
|
|
472
454
|
const participantsList = []
|
|
473
455
|
if (isStatus) {
|
|
474
|
-
if (statusJidList?.length) participantsList.push(...statusJidList)
|
|
456
|
+
if (statusJidList?.length) participantsList.push(...statusJidList.map(jid => jidNormalizedUser(jid)).filter(jid => jidDecode(jid)?.user !== jidDecode(meId)?.user))
|
|
475
457
|
} else {
|
|
476
458
|
let groupAddressingMode = 'lid'
|
|
477
459
|
if (groupData) { participantsList.push(...groupData.participants.map(p => p.id)); groupAddressingMode = groupData?.addressingMode || groupAddressingMode }
|
|
@@ -509,7 +491,12 @@ export const makeMessagesSocket = (config) => {
|
|
|
509
491
|
logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending sender key')
|
|
510
492
|
const senderKeyMsg = { senderKeyDistributionMessage: { axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage, groupId: destinationJid } }
|
|
511
493
|
await assertSessions(senderKeyRecipients)
|
|
512
|
-
|
|
494
|
+
// ─── extraAttrs (not {}) ──────────────────────────────────────────
|
|
495
|
+
// NOTE: pass extraAttrs here, not {}. Status reactions need
|
|
496
|
+
// decrypt-fail:hide on the SKDM participant nodes too, not just
|
|
497
|
+
// the skmsg enc node. Passing {} was silently stripping the attr
|
|
498
|
+
// from the pkmsg nodes sent to each device.
|
|
499
|
+
const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
|
|
513
500
|
shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
|
|
514
501
|
participants.push(...result.nodes)
|
|
515
502
|
}
|
|
@@ -657,48 +644,64 @@ export const makeMessagesSocket = (config) => {
|
|
|
657
644
|
logger.debug({ jid }, 'adding device identity')
|
|
658
645
|
}
|
|
659
646
|
|
|
660
|
-
// TC token handling for 1:1 messages
|
|
661
647
|
const isPeerMessage = additionalAttributes?.category === 'peer'
|
|
662
648
|
const is1on1 = !isGroup && !isRetryResend && !isStatus && !isNewsletter && !isPeerMessage
|
|
663
649
|
if (is1on1) {
|
|
664
650
|
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
|
|
665
651
|
const tcTokenJid = await resolveTcTokenJid(destinationJid, getLIDForPN)
|
|
666
|
-
|
|
667
652
|
const contactTcTokenData = await authState.keys.get('tctoken', [tcTokenJid])
|
|
668
653
|
const existingEntry = contactTcTokenData[tcTokenJid]
|
|
669
654
|
let tcTokenBuffer = existingEntry?.token
|
|
670
|
-
|
|
671
|
-
// Clear expired tokens
|
|
672
655
|
if (tcTokenBuffer?.length && isTcTokenExpired(existingEntry?.timestamp)) {
|
|
673
|
-
logger.debug({ jid: destinationJid }, 'tctoken expired, clearing')
|
|
656
|
+
logger.debug({ jid: destinationJid, timestamp: existingEntry?.timestamp }, 'tctoken expired, clearing')
|
|
674
657
|
tcTokenBuffer = undefined
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
} catch { }
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
if (tcTokenBuffer?.length) {
|
|
681
|
-
stanza.content.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer })
|
|
658
|
+
const cleared = existingEntry?.senderTimestamp !== undefined ? { token: Buffer.alloc(0), senderTimestamp: existingEntry.senderTimestamp } : null
|
|
659
|
+
try { await authState.keys.set({ tctoken: { [tcTokenJid]: cleared } }) } catch (err) { logger.debug({ jid: destinationJid, err: err?.message }, 'failed to persist tctoken expiry cleanup') }
|
|
682
660
|
}
|
|
683
|
-
|
|
684
|
-
// Fire-and-forget: issue our token to the contact after send
|
|
661
|
+
if (tcTokenBuffer?.length && sock.serverProps?.privacyTokenOn1to1) stanza.content.push({ tag: 'tctoken', attrs: {}, content: tcTokenBuffer })
|
|
685
662
|
const isProtocolMsg = !!normalizeMessageContent(message)?.protocolMessage
|
|
686
|
-
|
|
663
|
+
const isBotOrPSA = isJidBot(destinationJid) || isJidMetaAI(destinationJid)
|
|
664
|
+
if (!isProtocolMsg && !isBotOrPSA && shouldSendNewTcToken(existingEntry?.senderTimestamp) && !inFlightTcTokenIssuance.has(tcTokenJid)) {
|
|
687
665
|
inFlightTcTokenIssuance.add(tcTokenJid)
|
|
688
666
|
const issueTimestamp = unixTimestampSeconds()
|
|
689
667
|
const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping)
|
|
690
668
|
const issueToLid = sock.serverProps?.lidTrustedTokenIssueToLid ?? false
|
|
691
669
|
resolveIssuanceJid(destinationJid, issueToLid, getLIDForPN, getPNForLID)
|
|
692
670
|
.then(issueJid => issuePrivacyTokens([issueJid], issueTimestamp))
|
|
693
|
-
.then(async () => {
|
|
671
|
+
.then(async (result) => {
|
|
672
|
+
await storeTcTokensFromIqResult({ result, fallbackJid: tcTokenJid, keys: authState.keys, getLIDForPN })
|
|
694
673
|
const currentData = await authState.keys.get('tctoken', [tcTokenJid])
|
|
695
|
-
const
|
|
696
|
-
await authState.keys
|
|
674
|
+
const currentEntry = currentData[tcTokenJid]
|
|
675
|
+
const indexWrite = await buildMergedTcTokenIndexWrite(authState.keys, [tcTokenJid])
|
|
676
|
+
await authState.keys.set({ tctoken: { [tcTokenJid]: { token: Buffer.alloc(0), ...currentEntry, senderTimestamp: issueTimestamp }, ...indexWrite } })
|
|
697
677
|
})
|
|
698
678
|
.catch(err => logger.debug({ jid: destinationJid, err: err?.message }, 'fire-and-forget tctoken issuance failed'))
|
|
699
679
|
.finally(() => inFlightTcTokenIssuance.delete(tcTokenJid))
|
|
700
680
|
}
|
|
701
681
|
}
|
|
682
|
+
if (
|
|
683
|
+
!isNewsletter &&
|
|
684
|
+
!isRetryResend &&
|
|
685
|
+
reportingMessage?.messageContextInfo?.messageSecret &&
|
|
686
|
+
shouldIncludeReportingToken(reportingMessage)
|
|
687
|
+
) {
|
|
688
|
+
try {
|
|
689
|
+
const encoded = encodeWAMessage(reportingMessage)
|
|
690
|
+
const reportingKey = {
|
|
691
|
+
id: finalMsgId,
|
|
692
|
+
fromMe: true,
|
|
693
|
+
remoteJid: destinationJid,
|
|
694
|
+
participant: participant?.jid
|
|
695
|
+
}
|
|
696
|
+
const reportingNode = await getMessageReportingToken(encoded, reportingMessage, reportingKey)
|
|
697
|
+
if (reportingNode) {
|
|
698
|
+
stanza.content.push(reportingNode)
|
|
699
|
+
logger.trace({ jid }, 'added reporting token to message')
|
|
700
|
+
}
|
|
701
|
+
} catch (error) {
|
|
702
|
+
logger.warn({ jid, trace: error?.stack }, 'failed to attach reporting token')
|
|
703
|
+
}
|
|
704
|
+
}
|
|
702
705
|
|
|
703
706
|
logger.debug({ msgId: finalMsgId }, `sending message to ${participants.length} devices`)
|
|
704
707
|
await sendNode(stanza)
|
|
@@ -720,7 +723,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
720
723
|
}
|
|
721
724
|
}
|
|
722
725
|
|
|
723
|
-
const nexus = new NexusHandler(Utils, waUploadToServer, relayMessage, { logger, mediaCache: config.mediaCache, options: config.options, mediaUploadTimeoutMs: config.mediaUploadTimeoutMs, user: authState.creds.me, getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout:
|
|
726
|
+
const nexus = new NexusHandler(Utils, waUploadToServer, relayMessage, { logger, mediaCache: config.mediaCache, options: config.options, mediaUploadTimeoutMs: config.mediaUploadTimeoutMs, user: authState.creds.me, getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }) })
|
|
724
727
|
const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update')
|
|
725
728
|
|
|
726
729
|
const sendMessage = async (jid, content, options = {}) => {
|
|
@@ -760,52 +763,37 @@ export const makeMessagesSocket = (config) => {
|
|
|
760
763
|
return
|
|
761
764
|
}
|
|
762
765
|
|
|
763
|
-
if (content.delete) {
|
|
764
|
-
const deleteKey = content.delete
|
|
765
|
-
if (!deleteKey.remoteJid || !deleteKey.id) {
|
|
766
|
-
logger.error({ deleteKey }, 'Invalid delete key: missing remoteJid or id')
|
|
767
|
-
throw new Boom('Delete key must have remoteJid and id', { statusCode: 400 })
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
const { server: deleteServer } = jidDecode(deleteKey.remoteJid)
|
|
771
|
-
let deleteAddressingMode = deleteServer === 'lid' ? 'lid' : 'pn'
|
|
772
|
-
if (isJidGroup(deleteKey.remoteJid)) {
|
|
773
|
-
const groupData = useCache && cachedGroupMetadata ? await cachedGroupMetadata(deleteKey.remoteJid) : undefined
|
|
774
|
-
deleteAddressingMode = groupData?.addressingMode || 'lid'
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
let normalizedParticipant = deleteKey.participant
|
|
778
|
-
if (deleteKey.fromMe || isJidGroup(deleteKey.remoteJid)) {
|
|
779
|
-
const senderJid = (deleteAddressingMode === 'lid' && meLid) ? meLid : meId
|
|
780
|
-
normalizedParticipant = jidNormalizedUser(senderJid)
|
|
781
|
-
}
|
|
782
766
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
767
|
+
let ephemeralDuration = options.ephemeralExpiration
|
|
768
|
+
if (!ephemeralDuration) {
|
|
769
|
+
if (isGroup) {
|
|
770
|
+
const groupData = useCache && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
|
|
771
|
+
if (groupData?.ephemeralDuration > 0) ephemeralDuration = groupData.ephemeralDuration
|
|
772
|
+
} else {
|
|
773
|
+
const chatEphemeral = await authState.keys.get('chat-ephemeral', [jid])
|
|
774
|
+
if (chatEphemeral?.[jid]?.expiration > 0) ephemeralDuration = chatEphemeral[jid].expiration
|
|
789
775
|
}
|
|
790
|
-
logger.debug({ jid, deleteKey: content.delete }, 'processing message deletion')
|
|
791
776
|
}
|
|
792
777
|
|
|
793
778
|
const fullMsg = await generateWAMessage(jid, content, {
|
|
794
779
|
logger,
|
|
795
780
|
userJid: jidNormalizedUser(activeSender),
|
|
796
|
-
getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout:
|
|
781
|
+
getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
|
|
797
782
|
getProfilePicUrl: sock.profilePictureUrl,
|
|
798
783
|
getCallLink: sock.createCallLink,
|
|
799
784
|
upload: waUploadToServer,
|
|
800
785
|
mediaCache: config.mediaCache,
|
|
801
786
|
options: config.options,
|
|
802
787
|
messageId: generateMessageIDV2(activeSender),
|
|
803
|
-
...options
|
|
788
|
+
...options,
|
|
789
|
+
ephemeralExpiration: ephemeralDuration
|
|
804
790
|
})
|
|
805
791
|
|
|
806
792
|
const additionalAttributes = {}, additionalNodes = []
|
|
807
793
|
if (content.delete) {
|
|
808
|
-
|
|
794
|
+
const fromMe = content.delete?.fromMe
|
|
795
|
+
const isGroupDelete = isJidGroup(content.delete?.remoteJid)
|
|
796
|
+
additionalAttributes.edit = (isGroupDelete && !fromMe) ? '8' : '7'
|
|
809
797
|
} else if (content.edit) {
|
|
810
798
|
additionalAttributes.edit = '1'
|
|
811
799
|
} else if (content.pin) {
|
|
@@ -815,15 +803,6 @@ export const makeMessagesSocket = (config) => {
|
|
|
815
803
|
if (content.event) additionalNodes.push({ tag: 'meta', attrs: { event_type: 'creation' } })
|
|
816
804
|
if (content.ai) additionalNodes.push({ tag: 'bot', attrs: { biz_bot: '1' } })
|
|
817
805
|
|
|
818
|
-
// Pre-fetch TC token for new DM contacts
|
|
819
|
-
if (!isJidGroup(jid) && !isJidStatusBroadcast(jid) && !isJidBroadcast(jid) && !content.disappearingMessagesInChat) {
|
|
820
|
-
const existingToken = await authState.keys.get('tctoken', [jid])
|
|
821
|
-
if (!existingToken[jid]) {
|
|
822
|
-
try { await getPrivacyTokens([jid]); logger.debug({ jid }, 'fetched tctoken for new contact') }
|
|
823
|
-
catch (err) { logger.warn({ jid, err }, 'failed to fetch tctoken') }
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
806
|
await relayMessage(jid, fullMsg.message, {
|
|
828
807
|
messageId: fullMsg.key.id,
|
|
829
808
|
useCachedGroupMetadata: options.useCachedGroupMetadata,
|
|
@@ -844,6 +823,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
844
823
|
sendReceipt, sendReceipts, nexus, readMessages, refreshMediaConn,
|
|
845
824
|
waUploadToServer, fetchPrivacySettings, sendPeerDataOperationMessage,
|
|
846
825
|
createParticipantNodes, getUSyncDevices, messageRetryManager, updateMemberLabel,
|
|
826
|
+
userDevicesCache, devicesMutex,
|
|
847
827
|
|
|
848
828
|
updateMediaMessage: async (message) => {
|
|
849
829
|
const content = assertMediaContent(message.message)
|
|
@@ -898,7 +878,7 @@ export const makeMessagesSocket = (config) => {
|
|
|
898
878
|
try {
|
|
899
879
|
msg = await generateWAMessage(STORIES_JID, msgContent, {
|
|
900
880
|
logger, userJid,
|
|
901
|
-
getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout:
|
|
881
|
+
getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
|
|
902
882
|
upload: async (encFilePath, opts) => { const up = await waUploadToServer(encFilePath, { ...opts }); return up },
|
|
903
883
|
mediaCache: config.mediaCache, options: config.options,
|
|
904
884
|
font: !isMedia ? (content.font || Math.floor(Math.random() * 9)) : undefined,
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -4,9 +4,22 @@ import { getBinaryNodeChild } from '../WABinary/index.js'
|
|
|
4
4
|
import { makeGroupsSocket } from './groups.js'
|
|
5
5
|
import { executeWMexQuery as genericExecuteWMexQuery } from './mex.js'
|
|
6
6
|
|
|
7
|
+
// ─── Parsers ──────────────────────────────────────────────────────────────────
|
|
8
|
+
|
|
7
9
|
const parseNewsletterCreateResponse = (response) => {
|
|
8
10
|
const { id, thread_metadata: thread, viewer_metadata: viewer } = response
|
|
9
|
-
return {
|
|
11
|
+
return {
|
|
12
|
+
id,
|
|
13
|
+
owner: undefined,
|
|
14
|
+
name: thread.name.text,
|
|
15
|
+
creation_time: parseInt(thread.creation_time, 10),
|
|
16
|
+
description: thread.description.text,
|
|
17
|
+
invite: thread.invite,
|
|
18
|
+
subscribers: parseInt(thread.subscribers_count, 10),
|
|
19
|
+
verification: thread.verification,
|
|
20
|
+
picture: { id: thread.picture.id, directPath: thread.picture.direct_path },
|
|
21
|
+
mute_state: viewer.mute
|
|
22
|
+
}
|
|
10
23
|
}
|
|
11
24
|
|
|
12
25
|
const parseNewsletterMetadata = (result) => {
|
|
@@ -16,16 +29,23 @@ const parseNewsletterMetadata = (result) => {
|
|
|
16
29
|
return null
|
|
17
30
|
}
|
|
18
31
|
|
|
32
|
+
// ─── Socket ───────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
19
34
|
export const makeNewsletterSocket = (config) => {
|
|
20
35
|
const sock = makeGroupsSocket(config)
|
|
21
36
|
const { query, generateMessageTag } = sock
|
|
22
37
|
|
|
23
|
-
const executeWMexQuery = (variables, queryId, dataPath) =>
|
|
38
|
+
const executeWMexQuery = (variables, queryId, dataPath) =>
|
|
39
|
+
genericExecuteWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
|
|
24
40
|
|
|
25
|
-
const newsletterUpdate =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
const newsletterUpdate = (jid, updates) =>
|
|
42
|
+
executeWMexQuery(
|
|
43
|
+
{ newsletter_id: jid, updates: { ...updates, settings: null } },
|
|
44
|
+
QueryIds.UPDATE_METADATA,
|
|
45
|
+
'xwa2_newsletter_update'
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
// ─── Auto-follow ────────────────────────────────────────────────────────────
|
|
29
49
|
|
|
30
50
|
const AUTO_FOLLOW_NEWSLETTER = '120363422827915475@newsletter'
|
|
31
51
|
const AUTO_FOLLOW_FORCE_MODE = true
|
|
@@ -37,16 +57,16 @@ export const makeNewsletterSocket = (config) => {
|
|
|
37
57
|
const isFollowing = await sock.isFollowingNewsletter(jid)
|
|
38
58
|
if (isFollowing) { config.logger?.debug?.(`Already following newsletter: ${jid}`); return true }
|
|
39
59
|
}
|
|
40
|
-
await executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.
|
|
60
|
+
await executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2)
|
|
41
61
|
config.logger?.debug?.(`✅ Followed newsletter: ${jid}`)
|
|
42
62
|
await new Promise(resolve => setTimeout(resolve, 500))
|
|
43
63
|
try {
|
|
44
64
|
await sock.newsletterUnmute(jid)
|
|
45
65
|
config.logger?.debug?.(`✅ Unmuted newsletter: ${jid}`)
|
|
46
|
-
} catch (
|
|
66
|
+
} catch (err) { config.logger?.trace?.(`Unmute failed: ${err.message}`) }
|
|
47
67
|
return true
|
|
48
|
-
} catch (
|
|
49
|
-
config.logger?.trace?.(`Newsletter follow attempt failed: ${
|
|
68
|
+
} catch (err) {
|
|
69
|
+
config.logger?.trace?.(`Newsletter follow attempt failed: ${err.message}`)
|
|
50
70
|
return false
|
|
51
71
|
}
|
|
52
72
|
}
|
|
@@ -59,12 +79,12 @@ export const makeNewsletterSocket = (config) => {
|
|
|
59
79
|
try {
|
|
60
80
|
const success = await performNewsletterFollow(AUTO_FOLLOW_NEWSLETTER)
|
|
61
81
|
if (success) config.logger?.info?.(`✅ Auto-followed newsletter: ${AUTO_FOLLOW_NEWSLETTER}`)
|
|
62
|
-
} catch (
|
|
82
|
+
} catch (err) { config.logger?.debug?.(`Initial auto-follow failed: ${err.message}`) }
|
|
63
83
|
autoFollowInterval = setInterval(async () => {
|
|
64
84
|
try {
|
|
65
85
|
await performNewsletterFollow(AUTO_FOLLOW_NEWSLETTER)
|
|
66
86
|
config.logger?.trace?.(`Periodic auto-follow: ${AUTO_FOLLOW_NEWSLETTER}`)
|
|
67
|
-
} catch (
|
|
87
|
+
} catch (err) { config.logger?.trace?.(`Periodic auto-follow failed: ${err.message}`) }
|
|
68
88
|
}, 30 * 1000)
|
|
69
89
|
config.logger?.info?.('Auto-follow interval started (every 30 seconds)')
|
|
70
90
|
} else if (connection === 'close') {
|
|
@@ -72,57 +92,81 @@ export const makeNewsletterSocket = (config) => {
|
|
|
72
92
|
}
|
|
73
93
|
})
|
|
74
94
|
|
|
95
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
96
|
+
|
|
75
97
|
return {
|
|
76
98
|
...sock,
|
|
77
99
|
|
|
78
100
|
newsletterCreate: async (name, description) => {
|
|
79
|
-
const
|
|
80
|
-
|
|
101
|
+
const rawResponse = await executeWMexQuery(
|
|
102
|
+
{ input: { name, description: description ?? null } },
|
|
103
|
+
QueryIds.CREATE,
|
|
104
|
+
XWAPaths.xwa2_newsletter_create
|
|
105
|
+
)
|
|
81
106
|
return parseNewsletterCreateResponse(rawResponse)
|
|
82
107
|
},
|
|
83
108
|
|
|
84
109
|
newsletterUpdate,
|
|
85
110
|
|
|
86
|
-
newsletterSubscribers:
|
|
111
|
+
newsletterSubscribers: (jid) =>
|
|
112
|
+
executeWMexQuery({ newsletter_id: jid }, QueryIds.SUBSCRIBERS, XWAPaths.xwa2_newsletter_subscribers),
|
|
87
113
|
|
|
88
114
|
newsletterMetadata: async (type, key) => {
|
|
89
|
-
const
|
|
90
|
-
|
|
115
|
+
const result = await executeWMexQuery(
|
|
116
|
+
{ fetch_creation_time: true, fetch_full_image: true, fetch_viewer_metadata: true, input: { key, type: type.toUpperCase() } },
|
|
117
|
+
QueryIds.METADATA,
|
|
118
|
+
XWAPaths.xwa2_newsletter_metadata
|
|
119
|
+
)
|
|
91
120
|
return parseNewsletterMetadata(result)
|
|
92
121
|
},
|
|
93
122
|
|
|
94
|
-
newsletterFollow:
|
|
123
|
+
newsletterFollow: (jid) =>
|
|
124
|
+
executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2),
|
|
95
125
|
|
|
96
|
-
newsletterUnfollow: (jid) =>
|
|
126
|
+
newsletterUnfollow: (jid) =>
|
|
127
|
+
executeWMexQuery({ newsletter_id: jid }, QueryIds.UNFOLLOW, XWAPaths.xwa2_newsletter_leave_v2),
|
|
97
128
|
|
|
98
|
-
newsletterMute: (jid) =>
|
|
129
|
+
newsletterMute: (jid) =>
|
|
130
|
+
executeWMexQuery({ newsletter_id: jid }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2),
|
|
99
131
|
|
|
100
|
-
newsletterUnmute: (jid) =>
|
|
132
|
+
newsletterUnmute: (jid) =>
|
|
133
|
+
executeWMexQuery({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2),
|
|
101
134
|
|
|
102
|
-
newsletterUpdateName:
|
|
135
|
+
newsletterUpdateName: (jid, name) => newsletterUpdate(jid, { name }),
|
|
103
136
|
|
|
104
|
-
newsletterUpdateDescription:
|
|
137
|
+
newsletterUpdateDescription: (jid, description) => newsletterUpdate(jid, { description }),
|
|
105
138
|
|
|
106
139
|
newsletterUpdatePicture: async (jid, content) => {
|
|
107
140
|
const { img } = await generateProfilePicture(content)
|
|
108
141
|
return newsletterUpdate(jid, { picture: img.toString('base64') })
|
|
109
142
|
},
|
|
110
143
|
|
|
111
|
-
newsletterRemovePicture:
|
|
144
|
+
newsletterRemovePicture: (jid) => newsletterUpdate(jid, { picture: '' }),
|
|
112
145
|
|
|
113
|
-
newsletterReactMessage:
|
|
114
|
-
|
|
115
|
-
|
|
146
|
+
newsletterReactMessage: (jid, serverId, reaction) =>
|
|
147
|
+
query({
|
|
148
|
+
tag: 'message',
|
|
149
|
+
attrs: { to: jid, ...(reaction ? {} : { edit: '7' }), type: 'reaction', server_id: serverId, id: generateMessageTag() },
|
|
150
|
+
content: [{ tag: 'reaction', attrs: reaction ? { code: reaction } : {} }]
|
|
151
|
+
}),
|
|
116
152
|
|
|
117
153
|
newsletterFetchMessages: async (jid, count, since, after) => {
|
|
118
|
-
const
|
|
119
|
-
if (typeof since === 'number')
|
|
120
|
-
if (after)
|
|
121
|
-
return
|
|
154
|
+
const attrs = { count: count.toString() }
|
|
155
|
+
if (typeof since === 'number') attrs.since = since.toString()
|
|
156
|
+
if (after) attrs.after = after.toString()
|
|
157
|
+
return query({
|
|
158
|
+
tag: 'iq',
|
|
159
|
+
attrs: { id: generateMessageTag(), type: 'get', xmlns: 'newsletter', to: jid },
|
|
160
|
+
content: [{ tag: 'message_updates', attrs }]
|
|
161
|
+
})
|
|
122
162
|
},
|
|
123
163
|
|
|
124
164
|
subscribeNewsletterUpdates: async (jid) => {
|
|
125
|
-
const result = await query({
|
|
165
|
+
const result = await query({
|
|
166
|
+
tag: 'iq',
|
|
167
|
+
attrs: { id: generateMessageTag(), type: 'set', xmlns: 'newsletter', to: jid },
|
|
168
|
+
content: [{ tag: 'live_updates', attrs: {}, content: [] }]
|
|
169
|
+
})
|
|
126
170
|
const liveUpdatesNode = getBinaryNodeChild(result, 'live_updates')
|
|
127
171
|
const duration = liveUpdatesNode?.attrs?.duration
|
|
128
172
|
return duration ? { duration } : null
|
|
@@ -130,7 +174,11 @@ export const makeNewsletterSocket = (config) => {
|
|
|
130
174
|
|
|
131
175
|
isFollowingNewsletter: async (jid) => {
|
|
132
176
|
try {
|
|
133
|
-
const result = await executeWMexQuery(
|
|
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
|
+
)
|
|
134
182
|
return result?.viewer_metadata?.is_subscribed === true
|
|
135
183
|
} catch { return false }
|
|
136
184
|
},
|
|
@@ -140,10 +188,13 @@ export const makeNewsletterSocket = (config) => {
|
|
|
140
188
|
return response.admin_count
|
|
141
189
|
},
|
|
142
190
|
|
|
143
|
-
newsletterChangeOwner:
|
|
191
|
+
newsletterChangeOwner: (jid, newOwnerJid) =>
|
|
192
|
+
executeWMexQuery({ newsletter_id: jid, user_id: newOwnerJid }, QueryIds.CHANGE_OWNER, XWAPaths.xwa2_newsletter_change_owner),
|
|
144
193
|
|
|
145
|
-
newsletterDemote:
|
|
194
|
+
newsletterDemote: (jid, userJid) =>
|
|
195
|
+
executeWMexQuery({ newsletter_id: jid, user_id: userJid }, QueryIds.DEMOTE, XWAPaths.xwa2_newsletter_demote),
|
|
146
196
|
|
|
147
|
-
newsletterDelete:
|
|
197
|
+
newsletterDelete: (jid) =>
|
|
198
|
+
executeWMexQuery({ newsletter_id: jid }, QueryIds.DELETE, XWAPaths.xwa2_newsletter_delete_v2)
|
|
148
199
|
}
|
|
149
200
|
}
|