@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
|
@@ -37,6 +37,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
37
37
|
signalRepository,
|
|
38
38
|
query,
|
|
39
39
|
upsertMessage,
|
|
40
|
+
getUSyncDevices,
|
|
40
41
|
resyncAppState,
|
|
41
42
|
onUnexpectedError,
|
|
42
43
|
assertSessions,
|
|
@@ -46,10 +47,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
46
47
|
uploadPreKeys,
|
|
47
48
|
sendPeerDataOperationMessage,
|
|
48
49
|
messageRetryManager,
|
|
49
|
-
issuePrivacyTokens
|
|
50
|
-
getUSyncDevices,
|
|
51
|
-
createParticipantNodes,
|
|
52
|
-
newsletterServerIdCache
|
|
50
|
+
issuePrivacyTokens
|
|
53
51
|
} = sock
|
|
54
52
|
const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
|
|
55
53
|
// Track when the socket fully opens so pending pre-connect messages are treated as history
|
|
@@ -147,14 +145,13 @@ const makeMessagesRecvSocket = config => {
|
|
|
147
145
|
*
|
|
148
146
|
* @param {string} [registrationTraceId] - Optional trace ID for this registration attempt.
|
|
149
147
|
*/
|
|
150
|
-
const requestCompanionCanonicalNonce = async registrationTraceId => {
|
|
148
|
+
const requestCompanionCanonicalNonce = async (registrationTraceId) => {
|
|
151
149
|
if (!authState.creds.me?.id) {
|
|
152
150
|
throw new boom_1.Boom('Not authenticated')
|
|
153
151
|
}
|
|
154
152
|
return sendPeerDataOperationMessage({
|
|
155
153
|
companionCanonicalUserNonceFetchRequest: registrationTraceId ? { registrationTraceId } : {},
|
|
156
|
-
peerDataOperationRequestType:
|
|
157
|
-
index_js_1.proto.Message.PeerDataOperationRequestType.COMPANION_CANONICAL_USER_NONCE_FETCH
|
|
154
|
+
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.COMPANION_CANONICAL_USER_NONCE_FETCH
|
|
158
155
|
})
|
|
159
156
|
}
|
|
160
157
|
/**
|
|
@@ -170,135 +167,54 @@ const makeMessagesRecvSocket = config => {
|
|
|
170
167
|
peerDataOperationRequestType: index_js_1.proto.Message.PeerDataOperationRequestType.COMPANION_META_NONCE_FETCH
|
|
171
168
|
})
|
|
172
169
|
}
|
|
173
|
-
// Handles mex
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
logger.debug({ node }, 'mex notification with no update children')
|
|
170
|
+
// Handles mex newsletter notifications
|
|
171
|
+
const handleMexNewsletterNotification = async node => {
|
|
172
|
+
const mexNode = (0, WABinary_1.getBinaryNodeChild)(node, 'mex')
|
|
173
|
+
if (!mexNode?.content) {
|
|
174
|
+
logger.warn({ node }, 'Invalid mex newsletter notification')
|
|
179
175
|
return
|
|
180
176
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// d.xwa2_notify_newsletter_on_metadata_update: { id, thread_metadata: { settings } }
|
|
200
|
-
const upd = d.xwa2_notify_newsletter_on_metadata_update
|
|
201
|
-
if (upd?.id) {
|
|
202
|
-
ev.emit('newsletter-settings.update', {
|
|
203
|
-
id: upd.id,
|
|
204
|
-
update: upd.thread_metadata?.settings ?? {}
|
|
205
|
-
})
|
|
206
|
-
}
|
|
207
|
-
break
|
|
208
|
-
}
|
|
209
|
-
case 'NotificationNewsletterJoin': {
|
|
210
|
-
// d.xwa2_notify_newsletter_on_join: full newsletter metadata on subscribe
|
|
211
|
-
const upd = d.xwa2_notify_newsletter_on_join
|
|
212
|
-
if (upd?.id) {
|
|
213
|
-
ev.emit('newsletter-participants.update', {
|
|
214
|
-
id: upd.id,
|
|
215
|
-
author: node.attrs.from,
|
|
216
|
-
user: (0, WABinary_1.jidNormalizedUser)(node.attrs.from),
|
|
217
|
-
new_role: upd.viewer_metadata?.role ?? 'SUBSCRIBER',
|
|
218
|
-
action: 'join',
|
|
219
|
-
metadata: upd.thread_metadata
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
break
|
|
223
|
-
}
|
|
224
|
-
case 'NotificationNewsletterMuteChange': {
|
|
225
|
-
// d.xwa2_notify_newsletter_on_mute_change: { id, mute: "ON"|"OFF" }
|
|
226
|
-
const upd = d.xwa2_notify_newsletter_on_mute_change
|
|
227
|
-
if (upd?.id) {
|
|
228
|
-
ev.emit('newsletter-settings.update', {
|
|
229
|
-
id: upd.id,
|
|
230
|
-
update: { mute: upd.mute }
|
|
231
|
-
})
|
|
232
|
-
}
|
|
233
|
-
break
|
|
234
|
-
}
|
|
235
|
-
case 'NotificationNewsletterUserSettingChange': {
|
|
236
|
-
// d.xwa2_notify_newsletter_on_user_setting_change: { id, setting: { type, value } }
|
|
237
|
-
const upd = d.xwa2_notify_newsletter_on_user_setting_change
|
|
238
|
-
if (upd?.id && upd.setting) {
|
|
177
|
+
let data
|
|
178
|
+
try {
|
|
179
|
+
data = JSON.parse(mexNode.content.toString())
|
|
180
|
+
} catch (error) {
|
|
181
|
+
logger.error({ err: error, node }, 'Failed to parse mex newsletter notification')
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
const operation = data?.operation
|
|
185
|
+
const updates = data?.updates
|
|
186
|
+
if (!updates || !operation) {
|
|
187
|
+
logger.warn({ data }, 'Invalid mex newsletter notification content')
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
logger.info({ operation, updates }, 'got mex newsletter notification')
|
|
191
|
+
switch (operation) {
|
|
192
|
+
case 'NotificationNewsletterUpdate':
|
|
193
|
+
for (const update of updates) {
|
|
194
|
+
if (update.jid && update.settings && Object.keys(update.settings).length > 0) {
|
|
239
195
|
ev.emit('newsletter-settings.update', {
|
|
240
|
-
id:
|
|
241
|
-
update:
|
|
196
|
+
id: update.jid,
|
|
197
|
+
update: update.settings
|
|
242
198
|
})
|
|
243
199
|
}
|
|
244
|
-
break
|
|
245
200
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (
|
|
201
|
+
break
|
|
202
|
+
case 'NotificationNewsletterAdminPromote':
|
|
203
|
+
for (const update of updates) {
|
|
204
|
+
if (update.jid && update.user) {
|
|
250
205
|
ev.emit('newsletter-participants.update', {
|
|
251
|
-
id:
|
|
206
|
+
id: update.jid,
|
|
252
207
|
author: node.attrs.from,
|
|
253
|
-
user:
|
|
208
|
+
user: update.user,
|
|
254
209
|
new_role: 'ADMIN',
|
|
255
210
|
action: 'promote'
|
|
256
211
|
})
|
|
257
212
|
}
|
|
258
|
-
break
|
|
259
|
-
}
|
|
260
|
-
case 'NotificationGroupMemberLinkPropertyUpdate': {
|
|
261
|
-
// d.xwa2_notify_group_on_prop_change: { id, properties: { member_link_mode } }
|
|
262
|
-
const upd = d.xwa2_notify_group_on_prop_change
|
|
263
|
-
if (upd?.id && upd.properties?.member_link_mode !== undefined) {
|
|
264
|
-
ev.emit('groups.update', [
|
|
265
|
-
{
|
|
266
|
-
id: upd.id,
|
|
267
|
-
memberAddMode: upd.properties.member_link_mode
|
|
268
|
-
}
|
|
269
|
-
])
|
|
270
|
-
}
|
|
271
|
-
break
|
|
272
|
-
}
|
|
273
|
-
case 'NotificationGroupLimitSharingPropertyUpdate': {
|
|
274
|
-
// d.xwa2_notify_group_on_prop_change: { id, properties: { limit_sharing } }
|
|
275
|
-
const upd = d.xwa2_notify_group_on_prop_change
|
|
276
|
-
if (upd?.id && upd.properties?.limit_sharing !== undefined) {
|
|
277
|
-
ev.emit('groups.update', [
|
|
278
|
-
{
|
|
279
|
-
id: upd.id,
|
|
280
|
-
limitSharing: upd.properties.limit_sharing
|
|
281
|
-
}
|
|
282
|
-
])
|
|
283
|
-
}
|
|
284
|
-
break
|
|
285
213
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
ev.emit('groups.update', [
|
|
291
|
-
{
|
|
292
|
-
id: upd.id,
|
|
293
|
-
memberShareHistoryMode: upd.properties.member_share_group_history_mode
|
|
294
|
-
}
|
|
295
|
-
])
|
|
296
|
-
}
|
|
297
|
-
break
|
|
298
|
-
}
|
|
299
|
-
default:
|
|
300
|
-
logger.debug({ opName, from: node.attrs.from }, 'unhandled mex op')
|
|
301
|
-
}
|
|
214
|
+
break
|
|
215
|
+
default:
|
|
216
|
+
logger.info({ operation, data }, 'Unhandled mex newsletter notification')
|
|
217
|
+
break
|
|
302
218
|
}
|
|
303
219
|
}
|
|
304
220
|
// Handles newsletter notifications
|
|
@@ -351,9 +267,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
351
267
|
})
|
|
352
268
|
}
|
|
353
269
|
break
|
|
354
|
-
case 'message':
|
|
355
|
-
const viewCount = child.attrs.view_count !== undefined ? +child.attrs.view_count : undefined
|
|
356
|
-
const impressionCount = child.attrs.impression_count !== undefined ? +child.attrs.impression_count : undefined
|
|
270
|
+
case 'message':
|
|
357
271
|
const plaintextNode = (0, WABinary_1.getBinaryNodeChild)(child, 'plaintext')
|
|
358
272
|
if (plaintextNode?.content) {
|
|
359
273
|
try {
|
|
@@ -366,16 +280,11 @@ const makeMessagesRecvSocket = config => {
|
|
|
366
280
|
key: {
|
|
367
281
|
remoteJid: from,
|
|
368
282
|
id: child.attrs.message_id || child.attrs.server_id,
|
|
369
|
-
|
|
370
|
-
// which always originate from the channel, never from us
|
|
371
|
-
fromMe: false
|
|
283
|
+
fromMe: false // TODO: is this really true though
|
|
372
284
|
},
|
|
373
285
|
message: messageProto,
|
|
374
286
|
messageTimestamp: +child.attrs.t
|
|
375
287
|
}).toJSON()
|
|
376
|
-
// Attach insight counters when the server includes them
|
|
377
|
-
if (viewCount !== undefined) fullMessage.views = viewCount
|
|
378
|
-
if (impressionCount !== undefined) fullMessage.impressions = impressionCount
|
|
379
288
|
await upsertMessage(fullMessage, 'append')
|
|
380
289
|
logger.info('Processed plaintext newsletter message')
|
|
381
290
|
} catch (error) {
|
|
@@ -383,44 +292,23 @@ const makeMessagesRecvSocket = config => {
|
|
|
383
292
|
}
|
|
384
293
|
}
|
|
385
294
|
break
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const fwdNode = (0, WABinary_1.getBinaryNodeChild)(msgNode, 'forwards_count')
|
|
396
|
-
const forwardsCount = fwdNode?.attrs?.count !== undefined ? +fwdNode.attrs.count : undefined
|
|
397
|
-
const reactionsNode = (0, WABinary_1.getBinaryNodeChild)(msgNode, 'reactions')
|
|
398
|
-
const reactions = (0, WABinary_1.getBinaryNodeChildren)(reactionsNode ?? msgNode, 'reaction').map(r => ({
|
|
399
|
-
code: r.attrs.code,
|
|
400
|
-
count: +r.attrs.count
|
|
401
|
-
}))
|
|
402
|
-
ev.emit('newsletter.live-update', {
|
|
403
|
-
id: from,
|
|
404
|
-
server_id: serverId,
|
|
405
|
-
timestamp: msgTs,
|
|
406
|
-
forwardsCount,
|
|
407
|
-
reactions
|
|
408
|
-
})
|
|
409
|
-
}
|
|
295
|
+
case 'live_updates':
|
|
296
|
+
// Live view count / engagement update
|
|
297
|
+
const liveCount = parseInt(child.attrs.count || child.content?.toString() || '0', 10)
|
|
298
|
+
ev.emit('newsletter.live-update', {
|
|
299
|
+
id: from,
|
|
300
|
+
server_id: child.attrs.message_id || child.attrs.server_id,
|
|
301
|
+
liveViewers: liveCount,
|
|
302
|
+
timestamp: child.attrs.t ? +child.attrs.t : undefined
|
|
303
|
+
})
|
|
410
304
|
break
|
|
411
|
-
|
|
412
|
-
case 'pin': {
|
|
413
|
-
const pinnedServerId = child.attrs.message_id || child.attrs.server_id
|
|
414
|
-
const isPinned = child.attrs.action !== 'unpin'
|
|
305
|
+
case 'pin':
|
|
415
306
|
ev.emit('newsletter.pin', {
|
|
416
307
|
id: from,
|
|
417
|
-
server_id:
|
|
418
|
-
pinned:
|
|
308
|
+
server_id: child.attrs.message_id || child.attrs.server_id,
|
|
309
|
+
pinned: child.attrs.action !== 'unpin'
|
|
419
310
|
})
|
|
420
|
-
// Sync the pinnedMessage field on the newsletter metadata object
|
|
421
|
-
ev.emit('newsletters.update', [{ id: from, pinnedMessage: isPinned ? pinnedServerId : null }])
|
|
422
311
|
break
|
|
423
|
-
}
|
|
424
312
|
case 'category':
|
|
425
313
|
ev.emit('newsletter-settings.update', {
|
|
426
314
|
id: from,
|
|
@@ -440,88 +328,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
440
328
|
break
|
|
441
329
|
}
|
|
442
330
|
}
|
|
443
|
-
// Handles incoming <status> stanzas pushed by the server for newsletter posts.
|
|
444
|
-
// These arrive on the CB:status channel (AB-gated by status_e2ee_recv_over_status_stanza).
|
|
445
|
-
const handleNewsletterStatus = async node => {
|
|
446
|
-
const { id, from, server_id, t, is_sender, offline, type, edit } = node.attrs
|
|
447
|
-
const serverId = server_id ? +server_id : undefined
|
|
448
|
-
const timestamp = t ? +t : undefined
|
|
449
|
-
const isSender = is_sender === 'true'
|
|
450
|
-
const offlineIndex = offline !== undefined ? +offline : undefined
|
|
451
|
-
// Cache messageId → serverId so the bot can look up server_id when a message is quoted
|
|
452
|
-
if (id && serverId != null) {
|
|
453
|
-
newsletterServerIdCache?.set(id, serverId)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Parse optional <meta> child: edit timestamps, interaction type, admin profile
|
|
457
|
-
const metaNode = (0, WABinary_1.getBinaryNodeChild)(node, 'meta')
|
|
458
|
-
const meta = metaNode
|
|
459
|
-
? {
|
|
460
|
-
...(metaNode.attrs.msg_edit_t ? { editedAt: +metaNode.attrs.msg_edit_t } : {}),
|
|
461
|
-
...(metaNode.attrs.original_msg_t ? { originalTimestamp: +metaNode.attrs.original_msg_t } : {}),
|
|
462
|
-
...(metaNode.attrs.interaction_type ? { interactionType: metaNode.attrs.interaction_type } : {}),
|
|
463
|
-
...(metaNode.attrs.parent_server_id ? { parentServerId: +metaNode.attrs.parent_server_id } : {}),
|
|
464
|
-
...(metaNode.attrs.response_server_id ? { responseServerId: +metaNode.attrs.response_server_id } : {})
|
|
465
|
-
}
|
|
466
|
-
: undefined
|
|
467
|
-
|
|
468
|
-
// Parse engagement counters
|
|
469
|
-
const viewsNode = (0, WABinary_1.getBinaryNodeChild)(node, 'views_count')
|
|
470
|
-
const viewsCount = viewsNode?.attrs?.count !== undefined ? +viewsNode.attrs.count : undefined
|
|
471
|
-
const responsesNode = (0, WABinary_1.getBinaryNodeChild)(node, 'responses_count')
|
|
472
|
-
const responsesCount = responsesNode?.attrs?.count !== undefined ? +responsesNode.attrs.count : undefined
|
|
473
|
-
const reactionsNode = (0, WABinary_1.getBinaryNodeChild)(node, 'reactions')
|
|
474
|
-
const reactionCounts = (0, WABinary_1.getBinaryNodeChildren)(reactionsNode ?? { content: [] }, 'reaction').map(
|
|
475
|
-
r => ({ code: r.attrs.code, count: +r.attrs.count })
|
|
476
|
-
)
|
|
477
|
-
|
|
478
|
-
let content = null
|
|
479
|
-
let mediaType = undefined
|
|
480
|
-
|
|
481
|
-
if (type === 'reaction') {
|
|
482
|
-
const reactionNode = (0, WABinary_1.getBinaryNodeChild)(node, 'reaction')
|
|
483
|
-
content = { type: 'reaction', code: reactionNode?.attrs?.code }
|
|
484
|
-
} else if (type === 'text' || type === 'media') {
|
|
485
|
-
const plaintextNode = (0, WABinary_1.getBinaryNodeChild)(node, 'plaintext')
|
|
486
|
-
if (type === 'media') mediaType = plaintextNode?.attrs?.mediatype
|
|
487
|
-
if (edit === '7' || edit === '8') {
|
|
488
|
-
content = { type: 'revoke', edit }
|
|
489
|
-
} else if (plaintextNode?.content) {
|
|
490
|
-
try {
|
|
491
|
-
const buf = Buffer.isBuffer(plaintextNode.content)
|
|
492
|
-
? plaintextNode.content
|
|
493
|
-
: Buffer.from(plaintextNode.content)
|
|
494
|
-
const message = index_js_1.proto.Message.decode(buf).toJSON()
|
|
495
|
-
content = { type, message, ...(mediaType ? { mediaType } : {}) }
|
|
496
|
-
} catch (err) {
|
|
497
|
-
logger.error({ err }, 'Failed to decode newsletter status plaintext')
|
|
498
|
-
content = { type, raw: true, ...(mediaType ? { mediaType } : {}) }
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// Send ACK back to server
|
|
504
|
-
const ackType = type === 'reaction' ? 'reaction' : edit ? 'revoke' : type
|
|
505
|
-
await sendNode({
|
|
506
|
-
tag: 'ack',
|
|
507
|
-
attrs: { id, to: from, class: 'status', type: ackType || 'text' },
|
|
508
|
-
content: undefined
|
|
509
|
-
})
|
|
510
|
-
|
|
511
|
-
ev.emit('newsletter.status', {
|
|
512
|
-
id: from,
|
|
513
|
-
messageId: id,
|
|
514
|
-
serverId,
|
|
515
|
-
timestamp,
|
|
516
|
-
isSender,
|
|
517
|
-
...(offlineIndex !== undefined ? { offlineIndex } : {}),
|
|
518
|
-
...(meta ? { meta } : {}),
|
|
519
|
-
...(viewsCount !== undefined ? { viewsCount } : {}),
|
|
520
|
-
...(responsesCount !== undefined ? { responsesCount } : {}),
|
|
521
|
-
...(reactionCounts.length ? { reactionCounts } : {}),
|
|
522
|
-
content
|
|
523
|
-
})
|
|
524
|
-
}
|
|
525
331
|
const sendMessageAck = async (node, errorCode) => {
|
|
526
332
|
const stanza = (0, stanza_ack_1.buildAckStanza)(node, errorCode, authState.creds.me.id)
|
|
527
333
|
logger.debug({ recv: { tag: node.tag, attrs: node.attrs }, sent: stanza.attrs }, 'sent ack')
|
|
@@ -871,9 +677,8 @@ const makeMessagesRecvSocket = config => {
|
|
|
871
677
|
id: node.attrs.id,
|
|
872
678
|
t: node.attrs.t,
|
|
873
679
|
v: '1',
|
|
874
|
-
//
|
|
875
|
-
|
|
876
|
-
error: '1'
|
|
680
|
+
// ADD ERROR FIELD
|
|
681
|
+
error: '0'
|
|
877
682
|
}
|
|
878
683
|
},
|
|
879
684
|
{
|
|
@@ -921,7 +726,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
921
726
|
const tcJid = await (0, tc_token_utils_1.resolveTcTokenJid)(normalizedJid, getLIDForPN)
|
|
922
727
|
const tcTokenData = await authState.keys.get('tctoken', [tcJid])
|
|
923
728
|
const senderTs = tcTokenData?.[tcJid]?.senderTimestamp
|
|
924
|
-
if (senderTs
|
|
729
|
+
if (senderTs === null || senderTs === undefined || (0, tc_token_utils_1.isTcTokenExpired)(senderTs)) {
|
|
925
730
|
return
|
|
926
731
|
}
|
|
927
732
|
logger.debug({ jid: normalizedJid, senderTimestamp: senderTs }, 'identity changed, re-issuing tctoken')
|
|
@@ -969,21 +774,8 @@ const makeMessagesRecvSocket = config => {
|
|
|
969
774
|
}
|
|
970
775
|
}
|
|
971
776
|
}
|
|
972
|
-
// Group participant nodes carry authoritative LID<->PN pairs (jid + phone_number/lid
|
|
973
|
-
// attrs). Persist them so PN<->LID resolution elsewhere doesn't need a usync round trip.
|
|
974
|
-
const storeParticipantLidPairs = participants => {
|
|
975
|
-
const pairs = []
|
|
976
|
-
for (const p of participants || []) {
|
|
977
|
-
if (p.phoneNumber) pairs.push({ lid: p.id, pn: p.phoneNumber })
|
|
978
|
-
else if (p.lid) pairs.push({ lid: p.lid, pn: p.id })
|
|
979
|
-
}
|
|
980
|
-
if (!pairs.length) return
|
|
981
|
-
signalRepository.lidMapping
|
|
982
|
-
.storeLIDPNMappings(pairs)
|
|
983
|
-
.catch(error => logger.debug({ error }, 'failed to store LID/PN mappings from group notification'))
|
|
984
|
-
}
|
|
985
777
|
const handleGroupNotification = (fullNode, child, msg) => {
|
|
986
|
-
// PN/LID
|
|
778
|
+
// TODO: Support PN/LID (Here is only LID now)
|
|
987
779
|
const actingParticipantLid = fullNode.attrs.participant
|
|
988
780
|
const actingParticipantPn = fullNode.attrs.participant_pn
|
|
989
781
|
const actingParticipantUsername = fullNode.attrs.participant_username
|
|
@@ -997,7 +789,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
997
789
|
msg.messageStubType = Types_1.WAMessageStubType.GROUP_CREATE
|
|
998
790
|
msg.messageStubParameters = [metadata.subject]
|
|
999
791
|
msg.key = { participant: metadata.owner, participantAlt: metadata.ownerPn }
|
|
1000
|
-
storeParticipantLidPairs(metadata.participants)
|
|
1001
792
|
ev.emit('chats.upsert', [
|
|
1002
793
|
{
|
|
1003
794
|
id: metadata.id,
|
|
@@ -1036,6 +827,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
1036
827
|
const stubType = `GROUP_PARTICIPANT_${child.tag.toUpperCase()}`
|
|
1037
828
|
msg.messageStubType = Types_1.WAMessageStubType[stubType]
|
|
1038
829
|
const participants = (0, WABinary_1.getBinaryNodeChildren)(child, 'participant').map(({ attrs }) => {
|
|
830
|
+
// TODO: Store LID MAPPINGS
|
|
1039
831
|
return {
|
|
1040
832
|
id: attrs.jid,
|
|
1041
833
|
phoneNumber:
|
|
@@ -1044,8 +836,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
1044
836
|
: undefined,
|
|
1045
837
|
lid: (0, WABinary_1.isPnUser)(attrs.jid) && (0, WABinary_1.isLidUser)(attrs.lid) ? attrs.lid : undefined,
|
|
1046
838
|
username: attrs.participant_username || attrs.username || undefined,
|
|
1047
|
-
admin: attrs.type || null
|
|
1048
|
-
uuid: attrs.uuid || attrs.participant_uuid || undefined
|
|
839
|
+
admin: attrs.type || null
|
|
1049
840
|
}
|
|
1050
841
|
})
|
|
1051
842
|
if (
|
|
@@ -1058,7 +849,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
1058
849
|
) {
|
|
1059
850
|
msg.messageStubType = Types_1.WAMessageStubType.GROUP_PARTICIPANT_LEAVE
|
|
1060
851
|
}
|
|
1061
|
-
storeParticipantLidPairs(participants)
|
|
1062
852
|
msg.messageStubParameters = participants.map(a => JSON.stringify(a))
|
|
1063
853
|
break
|
|
1064
854
|
case 'subject':
|
|
@@ -1108,51 +898,13 @@ const makeMessagesRecvSocket = config => {
|
|
|
1108
898
|
break
|
|
1109
899
|
case 'revoked_membership_requests':
|
|
1110
900
|
const isDenied = (0, WABinary_1.areJidsSameUser)(affectedParticipantLid, actingParticipantLid)
|
|
1111
|
-
|
|
1112
|
-
storeParticipantLidPairs([{ id: affectedParticipantLid, phoneNumber: affectedParticipantPn }])
|
|
1113
|
-
}
|
|
901
|
+
// TODO: LIDMAPPING SUPPORT
|
|
1114
902
|
msg.messageStubType = Types_1.WAMessageStubType.GROUP_MEMBERSHIP_JOIN_APPROVAL_REQUEST_NON_ADMIN_ADD
|
|
1115
903
|
msg.messageStubParameters = [
|
|
1116
904
|
JSON.stringify({ lid: affectedParticipantLid, pn: affectedParticipantPn }),
|
|
1117
905
|
isDenied ? 'revoked' : 'rejected'
|
|
1118
906
|
]
|
|
1119
907
|
break
|
|
1120
|
-
case 'sibling_link': {
|
|
1121
|
-
const linkedGroupJid = (0, WABinary_1.getBinaryNodeChild)(child, 'group')?.attrs?.jid || child.attrs?.jid
|
|
1122
|
-
ev.emit('groups.update', [
|
|
1123
|
-
{
|
|
1124
|
-
id: fullNode.attrs.from,
|
|
1125
|
-
siblingGroupLinked: linkedGroupJid || true,
|
|
1126
|
-
author: actingParticipantLid,
|
|
1127
|
-
authorPn: actingParticipantPn
|
|
1128
|
-
}
|
|
1129
|
-
])
|
|
1130
|
-
break
|
|
1131
|
-
}
|
|
1132
|
-
case 'sibling_unlink': {
|
|
1133
|
-
const unlinkedGroupJid = (0, WABinary_1.getBinaryNodeChild)(child, 'group')?.attrs?.jid || child.attrs?.jid
|
|
1134
|
-
ev.emit('groups.update', [
|
|
1135
|
-
{
|
|
1136
|
-
id: fullNode.attrs.from,
|
|
1137
|
-
siblingGroupUnlinked: unlinkedGroupJid || true,
|
|
1138
|
-
author: actingParticipantLid,
|
|
1139
|
-
authorPn: actingParticipantPn
|
|
1140
|
-
}
|
|
1141
|
-
])
|
|
1142
|
-
break
|
|
1143
|
-
}
|
|
1144
|
-
case 'clear_history': {
|
|
1145
|
-
const historyClearTimestamp = child.attrs?.t ? +child.attrs.t : (0, Date)()
|
|
1146
|
-
ev.emit('groups.update', [
|
|
1147
|
-
{
|
|
1148
|
-
id: fullNode.attrs.from,
|
|
1149
|
-
historyClearTimestamp,
|
|
1150
|
-
author: actingParticipantLid,
|
|
1151
|
-
authorPn: actingParticipantPn
|
|
1152
|
-
}
|
|
1153
|
-
])
|
|
1154
|
-
break
|
|
1155
|
-
}
|
|
1156
908
|
}
|
|
1157
909
|
}
|
|
1158
910
|
const normalizeNotificationParticipant = async (jid, groupData) => {
|
|
@@ -1326,17 +1078,15 @@ const makeMessagesRecvSocket = config => {
|
|
|
1326
1078
|
if (childTag === 'peer_device_presence') {
|
|
1327
1079
|
const jid = attrs.jid || (0, WABinary_1.jidNormalizedUser)(node.attrs.from)
|
|
1328
1080
|
const presence = attrs.type === 'unavailable' ? 'unavailable' : 'available'
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
ev.emit('presence.update', { id: jid, presences: { [jid]: { lastKnownPresence: presence } }, isIosInterop })
|
|
1081
|
+
logger.debug({ jid, presence }, '[interop] peer_device_presence')
|
|
1082
|
+
ev.emit('presence.update', { id: jid, presences: { [jid]: { lastKnownPresence: presence } } })
|
|
1332
1083
|
return
|
|
1333
1084
|
}
|
|
1334
1085
|
|
|
1335
1086
|
// fbid:thread / fbid:devices — Meta thread or device list association
|
|
1336
1087
|
if (childTag === 'fbid_thread' || childTag === 'fbid_devices') {
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
ev.emit('interop.fbid-update', { type: childTag, jid: node.attrs.from, attrs, isIosInterop })
|
|
1088
|
+
logger.debug({ childTag, attrs, from: node.attrs.from }, '[interop] fbid association update')
|
|
1089
|
+
ev.emit('interop.fbid-update', { type: childTag, jid: node.attrs.from, attrs })
|
|
1340
1090
|
return
|
|
1341
1091
|
}
|
|
1342
1092
|
|
|
@@ -1363,24 +1113,10 @@ const makeMessagesRecvSocket = config => {
|
|
|
1363
1113
|
await handleNewsletterNotification(node)
|
|
1364
1114
|
break
|
|
1365
1115
|
case 'mex':
|
|
1366
|
-
await
|
|
1116
|
+
await handleMexNewsletterNotification(node)
|
|
1367
1117
|
break
|
|
1368
1118
|
case 'w:gp2':
|
|
1369
|
-
|
|
1370
|
-
// Bulk "these groups are stale, refetch" signal — not a per-group stub message,
|
|
1371
|
-
// so it doesn't go through handleGroupNotification (which assumes a single group).
|
|
1372
|
-
const dirtyGroupJids = (0, WABinary_1.getBinaryNodeChildren)(child, 'group')
|
|
1373
|
-
.map(g => g.attrs.jid)
|
|
1374
|
-
.filter(Boolean)
|
|
1375
|
-
if (dirtyGroupJids.length) {
|
|
1376
|
-
ev.emit(
|
|
1377
|
-
'groups.update',
|
|
1378
|
-
dirtyGroupJids.map(jid => ({ id: jid }))
|
|
1379
|
-
)
|
|
1380
|
-
}
|
|
1381
|
-
break
|
|
1382
|
-
}
|
|
1383
|
-
// PN/LID resolution for participant fields happens below via normalizeNotificationResult
|
|
1119
|
+
// TODO: HANDLE PARTICIPANT_PN
|
|
1384
1120
|
const groupData = await getNotificationGroupData(node)
|
|
1385
1121
|
handleGroupNotification(node, child, result)
|
|
1386
1122
|
|
|
@@ -1393,39 +1129,26 @@ const makeMessagesRecvSocket = config => {
|
|
|
1393
1129
|
case 'encrypt':
|
|
1394
1130
|
await handleEncryptNotification(node)
|
|
1395
1131
|
break
|
|
1396
|
-
case 'devices':
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
// device-list hash refresh) — don't misreport it as "removed nothing".
|
|
1400
|
-
const addNode = (0, WABinary_1.getBinaryNodeChild)(node, 'add')
|
|
1401
|
-
const removeNode = (0, WABinary_1.getBinaryNodeChild)(node, 'remove')
|
|
1402
|
-
const changedNode = addNode || removeNode
|
|
1403
|
-
if (!changedNode) {
|
|
1404
|
-
logger.debug({ node }, 'devices hash refresh, no add/remove list to report')
|
|
1405
|
-
break
|
|
1406
|
-
}
|
|
1407
|
-
const isAdded = !!addNode
|
|
1408
|
-
const devices = (0, WABinary_1.getBinaryNodeChildren)(changedNode, 'device')
|
|
1409
|
-
const deviceOwnerJid = from
|
|
1132
|
+
case 'devices':
|
|
1133
|
+
const devices = (0, WABinary_1.getBinaryNodeChildren)(child, 'device')
|
|
1134
|
+
const deviceOwnerJid = child.attrs.jid || child.attrs.lid
|
|
1410
1135
|
const deviceData = devices.map(d => ({
|
|
1411
1136
|
id: d.attrs.jid,
|
|
1412
1137
|
lid: d.attrs.lid,
|
|
1413
|
-
|
|
1414
|
-
keyIndex: d.attrs['key-index'] ? +d.attrs['key-index'] : undefined,
|
|
1138
|
+
keyIndex: d.attrs.key_index ? +d.attrs.key_index : undefined,
|
|
1415
1139
|
platform: d.attrs.platform || undefined,
|
|
1416
1140
|
isCompanion: d.attrs.companion === 'true' || undefined
|
|
1417
1141
|
}))
|
|
1418
|
-
|
|
1419
|
-
(0, WABinary_1.areJidsSameUser)(
|
|
1420
|
-
(0, WABinary_1.areJidsSameUser)(
|
|
1421
|
-
|
|
1422
|
-
logger.info({ deviceData
|
|
1423
|
-
|
|
1424
|
-
if (deviceOwnerJid) {
|
|
1425
|
-
ev.emit('devices.update', { id: deviceOwnerJid, devices: deviceData, isSelf
|
|
1142
|
+
if (
|
|
1143
|
+
(0, WABinary_1.areJidsSameUser)(child.attrs.jid, authState.creds.me.id) ||
|
|
1144
|
+
(0, WABinary_1.areJidsSameUser)(child.attrs.lid, authState.creds.me.lid)
|
|
1145
|
+
) {
|
|
1146
|
+
logger.info({ deviceData }, 'my own devices changed')
|
|
1147
|
+
ev.emit('devices.update', { id: deviceOwnerJid, devices: deviceData, isSelf: true })
|
|
1148
|
+
} else if (deviceOwnerJid) {
|
|
1149
|
+
ev.emit('devices.update', { id: deviceOwnerJid, devices: deviceData, isSelf: false })
|
|
1426
1150
|
}
|
|
1427
1151
|
break
|
|
1428
|
-
}
|
|
1429
1152
|
case 'server_sync':
|
|
1430
1153
|
const update = (0, WABinary_1.getBinaryNodeChild)(node, 'collection')
|
|
1431
1154
|
if (update) {
|
|
@@ -1433,18 +1156,13 @@ const makeMessagesRecvSocket = config => {
|
|
|
1433
1156
|
await resyncAppState([name], false)
|
|
1434
1157
|
}
|
|
1435
1158
|
break
|
|
1436
|
-
case 'picture':
|
|
1159
|
+
case 'picture':
|
|
1437
1160
|
const setPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'set')
|
|
1438
1161
|
const delPicture = (0, WABinary_1.getBinaryNodeChild)(node, 'delete')
|
|
1439
|
-
|
|
1440
|
-
if (!pictureOwnerJid) {
|
|
1441
|
-
// a picture content hash is not a valid contact id — nothing usable to emit without "from"
|
|
1442
|
-
logger.debug({ node }, 'picture notification missing "from", skipping contacts.update')
|
|
1443
|
-
break
|
|
1444
|
-
}
|
|
1162
|
+
// TODO: WAJIDHASH stuff proper support inhouse
|
|
1445
1163
|
ev.emit('contacts.update', [
|
|
1446
1164
|
{
|
|
1447
|
-
id:
|
|
1165
|
+
id: (0, WABinary_1.jidNormalizedUser)(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
|
|
1448
1166
|
imgUrl: setPicture ? 'changed' : 'removed'
|
|
1449
1167
|
}
|
|
1450
1168
|
])
|
|
@@ -1461,7 +1179,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
1461
1179
|
}
|
|
1462
1180
|
}
|
|
1463
1181
|
break
|
|
1464
|
-
}
|
|
1465
1182
|
case 'account_sync':
|
|
1466
1183
|
if (child.tag === 'disappearing_mode') {
|
|
1467
1184
|
const newDuration = +child.attrs.duration
|
|
@@ -1477,60 +1194,18 @@ const makeMessagesRecvSocket = config => {
|
|
|
1477
1194
|
}
|
|
1478
1195
|
})
|
|
1479
1196
|
} else if (child.tag === 'blocklist') {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
// Full device-list sync: all linked devices + signed key-index-list.
|
|
1486
|
-
// Sent when a device is added, removed, or key index changes.
|
|
1487
|
-
const dhash = child.attrs.dhash
|
|
1488
|
-
const deviceNodes = (0, WABinary_1.getBinaryNodeChildren)(child, 'device')
|
|
1489
|
-
const keyIndexListNode = (0, WABinary_1.getBinaryNodeChild)(child, 'key-index-list')
|
|
1490
|
-
const devices = deviceNodes.map(d => ({
|
|
1491
|
-
jid: d.attrs.jid ? String(d.attrs.jid) : undefined,
|
|
1492
|
-
keyIndex: d.attrs['key-index'] ? +d.attrs['key-index'] : undefined
|
|
1493
|
-
}))
|
|
1494
|
-
logger.info({ dhash, deviceCount: devices.length }, 'account devices list synced')
|
|
1495
|
-
ev.emit('account.devices-synced', {
|
|
1496
|
-
dhash,
|
|
1497
|
-
devices,
|
|
1498
|
-
keyIndexListTimestamp: keyIndexListNode?.attrs?.ts ? +keyIndexListNode.attrs.ts : undefined,
|
|
1499
|
-
keyIndexList: keyIndexListNode?.content ? Buffer.from(keyIndexListNode.content) : undefined
|
|
1500
|
-
})
|
|
1501
|
-
}
|
|
1502
|
-
break
|
|
1503
|
-
case 'disappearing_mode': {
|
|
1504
|
-
// Same payload as the account_sync-nested variant above, but WhatsApp also
|
|
1505
|
-
// pushes it as its own top-level notification type on some accounts.
|
|
1506
|
-
const newDuration = +child.attrs.duration
|
|
1507
|
-
const timestamp = +child.attrs.t
|
|
1508
|
-
logger.info({ newDuration }, 'updated account disappearing mode')
|
|
1509
|
-
ev.emit('creds.update', {
|
|
1510
|
-
accountSettings: {
|
|
1511
|
-
...authState.creds.accountSettings,
|
|
1512
|
-
defaultDisappearingMode: {
|
|
1513
|
-
ephemeralExpiration: newDuration,
|
|
1514
|
-
ephemeralSettingTimestamp: timestamp
|
|
1515
|
-
}
|
|
1197
|
+
const blocklists = (0, WABinary_1.getBinaryNodeChildren)(child, 'item')
|
|
1198
|
+
for (const { attrs } of blocklists) {
|
|
1199
|
+
const blocklist = [attrs.jid]
|
|
1200
|
+
const type = attrs.action === 'block' ? 'add' : 'remove'
|
|
1201
|
+
ev.emit('blocklist.update', { blocklist, type })
|
|
1516
1202
|
}
|
|
1517
|
-
})
|
|
1518
|
-
break
|
|
1519
|
-
}
|
|
1520
|
-
case 'contacts': {
|
|
1521
|
-
// <update jid="..."/> = single contact touched; <update hash="..."/> or a
|
|
1522
|
-
// sibling <sync after="..."/> = list-level signal with no actionable jid.
|
|
1523
|
-
const updateChild = (0, WABinary_1.getBinaryNodeChild)(node, 'update')
|
|
1524
|
-
if (updateChild?.attrs?.jid) {
|
|
1525
|
-
ev.emit('contacts.update', [{ id: (0, WABinary_1.jidNormalizedUser)(updateChild.attrs.jid) }])
|
|
1526
|
-
} else {
|
|
1527
|
-
logger.debug({ node }, 'contacts list changed (hash/sync signal), no per-contact jid to update')
|
|
1528
1203
|
}
|
|
1529
1204
|
break
|
|
1530
|
-
}
|
|
1531
1205
|
case 'business':
|
|
1206
|
+
// SMB privacy / data-sharing settings sync push
|
|
1207
|
+
// (WhatsApp Web: WASmaxInBizSettingsSyncPrivacySettingRequest)
|
|
1532
1208
|
if (child?.tag === 'privacy') {
|
|
1533
|
-
// SMB privacy / data-sharing settings sync push
|
|
1534
1209
|
ev.emit('business.privacy-settings-sync', {
|
|
1535
1210
|
jid: from,
|
|
1536
1211
|
categories: (0, WABinary_1.getBinaryNodeChildren)(child, 'category').map(c => ({
|
|
@@ -1539,32 +1214,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
1539
1214
|
})),
|
|
1540
1215
|
attrs: child.attrs
|
|
1541
1216
|
})
|
|
1542
|
-
} else if (child?.tag === 'profile') {
|
|
1543
|
-
// Business profile updated for a contact (tag = profile version hash)
|
|
1544
|
-
ev.emit('contacts.update', [
|
|
1545
|
-
{
|
|
1546
|
-
id: (0, WABinary_1.jidNormalizedUser)(child.attrs.jid || from),
|
|
1547
|
-
businessProfileTag: child.attrs.tag
|
|
1548
|
-
}
|
|
1549
|
-
])
|
|
1550
|
-
} else if (child?.tag === 'verified_name') {
|
|
1551
|
-
// Verified business name changed — content is a proto payload
|
|
1552
|
-
ev.emit('contacts.update', [
|
|
1553
|
-
{
|
|
1554
|
-
id: (0, WABinary_1.jidNormalizedUser)(child.attrs.jid || from),
|
|
1555
|
-
verifiedName: {
|
|
1556
|
-
verifiedLevel: child.attrs.verified_level,
|
|
1557
|
-
serial: child.attrs.serial,
|
|
1558
|
-
version: child.attrs.v
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
])
|
|
1562
|
-
} else if (child?.tag === 'remove') {
|
|
1563
|
-
// Business account downgraded/removed. <remove jid="..."/> names the account;
|
|
1564
|
-
// <remove hash="..."/> is a list-level signal with no actionable jid.
|
|
1565
|
-
if (child.attrs.jid) {
|
|
1566
|
-
ev.emit('contacts.update', [{ id: (0, WABinary_1.jidNormalizedUser)(child.attrs.jid), isBusiness: false }])
|
|
1567
|
-
}
|
|
1568
1217
|
}
|
|
1569
1218
|
break
|
|
1570
1219
|
case 'hosted':
|
|
@@ -1838,10 +1487,11 @@ const makeMessagesRecvSocket = config => {
|
|
|
1838
1487
|
const newValue = ((await msgRetryCache.get(key)) || 0) + 1
|
|
1839
1488
|
await msgRetryCache.set(key, newValue)
|
|
1840
1489
|
}
|
|
1841
|
-
const sendMessagesAgain = async (key, ids, retryNode) => {
|
|
1490
|
+
const sendMessagesAgain = async (key, ids, retryNode, receiptNode) => {
|
|
1842
1491
|
const remoteJid = key.remoteJid
|
|
1843
1492
|
const participant = key.participant || remoteJid
|
|
1844
1493
|
const retryCount = +retryNode.attrs.count || 1
|
|
1494
|
+
const msgId = ids[0]
|
|
1845
1495
|
// Try to get messages from cache first, then fallback to getMessage
|
|
1846
1496
|
const msgs = []
|
|
1847
1497
|
for (const id of ids) {
|
|
@@ -1873,12 +1523,53 @@ const makeMessagesRecvSocket = config => {
|
|
|
1873
1523
|
// just re-send the message to everyone
|
|
1874
1524
|
// prevents the first message decryption failure
|
|
1875
1525
|
const sendToAll = !(0, WABinary_1.jidDecode)(participant)?.device
|
|
1526
|
+
const sessionId = signalRepository.jidToSignalProtocolAddress(participant)
|
|
1527
|
+
let injectedFromBundle = false
|
|
1528
|
+
if (typeof signalRepository.injectE2ESession === 'function' && typeof Utils_1.extractE2ESessionFromRetryReceipt === 'function') {
|
|
1529
|
+
const bundle = Utils_1.extractE2ESessionFromRetryReceipt(receiptNode)
|
|
1530
|
+
if (bundle) {
|
|
1531
|
+
try {
|
|
1532
|
+
await signalRepository.injectE2ESession({ jid: participant, session: bundle })
|
|
1533
|
+
injectedFromBundle = true
|
|
1534
|
+
logger.debug({ participant, retryCount }, 'injected session from retry receipt key bundle')
|
|
1535
|
+
} catch (error) {
|
|
1536
|
+
logger.warn({ error, participant }, 'failed to inject session from retry receipt')
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
if (!injectedFromBundle && signalRepository.getSessionInfo) {
|
|
1541
|
+
const receivedRegId = (0, WABinary_1.getBinaryNodeChildUInt)(receiptNode, 'registration', 4)
|
|
1542
|
+
if (typeof receivedRegId === 'number' && Number.isInteger(receivedRegId)) {
|
|
1543
|
+
const info = await signalRepository.getSessionInfo(participant)
|
|
1544
|
+
if (info && info.registrationId !== 0 && info.registrationId !== receivedRegId) {
|
|
1545
|
+
logger.info(
|
|
1546
|
+
{ participant, stored: info.registrationId, received: receivedRegId },
|
|
1547
|
+
'reg id mismatch on retry without bundle, deleting session'
|
|
1548
|
+
)
|
|
1549
|
+
await authState.keys.set({ session: { [sessionId]: null } })
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
const BASE_KEY_CHECK_RETRY = 2
|
|
1554
|
+
if (msgId && messageRetryManager && signalRepository.getSessionInfo) {
|
|
1555
|
+
const info = await signalRepository.getSessionInfo(participant)
|
|
1556
|
+
if (info) {
|
|
1557
|
+
if (retryCount === BASE_KEY_CHECK_RETRY) {
|
|
1558
|
+
messageRetryManager.saveBaseKey(sessionId, msgId, info.baseKey)
|
|
1559
|
+
} else if (retryCount > BASE_KEY_CHECK_RETRY) {
|
|
1560
|
+
if (messageRetryManager.hasSameBaseKey(sessionId, msgId, info.baseKey)) {
|
|
1561
|
+
logger.warn({ participant, retryCount }, 'base key collision on retry, forcing fresh session')
|
|
1562
|
+
await authState.keys.set({ session: { [sessionId]: null } })
|
|
1563
|
+
}
|
|
1564
|
+
messageRetryManager.deleteBaseKey(sessionId, msgId)
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1876
1568
|
// Check if we should recreate session for this retry
|
|
1877
1569
|
let shouldRecreateSession = false
|
|
1878
1570
|
let recreateReason = ''
|
|
1879
|
-
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1) {
|
|
1571
|
+
if (enableAutoSessionRecreation && messageRetryManager && retryCount > 1 && !injectedFromBundle) {
|
|
1880
1572
|
try {
|
|
1881
|
-
const sessionId = signalRepository.jidToSignalProtocolAddress(participant)
|
|
1882
1573
|
const hasSession = await signalRepository.validateSession(participant)
|
|
1883
1574
|
const result = messageRetryManager.shouldRecreateSession(participant, hasSession.exists)
|
|
1884
1575
|
shouldRecreateSession = result.recreate
|
|
@@ -1891,7 +1582,9 @@ const makeMessagesRecvSocket = config => {
|
|
|
1891
1582
|
logger.warn({ error, participant }, 'failed to check session recreation for outgoing retry')
|
|
1892
1583
|
}
|
|
1893
1584
|
}
|
|
1894
|
-
|
|
1585
|
+
if (!injectedFromBundle) {
|
|
1586
|
+
await assertSessions([participant], true)
|
|
1587
|
+
}
|
|
1895
1588
|
if ((0, WABinary_1.isJidGroup)(remoteJid)) {
|
|
1896
1589
|
await authState.keys.set({ 'sender-key-memory': { [remoteJid]: null } })
|
|
1897
1590
|
}
|
|
@@ -1940,51 +1633,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
1940
1633
|
const items = (0, WABinary_1.getBinaryNodeChildren)(content[0], 'item')
|
|
1941
1634
|
ids.push(...items.map(i => i.attrs.id))
|
|
1942
1635
|
}
|
|
1943
|
-
// E. Media Retry Notification Receipt
|
|
1944
|
-
// Server tells us a specific message's media should be re-fetched
|
|
1945
|
-
if (attrs.type === 'media-retry') {
|
|
1946
|
-
const mediaRetryNode =
|
|
1947
|
-
(0, WABinary_1.getBinaryNodeChild)(node, 'media-retry') ||
|
|
1948
|
-
(0, WABinary_1.getBinaryNodeChild)(node, 'media_retry')
|
|
1949
|
-
ev.emit('messages.media-retry', {
|
|
1950
|
-
ids,
|
|
1951
|
-
from: attrs.from,
|
|
1952
|
-
participant: attrs.participant,
|
|
1953
|
-
t: attrs.t,
|
|
1954
|
-
retryAttrs: mediaRetryNode?.attrs
|
|
1955
|
-
})
|
|
1956
|
-
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack media-retry receipt'))
|
|
1957
|
-
return
|
|
1958
|
-
}
|
|
1959
|
-
// F. Server Error Receipt
|
|
1960
|
-
// Server signals a delivery error for one or more messages
|
|
1961
|
-
if (attrs.type === 'server-error') {
|
|
1962
|
-
const errorNode = (0, WABinary_1.getBinaryNodeChild)(node, 'error')
|
|
1963
|
-
ev.emit('messages.server-error', {
|
|
1964
|
-
ids,
|
|
1965
|
-
from: attrs.from,
|
|
1966
|
-
participant: attrs.participant,
|
|
1967
|
-
t: attrs.t,
|
|
1968
|
-
errorCode: errorNode?.attrs?.code || attrs.error_code,
|
|
1969
|
-
errorText: errorNode?.attrs?.text || errorNode?.content?.toString?.()
|
|
1970
|
-
})
|
|
1971
|
-
await sendMessageAck(node).catch(ackErr => logger.error({ ackErr }, 'failed to ack server-error receipt'))
|
|
1972
|
-
return
|
|
1973
|
-
}
|
|
1974
|
-
// D. Receipt aggregation
|
|
1975
|
-
// Server sent a batched-receipt (receipt_agg attr) — emit dedicated event AND fall through
|
|
1976
|
-
// to the normal status-update pipeline so all IDs in the batch get their status updated.
|
|
1977
|
-
if (attrs.receipt_agg) {
|
|
1978
|
-
ev.emit('receipt.batched', {
|
|
1979
|
-
ids,
|
|
1980
|
-
from: attrs.from,
|
|
1981
|
-
participant: attrs.participant,
|
|
1982
|
-
type: attrs.type,
|
|
1983
|
-
t: attrs.t,
|
|
1984
|
-
receiptAgg: attrs.receipt_agg
|
|
1985
|
-
})
|
|
1986
|
-
// Do not return — let all IDs be processed by the normal receipt path below.
|
|
1987
|
-
}
|
|
1988
1636
|
try {
|
|
1989
1637
|
await Promise.all([
|
|
1990
1638
|
receiptMutex.mutex(async () => {
|
|
@@ -2029,7 +1677,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
2029
1677
|
try {
|
|
2030
1678
|
await updateSendMessageAgainCount(ids[0], key.participant)
|
|
2031
1679
|
logger.debug({ attrs, key }, 'recv retry request')
|
|
2032
|
-
await sendMessagesAgain(key, ids, retryNode)
|
|
1680
|
+
await sendMessagesAgain(key, ids, retryNode, node)
|
|
2033
1681
|
} catch (error) {
|
|
2034
1682
|
logger.error(
|
|
2035
1683
|
{ key, ids, trace: error instanceof Error ? error.stack : 'Unknown error' },
|
|
@@ -2419,19 +2067,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
2419
2067
|
}
|
|
2420
2068
|
}
|
|
2421
2069
|
}
|
|
2422
|
-
// group_info roster: <group_info><user jid=.. state=.. user_pn=..><device jid=..
|
|
2423
|
-
// platform=..><capability ver=../></device></user>...</group_info>. Baileys previously
|
|
2424
|
-
// only kept the flat group_info-level attrs (call.payload), dropping the roster entirely.
|
|
2425
|
-
const parseGroupCallRoster = groupInfoNode =>
|
|
2426
|
-
(0, WABinary_1.getBinaryNodeChildren)(groupInfoNode, 'user').map(userNode => ({
|
|
2427
|
-
jid: userNode.attrs.jid,
|
|
2428
|
-
state: userNode.attrs.state,
|
|
2429
|
-
userPn: userNode.attrs.user_pn,
|
|
2430
|
-
devices: (0, WABinary_1.getBinaryNodeChildren)(userNode, 'device').map(d => ({
|
|
2431
|
-
jid: d.attrs.jid,
|
|
2432
|
-
platform: d.attrs.platform
|
|
2433
|
-
}))
|
|
2434
|
-
}))
|
|
2435
2070
|
const handleCall = async node => {
|
|
2436
2071
|
try {
|
|
2437
2072
|
const { attrs } = node
|
|
@@ -2459,45 +2094,10 @@ const makeMessagesRecvSocket = config => {
|
|
|
2459
2094
|
}
|
|
2460
2095
|
}
|
|
2461
2096
|
if (status === 'offer') {
|
|
2462
|
-
|
|
2463
|
-
const audioNodes = (0, WABinary_1.getBinaryNodeChildren)(infoChild, 'audio')
|
|
2464
|
-
const silenceNode = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'silence')
|
|
2465
|
-
call.isVideo = !!videoNode
|
|
2097
|
+
call.isVideo = !!(0, WABinary_1.getBinaryNodeChild)(infoChild, 'video')
|
|
2466
2098
|
call.isGroup = infoChild.attrs.type === 'group' || !!infoChild.attrs['group-jid']
|
|
2467
2099
|
call.groupJid = infoChild.attrs['group-jid']
|
|
2468
|
-
// lightweight=1 marks a silent/wave-style group ring (no full ringing UX expected)
|
|
2469
|
-
call.isLightweight = infoChild.attrs.lightweight === '1'
|
|
2470
|
-
if (silenceNode?.attrs?.reason) {
|
|
2471
|
-
call.silenceReason = silenceNode.attrs.reason
|
|
2472
|
-
}
|
|
2473
|
-
// Wire attrs are "enc"/"rate" (audio) and "dec" (video) — not "codec".
|
|
2474
|
-
// <audio> can repeat once per supported sample rate.
|
|
2475
|
-
if (audioNodes.length) {
|
|
2476
|
-
call.audioCodecs = audioNodes.map(n => ({ enc: n.attrs.enc, rate: n.attrs.rate ? +n.attrs.rate : undefined }))
|
|
2477
|
-
call.audioCodec = audioNodes[0].attrs.enc
|
|
2478
|
-
}
|
|
2479
|
-
if (videoNode?.attrs?.dec) {
|
|
2480
|
-
call.videoCodec = videoNode.attrs.dec
|
|
2481
|
-
}
|
|
2482
|
-
// Decrypt Signal-encrypted callKey from <enc> child
|
|
2483
|
-
const encNode = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'enc')
|
|
2484
|
-
if (encNode?.content) {
|
|
2485
|
-
try {
|
|
2486
|
-
const encType = encNode.attrs.type || 'msg'
|
|
2487
|
-
const ciphertext = Buffer.isBuffer(encNode.content) ? encNode.content : Buffer.from(encNode.content)
|
|
2488
|
-
const decrypted = await signalRepository.decryptMessage({ jid: attrs.from, type: encType, ciphertext })
|
|
2489
|
-
const unpadded = (0, Utils_1.unpadRandomMax16)(decrypted)
|
|
2490
|
-
const callMsg = index_js_1.proto.Message.decode(unpadded)
|
|
2491
|
-
if (callMsg?.call?.callKey?.length) {
|
|
2492
|
-
call.callKey = Buffer.from(callMsg.call.callKey)
|
|
2493
|
-
}
|
|
2494
|
-
} catch (e) {
|
|
2495
|
-
logger.debug({ e }, 'failed to decrypt call enc')
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
2100
|
await callOfferCache.set(call.id, call)
|
|
2499
|
-
} else if (status === 'waiting_room_request') {
|
|
2500
|
-
call.peerJid = infoChild.attrs.from || infoChild.attrs['peer-jid'] || infoChild.attrs['user-jid']
|
|
2501
2101
|
}
|
|
2502
2102
|
const existingCall = await callOfferCache.get(call.id)
|
|
2503
2103
|
// use existing call info to populate this event
|
|
@@ -2506,41 +2106,8 @@ const makeMessagesRecvSocket = config => {
|
|
|
2506
2106
|
call.isGroup = existingCall.isGroup
|
|
2507
2107
|
call.callerPn = call.callerPn || existingCall.callerPn
|
|
2508
2108
|
}
|
|
2509
|
-
// Enrich event payload for signalling-only statuses
|
|
2510
|
-
if (status === 'peer_state') {
|
|
2511
|
-
const stateChild = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'peer_state')
|
|
2512
|
-
call.state = stateChild?.attrs?.state || infoChild.attrs?.state
|
|
2513
|
-
} else if (status === 'group_info') {
|
|
2514
|
-
call.payload = infoChild.attrs
|
|
2515
|
-
call.participants = parseGroupCallRoster(infoChild)
|
|
2516
|
-
} else if (status === 'video_state') {
|
|
2517
|
-
const vsChild = (0, WABinary_1.getBinaryNodeChild)(infoChild, 'video_state')
|
|
2518
|
-
const enabledRaw = vsChild?.attrs?.enabled ?? infoChild.attrs?.enabled
|
|
2519
|
-
call.enabled = enabledRaw === 'true' || enabledRaw === '1'
|
|
2520
|
-
} else if (status === 'enc_rekey') {
|
|
2521
|
-
const rekeyChild =
|
|
2522
|
-
(0, WABinary_1.getBinaryNodeChild)(infoChild, 'enc-rekey') ||
|
|
2523
|
-
(0, WABinary_1.getBinaryNodeChild)(infoChild, 'enc_rekey')
|
|
2524
|
-
if (rekeyChild?.content && Buffer.isBuffer(rekeyChild.content)) {
|
|
2525
|
-
try {
|
|
2526
|
-
call.rekeyPayload = (0, Utils_1.decodeE2eRekeyPayload)(rekeyChild.content)
|
|
2527
|
-
} catch (e) {
|
|
2528
|
-
logger.debug({ e }, 'failed to decode enc-rekey payload in handleCall')
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
2109
|
// delete data once call has ended
|
|
2533
|
-
if (
|
|
2534
|
-
status === 'reject' ||
|
|
2535
|
-
status === 'accept' ||
|
|
2536
|
-
status === 'timeout' ||
|
|
2537
|
-
status === 'terminate' ||
|
|
2538
|
-
status === 'reject_do_not_disturb' ||
|
|
2539
|
-
status === 'mic_permission_denied' ||
|
|
2540
|
-
status === 'camera_permission_denied' ||
|
|
2541
|
-
status === 'remote_busy' ||
|
|
2542
|
-
status === 'remote_offline'
|
|
2543
|
-
) {
|
|
2110
|
+
if (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate') {
|
|
2544
2111
|
await callOfferCache.del(call.id)
|
|
2545
2112
|
}
|
|
2546
2113
|
await normalizeCallEventJids(call, infoChild)
|
|
@@ -2555,24 +2122,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
2555
2122
|
// <mute_v2>, <transport>, … each with a call-id) instead of wrapped in <call>.
|
|
2556
2123
|
// This additively handles those: emit a 'call' event for state stanzas and ack ALL
|
|
2557
2124
|
// of them (otherwise WhatsApp keeps redelivering). The <call> path above is untouched.
|
|
2558
|
-
const CALL_STATE_TAGS = new Set([
|
|
2559
|
-
'offer',
|
|
2560
|
-
'offer_notice',
|
|
2561
|
-
'terminate',
|
|
2562
|
-
'accept',
|
|
2563
|
-
'reject',
|
|
2564
|
-
'preaccept',
|
|
2565
|
-
'accept_ack',
|
|
2566
|
-
'enc-rekey',
|
|
2567
|
-
'enc_rekey',
|
|
2568
|
-
'peer_state',
|
|
2569
|
-
'group_info',
|
|
2570
|
-
'video_state',
|
|
2571
|
-
'video_state_ack',
|
|
2572
|
-
'flow_control',
|
|
2573
|
-
'mute_v2',
|
|
2574
|
-
'waiting_room_request'
|
|
2575
|
-
])
|
|
2125
|
+
const CALL_STATE_TAGS = new Set(['offer', 'offer_notice', 'terminate', 'accept', 'reject', 'preaccept'])
|
|
2576
2126
|
const handleStandaloneCallStanza = async node => {
|
|
2577
2127
|
try {
|
|
2578
2128
|
if (!CALL_STATE_TAGS.has(node.tag)) {
|
|
@@ -2592,24 +2142,9 @@ const makeMessagesRecvSocket = config => {
|
|
|
2592
2142
|
status
|
|
2593
2143
|
}
|
|
2594
2144
|
if (status === 'offer') {
|
|
2595
|
-
|
|
2596
|
-
const audioNodes = (0, WABinary_1.getBinaryNodeChildren)(node, 'audio')
|
|
2597
|
-
const silenceNode = (0, WABinary_1.getBinaryNodeChild)(node, 'silence')
|
|
2598
|
-
call.isVideo = !!videoNode
|
|
2145
|
+
call.isVideo = !!(0, WABinary_1.getBinaryNodeChild)(node, 'video')
|
|
2599
2146
|
call.isGroup = attrs.type === 'group' || !!attrs['group-jid']
|
|
2600
2147
|
call.groupJid = attrs['group-jid']
|
|
2601
|
-
call.isLightweight = attrs.lightweight === '1'
|
|
2602
|
-
if (silenceNode?.attrs?.reason) {
|
|
2603
|
-
call.silenceReason = silenceNode.attrs.reason
|
|
2604
|
-
}
|
|
2605
|
-
// Wire attrs are "enc"/"rate" (audio) and "dec" (video) — not "codec".
|
|
2606
|
-
if (audioNodes.length) {
|
|
2607
|
-
call.audioCodecs = audioNodes.map(n => ({ enc: n.attrs.enc, rate: n.attrs.rate ? +n.attrs.rate : undefined }))
|
|
2608
|
-
call.audioCodec = audioNodes[0].attrs.enc
|
|
2609
|
-
}
|
|
2610
|
-
if (videoNode?.attrs?.dec) {
|
|
2611
|
-
call.videoCodec = videoNode.attrs.dec
|
|
2612
|
-
}
|
|
2613
2148
|
if (callId) {
|
|
2614
2149
|
await callOfferCache.set(callId, call)
|
|
2615
2150
|
}
|
|
@@ -2620,45 +2155,7 @@ const makeMessagesRecvSocket = config => {
|
|
|
2620
2155
|
call.isGroup = existingCall.isGroup
|
|
2621
2156
|
call.callerPn = call.callerPn || existingCall.callerPn
|
|
2622
2157
|
}
|
|
2623
|
-
|
|
2624
|
-
if (status === 'peer_state') {
|
|
2625
|
-
const stateChild = (0, WABinary_1.getBinaryNodeChild)(node, 'peer_state')
|
|
2626
|
-
call.state = stateChild?.attrs?.state || attrs?.state
|
|
2627
|
-
} else if (status === 'group_info') {
|
|
2628
|
-
call.payload = attrs
|
|
2629
|
-
call.participants = parseGroupCallRoster(node)
|
|
2630
|
-
} else if (status === 'video_state') {
|
|
2631
|
-
const vsChild = (0, WABinary_1.getBinaryNodeChild)(node, 'video_state')
|
|
2632
|
-
const enabledRaw = vsChild?.attrs?.enabled ?? attrs?.enabled
|
|
2633
|
-
call.enabled = enabledRaw === 'true' || enabledRaw === '1'
|
|
2634
|
-
} else if (status === 'enc_rekey') {
|
|
2635
|
-
const rekeyChild =
|
|
2636
|
-
(0, WABinary_1.getBinaryNodeChild)(node, 'enc-rekey') || (0, WABinary_1.getBinaryNodeChild)(node, 'enc_rekey')
|
|
2637
|
-
if (rekeyChild?.content && Buffer.isBuffer(rekeyChild.content)) {
|
|
2638
|
-
try {
|
|
2639
|
-
call.rekeyPayload = (0, Utils_1.decodeE2eRekeyPayload)(rekeyChild.content)
|
|
2640
|
-
} catch (e) {
|
|
2641
|
-
logger.debug({ e }, 'failed to decode enc-rekey payload in standalone call stanza')
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
} else if (status === 'mute') {
|
|
2645
|
-
// mute_v2 IS the top-level tag on real traffic (no wrapping/nested child) —
|
|
2646
|
-
// attrs are { call-id, call-creator, mute-state: "0"|"1" }, never
|
|
2647
|
-
// muted/audio_muted, and there's no nested <audio> child.
|
|
2648
|
-
call.muted = node.attrs['mute-state'] === '1'
|
|
2649
|
-
}
|
|
2650
|
-
if (
|
|
2651
|
-
callId &&
|
|
2652
|
-
(status === 'reject' ||
|
|
2653
|
-
status === 'accept' ||
|
|
2654
|
-
status === 'timeout' ||
|
|
2655
|
-
status === 'terminate' ||
|
|
2656
|
-
status === 'reject_do_not_disturb' ||
|
|
2657
|
-
status === 'mic_permission_denied' ||
|
|
2658
|
-
status === 'camera_permission_denied' ||
|
|
2659
|
-
status === 'remote_busy' ||
|
|
2660
|
-
status === 'remote_offline')
|
|
2661
|
-
) {
|
|
2158
|
+
if (callId && (status === 'reject' || status === 'accept' || status === 'timeout' || status === 'terminate')) {
|
|
2662
2159
|
await callOfferCache.del(callId)
|
|
2663
2160
|
}
|
|
2664
2161
|
await normalizeCallEventJids(call, node)
|
|
@@ -2727,37 +2224,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
2727
2224
|
// }
|
|
2728
2225
|
}
|
|
2729
2226
|
}
|
|
2730
|
-
const handleChatstate = async node => {
|
|
2731
|
-
const { from, state } = node.attrs
|
|
2732
|
-
if (!from) return
|
|
2733
|
-
const isTyping = state === 'typing'
|
|
2734
|
-
ev.emit('presence.update', [
|
|
2735
|
-
{
|
|
2736
|
-
id: from,
|
|
2737
|
-
presences: {
|
|
2738
|
-
[from]: isTyping ? 'composing' : 'paused'
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
])
|
|
2742
|
-
}
|
|
2743
|
-
const handlePresence = async node => {
|
|
2744
|
-
const { from, type } = node.attrs
|
|
2745
|
-
if (!from || !type) return
|
|
2746
|
-
const presenceMap = {
|
|
2747
|
-
available: 'available',
|
|
2748
|
-
unavailable: 'unavailable'
|
|
2749
|
-
}
|
|
2750
|
-
const presence = presenceMap[type] || type
|
|
2751
|
-
ev.emit('presence.update', [
|
|
2752
|
-
{
|
|
2753
|
-
id: from,
|
|
2754
|
-
presences: {
|
|
2755
|
-
[from]: presence
|
|
2756
|
-
},
|
|
2757
|
-
lastSeen: node.attrs.t ? Number(node.attrs.t) * 1000 : undefined
|
|
2758
|
-
}
|
|
2759
|
-
])
|
|
2760
|
-
}
|
|
2761
2227
|
/// processes a node with the given function
|
|
2762
2228
|
/// and adds the task to the existing buffer if we're buffering events
|
|
2763
2229
|
const processNodeWithBuffer = async (node, identifier, exec) => {
|
|
@@ -2809,33 +2275,8 @@ const makeMessagesRecvSocket = config => {
|
|
|
2809
2275
|
})
|
|
2810
2276
|
// additive: top-level call-signalling stanzas (some accounts send these instead of <call>)
|
|
2811
2277
|
for (const callTag of [
|
|
2812
|
-
'offer',
|
|
2813
|
-
'
|
|
2814
|
-
'terminate',
|
|
2815
|
-
'accept',
|
|
2816
|
-
'reject',
|
|
2817
|
-
'preaccept',
|
|
2818
|
-
'accept_ack',
|
|
2819
|
-
'enc-rekey',
|
|
2820
|
-
'enc_rekey',
|
|
2821
|
-
'peer_state',
|
|
2822
|
-
'group_info',
|
|
2823
|
-
'video_state',
|
|
2824
|
-
'video_state_ack',
|
|
2825
|
-
'flow_control',
|
|
2826
|
-
'transport',
|
|
2827
|
-
'video',
|
|
2828
|
-
'duration',
|
|
2829
|
-
'mute_v2',
|
|
2830
|
-
'lobby',
|
|
2831
|
-
'heartbeat',
|
|
2832
|
-
'relaylatency',
|
|
2833
|
-
'link_query',
|
|
2834
|
-
'waiting_room_request',
|
|
2835
|
-
// Present in Utils/stanza-ack.js's CALL_STANZA_TAGS (ack builder already treats it as a
|
|
2836
|
-
// call stanza) but was missing here — meant it was never routed/acked, so real traffic
|
|
2837
|
-
// (confirmed via live capture) would just get redelivered forever.
|
|
2838
|
-
'group_update'
|
|
2278
|
+
'offer', 'offer_notice', 'terminate', 'accept', 'reject', 'preaccept',
|
|
2279
|
+
'transport', 'video', 'duration', 'mute_v2', 'lobby', 'heartbeat', 'relaylatency', 'link_query'
|
|
2839
2280
|
]) {
|
|
2840
2281
|
ws.on('CB:' + callTag, node => {
|
|
2841
2282
|
nodelogger(node)
|
|
@@ -2850,18 +2291,6 @@ const makeMessagesRecvSocket = config => {
|
|
|
2850
2291
|
nodelogger(node)
|
|
2851
2292
|
await processNode('notification', node, 'handling notification', handleNotification)
|
|
2852
2293
|
})
|
|
2853
|
-
ws.on('CB:status', async node => {
|
|
2854
|
-
nodelogger(node)
|
|
2855
|
-
await handleNewsletterStatus(node).catch(error => onUnexpectedError(error, 'handling newsletter status'))
|
|
2856
|
-
})
|
|
2857
|
-
ws.on('CB:chatstate', async node => {
|
|
2858
|
-
nodelogger(node)
|
|
2859
|
-
await handleChatstate(node).catch(error => onUnexpectedError(error, 'handling chatstate'))
|
|
2860
|
-
})
|
|
2861
|
-
ws.on('CB:presence', async node => {
|
|
2862
|
-
nodelogger(node)
|
|
2863
|
-
await handlePresence(node).catch(error => onUnexpectedError(error, 'handling presence'))
|
|
2864
|
-
})
|
|
2865
2294
|
ws.on('CB:ack,class:message', node => {
|
|
2866
2295
|
nodelogger(node)
|
|
2867
2296
|
handleBadAck(node).catch(error => onUnexpectedError(error, 'handling bad ack'))
|