@badzz88/baileys 8.4.9 → 8.5.1
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.
- package/README.md +1 -1
- package/WAProto/WAProto.proto +6 -347
- package/WAProto/fix-imports.js +85 -86
- package/WAProto/index.d.ts +25 -4913
- package/WAProto/index.js +93 -14069
- package/{src → lib}/Defaults/index.js +2 -13
- package/{src → lib}/Signal/Group/group_cipher.js +5 -5
- package/{src → lib}/Signal/Group/keyhelper.js +4 -4
- package/{src → lib}/Signal/Group/sender-chain-key.js +2 -2
- package/{src → lib}/Signal/Group/sender-key-distribution-message.js +1 -1
- package/{src → lib}/Signal/Group/sender-key-message.js +3 -3
- package/{src → lib}/Signal/Group/sender-key-record.js +1 -1
- package/{src → lib}/Signal/Group/sender-message-key.js +2 -2
- package/{src → lib}/Signal/libsignal.js +26 -6
- package/{src → lib}/Socket/chats.js +58 -616
- package/{src → lib}/Socket/communities.js +28 -59
- package/{src → lib}/Socket/graphql.js +103 -362
- package/{src → lib}/Socket/groups.js +29 -77
- package/{src → lib}/Socket/index.js +5 -3
- package/{src → lib}/Socket/interactive-handler.js +60 -117
- package/{src → lib}/Socket/interop.js +19 -36
- package/lib/Socket/luxu.js +387 -0
- package/{src → lib}/Socket/managed-account.js +27 -58
- package/{src → lib}/Socket/messages-recv.js +144 -715
- package/{src → lib}/Socket/messages-send.js +102 -412
- package/lib/Socket/mex.js +60 -0
- package/{src → lib}/Socket/newsletter.js +23 -266
- package/{src → lib}/Socket/privacy.js +38 -41
- package/{src → lib}/Socket/registration.js +106 -113
- package/{src → lib}/Socket/socket.js +101 -88
- package/{src → lib}/Socket/text-router.js +17 -1
- package/lib/Socket/username.js +243 -0
- package/lib/Store/keyed-db.js +108 -0
- package/{src → lib}/Store/make-in-memory-store.js +104 -6
- package/lib/Types/Mex.js +112 -0
- package/{src → lib}/Types/Newsletter.js +41 -72
- package/{src → lib}/Types/index.js +1 -0
- package/{src → lib}/Utils/chat-utils.js +9 -73
- package/lib/Utils/command-loader.d.ts +31 -0
- package/lib/Utils/command-loader.js +100 -0
- package/lib/Utils/companion-reg-client-utils.js +42 -0
- package/lib/Utils/crypto.js +155 -0
- package/lib/Utils/curve25519-js.js +275 -0
- package/{src → lib}/Utils/decode-wa-message.js +36 -284
- package/{src → lib}/Utils/event-buffer.js +27 -6
- package/{src → lib}/Utils/generics.js +37 -114
- package/{src → lib}/Utils/index.js +5 -1
- package/lib/Utils/lt-hash.js +39 -0
- package/{src → lib}/Utils/message-retry-manager.js +39 -0
- package/{src → lib}/Utils/messages-media.js +58 -105
- package/{src → lib}/Utils/messages.js +529 -115
- package/lib/Utils/meta-ai-msmsg.js +262 -0
- package/lib/Utils/native-bridge.js +82 -0
- package/{src → lib}/Utils/noise-handler.js +15 -13
- package/{src → lib}/Utils/process-message.js +55 -54
- package/{src → lib}/Utils/reporting-utils.js +2 -2
- package/lib/Utils/session-pool.d.ts +16 -0
- package/lib/Utils/session-pool.js +94 -0
- package/{src → lib}/Utils/signal.js +51 -19
- package/{src → lib}/Utils/stanza-ack.js +2 -36
- package/lib/Utils/sticker.d.ts +17 -0
- package/lib/Utils/sticker.js +145 -0
- package/{src → lib}/Utils/tc-token-utils.js +1 -1
- package/{src → lib}/Utils/validate-connection.js +11 -40
- package/lib/Utils/view-once-cache.d.ts +9 -0
- package/lib/Utils/view-once-cache.js +79 -0
- package/{src → lib}/WABinary/decode.js +4 -38
- package/lib/WABinary/encode.js +228 -0
- package/{src → lib}/WABinary/generic-utils.js +93 -1
- package/{src → lib}/WABinary/jid-utils.js +3 -16
- package/{src → lib}/WAM/encode.js +1 -1
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
- package/{src → lib}/WAUSync/Protocols/USyncContactProtocol.js +1 -7
- package/{src → lib}/WAUSync/Protocols/USyncDeviceProtocol.js +5 -13
- package/{src → lib}/WAUSync/Protocols/USyncDisappearingModeProtocol.js +4 -1
- package/{src → lib}/WAUSync/Protocols/USyncFeatureProtocol.js +2 -17
- package/{src → lib}/WAUSync/Protocols/USyncStatusProtocol.js +2 -7
- package/{src → lib}/WAUSync/Protocols/USyncUsernameProtocol.js +1 -2
- package/{src → lib}/WAUSync/Protocols/index.js +0 -1
- package/{src → lib}/WAUSync/USyncQuery.js +6 -77
- package/{src → lib}/WAUSync/USyncUser.js +8 -16
- package/{src → lib}/WAUSync/index.js +0 -1
- package/{src → lib}/antiban.js +266 -355
- package/{src → lib}/index.js +2 -2
- package/package.json +19 -16
- package/WAProto/fix-import.js +0 -38
- package/src/Socket/mex.js +0 -47
- package/src/Socket/username.js +0 -234
- package/src/Utils/crypto.js +0 -125
- package/src/Utils/lt-hash.js +0 -10
- package/src/Utils/message-inspect.js +0 -400
- package/src/Utils/meta-ai-msmsg.js +0 -133
- package/src/WABinary/encode.js +0 -58
- package/src/WAUSync/Protocols/USyncBusinessProtocol.js +0 -100
- package/src/WAUSync/USyncBackoff.js +0 -31
- /package/{src → lib}/Defaults/phonenumber-mcc.json +0 -0
- /package/{src → lib}/Signal/Group/ciphertext-message.js +0 -0
- /package/{src → lib}/Signal/Group/group-session-builder.js +0 -0
- /package/{src → lib}/Signal/Group/index.js +0 -0
- /package/{src → lib}/Signal/Group/sender-key-name.js +0 -0
- /package/{src → lib}/Signal/Group/sender-key-state.js +0 -0
- /package/{src → lib}/Signal/lid-mapping.js +0 -0
- /package/{src → lib}/Socket/Client/index.js +0 -0
- /package/{src → lib}/Socket/Client/types.js +0 -0
- /package/{src → lib}/Socket/Client/websocket.js +0 -0
- /package/{src → lib}/Socket/aigroups.js +0 -0
- /package/{src → lib}/Socket/business.js +0 -0
- /package/{src → lib}/Store/index.js +0 -0
- /package/{src → lib}/Store/make-cache-manager-store.js +0 -0
- /package/{src → lib}/Store/make-ordered-dictionary.js +0 -0
- /package/{src → lib}/Store/object-repository.js +0 -0
- /package/{src → lib}/Types/Auth.js +0 -0
- /package/{src → lib}/Types/Bussines.js +0 -0
- /package/{src → lib}/Types/Call.js +0 -0
- /package/{src → lib}/Types/Chat.js +0 -0
- /package/{src → lib}/Types/Contact.js +0 -0
- /package/{src → lib}/Types/Events.js +0 -0
- /package/{src → lib}/Types/GroupMetadata.js +0 -0
- /package/{src → lib}/Types/Label.js +0 -0
- /package/{src → lib}/Types/LabelAssociation.js +0 -0
- /package/{src → lib}/Types/Message.js +0 -0
- /package/{src → lib}/Types/Product.js +0 -0
- /package/{src → lib}/Types/Signal.js +0 -0
- /package/{src → lib}/Types/Socket.js +0 -0
- /package/{src → lib}/Types/State.js +0 -0
- /package/{src → lib}/Types/USync.js +0 -0
- /package/{src → lib}/Utils/auth-utils.js +0 -0
- /package/{src → lib}/Utils/browser-utils.js +0 -0
- /package/{src → lib}/Utils/business.js +0 -0
- /package/{src → lib}/Utils/consumer-application.js +0 -0
- /package/{src → lib}/Utils/group-history.js +0 -0
- /package/{src → lib}/Utils/history.js +0 -0
- /package/{src → lib}/Utils/identity-change-handler.js +0 -0
- /package/{src → lib}/Utils/jid-display-normalization.js +0 -0
- /package/{src → lib}/Utils/link-preview.js +0 -0
- /package/{src → lib}/Utils/logger.js +0 -0
- /package/{src → lib}/Utils/make-mutex.js +0 -0
- /package/{src → lib}/Utils/message-composer.js +0 -0
- /package/{src → lib}/Utils/offline-node-processor.js +0 -0
- /package/{src → lib}/Utils/pre-key-manager.js +0 -0
- /package/{src → lib}/Utils/sync-action-utils.js +0 -0
- /package/{src → lib}/Utils/use-multi-file-auth-state.js +0 -0
- /package/{src → lib}/Utils/voip-rekey.js +0 -0
- /package/{src → lib}/WABinary/constants.js +0 -0
- /package/{src → lib}/WABinary/index.js +0 -0
- /package/{src → lib}/WABinary/types.js +0 -0
- /package/{src → lib}/WAM/BinaryInfo.js +0 -0
- /package/{src → lib}/WAM/constants.js +0 -0
- /package/{src → lib}/WAM/index.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncBotProfileProtocol.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncLIDProtocol.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncPictureProtocol.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncSidelistProtocol.js +0 -0
- /package/{src → lib}/WAUSync/Protocols/USyncTextStatusProtocol.js +0 -0
|
@@ -91,8 +91,6 @@ const makeChatsSocket = config => {
|
|
|
91
91
|
// Collections blocked on missing app state sync keys (mirrors WA Web's "Blocked" state).
|
|
92
92
|
// When a key arrives via APP_STATE_SYNC_KEY_SHARE, these are re-synced.
|
|
93
93
|
const blockedCollections = new Set()
|
|
94
|
-
/** messageId → numeric server_id for newsletter messages (persists within session) */
|
|
95
|
-
const _nlServerIdCache = new Map()
|
|
96
94
|
const placeholderResendCache =
|
|
97
95
|
config.placeholderResendCache ||
|
|
98
96
|
new node_cache_1.default({
|
|
@@ -118,63 +116,7 @@ const makeChatsSocket = config => {
|
|
|
118
116
|
},
|
|
119
117
|
content: [{ tag: 'privacy', attrs: {} }]
|
|
120
118
|
})
|
|
121
|
-
|
|
122
|
-
privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(privacyNode, 'category')
|
|
123
|
-
|
|
124
|
-
// A. Online Privacy Mode — may arrive as <category name="online" value="..."/> or
|
|
125
|
-
// as a dedicated <online_privacy_setting value="..."/> child node.
|
|
126
|
-
const onlinePrivacy =
|
|
127
|
-
privacySettings['online'] ||
|
|
128
|
-
(0, WABinary_1.getBinaryNodeChild)(privacyNode, 'online_privacy_setting')?.attrs?.value ||
|
|
129
|
-
'all'
|
|
130
|
-
|
|
131
|
-
// C. Enhanced Block — root attrs or dedicated child node.
|
|
132
|
-
const enhancedBlockEnabled =
|
|
133
|
-
privacyNode?.attrs?.enhanced_block_enabled === 'true' ||
|
|
134
|
-
(0, WABinary_1.getBinaryNodeChild)(privacyNode, 'enhanced_block')?.attrs?.enabled === 'true'
|
|
135
|
-
|
|
136
|
-
// E. MD Privacy V2 flag.
|
|
137
|
-
const mdPrivacyV2 =
|
|
138
|
-
privacyNode?.attrs?.md_privacy_v2 === 'true' ||
|
|
139
|
-
(0, WABinary_1.getBinaryNodeChild)(privacyNode, 'md_privacy_v2')?.attrs?.value === 'true'
|
|
140
|
-
|
|
141
|
-
// F. Syncd clear-chat / delete-chat flag.
|
|
142
|
-
const syncdClearChatDeleteChatEnabled =
|
|
143
|
-
privacyNode?.attrs?.syncd_clear_chat_delete_chat_enabled === 'true' ||
|
|
144
|
-
(0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_clear_chat')?.attrs?.delete_chat_enabled === 'true'
|
|
145
|
-
|
|
146
|
-
// A. Syncd anti-tampering fatal-exception gate.
|
|
147
|
-
const syncdAntiTamperingEnabled =
|
|
148
|
-
privacyNode?.attrs?.syncd_anti_tampering_fatal_exception_enabled === 'true' ||
|
|
149
|
-
(0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_anti_tampering')?.attrs?.fatal_exception_enabled ===
|
|
150
|
-
'true'
|
|
151
|
-
|
|
152
|
-
// B. Syncd key-index rotation gate.
|
|
153
|
-
const keyRotationEnabled =
|
|
154
|
-
privacyNode?.attrs?.syncd_key_rotation_enabled === 'true' ||
|
|
155
|
-
(0, WABinary_1.getBinaryNodeChild)(privacyNode, 'syncd_key_rotation')?.attrs?.enabled === 'true'
|
|
156
|
-
|
|
157
|
-
// Emit combined settings update so consumers can react without parsing raw creds.
|
|
158
|
-
ev.emit('settings.update', {
|
|
159
|
-
onlinePrivacy,
|
|
160
|
-
enhancedBlockEnabled,
|
|
161
|
-
mdPrivacyV2,
|
|
162
|
-
syncdClearChatDeleteChatEnabled,
|
|
163
|
-
...(syncdAntiTamperingEnabled ? { syncdAntiTamperingEnabled: true } : {}),
|
|
164
|
-
keyRotationEnabled
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
// Persist extended privacy flags in credentials.
|
|
168
|
-
ev.emit('creds.update', {
|
|
169
|
-
privacySettings: {
|
|
170
|
-
onlinePrivacy,
|
|
171
|
-
enhancedBlockEnabled,
|
|
172
|
-
mdPrivacyV2,
|
|
173
|
-
syncdClearChatDeleteChatEnabled,
|
|
174
|
-
syncdAntiTamperingEnabled,
|
|
175
|
-
keyRotationEnabled
|
|
176
|
-
}
|
|
177
|
-
})
|
|
119
|
+
privacySettings = (0, WABinary_1.reduceBinaryNodeToDictionary)(content?.[0], 'category')
|
|
178
120
|
}
|
|
179
121
|
return privacySettings
|
|
180
122
|
}
|
|
@@ -237,7 +179,7 @@ const makeChatsSocket = config => {
|
|
|
237
179
|
const privacyNode = result?.content?.[0]
|
|
238
180
|
if (!privacyNode) return null
|
|
239
181
|
const lists = []
|
|
240
|
-
for (const listNode of privacyNode.content || []) {
|
|
182
|
+
for (const listNode of (privacyNode.content || [])) {
|
|
241
183
|
const { type, id, listname, emoji, selected, deleted } = listNode.attrs || {}
|
|
242
184
|
const members = (listNode.content || []).map(u => u.attrs?.jid).filter(Boolean)
|
|
243
185
|
lists.push({ type, id, listname, emoji, selected: selected === 'true', deleted: deleted === 'true', members })
|
|
@@ -330,35 +272,12 @@ const makeChatsSocket = config => {
|
|
|
330
272
|
const timeframeNode = (0, WABinary_1.getBinaryNodeChild)(result, 'timeframe')
|
|
331
273
|
if (!limitsNode) return null
|
|
332
274
|
return {
|
|
333
|
-
messagesLeft: parseInt(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
),
|
|
339
|
-
totalLimit: parseInt(
|
|
340
|
-
limitsNode.attrs?.total_limit ?? (0, WABinary_1.getBinaryNodeChild)(limitsNode, 'total_limit')?.content ?? '0',
|
|
341
|
-
10
|
|
342
|
-
),
|
|
343
|
-
isHeavySender:
|
|
344
|
-
(limitsNode.attrs?.is_heavy_sender ??
|
|
345
|
-
(0, WABinary_1.getBinaryNodeChild)(limitsNode, 'is_heavy_sender')?.content) === 'true',
|
|
346
|
-
startTs: parseInt(
|
|
347
|
-
timeframeNode?.attrs?.start_ts_s ??
|
|
348
|
-
(0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'start_ts_s')?.content ??
|
|
349
|
-
'0',
|
|
350
|
-
10
|
|
351
|
-
),
|
|
352
|
-
endTs: parseInt(
|
|
353
|
-
timeframeNode?.attrs?.end_ts_s ?? (0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'end_ts_s')?.content ?? '0',
|
|
354
|
-
10
|
|
355
|
-
),
|
|
356
|
-
resetTs: parseInt(
|
|
357
|
-
timeframeNode?.attrs?.reset_ts_s ??
|
|
358
|
-
(0, WABinary_1.getBinaryNodeChild)(timeframeNode, 'reset_ts_s')?.content ??
|
|
359
|
-
'0',
|
|
360
|
-
10
|
|
361
|
-
)
|
|
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')
|
|
362
281
|
}
|
|
363
282
|
}
|
|
364
283
|
|
|
@@ -446,52 +365,6 @@ const makeChatsSocket = config => {
|
|
|
446
365
|
})
|
|
447
366
|
return (0, WABinary_1.getBinaryNodeChildren)(result, 'notice').map(n => ({ ...n.attrs }))
|
|
448
367
|
}
|
|
449
|
-
/**
|
|
450
|
-
* Accept a TOS/disclosure notice (`xmlns="tos"` set with `<trackable>`).
|
|
451
|
-
* Mirrors WASmaxUserNoticeTrackDisclosureRPC from WA Web.
|
|
452
|
-
* @param {string} noticeId the notice id (e.g. "20250211")
|
|
453
|
-
* @param {string|number} result the result code (e.g. "105")
|
|
454
|
-
*/
|
|
455
|
-
const acceptTosNotice = async (noticeId, result = '105') => {
|
|
456
|
-
await query({
|
|
457
|
-
tag: 'iq',
|
|
458
|
-
attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'tos', type: 'set' },
|
|
459
|
-
content: [{ tag: 'trackable', attrs: { id: String(noticeId), result: String(result) } }]
|
|
460
|
-
})
|
|
461
|
-
}
|
|
462
|
-
/**
|
|
463
|
-
* Report spam for a chat/group with sample messages.
|
|
464
|
-
* Mirrors WASmaxReportSpamRPC (xmlns="spam", type="set").
|
|
465
|
-
* @param {string} jid the JID of the chat or group being reported
|
|
466
|
-
* @param {Array<{id:string, from:string, t:number}>} messages up to ~5 recent messages as evidence
|
|
467
|
-
* @param {string} [spamFlow] e.g. "group_info_report" or "contact_info_report"
|
|
468
|
-
* @param {string} [subject] human-readable subject string (group name or contact name)
|
|
469
|
-
*/
|
|
470
|
-
const reportSpam = async (jid, messages, spamFlow = 'contact_info_report', subject) => {
|
|
471
|
-
const msgNodes = (messages || []).map(m => ({
|
|
472
|
-
tag: 'message',
|
|
473
|
-
attrs: {
|
|
474
|
-
from: String(jid),
|
|
475
|
-
t: String(m.t),
|
|
476
|
-
id: String(m.id)
|
|
477
|
-
}
|
|
478
|
-
}))
|
|
479
|
-
await query({
|
|
480
|
-
tag: 'iq',
|
|
481
|
-
attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'spam', type: 'set' },
|
|
482
|
-
content: [
|
|
483
|
-
{
|
|
484
|
-
tag: 'spam_list',
|
|
485
|
-
attrs: {
|
|
486
|
-
jid: String(jid),
|
|
487
|
-
spam_flow: String(spamFlow),
|
|
488
|
-
...(subject ? { subject: String(subject) } : {})
|
|
489
|
-
},
|
|
490
|
-
content: msgNodes
|
|
491
|
-
}
|
|
492
|
-
]
|
|
493
|
-
})
|
|
494
|
-
}
|
|
495
368
|
/**
|
|
496
369
|
* Get the account opt-out list (`optoutlist` IQ). Returns the raw result node.
|
|
497
370
|
*/
|
|
@@ -501,28 +374,6 @@ const makeChatsSocket = config => {
|
|
|
501
374
|
attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'optoutlist', type: 'get' }
|
|
502
375
|
})
|
|
503
376
|
}
|
|
504
|
-
/**
|
|
505
|
-
* Sign a blinded credential for privacy-preserving anonymous stats.
|
|
506
|
-
* Mirrors WASmaxPrivateStatsSignCredentialRPC (xmlns `privatestats`).
|
|
507
|
-
* @param {Buffer} blindedCredential 32-byte blinded credential
|
|
508
|
-
* @returns {Promise<Buffer|undefined>} signed credential bytes from server
|
|
509
|
-
*/
|
|
510
|
-
const signPrivateCredential = async blindedCredential => {
|
|
511
|
-
const result = await query({
|
|
512
|
-
tag: 'iq',
|
|
513
|
-
attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'privatestats', type: 'get' },
|
|
514
|
-
content: [
|
|
515
|
-
{
|
|
516
|
-
tag: 'sign_credential',
|
|
517
|
-
attrs: { version: '1' },
|
|
518
|
-
content: [{ tag: 'blinded_credential', attrs: {}, content: blindedCredential }]
|
|
519
|
-
}
|
|
520
|
-
]
|
|
521
|
-
})
|
|
522
|
-
const signedNode = (0, WABinary_1.getBinaryNodeChild)(result, 'sign_credential')
|
|
523
|
-
const signedBytes = (0, WABinary_1.getBinaryNodeChild)(signedNode, 'signed_credential')
|
|
524
|
-
return signedBytes?.content instanceof Uint8Array ? Buffer.from(signedBytes.content) : undefined
|
|
525
|
-
}
|
|
526
377
|
/**
|
|
527
378
|
* Get push-notification settings (`urn:xmpp:whatsapp:push`).
|
|
528
379
|
*/
|
|
@@ -551,7 +402,7 @@ const makeChatsSocket = config => {
|
|
|
551
402
|
for (const jid of jids) {
|
|
552
403
|
usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid))
|
|
553
404
|
}
|
|
554
|
-
const result = await executeUSyncQuery(usyncQuery)
|
|
405
|
+
const result = await sock.executeUSyncQuery(usyncQuery)
|
|
555
406
|
if (result) {
|
|
556
407
|
return result.list
|
|
557
408
|
}
|
|
@@ -561,7 +412,7 @@ const makeChatsSocket = config => {
|
|
|
561
412
|
for (const jid of jids) {
|
|
562
413
|
usyncQuery.withUser(new WAUSync_1.USyncUser().withId(jid))
|
|
563
414
|
}
|
|
564
|
-
const result = await executeUSyncQuery(usyncQuery)
|
|
415
|
+
const result = await sock.executeUSyncQuery(usyncQuery)
|
|
565
416
|
if (result) {
|
|
566
417
|
return result.list
|
|
567
418
|
}
|
|
@@ -641,24 +492,17 @@ const makeChatsSocket = config => {
|
|
|
641
492
|
const updateProfileName = async name => {
|
|
642
493
|
await chatModify({ pushNameSetting: name }, '')
|
|
643
494
|
}
|
|
644
|
-
|
|
645
|
-
// the current dhash unconditionally, and a fresh list only when it doesn't match.
|
|
646
|
-
// Confirmed from live capture: content is null for a full fetch, or a single
|
|
647
|
-
// <item dhash="..."/> child for the incremental check — never a <blocklist/> child.
|
|
648
|
-
const fetchBlocklist = async (dhash = null) => {
|
|
495
|
+
const fetchBlocklist = async () => {
|
|
649
496
|
const result = await query({
|
|
650
497
|
tag: 'iq',
|
|
651
498
|
attrs: {
|
|
652
499
|
xmlns: 'blocklist',
|
|
653
500
|
to: WABinary_1.S_WHATSAPP_NET,
|
|
654
501
|
type: 'get'
|
|
655
|
-
}
|
|
656
|
-
content: dhash ? [{ tag: 'item', attrs: { dhash } }] : null
|
|
502
|
+
}
|
|
657
503
|
})
|
|
658
504
|
const listNode = (0, WABinary_1.getBinaryNodeChild)(result, 'list')
|
|
659
|
-
|
|
660
|
-
ev.emit('blocklist.set', { blocklist: jids })
|
|
661
|
-
return jids
|
|
505
|
+
return (0, WABinary_1.getBinaryNodeChildren)(listNode, 'item').map(n => n.attrs.jid)
|
|
662
506
|
}
|
|
663
507
|
const updateBlockStatus = async (jid, action) => {
|
|
664
508
|
await query({
|
|
@@ -680,67 +524,26 @@ const makeChatsSocket = config => {
|
|
|
680
524
|
})
|
|
681
525
|
}
|
|
682
526
|
const getBusinessProfile = async jid => {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
tag: 'profile',
|
|
701
|
-
attrs: { jid }
|
|
702
|
-
}
|
|
703
|
-
]
|
|
704
|
-
}
|
|
705
|
-
]
|
|
706
|
-
}),
|
|
707
|
-
query({
|
|
708
|
-
tag: 'iq',
|
|
709
|
-
attrs: {
|
|
710
|
-
to: 's.whatsapp.net',
|
|
711
|
-
xmlns: 'w:biz',
|
|
712
|
-
type: 'get'
|
|
713
|
-
},
|
|
714
|
-
content: [
|
|
715
|
-
{
|
|
716
|
-
tag: 'verified_name',
|
|
717
|
-
attrs: { jid }
|
|
718
|
-
}
|
|
719
|
-
]
|
|
720
|
-
})
|
|
721
|
-
])
|
|
722
|
-
// Parse verified_name certificate if present in the response
|
|
723
|
-
const verifiedNameNode = (0, WABinary_1.getBinaryNodeChild)(verifiedNameResult, 'verified_name')
|
|
724
|
-
let verifiedNameCert
|
|
725
|
-
if (verifiedNameNode) {
|
|
726
|
-
const certNode = (0, WABinary_1.getBinaryNodeChild)(verifiedNameNode, 'certificate')
|
|
727
|
-
if (certNode) {
|
|
728
|
-
const detailsNode = (0, WABinary_1.getBinaryNodeChild)(certNode, 'details')
|
|
729
|
-
const localizedNames = (0, WABinary_1.getBinaryNodeChildren)(certNode, 'localized_name').map(n => ({
|
|
730
|
-
lg: n.attrs?.lg,
|
|
731
|
-
lc: n.attrs?.lc,
|
|
732
|
-
verifiedName: n.attrs?.verified_name || n.content?.toString()
|
|
733
|
-
}))
|
|
734
|
-
verifiedNameCert = {
|
|
735
|
-
certSerial: certNode.attrs?.serial ? +certNode.attrs.serial : undefined,
|
|
736
|
-
issuer: certNode.attrs?.issuer,
|
|
737
|
-
details: {
|
|
738
|
-
verifiedName: detailsNode?.attrs?.verified_name || detailsNode?.content?.toString(),
|
|
739
|
-
localizedNames
|
|
740
|
-
}
|
|
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
|
+
]
|
|
741
544
|
}
|
|
742
|
-
|
|
743
|
-
}
|
|
545
|
+
]
|
|
546
|
+
})
|
|
744
547
|
const profileNode = (0, WABinary_1.getBinaryNodeChild)(results, 'business_profile')
|
|
745
548
|
const profiles = (0, WABinary_1.getBinaryNodeChild)(profileNode, 'profile')
|
|
746
549
|
if (profiles) {
|
|
@@ -757,31 +560,9 @@ const makeChatsSocket = config => {
|
|
|
757
560
|
? (0, WABinary_1.getBinaryNodeChildren)(businessHours, 'business_hours_config')
|
|
758
561
|
: undefined
|
|
759
562
|
const websiteStr = website?.content?.toString()
|
|
760
|
-
|
|
761
|
-
// Catalog status from profile attrs
|
|
762
|
-
const catalogStatus = {
|
|
763
|
-
exists: profiles.attrs?.catalog_exists === 'true' || profiles.attrs?.catalog_exists === true || false,
|
|
764
|
-
sendAll: profiles.attrs?.catalog_send_all === 'true' || profiles.attrs?.catalog_send_all === true || false
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Cart flags from profile attrs
|
|
768
|
-
const cartEnabled =
|
|
769
|
-
profiles.attrs?.cart_enabled !== undefined
|
|
770
|
-
? profiles.attrs.cart_enabled === 'true' || profiles.attrs.cart_enabled === true
|
|
771
|
-
: undefined
|
|
772
|
-
const webCartEnabled =
|
|
773
|
-
profiles.attrs?.web_cart_enabled !== undefined
|
|
774
|
-
? profiles.attrs.web_cart_enabled === 'true' || profiles.attrs.web_cart_enabled === true
|
|
775
|
-
: undefined
|
|
776
|
-
const webCartOnOff = profiles.attrs?.web_cart_on_off
|
|
777
|
-
|
|
778
|
-
// Commerce experience from profile attrs
|
|
779
|
-
const commerceExperience = profiles.attrs?.commerce_experience
|
|
780
|
-
|
|
781
563
|
return {
|
|
782
564
|
wid: profiles.attrs?.jid,
|
|
783
565
|
address: address?.content?.toString(),
|
|
784
|
-
businessAddress: address?.content?.toString(),
|
|
785
566
|
description: description?.content?.toString() || '',
|
|
786
567
|
website: websiteStr ? [websiteStr] : [],
|
|
787
568
|
email: email?.content?.toString(),
|
|
@@ -789,24 +570,7 @@ const makeChatsSocket = config => {
|
|
|
789
570
|
business_hours: {
|
|
790
571
|
timezone: businessHours?.attrs?.timezone,
|
|
791
572
|
business_config: businessHoursConfig?.map(({ attrs }) => attrs)
|
|
792
|
-
}
|
|
793
|
-
businessHours: businessHoursConfig
|
|
794
|
-
? {
|
|
795
|
-
timezone: businessHours?.attrs?.timezone ?? null,
|
|
796
|
-
config: businessHoursConfig.map(({ attrs }) => ({
|
|
797
|
-
dayOfWeek: attrs?.day_of_week ?? null,
|
|
798
|
-
openTime: attrs?.open_time ?? null,
|
|
799
|
-
closeTime: attrs?.close_time ?? null,
|
|
800
|
-
mode: attrs?.mode ?? null
|
|
801
|
-
}))
|
|
802
|
-
}
|
|
803
|
-
: null,
|
|
804
|
-
catalogStatus,
|
|
805
|
-
cartEnabled,
|
|
806
|
-
webCartEnabled,
|
|
807
|
-
webCartOnOff,
|
|
808
|
-
commerceExperience,
|
|
809
|
-
verifiedNameCert
|
|
573
|
+
}
|
|
810
574
|
}
|
|
811
575
|
}
|
|
812
576
|
}
|
|
@@ -841,13 +605,6 @@ const makeChatsSocket = config => {
|
|
|
841
605
|
isInitialSync ? { accountSettings: authState.creds.accountSettings } : undefined,
|
|
842
606
|
logger
|
|
843
607
|
)
|
|
844
|
-
// Persist NCT salt received via App-State-Sync (SyncActionValue field 80)
|
|
845
|
-
const _nctSalt = mutation?.syncAction?.value?.nctSaltSyncAction?.salt
|
|
846
|
-
if (_nctSalt?.length) {
|
|
847
|
-
authState.keys
|
|
848
|
-
.set({ 'nct-salt': { default: Buffer.from(_nctSalt) } })
|
|
849
|
-
.catch(err => logger.debug({ err: err?.message }, 'nct: failed to persist salt'))
|
|
850
|
-
}
|
|
851
608
|
}
|
|
852
609
|
}
|
|
853
610
|
}
|
|
@@ -855,7 +612,7 @@ const makeChatsSocket = config => {
|
|
|
855
612
|
const appStateSyncKeyCache = new Map()
|
|
856
613
|
const getCachedAppStateSyncKey = async keyId => {
|
|
857
614
|
if (appStateSyncKeyCache.has(keyId)) {
|
|
858
|
-
return appStateSyncKeyCache.get(keyId)
|
|
615
|
+
return appStateSyncKeyCache.get(keyId) ?? undefined
|
|
859
616
|
}
|
|
860
617
|
const key = await getAppStateSyncKey(keyId)
|
|
861
618
|
appStateSyncKeyCache.set(keyId, key ?? null)
|
|
@@ -880,7 +637,7 @@ const makeChatsSocket = config => {
|
|
|
880
637
|
const result = await authState.keys.get('app-state-sync-version', [name])
|
|
881
638
|
let state = result[name]
|
|
882
639
|
if (state) {
|
|
883
|
-
if (initialVersionMap[name] === undefined) {
|
|
640
|
+
if (typeof initialVersionMap[name] === 'undefined') {
|
|
884
641
|
initialVersionMap[name] = state.version
|
|
885
642
|
}
|
|
886
643
|
} else {
|
|
@@ -1005,9 +762,7 @@ const makeChatsSocket = config => {
|
|
|
1005
762
|
* type = "avatar" for the avatar variant (no query attr, confirmed from interop logs)
|
|
1006
763
|
*/
|
|
1007
764
|
const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
|
|
1008
|
-
|
|
1009
|
-
// "preview" and "image" — not image-only as previously assumed.
|
|
1010
|
-
const picAttrs = { type, query: 'url' }
|
|
765
|
+
const picAttrs = type === 'image' ? { type, query: 'url' } : { type }
|
|
1011
766
|
const baseContent = [{ tag: 'picture', attrs: picAttrs }]
|
|
1012
767
|
// WA Web only includes tctoken for user JIDs (not groups/newsletters)
|
|
1013
768
|
// and never for own profile pic (Chat model for self has no tcToken).
|
|
@@ -1124,136 +879,25 @@ const makeChatsSocket = config => {
|
|
|
1124
879
|
})
|
|
1125
880
|
}
|
|
1126
881
|
}
|
|
1127
|
-
/**
|
|
1128
|
-
* Store privacy tokens received from a usync response per-contact.
|
|
1129
|
-
* Called externally when a USyncQuery result includes privacy_token data.
|
|
1130
|
-
*
|
|
1131
|
-
* @param {Array<{jid: string, privacyToken: Buffer, privacyModeTs: number|string}>} entries
|
|
1132
|
-
*/
|
|
1133
|
-
const storePrivacyTokens = async entries => {
|
|
1134
|
-
if (!entries?.length) return
|
|
1135
|
-
const write = {}
|
|
1136
|
-
for (const { jid, privacyToken, privacyModeTs } of entries) {
|
|
1137
|
-
if (!jid || !privacyToken?.length) continue
|
|
1138
|
-
write[jid] = {
|
|
1139
|
-
token: Buffer.isBuffer(privacyToken) ? privacyToken : Buffer.from(privacyToken),
|
|
1140
|
-
ts: String(privacyModeTs || '')
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
if (Object.keys(write).length) {
|
|
1144
|
-
await authState.keys.set({ 'privacy-token': write })
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
/**
|
|
1149
|
-
* Wrapper around sock.executeUSyncQuery that automatically persists any per-contact
|
|
1150
|
-
* privacy tokens (privacy_mode_ts + privacy_token) present in the usync result list,
|
|
1151
|
-
* and emits contacts.update for any entries flagged isBlockedByContact.
|
|
1152
|
-
*
|
|
1153
|
-
* @param {import('../WAUSync/USyncQuery').USyncQuery} usyncQuery
|
|
1154
|
-
*/
|
|
1155
|
-
const executeUSyncQuery = async usyncQuery => {
|
|
1156
|
-
const result = await sock.executeUSyncQuery(usyncQuery)
|
|
1157
|
-
if (!result) return result
|
|
1158
|
-
|
|
1159
|
-
// B. Auto-persist privacy tokens found in any usync response.
|
|
1160
|
-
const privacyEntries = []
|
|
1161
|
-
const blockedContacts = []
|
|
1162
|
-
for (const entry of [...(result.list || []), ...(result.sideList || [])]) {
|
|
1163
|
-
if (entry.privacy?.token) {
|
|
1164
|
-
privacyEntries.push({
|
|
1165
|
-
jid: entry.id,
|
|
1166
|
-
privacyToken: entry.privacy.token,
|
|
1167
|
-
privacyModeTs: entry.privacy.modeTs
|
|
1168
|
-
})
|
|
1169
|
-
}
|
|
1170
|
-
// G. Emit contacts.update for contacts that blocked us.
|
|
1171
|
-
if (entry.isBlockedByContact) {
|
|
1172
|
-
blockedContacts.push({ id: entry.id, isBlockedByContact: true })
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
if (privacyEntries.length) {
|
|
1176
|
-
storePrivacyTokens(privacyEntries).catch(err => logger.warn({ err }, 'failed to store privacy tokens from usync'))
|
|
1177
|
-
}
|
|
1178
|
-
if (blockedContacts.length) {
|
|
1179
|
-
ev.emit('contacts.update', blockedContacts)
|
|
1180
|
-
}
|
|
1181
|
-
return result
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
882
|
/**
|
|
1185
883
|
* @param toJid the jid to subscribe to
|
|
1186
|
-
* @param
|
|
1187
|
-
* @param options.presenceName optional presenceName attribute for the subscribe stanza
|
|
1188
|
-
* @param options.groupJid optional group JID — when set adds context="group" attribute
|
|
884
|
+
* @param tcToken token for subscription, use if present
|
|
1189
885
|
*/
|
|
1190
|
-
const presenceSubscribe = async
|
|
886
|
+
const presenceSubscribe = async toJid => {
|
|
1191
887
|
// Only include tctoken for user JIDs — groups/newsletters don't use tctokens
|
|
1192
888
|
const normalizedToJid = (0, WABinary_1.jidNormalizedUser)(toJid)
|
|
1193
889
|
const isUserJid = (0, WABinary_1.isPnUser)(normalizedToJid) || (0, WABinary_1.isLidUser)(normalizedToJid)
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
to: toJid,
|
|
1198
|
-
id: generateMessageTag(),
|
|
1199
|
-
type: 'subscribe'
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
// Accumulate child nodes for the presence stanza
|
|
1203
|
-
const presenceContent = []
|
|
1204
|
-
|
|
1205
|
-
// A. TC Token as attribute: fetch stored token and attach as base64 tc_token attr
|
|
1206
|
-
if (isUserJid) {
|
|
1207
|
-
try {
|
|
1208
|
-
const storageJid = await (0, tc_token_utils_1.resolveTcTokenJid)(normalizedToJid, getLIDForPN)
|
|
1209
|
-
const tcTokenData = await authState.keys.get('tctoken', [storageJid])
|
|
1210
|
-
const entry = tcTokenData?.[storageJid]
|
|
1211
|
-
const tcTokenBuffer = entry?.token
|
|
1212
|
-
if (tcTokenBuffer?.length && !(0, tc_token_utils_1.isTcTokenExpired)(entry?.timestamp)) {
|
|
1213
|
-
presenceAttrs.tc_token = tcTokenBuffer.toString('base64')
|
|
1214
|
-
}
|
|
1215
|
-
} catch (e) {
|
|
1216
|
-
// best-effort — proceed without tc_token if lookup fails
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
// B. Privacy Token — include per-contact privacy token when available.
|
|
1220
|
-
// This token is issued by the contact's device via usync and gates presence
|
|
1221
|
-
// visibility when their online privacy is set to "contacts" or "contact_blacklist".
|
|
1222
|
-
try {
|
|
1223
|
-
const privTokenData = await authState.keys.get('privacy-token', [normalizedToJid])
|
|
1224
|
-
const privEntry = privTokenData?.[normalizedToJid]
|
|
1225
|
-
if (privEntry?.token?.length) {
|
|
1226
|
-
const privTokenAttrs = {}
|
|
1227
|
-
if (privEntry.ts) privTokenAttrs.t = privEntry.ts
|
|
1228
|
-
presenceContent.push({
|
|
1229
|
-
tag: 'privacy_token',
|
|
1230
|
-
attrs: privTokenAttrs,
|
|
1231
|
-
content: privEntry.token
|
|
1232
|
-
})
|
|
1233
|
-
}
|
|
1234
|
-
} catch (e) {
|
|
1235
|
-
// best-effort — proceed without privacy_token if lookup fails
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// C. presenceType and presenceName attributes
|
|
1240
|
-
if (presenceType) {
|
|
1241
|
-
presenceAttrs.presenceType = presenceType
|
|
1242
|
-
}
|
|
1243
|
-
if (presenceName) {
|
|
1244
|
-
presenceAttrs.presenceName = presenceName
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
// D. Group presence context
|
|
1248
|
-
if (groupJid) {
|
|
1249
|
-
presenceAttrs.context = 'group'
|
|
1250
|
-
presenceAttrs.group_jid = (0, WABinary_1.jidNormalizedUser)(groupJid)
|
|
1251
|
-
}
|
|
1252
|
-
|
|
890
|
+
const tcTokenContent = isUserJid
|
|
891
|
+
? await (0, tc_token_utils_1.buildTcTokenFromJid)({ authState, jid: normalizedToJid, getLIDForPN })
|
|
892
|
+
: undefined
|
|
1253
893
|
return sendNode({
|
|
1254
894
|
tag: 'presence',
|
|
1255
|
-
attrs:
|
|
1256
|
-
|
|
895
|
+
attrs: {
|
|
896
|
+
to: toJid,
|
|
897
|
+
id: generateMessageTag(),
|
|
898
|
+
type: 'subscribe'
|
|
899
|
+
},
|
|
900
|
+
content: tcTokenContent
|
|
1257
901
|
})
|
|
1258
902
|
}
|
|
1259
903
|
const handlePresenceUpdate = ({ tag, attrs, content }) => {
|
|
@@ -1561,36 +1205,6 @@ const makeChatsSocket = config => {
|
|
|
1561
1205
|
const updatePrivateProcessingSetting = enabled => {
|
|
1562
1206
|
return chatModify({ privateProcessingSetting: enabled ? 'enabled' : 'disabled' }, '')
|
|
1563
1207
|
}
|
|
1564
|
-
/**
|
|
1565
|
-
* Update Meta AI features control
|
|
1566
|
-
* @param status 'enabled' | 'enabled_has_learning' | 'disabled'
|
|
1567
|
-
* @param replyMode 'muted' | 'ai_agent' | 'suggestions'
|
|
1568
|
-
*/
|
|
1569
|
-
const updateAIFeatures = (status = 'enabled', replyMode = 'suggestions') => {
|
|
1570
|
-
const statusEnum =
|
|
1571
|
-
require('../../WAProto/index.js').proto.SyncActionValue.MaibaAIFeaturesControlAction.MaibaAIFeatureStatus
|
|
1572
|
-
const replyModeEnum =
|
|
1573
|
-
require('../../WAProto/index.js').proto.SyncActionValue.MaibaAIFeaturesControlAction.MaibaAIReplyMode
|
|
1574
|
-
const statusMap = {
|
|
1575
|
-
enabled: statusEnum.ENABLED,
|
|
1576
|
-
enabled_has_learning: statusEnum.ENABLED_HAS_LEARNING,
|
|
1577
|
-
disabled: statusEnum.DISABLED
|
|
1578
|
-
}
|
|
1579
|
-
const replyModeMap = {
|
|
1580
|
-
muted: replyModeEnum.MUTED,
|
|
1581
|
-
ai_agent: replyModeEnum.AI_AGENT,
|
|
1582
|
-
suggestions: replyModeEnum.SUGGESTIONS
|
|
1583
|
-
}
|
|
1584
|
-
return chatModify(
|
|
1585
|
-
{
|
|
1586
|
-
maibaAIFeatures: {
|
|
1587
|
-
status: statusMap[status] ?? statusEnum.ENABLED,
|
|
1588
|
-
replyMode: replyModeMap[replyMode] ?? replyModeEnum.SUGGESTIONS
|
|
1589
|
-
}
|
|
1590
|
-
},
|
|
1591
|
-
''
|
|
1592
|
-
)
|
|
1593
|
-
}
|
|
1594
1208
|
/**
|
|
1595
1209
|
* Update bio/about privacy (who can see your About text)
|
|
1596
1210
|
* @param value 'all' | 'contacts' | 'contact_blacklist' | 'none'
|
|
@@ -1679,30 +1293,6 @@ const makeChatsSocket = config => {
|
|
|
1679
1293
|
const silenceChat = (jid, silent = true, until = null) => {
|
|
1680
1294
|
return chatModify({ silenceChat: { silent, until } }, jid)
|
|
1681
1295
|
}
|
|
1682
|
-
/**
|
|
1683
|
-
* Clear all messages in a chat
|
|
1684
|
-
*/
|
|
1685
|
-
const clearChat = (jid, lastMessages) => {
|
|
1686
|
-
return chatModify({ clear: true, lastMessages }, jid)
|
|
1687
|
-
}
|
|
1688
|
-
/**
|
|
1689
|
-
* Pin or unpin a chat to the top
|
|
1690
|
-
*/
|
|
1691
|
-
const pinChat = (jid, pinned = true) => {
|
|
1692
|
-
return chatModify({ pin: { pinned } }, jid)
|
|
1693
|
-
}
|
|
1694
|
-
/**
|
|
1695
|
-
* Star or unstar messages
|
|
1696
|
-
*/
|
|
1697
|
-
const starMessages = (jid, messageIds, starred = true) => {
|
|
1698
|
-
return chatModify({ star: { messages: messageIds, star: starred } }, jid)
|
|
1699
|
-
}
|
|
1700
|
-
/**
|
|
1701
|
-
* Set up a quick reply
|
|
1702
|
-
*/
|
|
1703
|
-
const setQuickReply = (text, shortcut) => {
|
|
1704
|
-
return chatModify({ quickReply: { text, shortcut } }, '')
|
|
1705
|
-
}
|
|
1706
1296
|
/**
|
|
1707
1297
|
* Fetch bot profiles for a list of JIDs using USyncBotProfileProtocol
|
|
1708
1298
|
*/
|
|
@@ -1713,7 +1303,7 @@ const makeChatsSocket = config => {
|
|
|
1713
1303
|
for (const jid of jids) {
|
|
1714
1304
|
q.withUser(new USyncUser().withId(jid))
|
|
1715
1305
|
}
|
|
1716
|
-
const result = await executeUSyncQuery(q)
|
|
1306
|
+
const result = await sock.executeUSyncQuery(q)
|
|
1717
1307
|
return result?.list || []
|
|
1718
1308
|
}
|
|
1719
1309
|
/**
|
|
@@ -1771,18 +1361,12 @@ const makeChatsSocket = config => {
|
|
|
1771
1361
|
/**
|
|
1772
1362
|
* Fetch AB-test (abt) props from server.
|
|
1773
1363
|
* Mirrors ABPropsProtocolHelper — protocol 1 or 2, optional hash/refresh_id/group.
|
|
1774
|
-
* Confirmed from live capture: group-scoped queries send only { group }, never combined
|
|
1775
|
-
* with protocol/hash/refresh_id; the account-level default protocol is "1", not "2".
|
|
1776
1364
|
*/
|
|
1777
|
-
const fetchABProps = async (protocol = '
|
|
1778
|
-
const propAttrs =
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
protocol,
|
|
1783
|
-
...(hash ? { hash } : {}),
|
|
1784
|
-
...(refreshId != null ? { refresh_id: String(refreshId) } : {})
|
|
1785
|
-
}
|
|
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)
|
|
1786
1370
|
const result = await query({
|
|
1787
1371
|
tag: 'iq',
|
|
1788
1372
|
attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'abt', type: 'get' },
|
|
@@ -1795,19 +1379,16 @@ const makeChatsSocket = config => {
|
|
|
1795
1379
|
/**
|
|
1796
1380
|
* Remove a companion (linked) device from the account.
|
|
1797
1381
|
* Mirrors CompanionDeviceRemovalJob — xmlns="md", child tag "remove-companion-device".
|
|
1798
|
-
*
|
|
1799
|
-
* reason: "user_initiated" | "unknown_companion" | any WA-defined reason string.
|
|
1800
|
-
* Confirmed from live capture — the previous { platform, id: keyIndex } attrs never
|
|
1801
|
-
* identified a device at all; the server needs the companion's full jid.
|
|
1382
|
+
* reason: "user_initiated" | "server_initiated" | any WA-defined reason string.
|
|
1802
1383
|
*/
|
|
1803
|
-
const removeCompanionDevice = async (
|
|
1384
|
+
const removeCompanionDevice = async (keyIndex, reason = 'user_initiated') => {
|
|
1804
1385
|
await query({
|
|
1805
1386
|
tag: 'iq',
|
|
1806
1387
|
attrs: { to: WABinary_1.S_WHATSAPP_NET, xmlns: 'md', type: 'set' },
|
|
1807
1388
|
content: [
|
|
1808
1389
|
{
|
|
1809
1390
|
tag: 'remove-companion-device',
|
|
1810
|
-
attrs: {
|
|
1391
|
+
attrs: { platform: 'true', reason, id: String(keyIndex) }
|
|
1811
1392
|
}
|
|
1812
1393
|
]
|
|
1813
1394
|
})
|
|
@@ -1831,51 +1412,6 @@ const makeChatsSocket = config => {
|
|
|
1831
1412
|
]
|
|
1832
1413
|
})
|
|
1833
1414
|
}
|
|
1834
|
-
/**
|
|
1835
|
-
* Build and push the companion's signed key-index-list on (re)connect.
|
|
1836
|
-
*
|
|
1837
|
-
* The official client sends this within ~0.8s of every session start (xmlns="md").
|
|
1838
|
-
* A companion that never re-asserts its ADV key index is treated as unverified and
|
|
1839
|
-
* gets <conflict type="device_removed"/>.
|
|
1840
|
-
*
|
|
1841
|
-
* Payload construction mirrors CompanionDeviceAdvUtil.A02 (C19350ud) from the APK:
|
|
1842
|
-
* details = proto ADVKeyIndexList { rawId, timestamp, currentIndex, validIndexes, accountType }
|
|
1843
|
-
* accountSignature = Curve.sign(signedIdentityKey.private, [6,2] || details)
|
|
1844
|
-
* accountSignatureKey = only attached for HOSTED accounts (skipped for consumer E2EE)
|
|
1845
|
-
* The signature uses our own device identity key (the same key that produces the ADV
|
|
1846
|
-
* deviceSignature during pairing) — a companion cannot re-sign with the account key.
|
|
1847
|
-
*/
|
|
1848
|
-
const sendKeyIndexList = async () => {
|
|
1849
|
-
const account = authState.creds.account
|
|
1850
|
-
const signedIdentityKey = authState.creds.signedIdentityKey
|
|
1851
|
-
if (!account?.details || !signedIdentityKey?.private) {
|
|
1852
|
-
logger.debug('no ADV account / signed identity key, skipping key-index-list')
|
|
1853
|
-
return
|
|
1854
|
-
}
|
|
1855
|
-
const deviceIdentity = index_js_1.proto.ADVDeviceIdentity.decode(account.details)
|
|
1856
|
-
const keyIndex = deviceIdentity.keyIndex || 0
|
|
1857
|
-
const accountType = deviceIdentity.accountType ?? index_js_1.proto.ADVEncryptionType.E2EE
|
|
1858
|
-
const isHosted = accountType === index_js_1.proto.ADVEncryptionType.HOSTED
|
|
1859
|
-
const ts = Math.floor(Date.now() / 1000)
|
|
1860
|
-
const details = index_js_1.proto.ADVKeyIndexList.encode({
|
|
1861
|
-
rawId: deviceIdentity.rawId || 0,
|
|
1862
|
-
timestamp: ts,
|
|
1863
|
-
currentIndex: keyIndex,
|
|
1864
|
-
validIndexes: [keyIndex],
|
|
1865
|
-
accountType
|
|
1866
|
-
}).finish()
|
|
1867
|
-
const sigPrefix = isHosted
|
|
1868
|
-
? Defaults_1.WA_ADV_HOSTED_KEY_INDEX_LIST_SIG_PREFIX
|
|
1869
|
-
: Defaults_1.WA_ADV_KEY_INDEX_LIST_SIG_PREFIX
|
|
1870
|
-
const accountSignature = Utils_1.Curve.sign(signedIdentityKey.private, Buffer.concat([sigPrefix, details]))
|
|
1871
|
-
const signedKeyIndexList = { details, accountSignature }
|
|
1872
|
-
if (isHosted && account.accountSignatureKey?.length) {
|
|
1873
|
-
signedKeyIndexList.accountSignatureKey = account.accountSignatureKey
|
|
1874
|
-
}
|
|
1875
|
-
const content = index_js_1.proto.ADVSignedKeyIndexList.encode(signedKeyIndexList).finish()
|
|
1876
|
-
await updateKeyIndexList(ts, Buffer.from(content))
|
|
1877
|
-
logger.info({ ts, keyIndex }, 'sent key-index-list')
|
|
1878
|
-
}
|
|
1879
1415
|
/**
|
|
1880
1416
|
* Request a media upload connection token from the server.
|
|
1881
1417
|
* Mirrors MediaConnFetcher — xmlns="w:m", type="set", optional last_id.
|
|
@@ -1932,8 +1468,6 @@ const makeChatsSocket = config => {
|
|
|
1932
1468
|
await query({
|
|
1933
1469
|
tag: 'iq',
|
|
1934
1470
|
attrs: {
|
|
1935
|
-
// reuse the challenge id at the iq level too, matching AccountDefenceDeviceLogoutJob
|
|
1936
|
-
id: String(id),
|
|
1937
1471
|
to: WABinary_1.S_WHATSAPP_NET,
|
|
1938
1472
|
xmlns: 'w:account_defence',
|
|
1939
1473
|
type: 'set',
|
|
@@ -1952,71 +1486,9 @@ const makeChatsSocket = config => {
|
|
|
1952
1486
|
* help ensure parity with WA Web
|
|
1953
1487
|
* */
|
|
1954
1488
|
const executeInitQueries = async () => {
|
|
1955
|
-
|
|
1956
|
-
// false = account has interop disabled, skip the TOS/opt-in flow on reconnect.
|
|
1957
|
-
const shouldInitInterop = authState.creds.interopEnabled !== false
|
|
1958
|
-
const [, , , abProps] = await Promise.all([
|
|
1959
|
-
fetchProps(),
|
|
1960
|
-
fetchBlocklist(),
|
|
1961
|
-
fetchPrivacySettings(),
|
|
1962
|
-
fetchABProps(),
|
|
1963
|
-
...(shouldInitInterop ? [initInterop()] : [])
|
|
1964
|
-
])
|
|
1965
|
-
const INTEROP_FLAGS = ['stella_interop_enabled', 'stella_ios_enabled']
|
|
1966
|
-
for (const flag of INTEROP_FLAGS) {
|
|
1967
|
-
if (flag in abProps) {
|
|
1968
|
-
const enabled = abProps[flag] === 'true' || abProps[flag] === '1'
|
|
1969
|
-
ev.emit('interop.feature-update', { feature: flag, enabled })
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
// Persist AB prop feature flags into connection credentials so they survive reconnect.
|
|
1973
|
-
const abCredsUpdate = {}
|
|
1974
|
-
if ('stella_interop_enabled' in abProps) {
|
|
1975
|
-
abCredsUpdate.interopEnabled =
|
|
1976
|
-
abProps['stella_interop_enabled'] === 'true' || abProps['stella_interop_enabled'] === '1'
|
|
1977
|
-
}
|
|
1978
|
-
if ('stella_ios_enabled' in abProps) {
|
|
1979
|
-
abCredsUpdate.interopIosEnabled =
|
|
1980
|
-
abProps['stella_ios_enabled'] === 'true' || abProps['stella_ios_enabled'] === '1'
|
|
1981
|
-
}
|
|
1982
|
-
if ('md_privacy_v2' in abProps) {
|
|
1983
|
-
abCredsUpdate.mdPrivacyV2 = abProps['md_privacy_v2'] === 'true' || abProps['md_privacy_v2'] === '1'
|
|
1984
|
-
}
|
|
1985
|
-
// Persist media feature-flag AB props so upload/download paths can gate on them.
|
|
1986
|
-
const MEDIA_AB_PROPS = [
|
|
1987
|
-
'hd_image_dual_upload',
|
|
1988
|
-
'hd_video_dual_upload',
|
|
1989
|
-
'hevc_video_dual_upload',
|
|
1990
|
-
'partial_pjpeg_enabled',
|
|
1991
|
-
'multi_scan_pjpeg',
|
|
1992
|
-
'media_poll'
|
|
1993
|
-
]
|
|
1994
|
-
const mediaAbProps = {}
|
|
1995
|
-
for (const prop of MEDIA_AB_PROPS) {
|
|
1996
|
-
if (prop in abProps) {
|
|
1997
|
-
mediaAbProps[prop] = abProps[prop] === 'true' || abProps[prop] === '1'
|
|
1998
|
-
}
|
|
1999
|
-
}
|
|
2000
|
-
if (Object.keys(mediaAbProps).length) {
|
|
2001
|
-
abCredsUpdate.mediaAbProps = { ...(authState.creds.mediaAbProps || {}), ...mediaAbProps }
|
|
2002
|
-
}
|
|
2003
|
-
if (Object.keys(abCredsUpdate).length) {
|
|
2004
|
-
ev.emit('creds.update', abCredsUpdate)
|
|
2005
|
-
}
|
|
1489
|
+
await Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings(), initInterop()])
|
|
2006
1490
|
}
|
|
2007
1491
|
const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
|
|
2008
|
-
// Cache newsletter server_id for use when this message is later quoted
|
|
2009
|
-
if (msg.newsletter && msg.newsletter_server_id && msg.key?.id) {
|
|
2010
|
-
_nlServerIdCache.set(msg.key.id, msg.newsletter_server_id)
|
|
2011
|
-
}
|
|
2012
|
-
// If this message quotes a newsletter post, enrich it with the cached server_id
|
|
2013
|
-
if (msg.message) {
|
|
2014
|
-
const ci = Object.values(msg.message).find(v => v?.contextInfo)?.contextInfo
|
|
2015
|
-
if (ci?.stanzaId && (0, WABinary_1.isJidNewsletter)(ci.remoteJid)) {
|
|
2016
|
-
const sid = _nlServerIdCache.get(ci.stanzaId)
|
|
2017
|
-
if (sid != null) msg.quotedNewsletterServerId = sid
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
1492
|
ev.emit('messages.upsert', { messages: [msg], type })
|
|
2021
1493
|
if (!!msg.pushName) {
|
|
2022
1494
|
let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid
|
|
@@ -2134,20 +1606,6 @@ const makeChatsSocket = config => {
|
|
|
2134
1606
|
})
|
|
2135
1607
|
ws.on('CB:presence', handlePresenceUpdate)
|
|
2136
1608
|
ws.on('CB:chatstate', handlePresenceUpdate)
|
|
2137
|
-
// Server device-legitimacy challenge (w:account_defence). Fires a few seconds after the
|
|
2138
|
-
// companion sends its first message; if we don't approve within ~20s the device is removed
|
|
2139
|
-
// with <conflict type="device_removed"/>. Wire the inbound challenge to confirmDeviceLogout.
|
|
2140
|
-
ws.on('CB:iq,xmlns:w:account_defence', async node => {
|
|
2141
|
-
const deviceLogout = (0, WABinary_1.getBinaryNodeChild)(node, 'device_logout')
|
|
2142
|
-
if (!deviceLogout) return
|
|
2143
|
-
const id = node.attrs.id
|
|
2144
|
-
logger.info({ id }, 'received account_defence device_logout challenge, approving')
|
|
2145
|
-
try {
|
|
2146
|
-
await confirmDeviceLogout(id, true)
|
|
2147
|
-
} catch (error) {
|
|
2148
|
-
onUnexpectedError(error, 'account_defence device_logout approve')
|
|
2149
|
-
}
|
|
2150
|
-
})
|
|
2151
1609
|
ws.on('CB:ib,,dirty', async node => {
|
|
2152
1610
|
const { attrs } = (0, WABinary_1.getBinaryNodeChild)(node, 'dirty')
|
|
2153
1611
|
const type = attrs.type
|
|
@@ -2183,9 +1641,6 @@ const makeChatsSocket = config => {
|
|
|
2183
1641
|
sendPresenceUpdate(markOnlineOnConnect ? 'available' : 'unavailable').catch(error =>
|
|
2184
1642
|
onUnexpectedError(error, 'presence update requests')
|
|
2185
1643
|
)
|
|
2186
|
-
// Re-assert our ADV key index to the server on every (re)connect, or the companion
|
|
2187
|
-
// is treated as unverified and removed. Best-effort: never block the open flow.
|
|
2188
|
-
sendKeyIndexList().catch(error => onUnexpectedError(error, 'send key-index-list'))
|
|
2189
1644
|
}
|
|
2190
1645
|
if (!receivedPendingNotifications || syncState !== State_1.SyncState.Connecting) {
|
|
2191
1646
|
return
|
|
@@ -2263,12 +1718,9 @@ const makeChatsSocket = config => {
|
|
|
2263
1718
|
getChatBlockingStatus,
|
|
2264
1719
|
updateChatBlockingStatus,
|
|
2265
1720
|
getUserDisclosures,
|
|
2266
|
-
acceptTosNotice,
|
|
2267
|
-
reportSpam,
|
|
2268
1721
|
getOptOutList,
|
|
2269
1722
|
getPushConfig,
|
|
2270
1723
|
setPushConfig,
|
|
2271
|
-
signPrivateCredential,
|
|
2272
1724
|
messageMutex,
|
|
2273
1725
|
receiptMutex,
|
|
2274
1726
|
appStatePatchMutex,
|
|
@@ -2317,7 +1769,6 @@ const makeChatsSocket = config => {
|
|
|
2317
1769
|
renameAIThread,
|
|
2318
1770
|
pinThreadMessage,
|
|
2319
1771
|
updatePrivateProcessingSetting,
|
|
2320
|
-
updateAIFeatures,
|
|
2321
1772
|
updateBioPrivacy,
|
|
2322
1773
|
blockBot,
|
|
2323
1774
|
unblockBot,
|
|
@@ -2331,10 +1782,6 @@ const makeChatsSocket = config => {
|
|
|
2331
1782
|
markChatAsUnread,
|
|
2332
1783
|
setChatEphemeral,
|
|
2333
1784
|
silenceChat,
|
|
2334
|
-
clearChat,
|
|
2335
|
-
pinChat,
|
|
2336
|
-
starMessages,
|
|
2337
|
-
setQuickReply,
|
|
2338
1785
|
fetchBotProfiles,
|
|
2339
1786
|
updateChatLock,
|
|
2340
1787
|
updateChatWallpaper,
|
|
@@ -2363,12 +1810,7 @@ const makeChatsSocket = config => {
|
|
|
2363
1810
|
fetchMediaConn,
|
|
2364
1811
|
deleteBroadcastList,
|
|
2365
1812
|
fetchQRCode,
|
|
2366
|
-
confirmDeviceLogout
|
|
2367
|
-
updateKeyIndexList,
|
|
2368
|
-
sendKeyIndexList,
|
|
2369
|
-
storePrivacyTokens,
|
|
2370
|
-
executeUSyncQuery,
|
|
2371
|
-
newsletterServerIdCache: _nlServerIdCache
|
|
1813
|
+
confirmDeviceLogout
|
|
2372
1814
|
}
|
|
2373
1815
|
}
|
|
2374
1816
|
exports.makeChatsSocket = makeChatsSocket
|