@badzz88/baileys 8.5.2 → 8.5.4

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.
Files changed (110) hide show
  1. package/README.md +1 -1
  2. package/WAProto/WAProto.proto +1256 -75
  3. package/WAProto/index.d.ts +36042 -19649
  4. package/WAProto/index.js +181990 -135645
  5. package/engine-requirements.js +7 -15
  6. package/lib/Defaults/index.js +22 -17
  7. package/lib/Signal/Group/group_cipher.js +5 -5
  8. package/lib/Signal/Group/keyhelper.js +4 -4
  9. package/lib/Signal/Group/sender-chain-key.js +2 -2
  10. package/lib/Signal/Group/sender-key-distribution-message.js +1 -1
  11. package/lib/Signal/Group/sender-key-message.js +3 -3
  12. package/lib/Signal/Group/sender-key-record.js +1 -1
  13. package/lib/Signal/Group/sender-message-key.js +5 -5
  14. package/lib/Signal/libsignal.js +39 -59
  15. package/lib/Signal/lid-mapping.js +28 -10
  16. package/lib/Socket/aigroups.js +5 -5
  17. package/lib/Socket/business.js +3 -11
  18. package/lib/Socket/chats.js +668 -319
  19. package/lib/Socket/communities.js +85 -89
  20. package/lib/Socket/graphql.js +435 -228
  21. package/lib/Socket/groups.js +132 -211
  22. package/lib/Socket/index.js +9 -7
  23. package/lib/Socket/interactive-handler.js +117 -60
  24. package/lib/Socket/interop.js +56 -175
  25. package/lib/Socket/managed-account.js +79 -48
  26. package/lib/Socket/messages-recv.js +898 -340
  27. package/lib/Socket/messages-send.js +462 -160
  28. package/lib/Socket/mex.js +40 -53
  29. package/lib/Socket/newsletter.js +291 -291
  30. package/lib/Socket/privacy.js +77 -265
  31. package/lib/Socket/registration.js +164 -157
  32. package/lib/Socket/socket.js +181 -199
  33. package/lib/Socket/username.js +144 -227
  34. package/lib/Store/make-cache-manager-store.js +2 -2
  35. package/lib/Store/make-in-memory-store.js +25 -141
  36. package/lib/Types/Auth.js +3 -3
  37. package/lib/Types/Label.js +1 -1
  38. package/lib/Types/LabelAssociation.js +1 -1
  39. package/lib/Types/Newsletter.js +96 -65
  40. package/lib/Types/State.js +4 -4
  41. package/lib/Types/index.js +1 -1
  42. package/lib/Utils/auth-utils.js +52 -57
  43. package/lib/Utils/browser-utils.js +1 -1
  44. package/lib/Utils/business.js +2 -6
  45. package/lib/Utils/chat-utils.js +102 -38
  46. package/lib/Utils/crypto.js +54 -84
  47. package/lib/Utils/decode-wa-message.js +316 -83
  48. package/lib/Utils/event-buffer.js +37 -61
  49. package/lib/Utils/generics.js +157 -103
  50. package/lib/Utils/group-history.js +3 -12
  51. package/lib/Utils/history.js +15 -15
  52. package/lib/Utils/index.js +1 -5
  53. package/lib/Utils/link-preview.js +3 -8
  54. package/lib/Utils/lt-hash.js +2 -35
  55. package/lib/Utils/make-mutex.js +1 -1
  56. package/lib/Utils/message-inspect.js +393 -0
  57. package/lib/Utils/message-retry-manager.js +27 -92
  58. package/lib/Utils/messages-media.js +159 -130
  59. package/lib/Utils/messages.js +144 -566
  60. package/lib/Utils/meta-ai-msmsg.js +62 -202
  61. package/lib/Utils/noise-handler.js +19 -21
  62. package/lib/Utils/offline-node-processor.js +3 -8
  63. package/lib/Utils/pre-key-manager.js +13 -23
  64. package/lib/Utils/process-message.js +197 -106
  65. package/lib/Utils/reporting-utils.js +2 -2
  66. package/lib/Utils/signal.js +28 -63
  67. package/lib/Utils/stanza-ack.js +39 -12
  68. package/lib/Utils/sync-action-utils.js +4 -7
  69. package/lib/Utils/tc-token-utils.js +20 -25
  70. package/lib/Utils/use-multi-file-auth-state.js +30 -14
  71. package/lib/Utils/validate-connection.js +59 -19
  72. package/lib/WABinary/decode.js +50 -16
  73. package/lib/WABinary/encode.js +54 -224
  74. package/lib/WABinary/generic-utils.js +7 -97
  75. package/lib/WABinary/jid-utils.js +27 -14
  76. package/lib/WAM/encode.js +7 -7
  77. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +58 -1
  78. package/lib/WAUSync/Protocols/USyncContactProtocol.js +7 -1
  79. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +13 -5
  80. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +1 -4
  81. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +20 -7
  82. package/lib/WAUSync/Protocols/USyncNewsletterProtocol.js +865 -0
  83. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +1 -1
  84. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +2 -2
  85. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +7 -2
  86. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +1 -1
  87. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +2 -1
  88. package/lib/WAUSync/Protocols/index.js +1 -0
  89. package/lib/WAUSync/USyncBackoff.js +26 -0
  90. package/lib/WAUSync/USyncQuery.js +77 -6
  91. package/lib/WAUSync/USyncUser.js +16 -8
  92. package/lib/WAUSync/index.js +1 -0
  93. package/lib/antiban.js +608 -854
  94. package/lib/index.js +3 -3
  95. package/package.json +16 -19
  96. package/lib/Socket/luxu.js +0 -387
  97. package/lib/Socket/text-router.js +0 -83
  98. package/lib/Store/keyed-db.js +0 -108
  99. package/lib/Types/Mex.js +0 -112
  100. package/lib/Utils/command-loader.d.ts +0 -31
  101. package/lib/Utils/command-loader.js +0 -100
  102. package/lib/Utils/companion-reg-client-utils.js +0 -42
  103. package/lib/Utils/curve25519-js.js +0 -275
  104. package/lib/Utils/native-bridge.js +0 -82
  105. package/lib/Utils/session-pool.d.ts +0 -16
  106. package/lib/Utils/session-pool.js +0 -94
  107. package/lib/Utils/sticker.d.ts +0 -17
  108. package/lib/Utils/sticker.js +0 -145
  109. package/lib/Utils/view-once-cache.d.ts +0 -9
  110. package/lib/Utils/view-once-cache.js +0 -79
