@nexustechpro/baileys 2.0.6 → 2.1.2

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.
@@ -278,7 +278,8 @@ export const makeMessagesRecvSocket = (config) => {
278
278
  if (senderTs === null || senderTs === undefined || isTcTokenExpired(senderTs)) return
279
279
  logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken')
280
280
  const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping)
281
- const issueJid = await resolveIssuanceJid(normalizedJid, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
281
+ const issueToLid = sock.serverProps?.lidTrustedTokenIssueToLid ?? false
282
+ const issueJid = await resolveIssuanceJid(normalizedJid, issueToLid, getLIDForPN, getPNForLID)
282
283
  const result = await issuePrivacyTokens([issueJid], senderTs)
283
284
  await storeTcTokensFromIqResult({ result, fallbackJid: tcJid, keys: authState.keys, getLIDForPN, onNewJidStored: trackTcTokenJid })
284
285
  })().catch(err => logger.debug({ jid: from, err: err?.message }, 'failed to re-issue tctoken after identity change'))
@@ -831,7 +832,8 @@ export const makeMessagesRecvSocket = (config) => {
831
832
  try {
832
833
  const getPNForLID = signalRepository.lidMapping.getPNForLID.bind(signalRepository.lidMapping)
833
834
  const tcStorageJid = await resolveTcTokenJid(ackFrom, getLIDForPN)
834
- const issueJid = await resolveIssuanceJid(ackFrom, sock.serverProps.lidTrustedTokenIssueToLid, getLIDForPN, getPNForLID)
835
+ const issueToLid = sock.serverProps?.lidTrustedTokenIssueToLid ?? false
836
+ const issueJid = await resolveIssuanceJid(ackFrom, issueToLid, getLIDForPN, getPNForLID)
835
837
  const result = await issuePrivacyTokens([issueJid], unixTimestampSeconds())
836
838
  await storeTcTokensFromIqResult({ result, fallbackJid: tcStorageJid, keys: authState.keys, getLIDForPN, onNewJidStored: trackTcTokenJid })
837
839
  logger.debug({ from: ackFrom }, 'completed 463 token recovery issuance')
@@ -362,12 +362,18 @@ export const makeMessagesSocket = (config) => {
362
362
  const relayMessage = async (jid, message, { messageId: msgId, participant, additionalAttributes, additionalNodes, useUserDevicesCache, useCachedGroupMetadata, statusJidList, quoted } = {}) => {
363
363
  const meId = authState.creds.me.id
364
364
  const meLid = authState.creds.me?.lid
365
- const { user, server } = jidDecode(jid)
365
+ let { user, server } = jidDecode(jid)
366
366
  const isGroup = server === 'g.us'
367
367
  const isStatus = jid === 'status@broadcast'
368
- const isLid = server === 'lid'
368
+ let isLid = server === 'lid'
369
369
  const isNewsletter = server === 'newsletter'
370
-
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
+ }
371
377
  let activeSender = meId
372
378
  let groupAddressingMode = 'pn'
373
379
  if (isGroup && !isStatus) {
@@ -388,7 +394,7 @@ export const makeMessagesSocket = (config) => {
388
394
  logger.debug({ jid }, 'relayMessage: auto-generating message from raw content')
389
395
  const generatedMsg = await generateWAMessage(jid, message, {
390
396
  logger, userJid: jidNormalizedUser(activeSender),
391
- getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 3000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
397
+ getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
392
398
  getProfilePicUrl: sock.profilePictureUrl, getCallLink: sock.createCallLink,
393
399
  upload: waUploadToServer, mediaCache: config.mediaCache, options: config.options,
394
400
  messageId: finalMsgId || generateMessageIDV2(activeSender), quoted
@@ -431,7 +437,12 @@ export const makeMessagesSocket = (config) => {
431
437
  return
432
438
  }
433
439
 
434
- if (messages?.pinInChatMessage || messages?.keepInChatMessage || (message.reactionMessage && !isStatus) || message.protocolMessage?.editedMessage) {
440
+ // ─── decrypt-fail: hide ───────────────────────────────────────────
441
+ // NOTE: do NOT add `&& !isStatus` to the reactionMessage check.
442
+ // Status reactions require decrypt-fail:hide on the skmsg enc node
443
+ // for the recipient's client to process them correctly. Removing
444
+ // this guard was the fix that made status reactions work.
445
+ if (messages?.pinInChatMessage || messages?.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
435
446
  extraAttrs['decrypt-fail'] = 'hide'
436
447
  }
437
448
 
@@ -448,7 +459,7 @@ export const makeMessagesSocket = (config) => {
448
459
 
449
460
  const participantsList = []
450
461
  if (isStatus) {
451
- if (statusJidList?.length) participantsList.push(...statusJidList)
462
+ if (statusJidList?.length) participantsList.push(...statusJidList.map(jid => jidNormalizedUser(jid)).filter(jid => jidDecode(jid)?.user !== jidDecode(meId)?.user))
452
463
  } else {
453
464
  let groupAddressingMode = 'lid'
454
465
  if (groupData) { participantsList.push(...groupData.participants.map(p => p.id)); groupAddressingMode = groupData?.addressingMode || groupAddressingMode }
@@ -474,10 +485,9 @@ export const makeMessagesSocket = (config) => {
474
485
  if (Array.isArray(patched)) throw new Boom('Per-jid patching not supported in groups')
475
486
 
476
487
  const bytes = encodeWAMessage(patched)
477
- const bytesU8 = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)
478
488
  const gAddressingMode = additionalAttributes?.addressing_mode || groupData?.addressingMode || 'lid'
479
489
  const groupSenderIdentity = gAddressingMode === 'lid' && meLid ? meLid : meId
480
- const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ group: destinationJid, data: bytesU8, meId: groupSenderIdentity })
490
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ group: destinationJid, data: bytes, meId: groupSenderIdentity })
481
491
 
482
492
  const senderKeyRecipients = devices
483
493
  .filter(d => !isHostedLidUser(d.jid) && !isHostedPnUser(d.jid) && d.device !== 99)
@@ -487,7 +497,12 @@ export const makeMessagesSocket = (config) => {
487
497
  logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending sender key')
488
498
  const senderKeyMsg = { senderKeyDistributionMessage: { axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage, groupId: destinationJid } }
489
499
  await assertSessions(senderKeyRecipients)
490
- const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, {})
500
+ // ─── extraAttrs (not {}) ──────────────────────────────────────────
501
+ // NOTE: pass extraAttrs here, not {}. Status reactions need
502
+ // decrypt-fail:hide on the SKDM participant nodes too, not just
503
+ // the skmsg enc node. Passing {} was silently stripping the attr
504
+ // from the pkmsg nodes sent to each device.
505
+ const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, extraAttrs)
491
506
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
492
507
  participants.push(...result.nodes)
493
508
  }
@@ -505,10 +520,9 @@ export const makeMessagesSocket = (config) => {
505
520
  if (Array.isArray(patched)) throw new Boom('Per-jid patching not supported in groups')
506
521
 
507
522
  const bytes = encodeWAMessage(patched)
508
- const bytesU8 = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)
509
523
  const gAddressingMode = additionalAttributes?.addressing_mode || groupData?.addressingMode || 'lid'
510
524
  const groupSenderIdentity = gAddressingMode === 'lid' && meLid ? meLid : meId
511
- const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ group: destinationJid, data: bytesU8, meId: groupSenderIdentity })
525
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ group: destinationJid, data: bytes, meId: groupSenderIdentity })
512
526
 
