@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
@@ -15,6 +15,7 @@ const State_1 = require('../Types/State')
15
15
  const Utils_1 = require('../Utils')
16
16
  const make_mutex_1 = require('../Utils/make-mutex')
17
17
  const process_message_1 = __importDefault(require('../Utils/process-message'))
18
+ const { unwrapSecretEncryptedMessage } = require('../Utils/process-message')
18
19
  const tc_token_utils_1 = require('../Utils/tc-token-utils')
19
20
  const WABinary_1 = require('../WABinary')
20
21
  const WAUSync_1 = require('../WAUSync')
@@ -62,45 +63,47 @@ const makeChatsSocket = config => {
62
63
  } = sock
63
64
  const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
64
65
  let privacySettings
65
- /** Server-assigned AB props for protocol behavior. */
66
+
66
67
  const serverProps = {
67
- /** AB prop 10518: gate tctoken on 1:1 messages. Default true (safe: avoids 463). */
68
+
68
69
  privacyTokenOn1to1: true,
69
- /** AB prop 9666: gate tctoken on profile picture IQs. WA Web default: true. */
70
+
70
71
  profilePicPrivacyToken: true,
71
- /** AB prop 14303: issue tctokens to LID instead of PN. WA Web default: false. */
72
+
72
73
  lidTrustedTokenIssueToLid: false
73
74
  }
74
75
  let syncState = State_1.SyncState.Connecting
75
- /** this mutex ensures that messages are processed in order */
76
+
76
77
  const messageMutex = (0, make_mutex_1.makeMutex)()
77
- /** this mutex ensures that receipts are processed in order */
78
+
78
79
  const receiptMutex = (0, make_mutex_1.makeMutex)()
79
- /** this mutex ensures that app state patches are processed in order */
80
+
80
81
  const appStatePatchMutex = (0, make_mutex_1.makeMutex)()
81
- /** this mutex ensures that notifications are processed in order */
82
+
82
83
  const notificationMutex = (0, make_mutex_1.makeMutex)()
83
- // Timeout for AwaitingInitialSync state
84
+
84
85
  let awaitingSyncTimeout
85
- // In-memory history sync completion tracking (resets on reconnection)
86
+
86
87
  const historySyncStatus = {
87
88
  initialBootstrapComplete: false,
88
89
  recentSyncComplete: false
89
90
  }
90
91
  let historySyncPausedTimeout
91
- // Collections blocked on missing app state sync keys (mirrors WA Web's "Blocked" state).
92
- // When a key arrives via APP_STATE_SYNC_KEY_SHARE, these are re-synced.
92
+
93
+
93
94
  const blockedCollections = new Set()
95
+
96
+ const _nlServerIdCache = new Map()
94
97
  const placeholderResendCache =
95
98
  config.placeholderResendCache ||
96
99
  new node_cache_1.default({
97
- stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY, // 1 hour
100
+ stdTTL: Defaults_1.DEFAULT_CACHE_TTLS.MSG_RETRY,
98
101
  useClones: false
99
102
  })
100
103
  if (!config.placeholderResendCache) {
101
104
  config.placeholderResendCache = placeholderResendCache
102
105
  }
103
- /** helper function to fetch the given app state sync key */
106
+
104
107
  const getAppStateSyncKey = async keyId => {
105
108
  const { [keyId]: key } = await authState.keys.get('app-state-sync-key', [keyId])
106
109
  return key
@@ -116,11 +119,67 @@ const makeChatsSocket = config => {
116
119
  },
117
120
  content: [{ tag: 'privacy', attrs: {} }]
118
121
  })
