@badzz88/baileys 8.5.4 → 8.5.5

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 (107) hide show
  1. package/README.md +1 -1
  2. package/WAProto/WAProto.proto +75 -1256
  3. package/WAProto/index.d.ts +19729 -36122
  4. package/WAProto/index.js +132193 -178538
  5. package/engine-requirements.js +15 -7
  6. package/lib/Defaults/index.js +14 -22
  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 +28 -39
  15. package/lib/Signal/lid-mapping.js +8 -28
  16. package/lib/Socket/aigroups.js +5 -5
  17. package/lib/Socket/business.js +11 -3
  18. package/lib/Socket/chats.js +319 -668
  19. package/lib/Socket/communities.js +89 -85
  20. package/lib/Socket/graphql.js +228 -435
  21. package/lib/Socket/groups.js +211 -132
  22. package/lib/Socket/index.js +7 -9
  23. package/lib/Socket/interactive-handler.js +60 -117
  24. package/lib/Socket/interop.js +175 -56
  25. package/lib/Socket/luxu.js +387 -0
  26. package/lib/Socket/managed-account.js +48 -79
  27. package/lib/Socket/messages-recv.js +340 -898
  28. package/lib/Socket/messages-send.js +160 -462
  29. package/lib/Socket/mex.js +53 -40
  30. package/lib/Socket/newsletter.js +295 -291
  31. package/lib/Socket/privacy.js +265 -77
  32. package/lib/Socket/registration.js +157 -164
  33. package/lib/Socket/socket.js +199 -181
  34. package/lib/Socket/text-router.js +83 -0
  35. package/lib/Socket/username.js +227 -144
  36. package/lib/Store/keyed-db.js +108 -0
  37. package/lib/Store/make-cache-manager-store.js +2 -2
  38. package/lib/Store/make-in-memory-store.js +141 -25
  39. package/lib/Types/Auth.js +3 -3
  40. package/lib/Types/Label.js +1 -1
  41. package/lib/Types/LabelAssociation.js +1 -1
  42. package/lib/Types/Mex.js +112 -0
  43. package/lib/Types/Newsletter.js +65 -96
  44. package/lib/Types/State.js +4 -4
  45. package/lib/Types/index.js +1 -1
  46. package/lib/Utils/auth-utils.js +57 -52
  47. package/lib/Utils/browser-utils.js +1 -1
  48. package/lib/Utils/business.js +6 -2
  49. package/lib/Utils/chat-utils.js +38 -102
  50. package/lib/Utils/command-loader.d.ts +31 -0
  51. package/lib/Utils/command-loader.js +100 -0
  52. package/lib/Utils/companion-reg-client-utils.js +42 -0
  53. package/lib/Utils/crypto.js +84 -54
  54. package/lib/Utils/curve25519-js.js +275 -0
  55. package/lib/Utils/decode-wa-message.js +83 -316
  56. package/lib/Utils/event-buffer.js +61 -37
  57. package/lib/Utils/generics.js +103 -157
  58. package/lib/Utils/group-history.js +12 -3
  59. package/lib/Utils/history.js +15 -15
  60. package/lib/Utils/index.js +5 -1
  61. package/lib/Utils/link-preview.js +8 -3
  62. package/lib/Utils/lt-hash.js +35 -2
  63. package/lib/Utils/make-mutex.js +1 -1
  64. package/lib/Utils/message-retry-manager.js +92 -27
  65. package/lib/Utils/messages-media.js +130 -159
  66. package/lib/Utils/messages.js +566 -144
  67. package/lib/Utils/meta-ai-msmsg.js +202 -62
  68. package/lib/Utils/native-bridge.js +82 -0
  69. package/lib/Utils/noise-handler.js +21 -19
  70. package/lib/Utils/offline-node-processor.js +8 -3
  71. package/lib/Utils/pre-key-manager.js +23 -13
  72. package/lib/Utils/process-message.js +106 -197
  73. package/lib/Utils/reporting-utils.js +2 -2
  74. package/lib/Utils/session-pool.d.ts +16 -0
  75. package/lib/Utils/session-pool.js +94 -0
  76. package/lib/Utils/signal.js +63 -28
  77. package/lib/Utils/stanza-ack.js +12 -39
  78. package/lib/Utils/sticker.d.ts +17 -0
  79. package/lib/Utils/sticker.js +145 -0
  80. package/lib/Utils/sync-action-utils.js +7 -4
  81. package/lib/Utils/tc-token-utils.js +25 -20
  82. package/lib/Utils/use-multi-file-auth-state.js +14 -30
  83. package/lib/Utils/validate-connection.js +19 -59
  84. package/lib/Utils/view-once-cache.d.ts +9 -0
  85. package/lib/Utils/view-once-cache.js +79 -0
  86. package/lib/WABinary/decode.js +16 -50
  87. package/lib/WABinary/encode.js +224 -54
  88. package/lib/WABinary/generic-utils.js +97 -7
  89. package/lib/WABinary/jid-utils.js +14 -27
  90. package/lib/WAM/encode.js +7 -7
  91. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +1 -58
  92. package/lib/WAUSync/Protocols/USyncContactProtocol.js +1 -7
  93. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +5 -13
  94. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +4 -1
  95. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +7 -20
  96. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +1 -1
  97. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +2 -2
  98. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +2 -7
  99. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +1 -1
  100. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +1 -2
  101. package/lib/WAUSync/Protocols/index.js +0 -1
  102. package/lib/WAUSync/USyncQuery.js +6 -77
  103. package/lib/WAUSync/USyncUser.js +8 -16
  104. package/lib/WAUSync/index.js +0 -1
  105. package/lib/antiban.js +854 -608
  106. package/lib/index.js +1 -1
  107. package/package.json +20 -16