@@ -14,7 +14,7 @@ const {
14
14
  } = require('../Utils/index.js')
15
15
  const { isJidGroup, isPnUser, jidNormalizedUser, STORIES_JID } = require('../WABinary/index.js')
16
16
 
17
- class InteractiveMessageHandler {
17
+ class badzz88 {
18
18
  constructor(waUploadToServer, relayMessageFn, config, sock) {
19
19
  this.relayMessage = relayMessageFn
20
20
  this.waUploadToServer = waUploadToServer
@@ -24,13 +24,12 @@ class InteractiveMessageHandler {
24
24
  detectType(content) {
25
25
  if (content.requestPaymentMessage) return 'PAYMENT'
26
26
  if (content.productMessage) return 'PRODUCT'
27
+ if (content.interactiveButtons) return 'INTERACTIVE_BUTTONS'
27
28
  if (content.interactiveMessage) return 'INTERACTIVE'
28
29
  if (content.albumMessage || content.album) return 'ALBUM'
29
30
  if (content.eventMessage) return 'EVENT'
30
31
  if (content.pollResultMessage) return 'POLL_RESULT'
31
32
  if (content.groupStatusMessage) return 'GROUP_STORY'
32
- if (content.orderMessage) return 'ORDER'
33
- if (content.groupLabel) return 'GROUP_LABEL'
34
33
  return null
35
34
  }
36
35
  async handlePayment(content, quoted) {
@@ -223,6 +222,114 @@ class InteractiveMessageHandler {
223
222
  }
224
223
  return { interactiveMessage }
225
224
  }
225
+ async handleInteractiveButtons(content, _jid, _quoted) {
226
+ const {
227
+ text,
228
+ caption,
229
+ title,
230
+ subtitle,
231
+ footer,
232
+ interactiveButtons,
233
+ hasMediaAttachment,
234
+ image,
235
+ video,
236
+ document,
237
+ mimetype,
238
+ jpegThumbnail,
239
+ location,
240
+ product,
241
+ businessOwnerJid
242
+ } = content
243
+ const bodyText = text || caption || ''
244
+ const buttons = interactiveButtons.map(btn => ({
245
+ name: btn.name,
246
+ buttonParamsJson:
247
+ typeof btn.buttonParamsJson === 'string' ? btn.buttonParamsJson : JSON.stringify(btn.buttonParamsJson)
248
+ }))
249
+ let headerContent = {}
250
+ let mediaAttached = typeof hasMediaAttachment === 'boolean' ? hasMediaAttachment : false
251
+ if (image) {
252
+ const src = typeof image === 'object' && image.url ? { image: { url: image.url } } : { image }
253
+ const uploaded = await prepareWAMessageMedia(src, { upload: this.waUploadToServer })
254
+ headerContent = { ...uploaded }
255
+ mediaAttached = typeof hasMediaAttachment === 'boolean' ? hasMediaAttachment : true
256
+ } else if (video) {
257
+ const src = typeof video === 'object' && video.url ? { video: { url: video.url } } : { video }
258
+ const uploaded = await prepareWAMessageMedia(src, { upload: this.waUploadToServer })
259
+ headerContent = { ...uploaded }
260
+ mediaAttached = typeof hasMediaAttachment === 'boolean' ? hasMediaAttachment : true
261
+ } else if (document) {
262
+ const docPayload =
263
+ typeof document === 'object' && document.url ? { document: { url: document.url } } : { document }
264
+ if (mimetype) docPayload.mimetype = mimetype
265
+ const uploaded = await prepareWAMessageMedia(docPayload, { upload: this.waUploadToServer })
266
+ if (jpegThumbnail) {
267
+ uploaded.documentMessage.jpegThumbnail =
268
+ typeof jpegThumbnail === 'string' ? Buffer.from(jpegThumbnail, 'base64') : jpegThumbnail
269
+ }
270
+ headerContent = { ...uploaded }
271
+ mediaAttached = typeof hasMediaAttachment === 'boolean' ? hasMediaAttachment : true
272
+ } else if (location) {
273
+ headerContent = {
274
+ locationMessage: {
275
+ degreesLatitude: location.degressLatitude || location.degreesLatitude || 0,
276
+ degreesLongitude: location.degressLongitude || location.degreesLongitude || 0,
277
+ name: location.name || ''
278
+ }
279
+ }
280
+ mediaAttached = typeof hasMediaAttachment === 'boolean' ? hasMediaAttachment : true
281
+ } else if (product) {
282
+ let productImage
283
+ if (product.productImage) {
284
+ const imgSrc =
285
+ typeof product.productImage === 'object' && product.productImage.url
286
+ ? { image: { url: product.productImage.url } }
287
+ : { image: product.productImage }
288
+ const uploaded = await prepareWAMessageMedia(imgSrc, { upload: this.waUploadToServer })
289
+ productImage = uploaded.imageMessage
290
+ }
291
+ headerContent = {
292
+ productMessage: {
293
+ product: {
294
+ productImage,
295
+ productId: product.productId,
296
+ title: product.title,
297
+ description: product.description,
298
+ currencyCode: product.currencyCode || 'IDR',
299
+ priceAmount1000: product.priceAmount1000,
300
+ retailerId: product.retailerId,
301
+ url: product.url,
302
+ productImageCount: product.productImageCount || 1
303
+ },
304
+ businessOwnerJid: businessOwnerJid || '0@s.whatsapp.net'
305
+ }
306
+ }
307
+ mediaAttached = typeof hasMediaAttachment === 'boolean' ? hasMediaAttachment : true
308
+ }
309
+ const interactiveMessage = {
310
+ body: { text: bodyText },
311
+ footer: { text: footer || '' },
312
+ header: {
313
+ title: title || '',
314
+ subtitle: subtitle || '',
315
+ hasMediaAttachment: mediaAttached,
316
+ ...headerContent
317
+ },
318
+ nativeFlowMessage: { buttons }
319
+ }
320
+ return {
321
+ viewOnceMessage: {
322
+ message: {
323
+ messageContextInfo: {
324
+ deviceListMetadata: {},
325
+ deviceListMetadataVersion: 2,
326
+ messageSecret: crypto.randomBytes(32)
327
+ },
328
+ interactiveMessage
329
+ }
330
+ }
331
+ }
332
+ }
226
333
  async handleAlbum(content, jid, quoted) {
227
334
  const array = content.albumMessage || content.album
228
335
  const ctxInfo = content.contextInfo || {}
@@ -237,7 +344,7 @@ class InteractiveMessageHandler {
237
344
  expectedVideoCount: array.filter(a => 'video' in a).length
238
345
  }
239
346
  },
240
- { userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ''), browser: this.config?.browser }
347
+ { userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || '') }
241
348
  )
242
349
  await this.relayMessage(jid, album.message, {
243
350
  messageId: album.key.id
@@ -287,18 +394,18 @@ class InteractiveMessageHandler {
287
394
  joinLink: eventData.joinLink || '',
288
395
  startTime:
289
396
  typeof eventData.startTime === 'string'
290
- ? parseInt(eventData.startTime)
397
+ ? parseInt(eventData.startTime, 10)
291
398
  : eventData.startTime || Date.now(),
292
399
  endTime:
293
400
  typeof eventData.endTime === 'string'
294
- ? parseInt(eventData.endTime)
401
+ ? parseInt(eventData.endTime, 10)
295
402
  : eventData.endTime || Date.now() + 3600000,
296
403
  extraGuestsAllowed: eventData.extraGuestsAllowed !== false
297
404
  }
298
405
  }
299
406
  }
300
407
  },