513
527
  const senderKeyMsg = { senderKeyDistributionMessage: { axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage, groupId: destinationJid } }
514
528
  await assertSessions([participant.jid])
@@ -715,7 +729,7 @@ export const makeMessagesSocket = (config) => {
715
729
  }
716
730
  }
717
731
 
718
- 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: 3000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }) })
732
+ 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 }) })
719
733
  const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update')
720
734
 
721
735
  const sendMessage = async (jid, content, options = {}) => {
@@ -744,9 +758,7 @@ export const makeMessagesSocket = (config) => {
744
758
  content = { ...rest, interactiveMessage: interactive }
745
759
  }
746
760
 
747
- // never route status reactions through nexus
748
- const isStatusJid = jid === 'status@broadcast'
749
- const messageType = !isStatusJid && nexus.detectType(content)
761
+ const messageType = nexus.detectType(content)
750
762
  if (messageType) return await nexus.processMessage(content, jid, quoted)
751
763
 
752
764
  if (content.disappearingMessagesInChat && isJidGroup(jid)) {
@@ -758,17 +770,29 @@ export const makeMessagesSocket = (config) => {
758
770
  }
759
771
 
760
772
 
773
+ let ephemeralDuration = options.ephemeralExpiration
774
+ if (!ephemeralDuration) {
775
+ if (isGroup) {
776
+ const groupData = useCache && cachedGroupMetadata ? await cachedGroupMetadata(jid) : undefined
777
+ if (groupData?.ephemeralDuration > 0) ephemeralDuration = groupData.ephemeralDuration
778
+ } else {
779
+ const chatEphemeral = await authState.keys.get('chat-ephemeral', [jid])
780
+ if (chatEphemeral?.[jid]?.expiration > 0) ephemeralDuration = chatEphemeral[jid].expiration
781
+ }
782
+ }
783
+
761
784
  const fullMsg = await generateWAMessage(jid, content, {
762
785
  logger,
763
786
  userJid: jidNormalizedUser(activeSender),
764
- getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 3000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
787
+ getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
765
788
  getProfilePicUrl: sock.profilePictureUrl,
766
789
  getCallLink: sock.createCallLink,
767
790
  upload: waUploadToServer,
768
791
  mediaCache: config.mediaCache,
769
792
  options: config.options,
770
793
  messageId: generateMessageIDV2(activeSender),
771
- ...options
794
+ ...options,
795
+ ephemeralExpiration: ephemeralDuration
772
796
  })
773
797
 
774
798
  const additionalAttributes = {}, additionalNodes = []
@@ -860,7 +884,7 @@ export const makeMessagesSocket = (config) => {
860
884
  try {
861
885
  msg = await generateWAMessage(STORIES_JID, msgContent, {
862
886
  logger, userJid,
863
- getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 3000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
887
+ getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
864
888
  upload: async (encFilePath, opts) => { const up = await waUploadToServer(encFilePath, { ...opts }); return up },
865
889
  mediaCache: config.mediaCache, options: config.options,
866
890
  font: !isMedia ? (content.font || Math.floor(Math.random() * 9)) : undefined,
@@ -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 { id, owner: undefined, name: thread.name.text, creation_time: parseInt(thread.creation_time, 10), description: thread.description.text, invite: thread.invite, subscribers: parseInt(thread.subscribers_count, 10), verification: thread.verification, picture: { id: thread.picture.id, directPath: thread.picture.direct_path }, mute_state: viewer.mute }
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) => genericExecuteWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
38
+ const executeWMexQuery = (variables, queryId, dataPath) =>
39
+ genericExecuteWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
24
40
 
25
- const newsletterUpdate = async (jid, updates) => {
26
- const variables = { newsletter_id: jid, updates: { ...updates, settings: null } }
27
- return executeWMexQuery(variables, QueryIds.UPDATE_METADATA, 'xwa2_newsletter_update')
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.xwa2_newsletter_follow)
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 (unmuteError) { config.logger?.trace?.(`Unmute failed: ${unmuteError.message}`) }
66
+ } catch (err) { config.logger?.trace?.(`Unmute failed: ${err.message}`) }
47
67
  return true
48
- } catch (error) {
49
- config.logger?.trace?.(`Newsletter follow attempt failed: ${error.message}`)
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 (error) { config.logger?.debug?.(`Initial auto-follow failed: ${error.message}`) }
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 (error) { config.logger?.trace?.(`Periodic auto-follow failed: ${error.message}`) }
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 variables = { input: { name, description: description ?? null } }
80
- const rawResponse = await executeWMexQuery(variables, QueryIds.CREATE, XWAPaths.xwa2_newsletter_create)
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: async (jid) => executeWMexQuery({ newsletter_id: jid }, QueryIds.SUBSCRIBERS, XWAPaths.xwa2_newsletter_subscribers),
111
+ newsletterSubscribers: (jid) =>
112
+ executeWMexQuery({ newsletter_id: jid }, QueryIds.SUBSCRIBERS, XWAPaths.xwa2_newsletter_subscribers),
87
113
 
88
114
  newsletterMetadata: async (type, key) => {
89
- const variables = { fetch_creation_time: true, fetch_full_image: true, fetch_viewer_metadata: true, input: { key, type: type.toUpperCase() } }
90
- const result = await executeWMexQuery(variables, QueryIds.METADATA, XWAPaths.xwa2_newsletter_metadata)
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: async (jid) => executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_follow),
123
+ newsletterFollow: (jid) =>
124
+ executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2),
95
125
 
96
- newsletterUnfollow: (jid) => executeWMexQuery({ newsletter_id: jid }, QueryIds.UNFOLLOW, XWAPaths.xwa2_newsletter_unfollow),
126
+ newsletterUnfollow: (jid) =>
127
+ executeWMexQuery({ newsletter_id: jid }, QueryIds.UNFOLLOW, XWAPaths.xwa2_newsletter_leave_v2),
97
128
 
98
- newsletterMute: (jid) => executeWMexQuery({ newsletter_id: jid }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2),
129
+ newsletterMute: (jid) =>
130
+ executeWMexQuery({ newsletter_id: jid }, QueryIds.MUTE, XWAPaths.xwa2_newsletter_mute_v2),
99
131
 
100
- newsletterUnmute: (jid) => executeWMexQuery({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2),
132
+ newsletterUnmute: (jid) =>
133
+ executeWMexQuery({ newsletter_id: jid }, QueryIds.UNMUTE, XWAPaths.xwa2_newsletter_unmute_v2),
101
134
 
102
- newsletterUpdateName: async (jid, name) => newsletterUpdate(jid, { name }),
135
+ newsletterUpdateName: (jid, name) => newsletterUpdate(jid, { name }),
103
136
 
104
- newsletterUpdateDescription: async (jid, description) => newsletterUpdate(jid, { description }),
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: async (jid) => newsletterUpdate(jid, { picture: '' }),
144
+ newsletterRemovePicture: (jid) => newsletterUpdate(jid, { picture: '' }),
112
145
 
113
- newsletterReactMessage: async (jid, serverId, reaction) => {
114
- await query({ tag: 'message', attrs: { to: jid, ...(reaction ? {} : { edit: '7' }), type: 'reaction', server_id: serverId, id: generateMessageTag() }, content: [{ tag: 'reaction', attrs: reaction ? { code: reaction } : {} }] })
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 messageUpdateAttrs = { count: count.toString() }
119
- if (typeof since === 'number') messageUpdateAttrs.since = since.toString()
120
- if (after) messageUpdateAttrs.after = after.toString()
121
- return await query({ tag: 'iq', attrs: { id: generateMessageTag(), type: 'get', xmlns: 'newsletter', to: jid }, content: [{ tag: 'message_updates', attrs: messageUpdateAttrs }] })
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({ tag: 'iq', attrs: { id: generateMessageTag(), type: 'set', xmlns: 'newsletter', to: jid }, content: [{ tag: 'live_updates', attrs: {}, content: [] }] })
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({ newsletter_id: jid, input: { key: jid, type: 'NEWSLETTER', view_role: 'GUEST' }, fetch_viewer_metadata: true }, QueryIds.METADATA, XWAPaths.xwa2_newsletter_metadata)
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: async (jid, newOwnerJid) => executeWMexQuery({ newsletter_id: jid, user_id: newOwnerJid }, QueryIds.CHANGE_OWNER, XWAPaths.xwa2_newsletter_change_owner),
191
+ newsletterChangeOwner: (jid, newOwnerJid) =>
192
+ executeWMexQuery({ newsletter_id: jid, user_id: newOwnerJid }, QueryIds.CHANGE_OWNER, XWAPaths.xwa2_newsletter_change_owner),
144
193
 
145
- newsletterDemote: async (jid, userJid) => executeWMexQuery({ newsletter_id: jid, user_id: userJid }, QueryIds.DEMOTE, XWAPaths.xwa2_newsletter_demote),
194
+ newsletterDemote: (jid, userJid) =>
195
+ executeWMexQuery({ newsletter_id: jid, user_id: userJid }, QueryIds.DEMOTE, XWAPaths.xwa2_newsletter_demote),
146
196
 
147
- newsletterDelete: async (jid) => executeWMexQuery({ newsletter_id: jid }, QueryIds.DELETE, XWAPaths.xwa2_newsletter_delete_v2)
197
+ newsletterDelete: (jid) =>
198
+ executeWMexQuery({ newsletter_id: jid }, QueryIds.DELETE, XWAPaths.xwa2_newsletter_delete_v2)
148
199
  }
149
200
  }
@@ -145,7 +145,7 @@ class NexusHandler {
145
145
  const res = await this.utils.generateWAMessageContent(src, { upload: this.upload })
146
146
  prodImg = res?.imageMessage || res?.message?.imageMessage
147
147
  }
148
- const product = proto.Message.ProductMessage.Product.create({
148
+ const product = proto.Message.ProductMessage.ProductSnapshot.create({
149
149
  productId: p.productId,
150
150
  title: p.title || '',
151
151
  description: p.description || '',
@@ -242,10 +242,10 @@ class NexusHandler {
242
242
  starred: true,
243
243
  labels: ['Y', 'Important'],
244
244
  isHighlighted: true,
245
- businessMessageForwardInfo: proto.BusinessMessageForwardInfo.create({ businessOwnerJid: jid }),
246
- dataSharingContext: proto.DataSharingContext.create({ showMmDisclosure: true })
245
+ businessMessageForwardInfo: proto.ContextInfo.BusinessMessageForwardInfo.create({ businessOwnerJid: jid }),
246
+ dataSharingContext: proto.ContextInfo.DataSharingContext.create({ showMmDisclosure: true })
247
247
  })
248
- img.message.forwardedNewsletterMessageInfo = proto.ForwardedNewsletterMessageInfo.create({
248
+ img.message.forwardedNewsletterMessageInfo = proto.ContextInfo.ForwardedNewsletterMessageInfo.create({
249
249
  newsletterJid: '0@newsletter',
250
250
  serverMessageId: 1,
251
251
  newsletterName: 'WhatsApp',
@@ -274,36 +274,32 @@ class NexusHandler {
274
274
  async handleEvent(content, jid, quoted) {
275
275
  const e = content.eventMessage
276
276
  const msg = await this.genFromContent(jid, {
277
- viewOnceMessage: {
278
- message: {
279
- messageContextInfo: proto.MessageContextInfo.create({
280
- deviceListMetadata: {},
281
- deviceListMetadataVersion: 2,
282
- messageSecret: crypto.randomBytes(32),
283
- supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') })
284
- }),
285
- eventMessage: proto.Message.EventMessage.create({
286
- contextInfo: proto.ContextInfo.create({
287
- mentionedJid: [jid],
288
- participant: jid,
289
- remoteJid: 'status@broadcast',
290
- forwardedNewsletterMessageInfo: proto.ForwardedNewsletterMessageInfo.create({
291
- newsletterName: 'Nexus Events',
292
- newsletterJid: '120363422827915475@newsletter',
293
- serverMessageId: 1
294
- })
295
- }),
296
- isCanceled: e.isCanceled || false,
297
- name: e.name,
298
- description: e.description,
299
- location: e.location || { degreesLatitude: 0, degreesLongitude: 0, name: 'Location' },
300
- joinLink: e.joinLink || '',
301
- startTime: this.parseTime(e.startTime, Date.now()),
302
- endTime: this.parseTime(e.endTime, Date.now() + 3600000),
303
- extraGuestsAllowed: e.extraGuestsAllowed !== false
277
+ messageContextInfo: proto.MessageContextInfo.create({
278
+ deviceListMetadata: {},
279
+ deviceListMetadataVersion: 2,
280
+ messageSecret: crypto.randomBytes(32),
281
+ supportPayload: JSON.stringify({ version: 2, is_ai_message: true, should_show_system_message: true, ticket_id: crypto.randomBytes(16).toString('hex') })
282
+ }),
283
+ eventMessage: proto.Message.EventMessage.create({
284
+ contextInfo: proto.ContextInfo.create({
285
+ mentionedJid: [jid],
286
+ participant: jid,
287
+ remoteJid: 'status@broadcast',
288
+ forwardedNewsletterMessageInfo: proto.ContextInfo.ForwardedNewsletterMessageInfo.create({
289
+ newsletterName: 'Nexus Events',
290
+ newsletterJid: '120363422827915475@newsletter',
291
+ serverMessageId: 1
304
292
  })
305
- }
306
- }
293
+ }),
294
+ isCanceled: e.isCanceled || false,
295
+ name: e.name,
296
+ description: e.description,
297
+ location: e.location || { degreesLatitude: 0, degreesLongitude: 0, name: 'Location' },
298
+ joinLink: e.joinLink || '',
299
+ startTime: this.parseTime(e.startTime, Date.now()),
300
+ endTime: this.parseTime(e.endTime, Date.now() + 3600000),
301
+ extraGuestsAllowed: e.extraGuestsAllowed !== false
302
+ })
307
303
  }, { quoted })
308
304
  await this.sendMsg(jid, msg.message, { messageId: msg.key.id })
309
305
  return msg
@@ -322,7 +318,7 @@ class NexusHandler {
322
318
  contextInfo: proto.ContextInfo.create({
323
319
  isForwarded: true,
324
320
  forwardingScore: 1,
325
- forwardedNewsletterMessageInfo: proto.ForwardedNewsletterMessageInfo.create({
321
+ forwardedNewsletterMessageInfo: proto.ContextInfo.ForwardedNewsletterMessageInfo.create({
326
322
  newsletterName: p.newsletter?.newsletterName || 'Newsletter',
327
323
  newsletterJid: p.newsletter?.newsletterJid || '120363399602691477@newsletter',
328
324
  serverMessageId: 1000,
@@ -365,17 +361,23 @@ class NexusHandler {
365
361
  async handleOrderMessage(content, jid, quoted) {
366
362
  const o = content.orderMessage
367
363
  const thumb = await this.downloadBuffer(o.thumbnail)
364
+ const cleanJid = (id) => {
365
+ if (!id) return null
366
+ const [user] = id.split(':')
367
+ return user.includes('@') ? user : `${user}@s.whatsapp.net`
368
+ }
369
+ const seller = cleanJid(o.sellerJid) || cleanJid(this.user?.id) || cleanJid(jid) || '0@s.whatsapp.net'
368
370
  const msg = await this.genFromContent(jid, {
369
371
  orderMessage: proto.Message.OrderMessage.create({
370
- orderId: '7NEXUS25022008',
372
+ orderId: o.orderId || '7NEXUS25022008',
371
373
  thumbnail: thumb,
372
374
  itemCount: o.itemCount || 0,
373
- status: proto.Message.OrderMessage.OrderStatus.ACCEPTED,
374
- surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
375
+ status: 2, // ACCEPTED
376
+ surface: 1, // CATALOG
375
377
  message: o.message,
376
378
  orderTitle: o.orderTitle,
377
- sellerJid: '0@whatsapp.net',
378
- token: 'NEXUS_EXAMPLE_TOKEN',
379
+ sellerJid: seller,
380
+ token: o.token || 'NEXUS_EXAMPLE_TOKEN',
379
381
  totalAmount1000: o.totalAmount1000 || 0,
380
382
  totalCurrencyCode: o.totalCurrencyCode || 'IDR',
381
383
  messageVersion: 2
@@ -394,7 +396,7 @@ class NexusHandler {
394
396
  logger: this.opts.logger
395
397
  })
396
398
  const msg = await this.genFromContent(jid, {
397
- groupStatusMessageV2: proto.Message.GroupStatusMessageV2.create({ message: mediaContent })
399
+ groupStatusMessageV2: proto.Message.FutureProofMessage.create({ message: proto.Message.fromObject(mediaContent) })
398
400
  }, { userJid: jid })
399
401
  return await this.sendMsg(jid, msg.message, {
400
402
  messageId: msg.key.id,
@@ -434,7 +436,7 @@ class NexusHandler {
434
436
  subtitle: card.headerSubtitle || '',
435
437
  hasMediaAttachment: false,
436
438
  productMessage: proto.Message.ProductMessage.create({
437
- product: proto.Message.ProductMessage.Product.create({
439
+ product: proto.Message.ProductMessage.ProductSnapshot.create({
438
440
  productImage: imgMedia?.imageMessage,
439
441
  productId: card.productId || '123456',
440
442
  title: card.productTitle,
@@ -489,7 +491,7 @@ class NexusHandler {
489
491
  interactiveMessage: proto.Message.InteractiveMessage.create({
490
492
  body: proto.Message.InteractiveMessage.Body.create({ text: c.body || '' }),
491
493
  footer: proto.Message.InteractiveMessage.Footer.create({ text: c.footer || '' }),
492
- carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards })
494
+ carouselMessage: proto.Message.InteractiveMessage.CarouselMessage.create({ cards, messageVersion: 1 })
493
495
  })
494
496
  }
495
497
  }
@@ -87,12 +87,7 @@ export const makeSocket = (config) => {
87
87
  makeSignalRepository
88
88
  } = config
89
89
 
90
- if (printQRInTerminal) {
91
- logger?.warn(
92
- {},
93
- '⚠️ printQRInTerminal is deprecated. Listen to connection.update and handle QR yourself.'
94
- )
95
- }
90
+
96
91
 
97
92
  const url = typeof waWebSocketUrl === 'string' ? new URL(waWebSocketUrl) : waWebSocketUrl
98
93
 
@@ -114,6 +109,14 @@ export const makeSocket = (config) => {
114
109
  logger.info({ url: url.toString() }, 'Initiating WebSocket connection')
115
110
  ws.connect()
116
111
 
112
+ if (printQRInTerminal) {
113
+ import('qrcode-terminal').then(({ default: qrcode }) => {
114
+ ev.on('connection.update', ({ qr }) => {
115
+ if (qr) qrcode.generate(qr, { small: true })
116
+ })
117
+ })
118
+ }
119
+
117
120
  const ev = makeEventBuffer(logger)
118
121
  const { creds } = authState
119
122
  const keys = addTransactionCapability(authState.keys, logger, transactionOpts)
@@ -237,8 +240,17 @@ export const makeSocket = (config) => {
237
240
  const onWhatsApp = async (...phoneNumbers) => {
238
241
  let usyncQuery = new USyncQuery()
239
242
  let contactEnabled = false
240
- for (const jid of phoneNumbers) {
241
- if (isLidUser(jid)) { logger?.warn('LIDs are not supported with onWhatsApp'); continue }
243
+ for (let jid of phoneNumbers) {
244
+ if (isLidUser(jid)) {
245
+ const pn = await signalRepository.lidMapping.getPNForLID(jid)
246
+ if (pn) {
247
+ jid = pn
248
+ } else {
249
+ if (!contactEnabled) { contactEnabled = true; usyncQuery = usyncQuery.withContactProtocol() }
250
+ usyncQuery.withUser(new USyncUser().withId(jid))
251
+ continue
252
+ }
253
+ }
242
254
  if (!contactEnabled) { contactEnabled = true; usyncQuery = usyncQuery.withContactProtocol() }
243
255
  const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`
244
256
  usyncQuery.withUser(new USyncUser().withPhone(phone))
@@ -246,7 +258,7 @@ export const makeSocket = (config) => {
246
258
  if (usyncQuery.users.length === 0) return []
247
259
  const results = await executeUSyncQuery(usyncQuery)
248
260
  return results
249
- ? results.list.filter((a) => !!a.contact).map(({ contact, id }) => ({ jid: id, exists: contact }))
261
+ ? results.list.filter((a) => !!a.contact && a.id && a.id !== 'undefined').map(({ contact, id }) => ({ jid: id, exists: contact }))
250
262
  : []
251
263
  }
252
264
 
@@ -567,7 +579,8 @@ export const makeSocket = (config) => {
567
579
  }
568
580
  ev.emit('connection.update', { connection: 'close', lastDisconnect: { error, date: new Date() } })
569
581
  ev.removeAllListeners('connection.update')
570
- ev.destroy()
582
+ ev.removeAllListeners()
583
+ ev.destroy?.()
571
584
  }
572
585
 
573
586
  const sendPassiveIq = (tag) =>