@@ -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 badzz88 {
17
+ class InteractiveMessageHandler {
18
18
  constructor(waUploadToServer, relayMessageFn, config, sock) {
19
19
  this.relayMessage = relayMessageFn
20
20
  this.waUploadToServer = waUploadToServer
@@ -24,12 +24,13 @@ class badzz88 {
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'
28
27
  if (content.interactiveMessage) return 'INTERACTIVE'
29
28
  if (content.albumMessage || content.album) return 'ALBUM'
30
29
  if (content.eventMessage) return 'EVENT'
31
30
  if (content.pollResultMessage) return 'POLL_RESULT'
32
31
  if (content.groupStatusMessage) return 'GROUP_STORY'
32
+ if (content.orderMessage) return 'ORDER'
33
+ if (content.groupLabel) return 'GROUP_LABEL'
33
34
  return null
34
35
  }
35
36
  async handlePayment(content, quoted) {
@@ -222,114 +223,6 @@ class badzz88 {
222
223
  }
223
224
  return { interactiveMessage }
224
225
  }
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
- }
333
226
  async handleAlbum(content, jid, quoted) {
334
227
  const array = content.albumMessage || content.album
335
228
  const ctxInfo = content.contextInfo || {}
@@ -344,7 +237,7 @@ class badzz88 {
344
237
  expectedVideoCount: array.filter(a => 'video' in a).length
345
238
  }
346
239
  },
347
- { userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || '') }
240
+ { userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ''), browser: this.config?.browser }
348
241
  )
349
242
  await this.relayMessage(jid, album.message, {
350
243
  messageId: album.key.id
@@ -394,18 +287,18 @@ class badzz88 {
394
287
  joinLink: eventData.joinLink || '',
395
288
  startTime:
396
289
  typeof eventData.startTime === 'string'
397
- ? parseInt(eventData.startTime, 10)
290
+ ? parseInt(eventData.startTime)
398
291
  : eventData.startTime || Date.now(),
399
292
  endTime:
400
293
  typeof eventData.endTime === 'string'
401
- ? parseInt(eventData.endTime, 10)
294
+ ? parseInt(eventData.endTime)
402
295
  : eventData.endTime || Date.now() + 3600000,
403
296
  extraGuestsAllowed: eventData.extraGuestsAllowed !== false
404
297
  }
405
298
  }
406
299
  }
407
300
  },
408
- { quoted, userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || '') }
301
+ { quoted, userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ''), browser: this.config?.browser }
409
302
  )
410
303
  await this.relayMessage(jid, msg.message, {
411
304
  messageId: msg.key.id
@@ -426,7 +319,7 @@ class badzz88 {
426
319
  }))
427
320
  }
428
321
  },
429
- { quoted, userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || '') }
322
+ { quoted, userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ''), browser: this.config?.browser }
430
323
  )
431
324
  await this.relayMessage(jid, msg.message, {
432
325
  messageId: msg.key.id
@@ -454,6 +347,55 @@ class badzz88 {
454
347
  messageId: generateMessageID()
455
348
  })
456
349
  }
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
+ }
457
399
  async sendStatusWhatsApp(content, jids = []) {
458
400
  const userJid = jidNormalizedUser(this.sock.authState.creds.me.id)
459
401
  const allUsers = new Set()
@@ -557,7 +499,8 @@ class badzz88 {
557
499
  }
558
500
  }