119
- privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(content?.[0], 'category')
122
+ const privacyNode = content?.[0]
123
+ privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(privacyNode, 'category')
124
+
125
+
126
+
127
+ const onlinePrivacy =
128
+ privacySettings['online'] ||
129
+ (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'online_privacy_setting')?.attrs?.value ||
130
+ 'all'
131
+
132
+
133
+ const enhancedBlockEnabled =
134
+ privacyNode?.attrs?.enhanced_block_enabled === 'true' ||
135
+ (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'enhanced_block')?.attrs?.enabled === 'true'
136
+
137
+
138
+ const mdPrivacyV2 =
139
+ privacyNode?.attrs?.md_privacy_v2 === 'true' ||
140
+ (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'md_privacy_v2')?.attrs?.value === 'true'
141
+
142
+
143
+ const syncdClearChatDeleteChatEnabled =
144
+ privacyNode?.attrs?.syncd_clear_chat_delete_chat_enabled === 'true' ||
145
+ (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_clear_chat')?.attrs?.delete_chat_enabled === 'true'
146
+
147
+
148
+ const syncdAntiTamperingEnabled =
149
+ privacyNode?.attrs?.syncd_anti_tampering_fatal_exception_enabled === 'true' ||
150
+ (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_anti_tampering')?.attrs?.fatal_exception_enabled ===
151
+ 'true'
152
+
153
+
154
+ const keyRotationEnabled =
155
+ privacyNode?.attrs?.syncd_key_rotation_enabled === 'true' ||
156
+ (0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_key_rotation')?.attrs?.enabled === 'true'
157
+
158
+
159
+ ev.emit('settings.update', {
160
+ onlinePrivacy,
161
+ enhancedBlockEnabled,
162
+ mdPrivacyV2,
163
+ syncdClearChatDeleteChatEnabled,
164
+ ...(syncdAntiTamperingEnabled ? { syncdAntiTamperingEnabled: true } : {}),
165
+ keyRotationEnabled
166
+ })
167
+
168
+
169
+ ev.emit('creds.update', {
170
+ privacySettings: {
171
+ onlinePrivacy,
172
+ enhancedBlockEnabled,
173
+ mdPrivacyV2,
174
+ syncdClearChatDeleteChatEnabled,
175
+ syncdAntiTamperingEnabled,
176
+ keyRotationEnabled
177
+ }
178
+ })
120
179
  }
121
180
  return privacySettings
122
181
  }
123
- /** helper function to run a privacy IQ query */
182
+
124
183
  const privacyQuery = async (name, value) => {
125
184
  await query({
126
185
  tag: 'iq',
@@ -161,11 +220,7 @@ const makeChatsSocket = config => {
161
220
  const updateStatusPrivacy = async value => {
162
221
  await privacyQuery('status', value)
163
222
  }
164
- /**
165
- * Fetch status privacy settings via `xmlns="status"` IQ GET.
166
- * Returns the current distribution type and any custom lists.
167
- * Source: GetStatusPrivacyJob.java, feature flag 3843 controls retry.
168
- */
223
+
169
224
  const getStatusPrivacy = async () => {
170
225
  const result = await query({
171
226
  tag: 'iq',
@@ -179,31 +234,24 @@ const makeChatsSocket = config => {
179
234
  const privacyNode = result?.content?.[0]
180
235
  if (!privacyNode) return null
181
236
  const lists = []
182
- for (const listNode of (privacyNode.content || [])) {
237
+ for (const listNode of privacyNode.content || []) {
183
238
  const { type, id, listname, emoji, selected, deleted } = listNode.attrs || {}
184
239
  const members = (listNode.content || []).map(u => u.attrs?.jid).filter(Boolean)
185
240
  lists.push({ type, id, listname, emoji, selected: selected === 'true', deleted: deleted === 'true', members })
186
241
  }
187
242
  return lists
188
243
  }
189
- /**
190
- * Set status privacy via `xmlns="status"` IQ SET.
191
- * Supports simple distribution types and contact-level whitelist/blacklist/customlist.
192
- *
193
- * @param {'contacts'|'whitelist'|'blacklist'|'null'} type - Distribution type
194
- * @param {string[]} [jids] - JIDs for the list (whitelist/blacklist)
195
- * @param {Array<{id: string, listname: string, emoji?: string, selected?: boolean, deleted?: boolean, members?: string[]}>} [customLists]
196
- */
244
+
197
245
  const setStatusPrivacy = async (type, jids = [], customLists = []) => {
198
246
  const content = []
199
- // main distribution list
247
+
200
248
  const mainList = {
201
249
  tag: 'list',
202
250
  attrs: { type },
203
251
  content: jids.map(jid => ({ tag: 'user', attrs: { jid }, content: [] }))
204
252
  }
205
253
  content.push(mainList)
206
- // custom named lists
254
+
207
255
  for (const cl of customLists) {
208
256
  const attrs = { type: 'customlist', id: cl.id, listname: cl.listname }
209
257
  if (cl.emoji) attrs.emoji = cl.emoji
@@ -249,12 +297,7 @@ const makeChatsSocket = config => {
249
297
  ]
250
298
  })
251
299
  }
252
- /**
253
- * Fetch broadcast list quota from the server.
254
- * Source: BroadcastListQuotaProtocol.java — IQ xmlns="w:biz", 32s timeout.
255
- *
256
- * Returns: { messagesLeft, totalLimit, isHeavySender, startTs, endTs, resetTs }
257
- */
300
+
258
301
  const fetchBroadcastListQuota = async () => {
259
302
  const result = await query(
260
303
  {
@@ -272,12 +315,35 @@ const makeChatsSocket = config => {
272
315
  const timeframeNode = (0, WABinary_1.getBinaryNodeChild)(result, 'timeframe')
273
316
  if (!limitsNode) return null
274
317
  return {
275
- messagesLeft: parseInt(limitsNode.attrs?.messages_left ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'messages_left')?.content ?? '0'),
276
- totalLimit: parseInt(limitsNode.attrs?.total_limit ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'total_limit')?.content ?? '0'),
277
- isHeavySender: (limitsNode.attrs?.is_heavy_sender ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'is_heavy_sender')?.content) === 'true',
278
- startTs: parseInt(timeframeNode?.attrs?.start_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'start_ts_s')?.content ?? '0'),
279
- endTs: parseInt(timeframeNode?.attrs?.end_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'end_ts_s')?.content ?? '0'),
280
- resetTs: parseInt(timeframeNode?.attrs?.reset_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'reset_ts_s')?.content ?? '0')
318
+ messagesLeft: parseInt(
319
+ limitsNode.attrs?.messages_left ??
320
+ (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'messages_left')?.content ??
321
+ '0',
322
+ 10
323
+ ),
324
+ totalLimit: parseInt(
325
+ limitsNode.attrs?.total_limit ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'total_limit')?.content ?? '0',
326
+ 10
327
+ ),
328
+ isHeavySender:
329
+ (limitsNode.attrs?.is_heavy_sender ??
330
+ (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'is_heavy_sender')?.content) === 'true',
331
+ startTs: parseInt(
332
+ timeframeNode?.attrs?.start_ts_s ??
333
+ (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'start_ts_s')?.content ??
334
+ '0',
335
+ 10
336
+ ),
337
+ endTs: parseInt(
338
+ timeframeNode?.attrs?.end_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'end_ts_s')?.content ?? '0',
339
+ 10
340
+ ),
341
+ resetTs: parseInt(
342
+ timeframeNode?.attrs?.reset_ts_s ??
343
+ (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'reset_ts_s')?.content ??
344
+ '0',
345
+ 10
346
+ )
281
347
  }
282
348
  }
283
349
 
@@ -312,11 +378,7 @@ const makeChatsSocket = config => {
312
378
  }
313
379
  return botList
314
380
  }
315
- /**
316
- * Get the global chat-blocking status (block messages from unknown accounts).
317
- * Ported from WhatsApp Web's WASmaxPsaChatBlockGetRPC (xmlns `w:comms:chat`).
318
- * @returns {Promise<'blocked' | 'unblocked' | undefined>}
319
- */
381
+
320
382
  const getChatBlockingStatus = async () => {
321
383
  const result = await query({
322
384
  tag: 'iq',
@@ -332,12 +394,7 @@ const makeChatsSocket = config => {
332
394
  (0, WABinary_1.getBinaryNodeChild)((0, WABinary_1.getBinaryNodeChild)(result, 'query'), 'blocking')
333
395
  return blocking?.attrs?.status
334
396
  }
335
- /**
336
- * Set the global chat-blocking status (block messages from unknown accounts).
337
- * Ported from WhatsApp Web's WASmaxPsaChatBlockSetRPC (xmlns `w:comms:chat`).
338
- * @param {'block' | 'unblock'} action
339
- * @returns {Promise<'blocked' | 'unblocked' | undefined>} the resulting status
340
- */
397
+
341
398
  const updateChatBlockingStatus = async action => {
342
399
  const result = await query({
343
400
  tag: 'iq',
@@ -351,12 +408,7 @@ const makeChatsSocket = config => {
351
408
  const blocking = (0, WABinary_1.getBinaryNodeChild)(result, 'blocking')
352
409
  return blocking?.attrs?.status
353
410
  }
354
- /**
355
- * Get the user's pending TOS disclosures / notices.
356
- * Ported from WhatsApp Web's WASmaxUserNoticeGetDisclosuresRPC (xmlns `tos`).
357
- * @param {number} [t] last-seen disclosure timestamp
358
- * @returns {Promise<Array<Record<string, string>>>} the `<notice>` attributes
359
- */
411
+
360
412
  const getUserDisclosures = async (t = 0) => {
361
413
  const result = await query({
362
414
  tag: 'iq',
@@ -365,18 +417,65 @@ const makeChatsSocket = config => {
365
417
  })
366
418
  return (0, WABinary_1.getBinaryNodeChildren)(result, 'notice').map(n => ({ ...n.attrs }))
367
419
  }
368
- /**
369
- * Get the account opt-out list (`optoutlist` IQ). Returns the raw result node.
370
- */
420
+
421
+ const acceptTosNotice = async (noticeId, result = '105') => {
422
+ await query({
423
+ tag: 'iq',
424
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'tos', type: 'set' },
425
+ content: [{ tag: 'trackable', attrs: { id: String(noticeId), result: String(result) } }]
426
+ })
427
+ }
428
+
429
+ const reportSpam = async (jid, messages, spamFlow = 'contact_info_report', subject) => {
430
+ const msgNodes = (messages || []).map(m => ({
431
+ tag: 'message',
432
+ attrs: {
433
+ from: String(jid),
434
+ t: String(m.t),
435
+ id: String(m.id)
436
+ }
437
+ }))
438
+ await query({
439
+ tag: 'iq',
440
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'spam', type: 'set' },
441
+ content: [
442
+ {
443
+ tag: 'spam_list',
444
+ attrs: {
445
+ jid: String(jid),
446
+ spam_flow: String(spamFlow),
447
+ ...(subject ? { subject: String(subject) } : {})
448
+ },
449
+ content: msgNodes
450
+ }
451
+ ]
452
+ })
453
+ }
454
+
371
455
  const getOptOutList = async () => {
372
456
  return query({
373
457
  tag: 'iq',
374
458
  attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'optoutlist', type: 'get' }
375
459
  })
376
460
  }
377
- /**
378
- * Get push-notification settings (`urn:xmpp:whatsapp:push`).
379
- */
461
+
462
+ const signPrivateCredential = async blindedCredential => {
463
+ const result = await query({
464
+ tag: 'iq',
465
+ attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'privatestats', type: 'get' },
466
+ content: [
467
+ {
468
+ tag: 'sign_credential',
469
+ attrs: { version: '1' },
470
+ content: [{ tag: 'blinded_credential', attrs: {}, content: blindedCredential }]
471
+ }
472
+ ]
473
+ })
474
+ const signedNode = (0, WABinary_1.getBinaryNodeChild)(result, 'sign_credential')
475
+ const signedBytes = (0, WABinary_1.getBinaryNodeChild)(signedNode, 'signed_credential')
476
+ return signedBytes?.content instanceof Uint8Array ? Buffer.from(signedBytes.content) : undefined
477
+ }
478
+
380
479
  const getPushConfig = async () => {
381
480
  const result = await query({
382
481
  tag: 'iq',
@@ -385,11 +484,7 @@ const makeChatsSocket = config => {
385
484
  })
386
485
  return (0, WABinary_1.getBinaryNodeChild)(result, 'settings')
387
486
  }
388
- /**
389
- * Set push-notification config (`urn:xmpp:whatsapp:push`). Mainly web push —
390
- * pass the FCM-style config (platform / endpoint / auth / p256dh).
391
- * @param {Record<string, string>} config
392
- */
487
+
393
488
  const setPushConfig = async config => {
394
489
  await query({
395
490
  tag: 'iq',
@@ -402,7 +497,7 @@ const makeChatsSocket = config => {
402
497
  for (const jid of jids) {
403
498
  usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid))
404
499
  }
405
- const result = await sock.executeUSyncQuery(usyncQuery)
500
+ const result = await executeUSyncQuery(usyncQuery)
406
501
  if (result) {
407
502
  return result.list
408
503
  }
@@ -412,12 +507,12 @@ const makeChatsSocket = config => {
412
507
  for (const jid of jids) {
413
508
  usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid))
414
509
  }
415
- const result = await sock.executeUSyncQuery(usyncQuery)
510
+ const result = await executeUSyncQuery(usyncQuery)
416
511
  if (result) {
417
512
  return result.list
418
513
  }
419
514
  }
420
- /** update the profile picture for yourself or a group */
515
+
421
516
  const updateProfilePicture = async (jid, content, dimensions) => {
422
517
  let targetJid
423
518
  if (!jid) {
@@ -426,7 +521,7 @@ const makeChatsSocket = config => {
426
521
  )
427
522
  }
428
523
  if ((0, WABinary_1.jidNormalizedUser)(jid) !== (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id)) {
429
- targetJid = (0, WABinary_1.jidNormalizedUser)(jid) // in case it is someone other than us
524
+ targetJid = (0, WABinary_1.jidNormalizedUser)(jid)
430
525
  } else {
431
526
  targetJid = undefined
432
527
  }
@@ -448,7 +543,7 @@ const makeChatsSocket = config => {
448
543
  ]
449
544
  })
450
545
  }
451
- /** remove the profile picture for yourself or a group */
546
+
452
547
  const removeProfilePicture = async jid => {
453
548
  let targetJid
454
549
  if (!jid) {
@@ -457,7 +552,7 @@ const makeChatsSocket = config => {
457
552
  )
458
553
  }
459
554
  if ((0, WABinary_1.jidNormalizedUser)(jid) !== (0, WABinary_1.jidNormalizedUser)(authState.creds.me.id)) {
460
- targetJid = (0, WABinary_1.jidNormalizedUser)(jid) // in case it is someone other than us
555
+ targetJid = (0, WABinary_1.jidNormalizedUser)(jid)
461
556
  } else {
462
557
  targetJid = undefined
463
558
  }
@@ -471,7 +566,7 @@ const makeChatsSocket = config => {
471
566
  }
472
567
  })
473
568
  }
474
- /** update the profile status for yourself */
569
+
475
570
  const updateProfileStatus = async status => {
476
571
  await query({
477
572
  tag: 'iq',
@@ -492,17 +587,24 @@ const makeChatsSocket = config => {
492
587
  const updateProfileName = async name => {
493
588
  await chatModify({ pushNameSetting: name }, '')
494
589
  }
495
- const fetchBlocklist = async () => {
590
+
591
+
592
+
593
+
594
+ const fetchBlocklist = async (dhash = null) => {
496
595
  const result = await query({
497
596
  tag: 'iq',
498
597
  attrs: {
499
598
  xmlns: 'blocklist',
500
599
  to: WABinary_1.S_WHATSAPP_NET,
501
600
  type: 'get'
502
- }
601
+ },
602
+ content: dhash ? [{ tag: 'item', attrs: { dhash } }] : null
503
603
  })
504
604
  const listNode = (0, WABinary_1.getBinaryNodeChild)(result, 'list')
505
- return (0, WABinary_1.getBinaryNodeChildren)(listNode, 'item').map(n => n.attrs.jid)
605
+ const jids = (0, WABinary_1.getBinaryNodeChildren)(listNode, 'item').map(n => n.attrs.jid)
606
+ ev.emit('blocklist.set', { blocklist: jids })
607
+ return jids
506
608
  }
507
609
  const updateBlockStatus = async (jid, action) => {
508
610
  await query({
@@ -524,26 +626,67 @@ const makeChatsSocket = config => {
524
626
  })
525
627
  }
526
628
  const getBusinessProfile = async jid => {
527
- const results = await query({
528
- tag: 'iq',
529
- attrs: {
530
- to: 's.whatsapp.net',
531
- xmlns: 'w:biz',
532
- type: 'get'
533
- },
534
- content: [
535
- {
536
- tag: 'business_profile',
537
- attrs: { v: '244' },
538
- content: [
539
- {
540
- tag: 'profile',
541
- attrs: { jid }
542
- }
543
- ]
629
+
630
+
631
+
632
+ const [results, verifiedNameResult] = await Promise.all([
633
+ query({
634
+ tag: 'iq',
635
+ attrs: {
636
+ to: 's.whatsapp.net',
637
+ xmlns: 'w:biz',
638
+ type: 'get'
639
+ },
640
+ content: [
641
+ {
642
+ tag: 'business_profile',
643
+ attrs: { v: '116' },
644
+ content: [
645
+ {
646
+ tag: 'profile',
647
+ attrs: { jid }
648
+ }
649
+ ]
650
+ }
651
+ ]
652
+ }),
653
+ query({
654
+ tag: 'iq',
655
+ attrs: {
656
+ to: 's.whatsapp.net',
657
+ xmlns: 'w:biz',
658
+ type: 'get'
659
+ },
660
+ content: [
661
+ {
662
+ tag: 'verified_name',
663
+ attrs: { jid }
664
+ }
665
+ ]
666
+ })
667
+ ])
668
+
669
+ const verifiedNameNode = (0, WABinary_1.getBinaryNodeChild)(verifiedNameResult, 'verified_name')
670
+ let verifiedNameCert
671
+ if (verifiedNameNode) {
672
+ const certNode = (0, WABinary_1.getBinaryNodeChild)(verifiedNameNode, 'certificate')
673
+ if (certNode) {
674
+ const detailsNode = (0, WABinary_1.getBinaryNodeChild)(certNode, 'details')
675
+ const localizedNames = (0, WABinary_1.getBinaryNodeChildren)(certNode, 'localized_name').map(n => ({
676
+ lg: n.attrs?.lg,
677
+ lc: n.attrs?.lc,
678
+ verifiedName: n.attrs?.verified_name || n.content?.toString()
679
+ }))
680
+ verifiedNameCert = {
681
+ certSerial: certNode.attrs?.serial ? +certNode.attrs.serial : undefined,
682
+ issuer: certNode.attrs?.issuer,
683
+ details: {
684
+ verifiedName: detailsNode?.attrs?.verified_name || detailsNode?.content?.toString(),
685
+ localizedNames
686
+ }
544
687
  }
545
- ]
546
- })
688
+ }
689
+ }
547
690
  const profileNode = (0, WABinary_1.getBinaryNodeChild)(results, 'business_profile')
548
691
  const profiles = (0, WABinary_1.getBinaryNodeChild)(profileNode, 'profile')
549
692
  if (profiles) {
@@ -560,9 +703,31 @@ const makeChatsSocket = config => {
560
703
  ? (0, WABinary_1.getBinaryNodeChildren)(businessHours, 'business_hours_config')
561
704
  : undefined
562
705
  const websiteStr = website?.content?.toString()
706
+
707
+
708
+ const catalogStatus = {
709
+ exists: profiles.attrs?.catalog_exists === 'true' || profiles.attrs?.catalog_exists === true || false,
710
+ sendAll: profiles.attrs?.catalog_send_all === 'true' || profiles.attrs?.catalog_send_all === true || false
711
+ }
712
+
713
+
714
+ const cartEnabled =
715
+ profiles.attrs?.cart_enabled !== undefined
716
+ ? profiles.attrs.cart_enabled === 'true' || profiles.attrs.cart_enabled === true
717
+ : undefined
718
+ const webCartEnabled =
719
+ profiles.attrs?.web_cart_enabled !== undefined
720
+ ? profiles.attrs.web_cart_enabled === 'true' || profiles.attrs.web_cart_enabled === true
721
+ : undefined
722
+ const webCartOnOff = profiles.attrs?.web_cart_on_off
723
+
724
+
725
+ const commerceExperience = profiles.attrs?.commerce_experience
726
+
563
727
  return {
564
728
  wid: profiles.attrs?.jid,
565
729
  address: address?.content?.toString(),
730
+ businessAddress: address?.content?.toString(),
566
731
  description: description?.content?.toString() || '',
567
732
  website: websiteStr ? [websiteStr] : [],
568
733
  email: email?.content?.toString(),
@@ -570,7 +735,24 @@ const makeChatsSocket = config => {
570
735
  business_hours: {
571
736
  timezone: businessHours?.attrs?.timezone,
572
737
  business_config: businessHoursConfig?.map(({ attrs }) => attrs)
573
- }
738
+ },
739
+ businessHours: businessHoursConfig
740
+ ? {
741
+ timezone: businessHours?.attrs?.timezone ?? null,
742
+ config: businessHoursConfig.map(({ attrs }) => ({
743
+ dayOfWeek: attrs?.day_of_week ?? null,
744
+ openTime: attrs?.open_time ?? null,
745
+ closeTime: attrs?.close_time ?? null,
746
+ mode: attrs?.mode ?? null
747
+ }))
748
+ }
749
+ : null,
750
+ catalogStatus,
751
+ cartEnabled,
752
+ webCartEnabled,
753
+ webCartOnOff,
754
+ commerceExperience,
755
+ verifiedNameCert
574
756
  }
575
757
  }
576
758
  }
@@ -605,6 +787,13 @@ const makeChatsSocket = config => {
605
787
  isInitialSync ? { accountSettings: authState.creds.accountSettings } : undefined,
606
788
  logger
607
789
  )
790
+
791
+ const _nctSalt = mutation?.syncAction?.value?.nctSaltSyncAction?.salt
792
+ if (_nctSalt?.length) {
793
+ authState.keys
794
+ .set({ 'nct-salt': { default: Buffer.from(_nctSalt) } })
795
+ .catch(err => logger.debug({ err: err?.message }, 'nct: failed to persist salt'))
796
+ }
608
797
  }
609
798
  }
610
799
  }
@@ -612,24 +801,24 @@ const makeChatsSocket = config => {
612
801
  const appStateSyncKeyCache = new Map()
613
802
  const getCachedAppStateSyncKey = async keyId => {
614
803
  if (appStateSyncKeyCache.has(keyId)) {
615
- return appStateSyncKeyCache.get(keyId) ?? undefined
804
+ return appStateSyncKeyCache.get(keyId)
616
805
  }
617
806
  const key = await getAppStateSyncKey(keyId)
618
807
  appStateSyncKeyCache.set(keyId, key ?? null)
619
808
  return key
620
809
  }
621
- // we use this to determine which events to fire
622
- // otherwise when we resync from scratch -- all notifications will fire
810
+
811
+
623
812
  const initialVersionMap = {}
624
813
  const globalMutationMap = {}
625
814
  await authState.keys.transaction(async () => {
626
815
  const collectionsToHandle = new Set(collections)
627
- // in case something goes wrong -- ensure we don't enter a loop that cannot be exited from
816
+
628
817
  const attemptsMap = {}
629
818
  const forceSnapshotCollections = new Set()
630
- // keep executing till all collections are done
631
- // sometimes a single patch request will not return all the patches (God knows why)
632
- // so we fetch till they're all done (this is determined by the "has_more_patches" flag)
819
+
820
+
821
+
633
822
  while (collectionsToHandle.size) {
634
823
  const states = {}
635
824
  const nodes = []
@@ -637,7 +826,7 @@ const makeChatsSocket = config => {
637
826
  const result = await authState.keys.get('app-state-sync-version', [name])
638
827
  let state = result[name]
639
828
  if (state) {
640
- if (typeof initialVersionMap[name] === 'undefined') {
829
+ if (initialVersionMap[name] === undefined) {
641
830
  initialVersionMap[name] = state.version
642
831
  }
643
832
  } else {
@@ -654,7 +843,7 @@ const makeChatsSocket = config => {
654
843
  attrs: {
655
844
  name,
656
845
  version: state.version.toString(),
657
- // return snapshot if syncing from scratch or forcing after a failed attempt
846
+
658
847
  return_snapshot: (shouldForceSnapshot || !state.version).toString()
659
848
  }
660
849
  })
@@ -674,7 +863,7 @@ const makeChatsSocket = config => {
674
863
  }
675
864
  ]
676
865
  })
677
- // extract from binary node
866
+
678
867
  const decoded = await (0, Utils_1.extractSyncdPatches)(result, config?.options)
679
868
  for (const key in decoded) {
680
869
  const name = key
@@ -693,7 +882,7 @@ const makeChatsSocket = config => {
693
882
  logger.info(`restored state of ${name} from snapshot to v${newState.version} with mutations`)
694
883
  await authState.keys.set({ 'app-state-sync-version': { [name]: newState } })
695
884
  }
696
- // only process if there are syncd patches
885
+
697
886
  if (patches.length) {
698
887
  const { state: newState, mutationMap } = await (0, Utils_1.decodePatches)(
699
888
  name,
@@ -713,7 +902,7 @@ const makeChatsSocket = config => {
713
902
  if (hasMorePatches) {
714
903
  logger.info(`${name} has more patches...`)
715
904
  } else {
716
- // collection is done with sync
905
+
717
906
  collectionsToHandle.delete(name)
718
907
  }
719
908
  } catch (error) {
@@ -755,18 +944,15 @@ const makeChatsSocket = config => {
755
944
  onMutation(globalMutationMap[key])
756
945
  }
757
946
  })
758
- /**
759
- * fetch the profile picture of a user/group
760
- * type = "preview" for a low res picture
761
- * type = "image" for the high res picture URL (adds query="url")
762
- * type = "avatar" for the avatar variant (no query attr, confirmed from interop logs)
763
- */
947
+
764
948
  const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
765
- const picAttrs = type === 'image' ? { type, query: 'url' } : { type }
949
+
950
+
951
+ const picAttrs = { type, query: 'url' }
766
952
  const baseContent = [{ tag: 'picture', attrs: picAttrs }]
767
- // WA Web only includes tctoken for user JIDs (not groups/newsletters)
768
- // and never for own profile pic (Chat model for self has no tcToken).
769
- // Including tctoken for own JID causes the server to never respond.
953
+
954
+
955
+
770
956
  const normalizedJid = (0, WABinary_1.jidNormalizedUser)(jid)
771
957
  const isUserJid = (0, WABinary_1.isPnUser)(normalizedJid) || (0, WABinary_1.isLidUser)(normalizedJid)
772
958
  const me = authState.creds.me
@@ -821,13 +1007,7 @@ const makeChatsSocket = config => {
821
1007
  const child = (0, WABinary_1.getBinaryNodeChild)(result, 'link_create')
822
1008
  return child?.attrs?.token
823
1009
  }
824
- /**
825
- * Toggle the waiting room on an existing call link.
826
- * Ported from WhatsApp Web's WASmaxVoipWaitingRoomToggleCallLinkRPC.
827
- * @param {string} linkToken the call link token (from createCallLink)
828
- * @param {boolean} enabled
829
- * @param {'audio' | 'video'} [media]
830
- */
1010
+
831
1011
  const toggleCallLinkWaitingRoom = async (linkToken, enabled, media = 'audio') => {
832
1012
  const result = await query({
833
1013
  tag: 'call',
@@ -879,25 +1059,120 @@ const makeChatsSocket = config => {
879
1059
  })
880
1060
  }
881
1061
  }
882
- /**
883
- * @param toJid the jid to subscribe to
884
- * @param tcToken token for subscription, use if present
885
- */
886
- const presenceSubscribe = async toJid => {
887
- // Only include tctoken for user JIDs — groups/newsletters don't use tctokens
1062
+
1063
+ const storePrivacyTokens = async entries => {
1064
+ if (!entries?.length) return
1065
+ const write = {}
1066
+ for (const { jid, privacyToken, privacyModeTs } of entries) {
1067
+ if (!jid || !privacyToken?.length) continue
1068
+ write[jid] = {
1069
+ token: Buffer.isBuffer(privacyToken) ? privacyToken : Buffer.from(privacyToken),
1070
+ ts: String(privacyModeTs || '')
1071
+ }
1072
+ }
1073
+ if (Object.keys(write).length) {
1074
+ await authState.keys.set({ 'privacy-token': write })
1075
+ }
1076
+ }
1077
+
1078
+
1079
+ const executeUSyncQuery = async usyncQuery => {
1080
+ const result = await sock.executeUSyncQuery(usyncQuery)
1081
+ if (!result) return result
1082
+
1083
+
1084
+ const privacyEntries = []
1085
+ const blockedContacts = []
1086
+ for (const entry of [...(result.list || []), ...(result.sideList || [])]) {
1087
+ if (entry.privacy?.token) {
1088
+ privacyEntries.push({
1089
+ jid: entry.id,
1090
+ privacyToken: entry.privacy.token,
1091
+ privacyModeTs: entry.privacy.modeTs
1092
+ })
1093
+ }
1094
+
1095
+ if (entry.isBlockedByContact) {
1096
+ blockedContacts.push({ id: entry.id, isBlockedByContact: true })
1097
+ }
1098
+ }
1099
+ if (privacyEntries.length) {
1100
+ storePrivacyTokens(privacyEntries).catch(err => logger.warn({ err }, 'failed to store privacy tokens from usync'))
1101
+ }
1102
+ if (blockedContacts.length) {
1103
+ ev.emit('contacts.update', blockedContacts)
1104
+ }
1105
+ return result
1106
+ }
1107
+
1108
+
1109
+ const presenceSubscribe = async (toJid, { presenceType, presenceName, groupJid } = {}) => {
1110
+
888
1111
  const normalizedToJid = (0, WABinary_1.jidNormalizedUser)(toJid)
889
1112
  const isUserJid = (0, WABinary_1.isPnUser)(normalizedToJid) || (0, WABinary_1.isLidUser)(normalizedToJid)
890
- const tcTokenContent = isUserJid
891
- ? await (0, tc_token_utils_1.buildTcTokenFromJid)({ authState, jid: normalizedToJid, getLIDForPN })
892
- : undefined
1113
+
1114
+
1115
+ const presenceAttrs = {
1116
+ to: toJid,
1117
+ id: generateMessageTag(),
1118
+ type: 'subscribe'
1119
+ }
1120
+
1121
+
1122
+ const presenceContent = []
1123
+
1124
+
1125
+ if (isUserJid) {
1126
+ try {
1127
+ const storageJid = await (0, tc_token_utils_1.resolveTcTokenJid)(normalizedToJid, getLIDForPN)
1128
+ const tcTokenData = await authState.keys.get('tctoken', [storageJid])
1129
+ const entry = tcTokenData?.[storageJid]
1130
+ const tcTokenBuffer = entry?.token
1131
+ if (tcTokenBuffer?.length && !(0, tc_token_utils_1.isTcTokenExpired)(entry?.timestamp)) {
1132
+ presenceAttrs.tc_token = tcTokenBuffer.toString('base64')
1133
+ }
1134
+ } catch (e) {
1135
+
1136
+ }
1137
+
1138
+
1139
+
1140
+
1141
+ try {
1142
+ const privTokenData = await authState.keys.get('privacy-token', [normalizedToJid])
1143
+ const privEntry = privTokenData?.[normalizedToJid]
1144
+ if (privEntry?.token?.length) {
1145
+ const privTokenAttrs = {}
1146
+ if (privEntry.ts) privTokenAttrs.t = privEntry.ts
1147
+ presenceContent.push({
1148
+ tag: 'privacy_token',
1149
+ attrs: privTokenAttrs,
1150
+ content: privEntry.token
1151
+ })
1152
+ }
1153
+ } catch (e) {
1154
+
1155
+ }
1156
+ }
1157
+
1158
+
1159
+ if (presenceType) {
1160
+ presenceAttrs.presenceType = presenceType
1161
+ }
1162
+ if (presenceName) {
1163
+ presenceAttrs.presenceName = presenceName
1164
+ }
1165
+
1166
+
1167
+ if (groupJid) {
1168
+ presenceAttrs.context = 'group'
1169
+ presenceAttrs.group_jid = (0, WABinary_1.jidNormalizedUser)(groupJid)
1170
+ }
1171
+
893
1172
  return sendNode({
894
1173
  tag: 'presence',
895
- attrs: {
896
- to: toJid,
897
- id: generateMessageTag(),
898
- type: 'subscribe'
899
- },
900
- content: tcTokenContent
1174
+ attrs: presenceAttrs,
1175
+ content: presenceContent.length ? presenceContent : undefined
901
1176
  })
902
1177
  }
903
1178
  const handlePresenceUpdate = ({ tag, attrs, content }) => {
@@ -996,9 +1271,9 @@ const makeChatsSocket = config => {
996
1271
  }
997
1272
  }
998
1273
  }
999
- /** sending non-abt props may fix QR scan fail if server expects */
1274
+
1000
1275
  const fetchProps = async () => {
1001
- //TODO: implement both protocol 1 and protocol 2 prop fetching, specially for abKey for WM
1276
+
1002
1277
  const resultNode = await query({
1003
1278
  tag: 'iq',
1004
1279
  attrs: {
@@ -1020,13 +1295,13 @@ const makeChatsSocket = config => {
1020
1295
  let props = {}
1021
1296
  if (propsNode) {
1022
1297
  if (propsNode.attrs?.hash) {
1023
- // on some clients, the hash is returning as undefined
1298
+
1024
1299
  authState.creds.lastPropHash = propsNode?.attrs?.hash
1025
1300
  ev.emit('creds.update', authState.creds)
1026
1301
  }
1027
1302
  props = (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop')
1028
1303
  }
1029
- // Extract protocol-relevant AB props (only the ones we need)
1304
+
1030
1305
  const privacyTokenProp = props['10518'] ?? props['privacy_token_sending_on_all_1_on_1_messages']
1031
1306
  if (privacyTokenProp !== undefined) {
1032
1307
  serverProps.privacyTokenOn1to1 = privacyTokenProp === 'true' || privacyTokenProp === '1'
@@ -1042,18 +1317,12 @@ const makeChatsSocket = config => {
1042
1317
  logger.debug({ serverProps }, 'fetched props')
1043
1318
  return props
1044
1319
  }
1045
- /**
1046
- * modify a chat -- mark unread, read etc.
1047
- * lastMessages must be sorted in reverse chronologically
1048
- * requires the last messages till the last message received; required for archive & unread
1049
- */
1320
+
1050
1321
  const chatModify = (mod, jid) => {
1051
1322
  const patch = (0, Utils_1.chatModificationToAppPatch)(mod, jid)
1052
1323
  return appPatch(patch)
1053
1324
  }
1054
- /**
1055
- * Enable/Disable link preview privacy, not related to baileys link preview generation
1056
- */
1325
+
1057
1326
  const updateDisableLinkPreviewsPrivacy = isPreviewsDisabled => {
1058
1327
  return chatModify(
1059
1328
  {
@@ -1062,9 +1331,7 @@ const makeChatsSocket = config => {
1062
1331
  ''
1063
1332
  )
1064
1333
  }
1065
- /**
1066
- * Star or Unstar a message
1067
- */
1334
+
1068
1335
  const star = (jid, messages, star) => {
1069
1336
  return chatModify(
1070
1337
  {
@@ -1076,9 +1343,7 @@ const makeChatsSocket = config => {
1076
1343
  jid
1077
1344
  )
1078
1345
  }
1079
- /**
1080
- * Add or Edit Contact
1081
- */
1346
+
1082
1347
  const addOrEditContact = (jid, contact) => {
1083
1348
  return chatModify(
1084
1349
  {
@@ -1087,9 +1352,7 @@ const makeChatsSocket = config => {
1087
1352
  jid
1088
1353
  )
1089
1354
  }
1090
- /**
1091
- * Remove Contact
1092
- */
1355
+
1093
1356
  const removeContact = jid => {
1094
1357
  return chatModify(
1095
1358
  {
@@ -1098,9 +1361,7 @@ const makeChatsSocket = config => {
1098
1361
  jid
1099
1362
  )
1100
1363
  }
1101
- /**
1102
- * Adds label
1103
- */
1364
+
1104
1365
  const addLabel = (jid, labels) => {
1105
1366
  return chatModify(
1106
1367
  {
@@ -1111,9 +1372,7 @@ const makeChatsSocket = config => {
1111
1372
  jid
1112
1373
  )
1113
1374
  }
1114
- /**
1115
- * Adds label for the chats
1116
- */
1375
+
1117
1376
  const addChatLabel = (jid, labelId) => {
1118
1377
  return chatModify(
1119
1378
  {
@@ -1124,9 +1383,7 @@ const makeChatsSocket = config => {
1124
1383
  jid
1125
1384
  )
1126
1385
  }
1127
- /**
1128
- * Removes label for the chat
1129
- */
1386
+
1130
1387
  const removeChatLabel = (jid, labelId) => {
1131
1388
  return chatModify(
1132
1389
  {
@@ -1137,9 +1394,7 @@ const makeChatsSocket = config => {
1137
1394
  jid
1138
1395
  )
1139
1396
  }
1140
- /**
1141
- * Adds label for the message
1142
- */
1397
+
1143
1398
  const addMessageLabel = (jid, messageId, labelId) => {
1144
1399
  return chatModify(
1145
1400
  {
@@ -1151,9 +1406,7 @@ const makeChatsSocket = config => {
1151
1406
  jid
1152
1407
  )
1153
1408
  }
1154
- /**
1155
- * Removes label for the message
1156
- */
1409
+
1157
1410
  const removeMessageLabel = (jid, messageId, labelId) => {
1158
1411
  return chatModify(
1159
1412
  {
@@ -1165,9 +1418,7 @@ const makeChatsSocket = config => {
1165
1418
  jid
1166
1419
  )
1167
1420
  }
1168
- /**
1169
- * Add or Edit Quick Reply
1170
- */
1421
+
1171
1422
  const addOrEditQuickReply = quickReply => {
1172
1423
  return chatModify(
1173
1424
  {
@@ -1176,9 +1427,7 @@ const makeChatsSocket = config => {
1176
1427
  ''
1177
1428
  )
1178
1429
  }
1179
- /**
1180
- * Remove Quick Reply
1181
- */
1430
+
1182
1431
  const removeQuickReply = timestamp => {
1183
1432
  return chatModify(
1184
1433
  {
@@ -1187,34 +1436,49 @@ const makeChatsSocket = config => {
1187
1436
  ''
1188
1437
  )
1189
1438
  }
1190
- /**
1191
- * Rename an AI chat thread
1192
- */
1439
+
1193
1440
  const renameAIThread = (jid, title) => {
1194
1441
  return chatModify({ aiThreadRename: { title } }, jid)
1195
1442
  }
1196
- /**
1197
- * Pin or unpin a message in a thread/AI chat
1198
- */
1443
+
1199
1444
  const pinThreadMessage = (jid, messageId, pinned = true) => {
1200
1445
  return chatModify({ threadPin: { pinned, messageId } }, jid)
1201
1446
  }
1202
- /**
1203
- * Toggle AI private processing (end-to-end encrypted AI processing)
1204
- */
1447
+
1205
1448
  const updatePrivateProcessingSetting = enabled => {
1206
1449
  return chatModify({ privateProcessingSetting: enabled ? 'enabled' : 'disabled' }, '')
1207
1450
  }
1208
- /**
1209
- * Update bio/about privacy (who can see your About text)
1210
- * @param value 'all' | 'contacts' | 'contact_blacklist' | 'none'
1211
- */
1451
+
1452
+ const updateAIFeatures = (status = 'enabled', replyMode = 'suggestions') => {
1453
+ const statusEnum =
1454
+ require('../../WAProto/index.js').proto.SyncActionValue.MaibaAIFeaturesControlAction.MaibaAIFeatureStatus
1455
+ const replyModeEnum =
1456
+ require('../../WAProto/index.js').proto.SyncActionValue.MaibaAIFeaturesControlAction.MaibaAIReplyMode
1457
+ const statusMap = {
1458
+ enabled: statusEnum.ENABLED,
1459
+ enabled_has_learning: statusEnum.ENABLED_HAS_LEARNING,
1460
+ disabled: statusEnum.DISABLED
1461
+ }
1462
+ const replyModeMap = {
1463
+ muted: replyModeEnum.MUTED,
1464
+ ai_agent: replyModeEnum.AI_AGENT,
1465
+ suggestions: replyModeEnum.SUGGESTIONS
1466
+ }
1467
+ return chatModify(
1468
+ {
1469
+ maibaAIFeatures: {
1470
+ status: statusMap[status] ?? statusEnum.ENABLED,
1471
+ replyMode: replyModeMap[replyMode] ?? replyModeEnum.SUGGESTIONS
1472
+ }
1473
+ },
1474
+ ''
1475
+ )
1476
+ }
1477
+
1212
1478
  const updateBioPrivacy = async value => {
1213
1479
  await privacyQuery('about', value)
1214
1480
  }
1215
- /**
1216
- * Block a bot JID
1217
- */
1481
+
1218
1482
  const blockBot = async botJid => {
1219
1483
  await query({
1220
1484
  tag: 'iq',
@@ -1231,71 +1495,63 @@ const makeChatsSocket = config => {
1231
1495
  ]
1232
1496
  })
1233
1497
  }
1234
- /**
1235
- * Unblock a bot JID
1236
- */
1498
+
1237
1499
  const unblockBot = async botJid => {
1238
1500
  await updateBlockStatus(botJid, 'unblock')
1239
1501
  }
1240
- /**
1241
- * Mute or unmute a contact's status updates
1242
- */
1502
+
1243
1503
  const muteContactStatus = (jid, muted = true) => {
1244
1504
  return chatModify({ muteStatus: { muted } }, jid)
1245
1505
  }
1246
- /**
1247
- * Add or remove a contact from favorites
1248
- */
1506
+
1249
1507
  const toggleFavorite = (jid, isFavorite = true) => {
1250
1508
  return chatModify({ favorite: { isFavorite } }, jid)
1251
1509
  }
1252
- /**
1253
- * Reorder labels
1254
- * @param sortedLabelIds ordered array of label IDs
1255
- */
1510
+
1256
1511
  const reorderLabels = sortedLabelIds => {
1257
1512
  return chatModify({ reorderLabel: { sortedLabelIds } }, '')
1258
1513
  }
1259
- /**
1260
- * Delete an individual call log entry
1261
- */
1514
+
1262
1515
  const deleteCallLog = (callId, jid = '') => {
1263
1516
  return chatModify({ deleteCallLog: { callId } }, jid)
1264
1517
  }
1265
- /**
1266
- * Create or update a note/draft for a chat
1267
- */
1518
+
1268
1519
  const setChatNote = (jid, note) => {
1269
1520
  return chatModify({ noteEdit: { note } }, jid)
1270
1521
  }
1271
- /**
1272
- * Delete the note/draft for a chat
1273
- */
1522
+
1274
1523
  const deleteChatNote = jid => {
1275
1524
  return chatModify({ noteEdit: { note: '', deleted: true } }, jid)
1276
1525
  }
1277
- /**
1278
- * Explicitly mark a chat as unread (shows unread dot even if read)
1279
- */
1526
+
1280
1527
  const markChatAsUnread = (jid, lastMessages) => {
1281
1528
  return chatModify({ markAsUnread: true, lastMessages }, jid)
1282
1529
  }
1283
- /**
1284
- * Set per-chat ephemeral message duration
1285
- * @param duration seconds (0 = off, 86400 = 1d, 604800 = 7d, 7776000 = 90d)
1286
- */
1530
+
1287
1531
  const setChatEphemeral = (jid, duration) => {
1288
1532
  return chatModify({ setChatEphemeral: duration }, jid)
1289
1533
  }
1290
- /**
1291
- * Silence a chat (mute without timestamp = permanent, or provide until timestamp)
1292
- */
1534
+
1293
1535
  const silenceChat = (jid, silent = true, until = null) => {
1294
1536
  return chatModify({ silenceChat: { silent, until } }, jid)
1295
1537
  }
1296
- /**
1297
- * Fetch bot profiles for a list of JIDs using USyncBotProfileProtocol
1298
- */
1538
+
1539
+ const clearChat = (jid, lastMessages) => {
1540
+ return chatModify({ clear: true, lastMessages }, jid)
1541
+ }
1542
+
1543
+ const pinChat = (jid, pinned = true) => {
1544
+ return chatModify({ pin: { pinned } }, jid)
1545
+ }
1546
+
1547
+ const starMessages = (jid, messageIds, starred = true) => {
1548
+ return chatModify({ star: { messages: messageIds, star: starred } }, jid)
1549
+ }
1550
+
1551
+ const setQuickReply = (text, shortcut) => {
1552
+ return chatModify({ quickReply: { text, shortcut } }, '')
1553
+ }
1554
+
1299
1555
  const fetchBotProfiles = async jids => {
1300
1556
  const { USyncQuery, USyncUser, USyncBotProfileProtocol } = require('../WAUSync')
1301
1557
  const q = new USyncQuery()
@@ -1303,37 +1559,25 @@ const makeChatsSocket = config => {
1303
1559
  for (const jid of jids) {
1304
1560
  q.withUser(new USyncUser().withId(jid))
1305
1561
  }
1306
- const result = await sock.executeUSyncQuery(q)
1562
+ const result = await executeUSyncQuery(q)
1307
1563
  return result?.list || []
1308
1564
  }
1309
- /**
1310
- * Lock or unlock a chat with an optional secret code
1311
- */
1565
+
1312
1566
  const updateChatLock = (jid, locked) => {
1313
1567
  return chatModify({ chatLock: { locked } }, jid)
1314
1568
  }
1315
- /**
1316
- * Set a custom wallpaper for a chat
1317
- * @param jid the chat JID
1318
- * @param wallpaper wallpaper data or null to remove
1319
- */
1569
+
1320
1570
  const updateChatWallpaper = (jid, wallpaper) => {
1321
1571
  if (!wallpaper) {
1322
1572
  return chatModify({ wallpaper: { remove: true } }, jid)
1323
1573
  }
1324
1574
  return chatModify({ wallpaper }, jid)
1325
1575
  }
1326
- /**
1327
- * Set media visibility (auto-download) for a chat
1328
- * @param jid the chat JID
1329
- * @param visibility 'default' | 'on' | 'off'
1330
- */
1576
+
1331
1577
  const updateChatMediaVisibility = (jid, visibility) => {
1332
1578
  return chatModify({ mediaVisibility: visibility }, jid)
1333
1579
  }
1334
- /**
1335
- * Fetch details for a specific bot by JID
1336
- */
1580
+
1337
1581
  const getBotProfile = async botJid => {
1338
1582
  const resp = await query({
1339
1583
  tag: 'iq',
@@ -1358,15 +1602,16 @@ const makeChatsSocket = config => {
1358
1602
  description: botNode.attrs.description
1359
1603
  }
1360
1604
  }
1361
- /**
1362
- * Fetch AB-test (abt) props from server.
1363
- * Mirrors ABPropsProtocolHelper — protocol 1 or 2, optional hash/refresh_id/group.
1364
- */
1365
- const fetchABProps = async (protocol = '2', hash = '', refreshId = null, group = null) => {
1366
- const propAttrs = { protocol }
1367
- if (hash) propAttrs.hash = hash
1368
- if (refreshId != null) propAttrs.refresh_id = String(refreshId)
1369
- if (group != null) propAttrs.group = String(group)
1605
+
1606
+ const fetchABProps = async (protocol = '1', hash = '', refreshId = null, group = null) => {
1607
+ const propAttrs =
1608
+ group != null
1609
+ ? { group: String(group) }
1610
+ : {
1611
+ protocol,
1612
+ ...(hash ? { hash } : {}),
1613
+ ...(refreshId != null ? { refresh_id: String(refreshId) } : {})
1614
+ }
1370
1615
  const result = await query({
1371
1616
  tag: 'iq',
1372
1617
  attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'abt', type: 'get' },
@@ -1376,29 +1621,20 @@ const makeChatsSocket = config => {
1376
1621
  if (!propsNode) return {}
1377
1622
  return (0, WABinary_1.reduceBinaryNodeToDictionary)(propsNode, 'prop')
1378
1623
  }
1379
- /**
1380
- * Remove a companion (linked) device from the account.
1381
- * Mirrors CompanionDeviceRemovalJob — xmlns="md", child tag "remove-companion-device".
1382
- * reason: "user_initiated" | "server_initiated" | any WA-defined reason string.
1383
- */
1384
- const removeCompanionDevice = async (keyIndex, reason = 'user_initiated') => {
1624
+
1625
+ const removeCompanionDevice = async (jid, reason = 'user_initiated') => {
1385
1626
  await query({
1386
1627
  tag: 'iq',
1387
1628
  attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'md', type: 'set' },
1388
1629
  content: [
1389
1630
  {
1390
1631
  tag: 'remove-companion-device',
1391
- attrs: { platform: 'true', reason, id: String(keyIndex) }
1632
+ attrs: { jid, reason }
1392
1633
  }
1393
1634
  ]
1394
1635
  })
1395
1636
  }
1396
- /**
1397
- * Push an updated key-index-list to the server (multi-device key announcement).
1398
- * Mirrors KeyIndexListJob — xmlns="md", child tag "key-index-list" with a binary proto body.
1399
- * ts: unix timestamp seconds (string or number).
1400
- * content: Buffer — serialized proto KeyIndexList.
1401
- */
1637
+
1402
1638
  const updateKeyIndexList = async (ts, content) => {
1403
1639
  await query({
1404
1640
  tag: 'iq',
@@ -1412,11 +1648,39 @@ const makeChatsSocket = config => {
1412
1648
  ]
1413
1649
  })
1414
1650
  }
1415
- /**
1416
- * Request a media upload connection token from the server.
1417
- * Mirrors MediaConnFetcher — xmlns="w:m", type="set", optional last_id.
1418
- * Returns the raw media_conn node.
1419
- */
1651
+
1652
+ const sendKeyIndexList = async () => {
1653
+ const account = authState.creds.account
1654
+ const signedIdentityKey = authState.creds.signedIdentityKey
1655
+ if (!account?.details || !signedIdentityKey?.private) {
1656
+ logger.debug('no ADV account / signed identity key, skipping key-index-list')
1657
+ return
1658
+ }
1659
+ const deviceIdentity = index_js_1.proto.ADVDeviceIdentity.decode(account.details)
1660
+ const keyIndex = deviceIdentity.keyIndex || 0
1661
+ const accountType = deviceIdentity.accountType ?? index_js_1.proto.ADVEncryptionType.E2EE
1662
+ const isHosted = accountType === index_js_1.proto.ADVEncryptionType.HOSTED
1663
+ const ts = Math.floor(Date.now() / 1000)
1664
+ const details = index_js_1.proto.ADVKeyIndexList.encode({
1665
+ rawId: deviceIdentity.rawId || 0,
1666
+ timestamp: ts,
1667
+ currentIndex: keyIndex,
1668
+ validIndexes: [keyIndex],
1669
+ accountType
1670
+ }).finish()
1671
+ const sigPrefix = isHosted
1672
+ ? Defaults_1.WA_ADV_HOSTED_KEY_INDEX_LIST_SIG_PREFIX
1673
+ : Defaults_1.WA_ADV_KEY_INDEX_LIST_SIG_PREFIX
1674
+ const accountSignature = Utils_1.Curve.sign(signedIdentityKey.private, Buffer.concat([sigPrefix, details]))
1675
+ const signedKeyIndexList = { details, accountSignature }
1676
+ if (isHosted && account.accountSignatureKey?.length) {
1677
+ signedKeyIndexList.accountSignatureKey = account.accountSignatureKey
1678
+ }
1679
+ const content = index_js_1.proto.ADVSignedKeyIndexList.encode(signedKeyIndexList).finish()
1680
+ await updateKeyIndexList(ts, Buffer.from(content))
1681
+ logger.info({ ts, keyIndex }, 'sent key-index-list')
1682
+ }
1683
+
1420
1684
  const fetchMediaConn = async (lastId = null) => {
1421
1685
  const attrs = {}
1422
1686
  if (lastId != null) attrs.last_id = String(lastId)
@@ -1427,10 +1691,7 @@ const makeChatsSocket = config => {
1427
1691
  })
1428
1692
  return (0, WABinary_1.getBinaryNodeChild)(result, 'media_conn') || null
1429
1693
  }
1430
- /**
1431
- * Delete a broadcast list by ID.
1432
- * Mirrors BroadcastListDeleteJob — xmlns="w:b", wraps <delete><list id="..."/></delete>.
1433
- */
1694
+
1434
1695
  const deleteBroadcastList = async listId => {
1435
1696
  await query({
1436
1697
  tag: 'iq',
@@ -1444,11 +1705,7 @@ const makeChatsSocket = config => {
1444
1705
  ]
1445
1706
  })
1446
1707
  }
1447
- /**
1448
- * Fetch a QR code from the server (e.g. for linked-device linking).
1449
- * Mirrors QRCodeFetcher — xmlns="w:qr", type="get".
1450
- * addressingMode: "lid" | undefined — set to "lid" for LID-addressed QR.
1451
- */
1708
+
1452
1709
  const fetchQRCode = async (code, addressingMode = null) => {
1453
1710
  const attrs = { code }
1454
1711
  if (addressingMode) attrs.addressing_mode = addressingMode
@@ -1459,15 +1716,13 @@ const makeChatsSocket = config => {
1459
1716
  })
1460
1717
  return (0, WABinary_1.getBinaryNodeChild)(result, 'qr') || null
1461
1718
  }
1462
- /**
1463
- * Confirm or deny a device-logout request from the server.
1464
- * Mirrors AccountDefenceDeviceLogoutJob — xmlns="w:account_defence", smax_id=87.
1465
- * approve: true to confirm the logout, false to deny.
1466
- */
1719
+
1467
1720
  const confirmDeviceLogout = async (id, approve = true) => {
1468
1721
  await query({
1469
1722
  tag: 'iq',
1470
1723
  attrs: {
1724
+
1725
+ id: String(id),
1471
1726
  to: WABinary_1.S_WHATSAPP_NET,
1472
1727
  xmlns: 'w:account_defence',
1473
1728
  type: 'set',
@@ -1481,14 +1736,78 @@ const makeChatsSocket = config => {
1481
1736
  ]
1482
1737
  })
1483
1738
  }
1484
- /**
1485
- * queries need to be fired on connection open
1486
- * help ensure parity with WA Web
1487
- * */
1739
+
1488
1740
  const executeInitQueries = async () => {
1489
- await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings(), initInterop()])
1741
+
1742
+
1743
+ const shouldInitInterop = authState.creds.interopEnabled !== false
1744
+ const [, , , abProps] = await Promise.all([
1745
+ fetchProps(),
1746
+ fetchBlocklist(),
1747
+ fetchPrivacySettings(),
1748
+ fetchABProps(),
1749
+ ...(shouldInitInterop ? [initInterop()] : [])
1750
+ ])
1751
+ const INTEROP_FLAGS = ['stella_interop_enabled', 'stella_ios_enabled']
1752
+ for (const flag of INTEROP_FLAGS) {
1753
+ if (flag in abProps) {
1754
+ const enabled = abProps[flag] === 'true' || abProps[flag] === '1'
1755
+ ev.emit('interop.feature-update', { feature: flag, enabled })
1756
+ }
1757
+ }
1758
+
1759
+ const abCredsUpdate = {}
1760
+ if ('stella_interop_enabled' in abProps) {
1761
+ abCredsUpdate.interopEnabled =
1762
+ abProps['stella_interop_enabled'] === 'true' || abProps['stella_interop_enabled'] === '1'
1763
+ }
1764
+ if ('stella_ios_enabled' in abProps) {
1765
+ abCredsUpdate.interopIosEnabled =
1766
+ abProps['stella_ios_enabled'] === 'true' || abProps['stella_ios_enabled'] === '1'
1767
+ }
1768
+ if ('md_privacy_v2' in abProps) {
1769
+ abCredsUpdate.mdPrivacyV2 = abProps['md_privacy_v2'] === 'true' || abProps['md_privacy_v2'] === '1'
1770
+ }
1771
+
1772
+ const MEDIA_AB_PROPS = [
1773
+ 'hd_image_dual_upload',
1774
+ 'hd_video_dual_upload',
1775
+ 'hevc_video_dual_upload',
1776
+ 'partial_pjpeg_enabled',
1777
+ 'multi_scan_pjpeg',
1778
+ 'media_poll'
1779
+ ]
1780
+ const mediaAbProps = {}
1781
+ for (const prop of MEDIA_AB_PROPS) {
1782
+ if (prop in abProps) {
1783
+ mediaAbProps[prop] = abProps[prop] === 'true' || abProps[prop] === '1'
1784
+ }
1785
+ }
1786
+ if (Object.keys(mediaAbProps).length) {
1787
+ abCredsUpdate.mediaAbProps = { ...(authState.creds.mediaAbProps || {}), ...mediaAbProps }
1788
+ }
1789
+ if (Object.keys(abCredsUpdate).length) {
1790
+ ev.emit('creds.update', abCredsUpdate)
1791
+ }
1490
1792
  }
1491
1793
  const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
1794
+
1795
+ if (msg.newsletter && msg.newsletter_server_id && msg.key?.id) {
1796
+ _nlServerIdCache.set(msg.key.id, msg.newsletter_server_id)
1797
+ }
1798
+
1799
+ if (msg.message) {
1800
+ const ci = Object.values(msg.message).find(v => v?.contextInfo)?.contextInfo
1801
+ if (ci?.stanzaId && (0, WABinary_1.isJidNewsletter)(ci.remoteJid)) {
1802
+ const sid = _nlServerIdCache.get(ci.stanzaId)
1803
+ if (sid != null) msg.quotedNewsletterServerId = sid
1804
+ }
1805
+ }
1806
+ if (msg.message?.secretEncryptedMessage || msg.message?.editedMessage?.message?.secretEncryptedMessage) {
1807
+
1808
+
1809
+ await unwrapSecretEncryptedMessage(msg, { creds: authState.creds, getMessage, logger })
1810
+ }
1492
1811
  ev.emit('messages.upsert', { messages: [msg], type })
1493
1812
  if (!!msg.pushName) {
1494
1813
  let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid
@@ -1496,7 +1815,7 @@ const makeChatsSocket = config => {
1496
1815
  if (!msg.key.fromMe) {
1497
1816
  ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }])
1498
1817
  }
1499
- // update our pushname too
1818
+
1500
1819
  if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) {
1501
1820
  ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } })
1502
1821
  }
@@ -1507,7 +1826,7 @@ const makeChatsSocket = config => {
1507
1826
  : false
1508
1827
  if (historyMsg && shouldProcessHistoryMsg) {
1509
1828
  const syncType = historyMsg.syncType
1510
- // INITIAL_BOOTSTRAP — fire immediately, no progress check
1829
+
1511
1830
  if (
1512
1831
  syncType === index_js_1.proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP &&
1513
1832
  !historySyncStatus.initialBootstrapComplete
@@ -1519,7 +1838,7 @@ const makeChatsSocket = config => {
1519
1838
  explicit: true
1520
1839
  })
1521
1840
  }
1522
- // RECENT with progress === 100 — explicit completion
1841
+
1523
1842
  if (
1524
1843
  syncType === index_js_1.proto.HistorySync.HistorySyncType.RECENT &&
1525
1844
  historyMsg.progress === 100 &&
@@ -1534,7 +1853,7 @@ const makeChatsSocket = config => {
1534
1853
  explicit: true
1535
1854
  })
1536
1855
  }
1537
- // Reset 120s paused timeout on any RECENT chunk
1856
+
1538
1857
  if (syncType === index_js_1.proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
1539
1858
  clearTimeout(historySyncPausedTimeout)
1540
1859
  historySyncPausedTimeout = setTimeout(() => {
@@ -1550,7 +1869,7 @@ const makeChatsSocket = config => {
1550
1869
  }, Defaults_1.HISTORY_SYNC_PAUSED_TIMEOUT_MS)
1551
1870
  }
1552
1871
  }
1553
- // State machine: decide on sync and flush
1872
+
1554
1873
  if (historyMsg && syncState === State_1.SyncState.AwaitingInitialSync) {
1555
1874
  if (awaitingSyncTimeout) {
1556
1875
  clearTimeout(awaitingSyncTimeout)
@@ -1559,7 +1878,7 @@ const makeChatsSocket = config => {
1559
1878
  if (shouldProcessHistoryMsg) {
1560
1879
  syncState = State_1.SyncState.Syncing
1561
1880
  logger.info('Transitioned to Syncing state')
1562
- // Let doAppStateSync handle the final flush after it's done
1881
+
1563
1882
  } else {
1564
1883
  syncState = State_1.SyncState.Online
1565
1884
  logger.info('History sync skipped, transitioning to Online state and flushing buffer')
@@ -1568,11 +1887,11 @@ const makeChatsSocket = config => {
1568
1887
  }
1569
1888
  const doAppStateSync = async () => {
1570
1889
  if (syncState === State_1.SyncState.Syncing) {
1571
- // All collections will be synced, so clear any blocked ones
1890
+
1572
1891
  blockedCollections.clear()
1573
1892
  logger.info('Doing app state sync')
1574
1893
  await resyncAppState(Types_1.ALL_WA_PATCH_NAMES, true)
1575
- // Sync is complete, go online and flush everything
1894
+
1576
1895
  syncState = State_1.SyncState.Online
1577
1896
  logger.info('App state sync complete, transitioning to Online state and flushing buffer')
1578
1897
  ev.flush()
@@ -1598,7 +1917,7 @@ const makeChatsSocket = config => {
1598
1917
  getMessage
1599
1918
  })
1600
1919
  ])
1601
- // If the app state key arrives and we are waiting to sync, trigger the sync now.
1920
+
1602
1921
  if (msg.message?.protocolMessage?.appStateSyncKeyShare && syncState === State_1.SyncState.Syncing) {
1603
1922
  logger.info('App state sync key arrived, triggering app state sync')
1604
1923
  await doAppStateSync()
@@ -1606,6 +1925,20 @@ const makeChatsSocket = config => {
1606
1925
  })
1607
1926
  ws.on('CB:presence', handlePresenceUpdate)
1608
1927
  ws.on('CB:chatstate', handlePresenceUpdate)
1928
+
1929
+
1930
+
1931
+ ws.on('CB:iq,xmlns:w:account_defence', async node => {
1932
+ const deviceLogout = (0, WABinary_1.getBinaryNodeChild)(node, 'device_logout')
1933
+ if (!deviceLogout) return
1934
+ const id = node.attrs.id
1935
+ logger.info({ id }, 'received account_defence device_logout challenge, approving')
1936
+ try {
1937
+ await confirmDeviceLogout(id, true)
1938
+ } catch (error) {
1939
+ onUnexpectedError(error, 'account_defence device_logout approve')
1940
+ }
1941
+ })
1609
1942
  ws.on('CB:ib,,dirty', async node => {
1610
1943
  const { attrs } = (0, WABinary_1.getBinaryNodeChild)(node, 'dirty')
1611
1944
  const type = attrs.type
@@ -1621,7 +1954,7 @@ const makeChatsSocket = config => {
1621
1954
  }
1622
1955
  break
1623
1956
  case 'groups':
1624
- // handled in groups.ts
1957
+
1625
1958
  break
1626
1959
  default:
1627
1960
  logger.info({ node }, 'received unknown sync')
@@ -1641,6 +1974,9 @@ const makeChatsSocket = config => {
1641
1974
  sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable').catch(error =>
1642
1975
  onUnexpectedError(error, 'presence update requests')
1643
1976
  )
1977
+
1978
+
1979
+ sendKeyIndexList().catch(error => onUnexpectedError(error, 'send key-index-list'))
1644
1980
  }
1645
1981
  if (!receivedPendingNotifications || syncState !== State_1.SyncState.Connecting) {
1646
1982
  return
@@ -1663,9 +1999,9 @@ const makeChatsSocket = config => {
1663
1999
  setTimeout(() => ev.flush(), 0)
1664
2000
  return
1665
2001
  }
1666
- // On reconnection (accountSyncCounter > 0), the server does not push
1667
- // history sync notifications — the device already has its data.
1668
- // Skip the 20s wait and go online immediately.
2002
+
2003
+
2004
+
1669
2005
  if (authState.creds.accountSyncCounter > 0) {
1670
2006
  logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.')
1671
2007
  syncState = State_1.SyncState.Online
@@ -1681,18 +2017,18 @@ const makeChatsSocket = config => {
1681
2017
  logger.warn('Timeout in AwaitingInitialSync, forcing state to Online and flushing buffer')
1682
2018
  syncState = State_1.SyncState.Online
1683
2019
  ev.flush()
1684
- // Increment so subsequent reconnections skip the 20s wait.
2020
+
1685
2021
  const accountSyncCounter = (authState.creds.accountSyncCounter || 0) + 1
1686
2022
  ev.emit('creds.update', { accountSyncCounter })
1687
2023
  }
1688
2024
  }, 20000)
1689
2025
  })
1690
- // When an app state sync key arrives and there are collections blocked on a missing key, re-sync them.
2026
+
1691
2027
  ev.on('creds.update', ({ myAppStateKeyId }) => {
1692
2028
  if (!myAppStateKeyId || blockedCollections.size === 0) {
1693
2029
  return
1694
2030
  }
1695
- // If we're in the middle of a full sync, doAppStateSync handles all collections
2031
+
1696
2032
  if (syncState === State_1.SyncState.Syncing) {
1697
2033
  blockedCollections.clear()
1698
2034
  return
@@ -1718,9 +2054,12 @@ const makeChatsSocket = config => {
1718
2054
  getChatBlockingStatus,
1719
2055
  updateChatBlockingStatus,
1720
2056
  getUserDisclosures,
2057
+ acceptTosNotice,
2058
+ reportSpam,
1721
2059
  getOptOutList,
1722
2060
  getPushConfig,
1723
2061
  setPushConfig,
2062
+ signPrivateCredential,
1724
2063
  messageMutex,
1725
2064
  receiptMutex,
1726
2065
  appStatePatchMutex,
@@ -1769,6 +2108,7 @@ const makeChatsSocket = config => {
1769
2108
  renameAIThread,
1770
2109
  pinThreadMessage,
1771
2110
  updatePrivateProcessingSetting,
2111
+ updateAIFeatures,
1772
2112
  updateBioPrivacy,
1773
2113
  blockBot,
1774
2114
  unblockBot,
@@ -1782,6 +2122,10 @@ const makeChatsSocket = config => {
1782
2122
  markChatAsUnread,
1783
2123
  setChatEphemeral,
1784
2124
  silenceChat,
2125
+ clearChat,
2126
+ pinChat,
2127
+ starMessages,
2128
+ setQuickReply,
1785
2129
  fetchBotProfiles,
1786
2130
  updateChatLock,
1787
2131
  updateChatWallpaper,
@@ -1810,7 +2154,12 @@ const makeChatsSocket = config => {
1810
2154
  fetchMediaConn,
1811
2155
  deleteBroadcastList,
1812
2156
  fetchQRCode,
1813
- confirmDeviceLogout
2157
+ confirmDeviceLogout,
2158
+ updateKeyIndexList,
2159
+ sendKeyIndexList,
2160
+ storePrivacyTokens,
2161
+ executeUSyncQuery,
2162
+ newsletterServerIdCache: _nlServerIdCache
1814
2163
  }
1815
2164
  }
1816
2165
  exports.makeChatsSocket = makeChatsSocket