@nexustechpro/baileys 2.0.6 → 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.
@@ -181,10 +181,19 @@ export const makeChatsSocket = (config) => {
181
181
  const usyncQuery = new USyncQuery()
182
182
  let contactEnabled = false
183
183
 
184
- for (const jid of jids) {
184
+ for (let jid of jids) {
185
185
  if (isLidUser(jid)) {
186
- logger.warn('LIDs not supported with onWhatsApp')
187
- continue
186
+ const pn = await signalRepository.lidMapping.getPNForLID(jid)
187
+ if (pn) {
188
+ jid = pn
189
+ } else {
190
+ if (!contactEnabled) {
191
+ contactEnabled = true
192
+ usyncQuery.withContactProtocol().withLIDProtocol()
193
+ }
194
+ usyncQuery.withUser(new USyncUser().withId(jid))
195
+ continue
196
+ }
188
197
  }
189
198
  if (!contactEnabled) {
190
199
  contactEnabled = true
@@ -201,18 +210,18 @@ export const makeChatsSocket = (config) => {
201
210
 
202
211
  return Promise.all(
203
212
  results.list
204
- .filter(a => a.contact === true)
213
+ .filter(a => a.contact === true && a.id && a.id !== 'undefined')
205
214
  .map(async ({ id, lid }) => {
206
215
  try {
207
216
  const businessProfile = await getBusinessProfile(id)
208
217
  const isBusiness = businessProfile && Object.keys(businessProfile).length > 0
209
218
  if (isBusiness) {
210
219
  const { wid, ...businessInfo } = businessProfile
211
- return { jid: id, exists: true, lid, status: 'business', businessInfo }
220
+ return { jid: id, exists: true, lid: lid || id, status: 'business', businessInfo }
212
221
  }
213
- return { jid: id, exists: true, lid, status: 'regular' }
222
+ return { jid: id, exists: true, lid: lid || id, status: 'regular' }
214
223
  } catch (error) {
215
- return { jid: id, exists: true, lid, status: 'error', error: error?.message }
224
+ return { jid: id, exists: true, lid: lid || id, status: 'error', error: error?.message }
216
225
  }
217
226
  })
218
227
  )
@@ -610,7 +619,9 @@ export const makeChatsSocket = (config) => {
610
619
 
611
620
  const historyMsg = getHistoryMsg(msg.message)
612
621
  const shouldProcessHistoryMsg = historyMsg
613
- ? shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
622
+ ? !!historyMsg.mediaKey?.length
623
+ && shouldSyncHistoryMessage(historyMsg)
624
+ && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
614
625
  : false
615
626
 
616
627
  // History sync progress tracking
@@ -388,7 +388,7 @@ export const makeMessagesSocket = (config) => {
388
388
  logger.debug({ jid }, 'relayMessage: auto-generating message from raw content')
389
389
  const generatedMsg = await generateWAMessage(jid, message, {
390
390
  logger, userJid: jidNormalizedUser(activeSender),
391
- getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 3000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
391
+ getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
392
392
  getProfilePicUrl: sock.profilePictureUrl, getCallLink: sock.createCallLink,
393
393
  upload: waUploadToServer, mediaCache: config.mediaCache, options: config.options,
394
394
  messageId: finalMsgId || generateMessageIDV2(activeSender), quoted
@@ -431,7 +431,12 @@ export const makeMessagesSocket = (config) => {
431
431
  return
432
432
  }
433
433
 
434
- if (messages?.pinInChatMessage || messages?.keepInChatMessage || (message.reactionMessage && !isStatus) || message.protocolMessage?.editedMessage) {
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.
439
+ if (messages?.pinInChatMessage || messages?.keepInChatMessage || message.reactionMessage || message.protocolMessage?.editedMessage) {
435
440
  extraAttrs['decrypt-fail'] = 'hide'
436
441
  }
437
442
 
@@ -448,7 +453,7 @@ export const makeMessagesSocket = (config) => {
448
453
 
449
454
  const participantsList = []
450
455
  if (isStatus) {
451
- 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))
452
457
  } else {
453
458
  let groupAddressingMode = 'lid'
454
459
  if (groupData) { participantsList.push(...groupData.participants.map(p => p.id)); groupAddressingMode = groupData?.addressingMode || groupAddressingMode }
@@ -474,10 +479,9 @@ export const makeMessagesSocket = (config) => {
474
479
  if (Array.isArray(patched)) throw new Boom('Per-jid patching not supported in groups')
475
480
 
476
481
  const bytes = encodeWAMessage(patched)
477
- const bytesU8 = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)
478
482
  const gAddressingMode = additionalAttributes?.addressing_mode || groupData?.addressingMode || 'lid'
479
483
  const groupSenderIdentity = gAddressingMode === 'lid' && meLid ? meLid : meId
480
- const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ group: destinationJid, data: bytesU8, meId: groupSenderIdentity })
484
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ group: destinationJid, data: bytes, meId: groupSenderIdentity })
481
485
 
482
486
  const senderKeyRecipients = devices
483
487
  .filter(d => !isHostedLidUser(d.jid) && !isHostedPnUser(d.jid) && d.device !== 99)
@@ -487,7 +491,12 @@ export const makeMessagesSocket = (config) => {
487
491
  logger.debug({ senderKeyJids: senderKeyRecipients }, 'sending sender key')
488
492
  const senderKeyMsg = { senderKeyDistributionMessage: { axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage, groupId: destinationJid } }
489
493
  await assertSessions(senderKeyRecipients)
490
- const result = await createParticipantNodes(senderKeyRecipients, senderKeyMsg, {})
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)
491
500
  shouldIncludeDeviceIdentity = shouldIncludeDeviceIdentity || result.shouldIncludeDeviceIdentity
492
501
  participants.push(...result.nodes)
493
502
  }
@@ -505,10 +514,9 @@ export const makeMessagesSocket = (config) => {
505
514
  if (Array.isArray(patched)) throw new Boom('Per-jid patching not supported in groups')
506
515
 
507
516
  const bytes = encodeWAMessage(patched)
508
- const bytesU8 = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength)
509
517
  const gAddressingMode = additionalAttributes?.addressing_mode || groupData?.addressingMode || 'lid'
510
518
  const groupSenderIdentity = gAddressingMode === 'lid' && meLid ? meLid : meId
511
- const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ group: destinationJid, data: bytesU8, meId: groupSenderIdentity })
519
+ const { ciphertext, senderKeyDistributionMessage } = await signalRepository.encryptGroupMessage({ group: destinationJid, data: bytes, meId: groupSenderIdentity })
512
520
 
513
521
  const senderKeyMsg = { senderKeyDistributionMessage: { axolotlSenderKeyDistributionMessage: senderKeyDistributionMessage, groupId: destinationJid } }
514
522
  await assertSessions([participant.jid])
@@ -715,7 +723,7 @@ export const makeMessagesSocket = (config) => {
715
723
  }
716
724
  }
717
725
 
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 }) })
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 }) })
719
727
  const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update')