301
- { quoted, userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ''), browser: this.config?.browser }
408
+ { quoted, userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || '') }
302
409
  )
303
410
  await this.relayMessage(jid, msg.message, {
304
411
  messageId: msg.key.id
@@ -319,7 +426,7 @@ class InteractiveMessageHandler {
319
426
  }))
320
427
  }
321
428
  },
322
- { quoted, userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ''), browser: this.config?.browser }
429
+ { quoted, userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || '') }
323
430
  )
324
431
  await this.relayMessage(jid, msg.message, {
325
432
  messageId: msg.key.id
@@ -347,55 +454,6 @@ class InteractiveMessageHandler {
347
454
  messageId: generateMessageID()
348
455
  })
349
456
  }
350
- async handleOrderMessage(content, jid, quoted) {
351
- const orderData = content.orderMessage
352
- const orderMsg = await generateWAMessageFromContent(jid, {
353
- orderMessage: {
354
- orderId: orderData.orderId || 'ORDER' + generateMessageID(),
355
- thumbnail: orderData.thumbnail || null,
356
- itemCount: orderData.itemCount || 0,
357
- status: orderData.status || 'ACCEPTED',
358
- surface: orderData.surface || 'CATALOG',
359
- message: orderData.message,
360
- orderTitle: orderData.orderTitle,
361
- sellerJid: orderData.sellerJid || jid,
362
- token: orderData.token || generateMessageID(),
363
- totalAmount1000: orderData.totalAmount1000 || 0,
364
- totalCurrencyCode: orderData.totalCurrencyCode || 'IDR',
365
- messageVersion: 2
366
- }
367
- }, { quoted, browser: this.config?.browser })
368
- await this.relayMessage(jid, orderMsg.message, {
369
- messageId: orderMsg.key.id
370
- })
371
- return orderMsg
372
- }
373
- async handleGbLabel(content, jid) {
374
- const labelData = content.groupLabel
375
- if (!isJidGroup(jid)) {
376
- throw new Error('handleGbLabel requires a group jid')
377
- }
378
- const msg = await generateWAMessageFromContent(jid, {
379
- protocolMessage: {
380
- type: 'GROUP_MEMBER_LABEL_CHANGE',
381
- memberLabel: {
382
- label: labelData.labelText.slice(0, 30)
383
- }
384
- }
385
- }, { browser: this.config?.browser })
386
- return await this.relayMessage(jid, msg.message, {
387
- additionalNodes: [
388
- {
389
- tag: 'meta',
390
- attrs: {
391
- tag_reason: 'user_update',
392
- appdata: 'member_tag'
393
- },
394
- content: undefined
395
- }
396
- ]
397
- })
398
- }
399
457
  async sendStatusWhatsApp(content, jids = []) {
400
458
  const userJid = jidNormalizedUser(this.sock.authState.creds.me.id)
401
459
  const allUsers = new Set()
@@ -499,8 +557,7 @@ class InteractiveMessageHandler {
499
557
  }
500
558
  }