559
501
  const statusMsg = await generateWAMessageFromContent(normalizedId, protocolMessage, {
560
- userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || '')
502
+ userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ''),
503
+ browser: this.config?.browser
561
504
  })
562
505
  await this.relayMessage(normalizedId, statusMsg.message, {
563
506
  additionalNodes: [
@@ -576,4 +519,4 @@ class badzz88 {
576
519
  }
577
520
  }
578
521
 
579
- module.exports = { badzz88 }
522
+ module.exports = { InteractiveMessageHandler }
@@ -5,36 +5,47 @@ exports.makeInteropSocket = void 0
5
5
  const { getBinaryNodeChild, getBinaryNodeChildren, S_WHATSAPP_NET } = require('../WABinary')
6
6
  const { executeWMexQuery } = require('./mex')
7
7
 
8
-
9
8
  const INTEROP_MEX_QUERY_IDS = {
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'
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
17
16
  }
18
17
 
19
-
18
+ /**
19
+ * Known integrator IDs (assigned by WhatsApp).
20
+ * BirdyChat → identifier_type="email"
21
+ * Haiket → identifier_type="pn"
22
+ */
20
23
  const INTEGRATOR_BIRDYCHAT = 12
21
24
  const INTEGRATOR_HAIKET = 13
22
25
 
23
-
26
+ /**
27
+ * TOS trackable results sent by WA on first interop opt-in.
28
+ * 105 = TOS shown, 160 = TOS accepted.
29
+ */
24
30
  const TOS_TRACKABLE_ID = '20240306'
25
31
  const TOS_RESULT_SHOWN = '105'
26
32
  const TOS_RESULT_ACCEPTED = '160'
27
33
 
28
-
34
+ /** Maximum users per batch lookup (enforced server-side too). */
29
35
  const INTEROP_BATCH_MAX = 256
30
36
 
31
37
  const makeInteropSocket = sock => {
32
- const { query, generateMessageTag, logger, signalRepository, masqueradeAsPrimary } = sock
38
+ const { query, generateMessageTag, logger, signalRepository } = sock
33
39
 
34
40
  const mexQuery = (variables, queryId, dataPath) =>
35
41
  executeWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
36
42
 
37
-
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
+ */
38
49
  const fetchIntegrators = async () => {
39
50
  const result = await query({
40
51
  tag: 'iq',
@@ -51,14 +62,14 @@ const makeInteropSocket = sock => {
51
62
  return getBinaryNodeChildren(listNode, 'integrator').map(node => {
52
63
  const featuresNode = getBinaryNodeChild(node, 'features')
53
64
  return {
54
- id: parseInt(node.attrs.id, 10),
65
+ id: parseInt(node.attrs.id),
55
66
  name: node.attrs.name,
56
-
67
+ // "active" | "onboarding" | "removed"
57
68
  status: node.attrs.status,
58
69
  icon: node.attrs.icon,
59
-
70
+ // "email" | "pn" | "username"
60
71
  identifierType: node.attrs.identifier_type,
61
-
72
+ // Whether we are already opted-in to this integrator
62
73
  optedIn: node.attrs.opted_in === 'true' || globalOptedIn,
63
74
  features: {
64
75
  groupMessaging: featuresNode?.attrs?.group_messaging === 'true'
@@ -67,7 +78,7 @@ const makeInteropSocket = sock => {
67
78
  })
68
79
  }
69
80
 
70
-
81
+ /** Send a single TOS trackable item (shown or accepted). */
71
82
  const sendTOSTrackable = async (id, result) => {
72
83
  await query({
73
84
  tag: 'iq',
@@ -76,13 +87,20 @@ const makeInteropSocket = sock => {
76
87
  })
77
88
  }
78
89
 
79
-
90
+ /**
91
+ * Accept TOS for interop.
92
+ * Sends two trackable items: shown (105) then accepted (160).
93
+ * Must be called before opting in.
94
+ */
80
95
  const acceptInteropTOS = async () => {
81
96
  await sendTOSTrackable(TOS_TRACKABLE_ID, TOS_RESULT_SHOWN)
82
97
  await sendTOSTrackable(TOS_TRACKABLE_ID, TOS_RESULT_ACCEPTED)
83
98
  }
84
99
 
85
-
100
+ /**
101
+ * Opt in to a list of integrators (by numeric ID).
102
+ * Defaults to both known integrators (BirdyChat + Haiket).
103
+ */
86
104
  const optInIntegrators = async (integratorIds = [INTEGRATOR_BIRDYCHAT, INTEGRATOR_HAIKET]) => {
87
105
  await query({
88
106
  tag: 'iq',
@@ -106,7 +124,10 @@ const makeInteropSocket = sock => {
106
124
  })
107
125
  }
108
126
 
109
-
127
+ /**
128
+ * Opt out of a list of integrators (by numeric ID).
129
+ * Mirror of optInIntegrators — uses <opt_out_integrators>.
130
+ */
110
131
  const optOutIntegrators = async (integratorIds = [INTEGRATOR_BIRDYCHAT, INTEGRATOR_HAIKET]) => {
111
132
  await query({
112
133
  tag: 'iq',
@@ -130,7 +151,18 @@ const makeInteropSocket = sock => {
130
151
  })
131
152
  }
132
153
 
133
-
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
+ */
134
166
  const resolveInteropUsers = async users => {
135
167
  if (!users || users.length === 0) return []
136
168
  if (users.length > INTEROP_BATCH_MAX) {
@@ -160,9 +192,9 @@ const makeInteropSocket = sock => {
160
192
  if (errorNode) {
161
193
  return {
162
194
  externalId: userNode.attrs.external_id,
163
- integratorId: parseInt(userNode.attrs.integrator_id, 10),
195
+ integratorId: parseInt(userNode.attrs.integrator_id),
164
196
  error: {
165
- code: parseInt(errorNode.attrs.code, 10),
197
+ code: parseInt(errorNode.attrs.code),
166
198
  text: errorNode.attrs.text
167
199
  }
168
200
  }
@@ -171,18 +203,25 @@ const makeInteropSocket = sock => {
171
203
  jid: userNode.attrs.jid,
172
204
  externalId: userNode.attrs.external_id,
173
205
  normalizedExternalId: userNode.attrs.normalized_external_id,
174
- integratorId: parseInt(userNode.attrs.integrator_id, 10)
206
+ integratorId: parseInt(userNode.attrs.integrator_id)
175
207
  }
176
208
  })
177
209
  }
178
210
 
179
-
211
+ /**
212
+ * Convenience wrapper: resolve a single interop user.
213
+ * Returns the result object or null.
214
+ */
180
215
  const resolveInteropUser = async (externalId, integratorId) => {
181
216
  const results = await resolveInteropUsers([{ externalId, integratorId }])
182
217
  return results[0] ?? null
183
218
  }
184
219
 
185
-
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
+ */
186
225
  const getReachabilitySettings = async () => {
187
226
  const result = await query({
188
227
  tag: 'iq',
@@ -195,13 +234,17 @@ const makeInteropSocket = sock => {
195
234
  enabled: settingsNode.attrs?.enabled,
196
235
  users: getBinaryNodeChildren(settingsNode, 'user').map(n => ({
197
236
  externalId: n.attrs.external_id,
198
- integratorId: parseInt(n.attrs.integrator_id, 10),
237
+ integratorId: parseInt(n.attrs.integrator_id),
199
238
  jid: n.attrs.jid
200
239
  }))
201
240
  }
202
241
  }
203
242
 
204
-
243
+ /**
244
+ * Set reachability settings (subscribe to presence) for interop contacts.
245
+ * users: array of { externalId, integratorId }
246
+ * enabled: "true" | "false"
247
+ */
205
248
  const setReachabilitySettings = async (users, enabled = 'true') => {
206
249
  await query({
207
250
  tag: 'iq',
@@ -222,7 +265,10 @@ const makeInteropSocket = sock => {
222
265
  })
223
266
  }
224
267
 
225
-
268
+ /**
269
+ * Block or unblock an interop user via the w:interop blocklist.
270
+ * Different from regular WA block (xmlns=blocklist).
271
+ */
226
272
  const updateInteropBlockStatus = async (jid, action) => {
227
273
  await query({
228
274
  tag: 'iq',
@@ -240,7 +286,10 @@ const makeInteropSocket = sock => {
240
286
  const blockInteropUser = jid => updateInteropBlockStatus(jid, 'block')
241
287
  const unblockInteropUser = jid => updateInteropBlockStatus(jid, 'unblock')
242
288
 
243
-
289
+ /**
290
+ * Report an interop contact as spam.
291
+ * spam_flow="account_info_block" = block + report (WA Web default).
292
+ */
244
293
  const reportInteropSpam = async (jid, spamFlow = 'account_info_block') => {
245
294
  await query({
246
295
  tag: 'iq',
@@ -249,7 +298,10 @@ const makeInteropSocket = sock => {
249
298
  })
250
299
  }
251
300
 
252
-
301
+ /**
302
+ * Mark an interop JID as trusted_contact in privacy tokens.
303
+ * WA calls this automatically after the first outgoing message.
304
+ */
253
305
  const trustInteropContact = async jid => {
254
306
  const t = Math.floor(Date.now() / 1000).toString()
255
307
  await query({
@@ -265,7 +317,15 @@ const makeInteropSocket = sock => {
265
317
  })
266
318
  }
267
319
 
268
-
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
+ */
269
329
  const initInterop = async () => {
270
330
  let integrators
271
331
  try {
@@ -286,23 +346,30 @@ const makeInteropSocket = sock => {
286
346
  } catch (err) {
287
347
  logger.warn({ err }, 'interop: failed to opt-in integrators')
288
348
  }
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
- )
349
+ logger.info({ integrators: toOptIn.map(i => i.name) }, 'interop: initialized')
296
350
  return integrators
297
351
  }
298
352
 
299
-
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
+ */
300
359
  const resetInteropSession = async jid => {
301
360
  await signalRepository.deleteSession([jid])
302
361
  logger.info({ jid }, '[interop] session reset — next send will use pkmsg')
303
362
  }
304
363
 
305
-
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
+ */
306
373
  const createInteropGroup = async participants => {
307
374
  const result = await mexQuery(
308
375
  { input: { participants: participants.map(jid => ({ jid })) } },
@@ -313,7 +380,15 @@ const makeInteropSocket = sock => {
313
380
  return result
314
381
  }
315
382
 
316
-
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
+ */
317
392
  const leaveInteropGroup = async jids => {
318
393
  const ids = Array.isArray(jids) ? jids : [jids]
319
394
  const result = await mexQuery(
@@ -325,7 +400,15 @@ const makeInteropSocket = sock => {
325
400
  return result
326
401
  }
327
402
 
328
-
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
+ */
329
412
  const addParticipantsToInteropGroup = async (groupJid, participants) => {
330
413
  const gid = groupJid.split('@')[0]
331
414
  return mexQuery(
@@ -335,13 +418,35 @@ const makeInteropSocket = sock => {
335
418
  )
336
419
  }
337
420
 
338
-
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
+ */
339
429
  const queryInteropGroupInfo = async groupJid => {
340
430
  const gid = groupJid.split('@')[0]
341
- return mexQuery({ group_input: { gid } }, INTEROP_MEX_QUERY_IDS.QUERY_GROUP_INFO, 'xwa2_interop_group_query_by_id')
431
+ return mexQuery(
432
+ { group_input: { gid } },
433
+ INTEROP_MEX_QUERY_IDS.QUERY_GROUP_INFO,
434
+ 'xwa2_interop_group_query_by_id'
435
+ )
342
436
  }
343
437
 
344
-
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
+ */
345
450
  const updateInteropPrivacySetting = async (feature, setting) => {
346
451
  return mexQuery(
347
452
  { feature, setting },
@@ -350,7 +455,20 @@ const makeInteropSocket = sock => {
350
455
  )
351
456
  }
352
457
 
353
-
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
+ */
354
472
  const updateInteropPrivacySettingWithContactList = async (
355
473
  feature,
356
474
  setting,
@@ -365,11 +483,13 @@ const makeInteropSocket = sock => {
365
483
  )
366
484
  }
367
485
 
368
-
369
- const queryInteropPrivacySettings = () =>
370
- mexQuery({}, INTEROP_MEX_QUERY_IDS.PRIVACY_SETTINGS_QUERY, 'xwa2_interop_privacy_settings')
371
-
372
-
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
+ */
373
493
  const getInteropGroupAddPrivacy = async (jid, integratorId) => {
374
494
  const result = await query({
375
495
  tag: 'iq',
@@ -392,8 +512,8 @@ const makeInteropSocket = sock => {
392
512
  })
393
513
  const privacyNode = getBinaryNodeChild(result, 'privacy')
394
514
  const userNode = getBinaryNodeChild(privacyNode, 'user')
395
-
396
-
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.
397
517
  return userNode?.attrs?.result === 'allowed'
398
518
  }
399
519
 
@@ -417,7 +537,6 @@ const makeInteropSocket = sock => {
417
537
  leaveInteropGroup,
418
538
  addParticipantsToInteropGroup,
419
539
  queryInteropGroupInfo,
420
- queryInteropPrivacySettings,
421
540
  updateInteropPrivacySetting,
422
541
  updateInteropPrivacySettingWithContactList,
423
542
  getInteropGroupAddPrivacy,