720
728
 
721
729
  const sendMessage = async (jid, content, options = {}) => {
@@ -744,9 +752,7 @@ export const makeMessagesSocket = (config) => {
744
752
  content = { ...rest, interactiveMessage: interactive }
745
753
  }
746
754
 
747
- // never route status reactions through nexus
748
- const isStatusJid = jid === 'status@broadcast'
749
- const messageType = !isStatusJid && nexus.detectType(content)
755
+ const messageType = nexus.detectType(content)
750
756
  if (messageType) return await nexus.processMessage(content, jid, quoted)
751
757
 
752
758
  if (content.disappearingMessagesInChat && isJidGroup(jid)) {
@@ -758,17 +764,29 @@ export const makeMessagesSocket = (config) => {
758
764
  }
759
765
 
760
766
 
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
775
+ }
776
+ }
777
+
761
778
  const fullMsg = await generateWAMessage(jid, content, {
762
779
  logger,
763
780
  userJid: jidNormalizedUser(activeSender),
764
- getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 3000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
781
+ getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
765
782
  getProfilePicUrl: sock.profilePictureUrl,
766
783
  getCallLink: sock.createCallLink,
767
784
  upload: waUploadToServer,
768
785
  mediaCache: config.mediaCache,
769
786
  options: config.options,
770
787
  messageId: generateMessageIDV2(activeSender),
771
- ...options
788
+ ...options,
789
+ ephemeralExpiration: ephemeralDuration
772
790
  })
773
791
 
774
792
  const additionalAttributes = {}, additionalNodes = []
@@ -860,7 +878,7 @@ export const makeMessagesSocket = (config) => {
860
878
  try {
861
879
  msg = await generateWAMessage(STORIES_JID, msgContent, {
862
880
  logger, userJid,
863
- getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 3000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
881
+ getUrlInfo: text => getUrlInfo(text, { thumbnailWidth: linkPreviewImageThumbnailWidth, fetchOpts: { timeout: 4000, ...(httpRequestOptions || {}) }, logger, uploadImage: generateHighQualityLinkPreview ? waUploadToServer : undefined }),
864
882
  upload: async (encFilePath, opts) => { const up = await waUploadToServer(encFilePath, { ...opts }); return up },
865
883
  mediaCache: config.mediaCache, options: config.options,
866
884
  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) =>