501
559
  const statusMsg = await generateWAMessageFromContent(normalizedId, protocolMessage, {
502
- userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ''),
503
- browser: this.config?.browser
560
+ userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || '')
504
561
  })
505
562
  await this.relayMessage(normalizedId, statusMsg.message, {
506
563
  additionalNodes: [
@@ -519,4 +576,4 @@ class InteractiveMessageHandler {
519
576
  }
520
577
  }
521
578
 
522
- module.exports = { InteractiveMessageHandler }
579
+ module.exports = { badzz88 }
@@ -5,47 +5,36 @@ exports.makeInteropSocket = void 0
5
5
  const { getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET } = require('../WABinary')
6
6
  const { executeWMexQuery } = require('./mex')
7
7
 
8
+
8
9
  const INTEROP_MEX_QUERY_IDS = {
9
- CREATE_GROUP: '25726817620301612', // GroupsCreateInteropGroup
10
- LEAVE_GROUP: '25346167795013270', // LeaveInteropGroup
11
- ADD_PARTICIPANTS: '25732168276369452', // AddParticipantsToInteropGroup
12
- QUERY_GROUP_INFO: '32734144032867936', // QueryInteropGroupInfo
13
- PRIVACY_SETTINGS_QUERY: '24849123668112656', // InteropPrivacySettingsQuery
14
- PRIVACY_SETTINGS_UPDATE: '25421856497452764', // InteropPrivacySettingsUpdate
15
- PRIVACY_SETTINGS_WITH_CONTACT_LIST: '24913399124998600' // InteropPrivacySettingWithContactListUpdate
10
+ CREATE_GROUP: '25726817620301611',
11
+ LEAVE_GROUP: '25346167795013271',
12
+ ADD_PARTICIPANTS: '25732168276369451',
13
+ QUERY_GROUP_INFO: '32734144032867938',
14
+ PRIVACY_SETTINGS_QUERY: '24849123668112654',
15
+ PRIVACY_SETTINGS_UPDATE: '25421856497452763',
16
+ PRIVACY_SETTINGS_WITH_CONTACT_LIST: '24913399124998598'
16
17
  }
17
18
 
18
- /**
19
- * Known integrator IDs (assigned by WhatsApp).
20
- * BirdyChat → identifier_type="email"
21
- * Haiket → identifier_type="pn"
22
- */
19
+
23
20
  const INTEGRATOR_BIRDYCHAT = 12
24
21
  const INTEGRATOR_HAIKET = 13
25
22
 
26
- /**
27
- * TOS trackable results sent by WA on first interop opt-in.
28
- * 105 = TOS shown, 160 = TOS accepted.
29
- */
23
+
30
24
  const TOS_TRACKABLE_ID = '20240306'
31
25
  const TOS_RESULT_SHOWN = '105'
32
26
  const TOS_RESULT_ACCEPTED = '160'
33
27
 
34
- /** Maximum users per batch lookup (enforced server-side too). */
28
+
35
29
  const INTEROP_BATCH_MAX = 256
36
30
 
37
31
  const makeInteropSocket = sock => {
38
- const { query, generateMessageTag, logger, signalRepository } = sock
32
+ const { query, generateMessageTag, logger, signalRepository, masqueradeAsPrimary } = sock
39
33
 
40
34
  const mexQuery = (variables, queryId, dataPath) =>
41
35
  executeWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
42
36
 
43
- /**
44
- * Fetch all available interop integrators from the server.
45
- * Each integrator carries: id, name, status, icon, identifierType,
46
- * optedIn, features.groupMessaging.
47
- * status can be "active" | "onboarding" | "removed".
48
- */
37
+
49
38
  const fetchIntegrators = async () => {
50
39
  const result = await query({
51
40
  tag: 'iq',
@@ -62,14 +51,14 @@ const makeInteropSocket = sock => {
62
51
  return getBinaryNodeChildren(listNode, 'integrator').map(node => {
63
52
  const featuresNode = getBinaryNodeChild(node, 'features')
64
53
  return {
65
- id: parseInt(node.attrs.id),
54
+ id: parseInt(node.attrs.id, 10),
66
55
  name: node.attrs.name,
67
- // "active" | "onboarding" | "removed"
56
+
68
57
  status: node.attrs.status,
69
58
  icon: node.attrs.icon,
70
- // "email" | "pn" | "username"
59
+
71
60
  identifierType: node.attrs.identifier_type,
72
- // Whether we are already opted-in to this integrator
61
+
73
62
  optedIn: node.attrs.opted_in === 'true' || globalOptedIn,
74
63
  features: {
75
64
  groupMessaging: featuresNode?.attrs?.group_messaging === 'true'
@@ -78,7 +67,7 @@ const makeInteropSocket = sock => {
78
67
  })
79
68
  }
80
69
 
81
- /** Send a single TOS trackable item (shown or accepted). */
70
+
82
71
  const sendTOSTrackable = async (id, result) => {
83
72
  await query({
84
73
  tag: 'iq',
@@ -87,20 +76,13 @@ const makeInteropSocket = sock => {
87
76
  })
88
77
  }
89
78
 
90
- /**
91
- * Accept TOS for interop.
92
- * Sends two trackable items: shown (105) then accepted (160).
93
- * Must be called before opting in.
94
- */
79
+
95
80
  const acceptInteropTOS = async () => {
96
81
  await sendTOSTrackable(TOS_TRACKABLE_ID, TOS_RESULT_SHOWN)
97
82
  await sendTOSTrackable(TOS_TRACKABLE_ID, TOS_RESULT_ACCEPTED)
98
83
  }
99
84
 
100
- /**
101
- * Opt in to a list of integrators (by numeric ID).
102
- * Defaults to both known integrators (BirdyChat + Haiket).
103
- */
85
+
104
86
  const optInIntegrators = async (integratorIds = [INTEGRATOR_BIRDYCHAT, INTEGRATOR_HAIKET]) => {
105
87
  await query({
106
88
  tag: 'iq',
@@ -124,10 +106,7 @@ const makeInteropSocket = sock => {
124
106
  })
125
107
  }
126
108
 
127
- /**
128
- * Opt out of a list of integrators (by numeric ID).
129
- * Mirror of optInIntegrators — uses <opt_out_integrators>.
130
- */
109
+
131
110
  const optOutIntegrators = async (integratorIds = [INTEGRATOR_BIRDYCHAT, INTEGRATOR_HAIKET]) => {
132
111
  await query({
133
112
  tag: 'iq',
@@ -151,18 +130,7 @@ const makeInteropSocket = sock => {
151
130
  })
152
131
  }
153
132
 
154
- /**
155
- * Resolve one or more interop users by external ID in a single IQ.
156
- * Each entry: { externalId, integratorId }
157
- * - BirdyChat (12): externalId = email address
158
- * - Haiket (13): externalId = phone number string (e.g. "19146088152")
159
- *
160
- * Max 256 entries per call (server limit).
161
- *
162
- * Returns an array of results:
163
- * { jid, externalId, normalizedExternalId, integratorId } on success
164
- * { externalId, integratorId, error: { code, text } } on failure
165
- */
133
+
166
134
  const resolveInteropUsers = async users => {
167
135
  if (!users || users.length === 0) return []
168
136
  if (users.length > INTEROP_BATCH_MAX) {
@@ -192,9 +160,9 @@ const makeInteropSocket = sock => {
192
160
  if (errorNode) {
193
161
  return {
194
162
  externalId: userNode.attrs.external_id,
195
- integratorId: parseInt(userNode.attrs.integrator_id),
163
+ integratorId: parseInt(userNode.attrs.integrator_id, 10),
196
164
  error: {
197
- code: parseInt(errorNode.attrs.code),
165
+ code: parseInt(errorNode.attrs.code, 10),
198
166
  text: errorNode.attrs.text
199
167
  }
200
168
  }
@@ -203,25 +171,18 @@ const makeInteropSocket = sock => {
203
171
  jid: userNode.attrs.jid,
204
172
  externalId: userNode.attrs.external_id,
205
173
  normalizedExternalId: userNode.attrs.normalized_external_id,
206
- integratorId: parseInt(userNode.attrs.integrator_id)
174
+ integratorId: parseInt(userNode.attrs.integrator_id, 10)
207
175
  }
208
176
  })
209
177
  }
210
178
 
211
- /**
212
- * Convenience wrapper: resolve a single interop user.
213
- * Returns the result object or null.
214
- */
179
+
215
180
  const resolveInteropUser = async (externalId, integratorId) => {
216
181
  const results = await resolveInteropUsers([{ externalId, integratorId }])
217
182
  return results[0] ?? null
218
183
  }
219
184
 
220
- /**
221
- * Get reachability settings for interop contacts.
222
- * Returns the raw <reachability_settings> node content.
223
- * WA uses this as the interop presence/subscription mechanism.
224
- */
185
+
225
186
  const getReachabilitySettings = async () => {
226
187
  const result = await query({
227
188
  tag: 'iq',
@@ -234,17 +195,13 @@ const makeInteropSocket = sock => {
234
195
  enabled: settingsNode.attrs?.enabled,
235
196
  users: getBinaryNodeChildren(settingsNode, 'user').map(n => ({
236
197
  externalId: n.attrs.external_id,
237
- integratorId: parseInt(n.attrs.integrator_id),
198
+ integratorId: parseInt(n.attrs.integrator_id, 10),
238
199
  jid: n.attrs.jid
239
200
  }))
240
201
  }
241
202
  }
242
203
 
243
- /**
244
- * Set reachability settings (subscribe to presence) for interop contacts.
245
- * users: array of { externalId, integratorId }
246
- * enabled: "true" | "false"
247
- */
204
+
248
205
  const setReachabilitySettings = async (users, enabled = 'true') => {
249
206
  await query({
250
207
  tag: 'iq',
@@ -265,10 +222,7 @@ const makeInteropSocket = sock => {
265
222
  })
266
223
  }
267
224
 
268
- /**
269
- * Block or unblock an interop user via the w:interop blocklist.
270
- * Different from regular WA block (xmlns=blocklist).
271
- */
225
+
272
226
  const updateInteropBlockStatus = async (jid, action) => {
273
227
  await query({
274
228
  tag: 'iq',
@@ -286,10 +240,7 @@ const makeInteropSocket = sock => {
286
240
  const blockInteropUser = jid => updateInteropBlockStatus(jid, 'block')
287
241
  const unblockInteropUser = jid => updateInteropBlockStatus(jid, 'unblock')
288
242
 
289
- /**
290
- * Report an interop contact as spam.
291
- * spam_flow="account_info_block" = block + report (WA Web default).
292
- */
243
+
293
244
  const reportInteropSpam = async (jid, spamFlow = 'account_info_block') => {
294
245
  await query({
295
246
  tag: 'iq',
@@ -298,10 +249,7 @@ const makeInteropSocket = sock => {
298
249
  })
299
250
  }
300
251
 
301
- /**
302
- * Mark an interop JID as trusted_contact in privacy tokens.
303
- * WA calls this automatically after the first outgoing message.
304
- */
252
+
305
253
  const trustInteropContact = async jid => {
306
254
  const t = Math.floor(Date.now() / 1000).toString()
307
255
  await query({
@@ -317,15 +265,7 @@ const makeInteropSocket = sock => {
317
265
  })
318
266
  }
319
267
 
320
- /**
321
- * Full interop initialization sequence matching WA Web:
322
- * 1. Fetch available integrators
323
- * 2. Accept TOS (shown 105 + accepted 160)
324
- * 3. Opt-in to all active/onboarding integrators
325
- *
326
- * Silently tolerates TOS/opt-in errors.
327
- * Returns the full integrator list.
328
- */
268
+
329
269
  const initInterop = async () => {
330
270
  let integrators
331
271
  try {
@@ -346,30 +286,23 @@ const makeInteropSocket = sock => {
346
286
  } catch (err) {
347
287
  logger.warn({ err }, 'interop: failed to opt-in integrators')
348
288
  }
349
- logger.info({ integrators: toOptIn.map(i => i.name) }, 'interop: initialized')
289
+ logger.info({ integrators: toOptIn.map(i => i.name) }, 'interop: opted in')
290
+ logger.warn(
291
+ { integrators: toOptIn.map(i => i.name) },
292
+ 'interop: receiving messages from interop contacts (BirdyChat/Haiket) is NOT YET SUPPORTED. ' +
293
+ 'The interop bridge only delivers to device 0 (primary phone) via a single Signal session — ' +
294
+ 'no fan-out to companion devices. Opt-in and sending may work, but inbound messages will not arrive.'
295
+ )
350
296
  return integrators
351
297
  }
352
298
 
353
- /**
354
- * Reset the Signal session with an interop contact and force a fresh pkmsg handshake.
355
- * Call this when incoming messages from an interop contact are not arriving or
356
- * failing to decrypt — it clears the stale session so the next sendMessage
357
- * generates a new pre-key message and re-establishes both directions.
358
- */
299
+
359
300
  const resetInteropSession = async jid => {
360
301
  await signalRepository.deleteSession([jid])
361
302
  logger.info({ jid }, '[interop] session reset — next send will use pkmsg')
362
303
  }
363
304
 
364
- /**
365
- * Create an interop group via MEX.
366
- * Confirmed from C80913Xf.java opcode 15:
367
- * input: { participants: [{ jid }] }
368
- * dataPath: xwa2_interop_group_create
369
- *
370
- * @param {string[]} participants - Interop JIDs to add
371
- * @returns {{ gid, creationTime, creator, participants }}
372
- */
305
+
373
306
  const createInteropGroup = async participants => {
374
307
  const result = await mexQuery(
375
308
  { input: { participants: participants.map(jid => ({ jid })) } },
@@ -380,15 +313,7 @@ const makeInteropSocket = sock => {
380
313
  return result
381
314
  }
382
315
 
383
- /**
384
- * Leave one or more interop groups via MEX.
385
- * Confirmed from C80913Xf.java opcode 28:
386
- * input: { groups_to_leave: [{ gid }] }
387
- * dataPath: xwa2_interop_group_leave
388
- *
389
- * gid is the raw interop group ID (without @g.us suffix).
390
- * @param {string|string[]} jids - Interop group JID(s)
391
- */
316
+
392
317
  const leaveInteropGroup = async jids => {
393
318
  const ids = Array.isArray(jids) ? jids : [jids]
394
319
  const result = await mexQuery(
@@ -400,15 +325,7 @@ const makeInteropSocket = sock => {
400
325
  return result
401
326
  }
402
327
 
403
- /**
404
- * Add participants to an existing interop group via MEX.
405
- * Confirmed from C3Wt.java opcode 9:
406
- * input: { gid, participants: [{ jid }] }
407
- * dataPath: xwa2_interop_add_participants_to_group
408
- *
409
- * @param {string} groupJid - Interop group JID
410
- * @param {string[]} participants - Interop JIDs to add
411
- */
328
+
412
329
  const addParticipantsToInteropGroup = async (groupJid, participants) => {
413
330
  const gid = groupJid.split('@')[0]
414
331
  return mexQuery(
@@ -418,35 +335,13 @@ const makeInteropSocket = sock => {
418
335
  )
419
336
  }
420
337
 
421
- /**
422
- * Query info about an interop group via MEX.
423
- * Confirmed from C3Wt.java opcode 10:
424
- * group_input: { gid }
425
- * dataPath: xwa2_interop_group_query_by_id
426
- *
427
- * @param {string} groupJid - Interop group JID
428
- */
338
+
429
339
  const queryInteropGroupInfo = async groupJid => {
430
340
  const gid = groupJid.split('@')[0]
431
- return mexQuery(
432
- { group_input: { gid } },
433
- INTEROP_MEX_QUERY_IDS.QUERY_GROUP_INFO,
434
- 'xwa2_interop_group_query_by_id'
435
- )
341
+ return mexQuery({ group_input: { gid } }, INTEROP_MEX_QUERY_IDS.QUERY_GROUP_INFO, 'xwa2_interop_group_query_by_id')
436
342
  }
437
343
 
438
- /**
439
- * Update an interop privacy setting via MEX.
440
- * Confirmed from C24385Ak0.java (InteropPrivacySettingsUpdate):
441
- * feature, setting
442
- * dataPath: xwa2_interop_privacy_setting_update
443
- *
444
- * Known features: "GROUPADD"
445
- * Known settings: "ALL" | "CONTACTS" | "NONE"
446
- *
447
- * @param {string} feature - Privacy feature name
448
- * @param {string} setting - New value
449
- */
344
+
450
345
  const updateInteropPrivacySetting = async (feature, setting) => {
451
346
  return mexQuery(
452
347
  { feature, setting },
@@ -455,20 +350,7 @@ const makeInteropSocket = sock => {
455
350
  )
456
351
  }
457
352
 
458
- /**
459
- * Update an interop privacy setting with an explicit contact list via MEX.
460
- * Confirmed from C24388Ak8.java (InteropPrivacySettingWithContactListUpdate):
461
- * feature, setting, contacts[], contact_list_type, dhash
462
- * dataPath: xwa2_interop_privacy_setting_with_contact_list_update
463
- *
464
- * Used when setting is "CONTACTS" and you want to specify exact allowed contacts.
465
- *
466
- * @param {string} feature - Privacy feature (e.g. "GROUPADD")
467
- * @param {string} setting - "CONTACTS"
468
- * @param {string[]} contacts - List of JIDs to allow
469
- * @param {string} contactListType - Contact list type string
470
- * @param {string} [dhash] - Hash of the current contact list (or null/"none")
471
- */
353
+
472
354
  const updateInteropPrivacySettingWithContactList = async (
473
355
  feature,
474
356
  setting,
@@ -483,13 +365,11 @@ const makeInteropSocket = sock => {
483
365
  )
484
366
  }
485
367
 
486
- /**
487
- * Check whether an interop user allows being added to groups (GROUPADD privacy).
488
- * Returns true if the user can be added, false if blocked by their privacy settings.
489
- *
490
- * Mirrors InteropPrivacySettingsManager.A01() in the APK which queries
491
- * the "GROUPADD" feature for the given account.
492
- */
368
+
369
+ const queryInteropPrivacySettings = () =>
370
+ mexQuery({}, INTEROP_MEX_QUERY_IDS.PRIVACY_SETTINGS_QUERY, 'xwa2_interop_privacy_settings')
371
+
372
+
493
373
  const getInteropGroupAddPrivacy = async (jid, integratorId) => {
494
374
  const result = await query({
495
375
  tag: 'iq',
@@ -512,8 +392,8 @@ const makeInteropSocket = sock => {
512
392
  })
513
393
  const privacyNode = getBinaryNodeChild(result, 'privacy')
514
394
  const userNode = getBinaryNodeChild(privacyNode, 'user')
515
- // "allowed" means the server confirmed the user can be added; any other value or
516
- // absence of the attribute means the privacy setting blocks the add.
395
+
396
+
517
397
  return userNode?.attrs?.result === 'allowed'
518
398
  }
519
399
 
@@ -537,6 +417,7 @@ const makeInteropSocket = sock => {
537
417
  leaveInteropGroup,
538
418
  addParticipantsToInteropGroup,
539
419
  queryInteropGroupInfo,
420
+ queryInteropPrivacySettings,
540
421
  updateInteropPrivacySetting,
541
422
  updateInteropPrivacySettingWithContactList,
542
423
  getInteropGroupAddPrivacy,