@nexustechpro/baileys 2.0.6 → 2.1.2

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.
@@ -1,82 +1,64 @@
1
1
  import NodeCache from '@cacheable/node-cache'
2
2
  import { Boom } from '@hapi/boom'
3
3
  import { proto } from '../../WAProto/index.js'
4
- import { DEFAULT_CACHE_TTLS, PROCESSABLE_HISTORY_TYPES, PHONENUMBER_MCC, MOBILE_TOKEN, MOBILE_USERAGENT, MOBILE_REGISTRATION_ENDPOINT } from '../Defaults/index.js'
4
+ import { DEFAULT_CACHE_TTLS, HISTORY_SYNC_PAUSED_TIMEOUT_MS, PROCESSABLE_HISTORY_TYPES } from '../Defaults/index.js'
5
5
  import { ALL_WA_PATCH_NAMES } from '../Types/index.js'
6
6
  import { SyncState } from '../Types/State.js'
7
- import {
8
- chatModificationToAppPatch,
9
- decodePatches,
10
- decodeSyncdSnapshot,
11
- encodeSyncdPatch,
12
- ensureLTHashStateVersion,
13
- extractSyncdPatches,
14
- generateProfilePicture,
15
- getHistoryMsg,
16
- newLTHashState,
17
- processSyncAction
18
- } from '../Utils/index.js'
7
+ import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, getHistoryMsg, isAppStateSyncIrrecoverable, isMissingKeyError, MAX_SYNC_ATTEMPTS, newLTHashState, processSyncAction } from '../Utils/index.js'
19
8
  import { makeMutex } from '../Utils/make-mutex.js'
20
9
  import processMessage from '../Utils/process-message.js'
21
- import {
22
- getBinaryNodeChild,
23
- getBinaryNodeChildren,
24
- isLidUser,
25
- jidDecode,
26
- jidNormalizedUser,
27
- reduceBinaryNodeToDictionary,
28
- S_WHATSAPP_NET,
29
- VIOLATION_TYPES
30
- } from '../WABinary/index.js'
10
+ import { buildTcTokenFromJid } from '../Utils/tc-token-utils.js'
11
+ import { getBinaryNodeChild, getBinaryNodeChildren, isHostedLidUser, isHostedPnUser, isLidUser, isPnUser, jidDecode, jidNormalizedUser, reduceBinaryNodeToDictionary, S_WHATSAPP_NET, VIOLATION_TYPES } from '../WABinary/index.js'
31
12
  import { USyncQuery, USyncUser } from '../WAUSync/index.js'
32
13
  import { makeSocket } from './socket.js'
33
14
 
34
- const MAX_SYNC_ATTEMPTS = 2
35
- const HISTORY_SYNC_PAUSED_TIMEOUT_MS = 120_000
36
15
  const APP_STATE_RESYNC_COOLDOWN_MS = 60_000
37
16
 
38
17
  export const makeChatsSocket = (config) => {
39
- const {
40
- logger,
41
- markOnlineOnConnect,
42
- fireInitQueries,
43
- appStateMacVerification,
44
- shouldIgnoreJid,
45
- shouldSyncHistoryMessage,
46
- getMessage
47
- } = config
18
+ const { logger, markOnlineOnConnect, fireInitQueries, appStateMacVerification, shouldIgnoreJid, shouldSyncHistoryMessage, getMessage } = config
48
19
 
49
20
  const sock = makeSocket(config)
50
- const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError } = sock
21
+ const { ev, ws, authState, generateMessageTag, sendNode, query, signalRepository, onUnexpectedError, sendUnifiedSession, registerSocketEndHandler } = sock
22
+
23
+ const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping)
51
24
 
52
25
  let privacySettings
26
+ /** Server-assigned AB props for protocol behavior. */
27
+ const serverProps = {
28
+ /** AB prop 10518: gate tctoken on 1:1 messages. Default true (safe: avoids 463). */
29
+ privacyTokenOn1to1: true,
30
+ /** AB prop 9666: gate tctoken on profile picture IQs. WA Web default: true. */
31
+ profilePicPrivacyToken: true,
32
+ /** AB prop 14303: issue tctokens to LID instead of PN. WA Web default: false. */
33
+ lidTrustedTokenIssueToLid: false
34
+ }
35
+
53
36
  let syncState = SyncState.Connecting
54
37
  let awaitingSyncTimeout
55
38
  let historySyncPausedTimeout
56
39
 
57
40
  const historySyncStatus = { initialBootstrapComplete: false, recentSyncComplete: false }
58
41
  const blockedCollections = new Set()
42
+ const appStateResyncCooldown = new Map()
59
43
 
44
+ /** this mutex ensures that processing happens in order */
60
45
  const processingMutex = makeMutex()
46
+ /** this mutex ensures that messages are processed in order */
61
47
  const messageMutex = makeMutex()
48
+ /** this mutex ensures that receipts are processed in order */
62
49
  const receiptMutex = makeMutex()
50
+ /** this mutex ensures that app state patches are processed in order */
63
51
  const appStatePatchMutex = makeMutex()
52
+ /** this mutex ensures that notifications are processed in order */
64
53
  const notificationMutex = makeMutex()
65
54
 
66
- const placeholderResendCache = config.placeholderResendCache || new NodeCache({
67
- stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY,
68
- useClones: false
69
- })
55
+ const placeholderResendCache = config.placeholderResendCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.MSG_RETRY, useClones: false })
70
56
  if (!config.placeholderResendCache) config.placeholderResendCache = placeholderResendCache
71
57
 
72
- const profilePictureUrlCache = config.profilePictureUrlCache || new NodeCache({
73
- stdTTL: DEFAULT_CACHE_TTLS.PROFILE_PIC,
74
- useClones: false
75
- })
58
+ const profilePictureUrlCache = config.profilePictureUrlCache || new NodeCache({ stdTTL: DEFAULT_CACHE_TTLS.PROFILE_PIC, useClones: false })
76
59
  if (!config.profilePictureUrlCache) config.profilePictureUrlCache = profilePictureUrlCache
77
60
 
78
61
  const inFlightProfilePictureUrl = new Map()
79
- const appStateResyncCooldown = new Map()
80
62
 
81
63
  // ─── Key helpers ────────────────────────────────────────────────────────────
82
64
 
@@ -85,45 +67,26 @@ export const makeChatsSocket = (config) => {
85
67
  return key
86
68
  }
87
69
 
88
-
89
70
  const interactiveQuery = async (userNodes, queryNode) => {
90
- const result = await query({
91
- tag: 'iq',
92
- attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'usync' },
93
- content: [{
94
- tag: 'usync',
95
- attrs: { sid: generateMessageTag(), mode: 'query', last: 'true', index: '0', context: 'interactive' },
96
- content: [
97
- { tag: 'query', attrs: {}, content: [queryNode] },
98
- { tag: 'list', attrs: {}, content: userNodes }
99
- ]
100
- }]
101
- })
71
+ const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'get', xmlns: 'usync' }, content: [{ tag: 'usync', attrs: { sid: generateMessageTag(), mode: 'query', last: 'true', index: '0', context: 'interactive' }, content: [{ tag: 'query', attrs: {}, content: [queryNode] }, { tag: 'list', attrs: {}, content: userNodes }] }] })
102
72
  const usyncNode = getBinaryNodeChild(result, 'usync')
103
73
  const listNode = getBinaryNodeChild(usyncNode, 'list')
104
74
  return getBinaryNodeChildren(listNode, 'user')
105
75
  }
106
76
 
107
- // ─── Privacy ─────────────────────────────────────────────────────────────────
77
+ // ─── Privacy ──────────────────────────────────────────────────────────────
108
78
 
109
79
  const fetchPrivacySettings = async (force = false) => {
110
80
  if (!privacySettings || force) {
111
- const { content } = await query({
112
- tag: 'iq',
113
- attrs: { xmlns: 'privacy', to: S_WHATSAPP_NET, type: 'get' },
114
- content: [{ tag: 'privacy', attrs: {} }]
115
- })
81
+ const { content } = await query({ tag: 'iq', attrs: { xmlns: 'privacy', to: S_WHATSAPP_NET, type: 'get' }, content: [{ tag: 'privacy', attrs: {} }] })
116
82
  privacySettings = reduceBinaryNodeToDictionary(content?.[0], 'category')
117
83
  }
118
84
  return privacySettings
119
85
  }
120
86
 
87
+ /** helper function to run a privacy IQ query */
121
88
  const privacyQuery = async (name, value) => {
122
- await query({
123
- tag: 'iq',
124
- attrs: { xmlns: 'privacy', to: S_WHATSAPP_NET, type: 'set' },
125
- content: [{ tag: 'privacy', attrs: {}, content: [{ tag: 'category', attrs: { name, value } }] }]
126
- })
89
+ await query({ tag: 'iq', attrs: { xmlns: 'privacy', to: S_WHATSAPP_NET, type: 'set' }, content: [{ tag: 'privacy', attrs: {}, content: [{ tag: 'category', attrs: { name, value } }] }] })
127
90
  }
128
91
 
129
92
  const updateMessagesPrivacy = (value) => privacyQuery('messages', value)
@@ -136,21 +99,13 @@ export const makeChatsSocket = (config) => {
136
99
  const updateGroupsAddPrivacy = (value) => privacyQuery('groupadd', value)
137
100
 
138
101
  const updateDefaultDisappearingMode = async (duration) => {
139
- await query({
140
- tag: 'iq',
141
- attrs: { xmlns: 'disappearing_mode', to: S_WHATSAPP_NET, type: 'set' },
142
- content: [{ tag: 'disappearing_mode', attrs: { duration: duration.toString() } }]
143
- })
102
+ await query({ tag: 'iq', attrs: { xmlns: 'disappearing_mode', to: S_WHATSAPP_NET, type: 'set' }, content: [{ tag: 'disappearing_mode', attrs: { duration: duration.toString() } }] })
144
103
  }
145
104
 
146
- // ─── Queries ─────────────────────────────────────────────────────────────────
105
+ // ─── Queries ──────────────────────────────────────────────────────────────
147
106
 
148
107
  const getBotListV2 = async () => {
149
- const resp = await query({
150
- tag: 'iq',
151
- attrs: { xmlns: 'bot', to: S_WHATSAPP_NET, type: 'get' },
152
- content: [{ tag: 'bot', attrs: { v: '2' } }]
153
- })
108
+ const resp = await query({ tag: 'iq', attrs: { xmlns: 'bot', to: S_WHATSAPP_NET, type: 'get' }, content: [{ tag: 'bot', attrs: { v: '2' } }] })
154
109
  const botNode = getBinaryNodeChild(resp, 'bot')
155
110
  const botList = []
156
111
  for (const section of getBinaryNodeChildren(botNode, 'section')) {
@@ -180,91 +135,84 @@ export const makeChatsSocket = (config) => {
180
135
  const onWhatsApp = async (...jids) => {
181
136
  const usyncQuery = new USyncQuery()
182
137
  let contactEnabled = false
183
-
184
- for (const jid of jids) {
138
+ for (let jid of jids) {
185
139
  if (isLidUser(jid)) {
186
- logger.warn('LIDs not supported with onWhatsApp')
187
- continue
188
- }
189
- if (!contactEnabled) {
190
- contactEnabled = true
191
- usyncQuery.withContactProtocol().withLIDProtocol()
140
+ const pn = await signalRepository.lidMapping.getPNForLID(jid)
141
+ if (pn) {
142
+ jid = pn
143
+ } else {
144
+ if (!contactEnabled) { contactEnabled = true; usyncQuery.withContactProtocol().withLIDProtocol() }
145
+ usyncQuery.withUser(new USyncUser().withId(jid))
146
+ continue
147
+ }
192
148
  }
149
+ if (!contactEnabled) { contactEnabled = true; usyncQuery.withContactProtocol().withLIDProtocol() }
193
150
  const phone = `+${jid.replace('+', '').split('@')[0].split(':')[0]}`
194
151
  usyncQuery.withUser(new USyncUser().withPhone(phone))
195
152
  }
196
-
197
153
  if (usyncQuery.users.length === 0) return []
198
-
199
154
  const results = await sock.executeUSyncQuery(usyncQuery)
200
155
  if (!results) return []
201
-
202
156
  return Promise.all(
203
157
  results.list
204
- .filter(a => a.contact === true)
158
+ .filter(a => a.contact === true && a.id && a.id !== 'undefined')
205
159
  .map(async ({ id, lid }) => {
206
160
  try {
207
161
  const businessProfile = await getBusinessProfile(id)
208
162
  const isBusiness = businessProfile && Object.keys(businessProfile).length > 0
209
163
  if (isBusiness) {
210
164
  const { wid, ...businessInfo } = businessProfile
211
- return { jid: id, exists: true, lid, status: 'business', businessInfo }
165
+ return { jid: id, exists: true, lid: lid || id, status: 'business', businessInfo }
212
166
  }
213
- return { jid: id, exists: true, lid, status: 'regular' }
167
+ return { jid: id, exists: true, lid: lid || id, status: 'regular' }
214
168
  } catch (error) {
215
- return { jid: id, exists: true, lid, status: 'error', error: error?.message }
169
+ return { jid: id, exists: true, lid: lid || id, status: 'error', error: error?.message }
216
170
  }
217
171
  })
218
172
  )
219
173
  }
220
174
 
221
- // ─── Profile ─────────────────────────────────────────────────────────────────
175
+ // ─── Profile ──────────────────────────────────────────────────────────────
222
176
 
223
- const updateProfilePicture = async (jid, content) => {
177
+ /** update the profile picture for yourself or a group */
178
+ const updateProfilePicture = async (jid, content, dimensions) => {
224
179
  if (!jid) throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update')
225
- let targetJid
226
- if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me.id)) targetJid = jidNormalizedUser(jid)
227
- const { img } = await generateProfilePicture(content)
228
- await query({
229
- tag: 'iq',
230
- attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) },
231
- content: [{ tag: 'picture', attrs: { type: 'image' }, content: img }]
232
- })
180
+ const targetJid = jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me.id) ? jidNormalizedUser(jid) : undefined
181
+ const { img } = await generateProfilePicture(content, dimensions)
182
+ await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) }, content: [{ tag: 'picture', attrs: { type: 'image' }, content: img }] })
233
183
  }
234
184
 
185
+ /** remove the profile picture for yourself or a group */
235
186
  const removeProfilePicture = async (jid) => {
236
187
  if (!jid) throw new Boom('Illegal no-jid profile update. Please specify either your ID or the ID of the chat you wish to update')
237
- let targetJid
238
- if (jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me.id)) targetJid = jidNormalizedUser(jid)
239
- await query({
240
- tag: 'iq',
241
- attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) }
242
- })
188
+ const targetJid = jidNormalizedUser(jid) !== jidNormalizedUser(authState.creds.me.id) ? jidNormalizedUser(jid) : undefined
189
+ await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:profile:picture', ...(targetJid ? { target: targetJid } : {}) } })
243
190
  }
244
191
 
192
+ /** update the profile status for yourself */
245
193
  const updateProfileStatus = async (status) => {
246
- await query({
247
- tag: 'iq',
248
- attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'status' },
249
- content: [{ tag: 'status', attrs: {}, content: Buffer.from(status, 'utf-8') }]
250
- })
194
+ await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'status' }, content: [{ tag: 'status', attrs: {}, content: Buffer.from(status, 'utf-8') }] })
251
195
  }
252
196
 
253
197
  const updateProfileName = (name) => chatModify({ pushNameSetting: name }, '')
254
198
 
255
199
  const profilePictureUrl = async (jid, type = 'preview', timeoutMs) => {
256
- jid = jidNormalizedUser(jid)
257
- const cacheKey = `${jid}:${type}`
200
+ const normalizedJid = jidNormalizedUser(jid)
201
+ const cacheKey = `${normalizedJid}:${type}`
258
202
  const cached = profilePictureUrlCache.get(cacheKey)
259
203
  if (typeof cached !== 'undefined') return cached || undefined
260
204
  const inFlight = inFlightProfilePictureUrl.get(cacheKey)
261
205
  if (inFlight) return inFlight
262
206
  const fetchPromise = (async () => {
263
- const result = await query({
264
- tag: 'iq',
265
- attrs: { target: jid, to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:profile:picture' },
266
- content: [{ tag: 'picture', attrs: { type, query: 'url' } }]
267
- }, timeoutMs)
207
+ const baseContent = [{ tag: 'picture', attrs: { type, query: 'url' } }]
208
+ const isUserJid = isPnUser(normalizedJid) || isLidUser(normalizedJid)
209
+ const me = authState.creds.me
210
+ const isSelf = me && (normalizedJid === jidNormalizedUser(me.id) || (me.lid && normalizedJid === jidNormalizedUser(me.lid)))
211
+ let content = baseContent
212
+ if (serverProps.profilePicPrivacyToken && isUserJid && !isSelf) {
213
+ content = await buildTcTokenFromJid({ authState, jid: normalizedJid, baseContent, getLIDForPN })
214
+ }
215
+ const result = await query({ tag: 'iq', attrs: { target: normalizedJid, to: S_WHATSAPP_NET, type: 'get', xmlns: 'w:profile:picture' }, content }, timeoutMs)
268
216
  const child = getBinaryNodeChild(result, 'picture')
269
217
  const url = child?.attrs?.url
270
218
  profilePictureUrlCache.set(cacheKey, url || null)
@@ -278,7 +226,7 @@ export const makeChatsSocket = (config) => {
278
226
  }
279
227
  }
280
228
 
281
- // ─── Blocklist ────────────────────────────────────────────────────────────────
229
+ // ─── Blocklist ────────────────────────────────────────────────────────────
282
230
 
283
231
  const fetchBlocklist = async () => {
284
232
  const result = await query({ tag: 'iq', attrs: { xmlns: 'blocklist', to: S_WHATSAPP_NET, type: 'get' } })
@@ -287,21 +235,36 @@ export const makeChatsSocket = (config) => {
287
235
  }
288
236
 
289
237
  const updateBlockStatus = async (jid, action) => {
290
- await query({
291
- tag: 'iq',
292
- attrs: { xmlns: 'blocklist', to: S_WHATSAPP_NET, type: 'set' },
293
- content: [{ tag: 'item', attrs: { action, jid } }]
294
- })
238
+ const normalizedJid = jidNormalizedUser(jid)
239
+ let lid
240
+ let pn_jid
241
+ if (isLidUser(normalizedJid) || isHostedLidUser(normalizedJid)) {
242
+ lid = normalizedJid
243
+ if (action === 'block') {
244
+ const pn = await signalRepository.lidMapping.getPNForLID(normalizedJid)
245
+ if (!pn) throw new Boom(`Unable to resolve PN JID for LID: ${jid}`, { statusCode: 400 })
246
+ pn_jid = jidNormalizedUser(pn)
247
+ }
248
+ } else if (isPnUser(normalizedJid) || isHostedPnUser(normalizedJid)) {
249
+ const mapped = await signalRepository.lidMapping.getLIDForPN(normalizedJid)
250
+ if (!mapped) throw new Boom(`Unable to resolve LID for PN JID: ${jid}`, { statusCode: 400 })
251
+ lid = mapped
252
+ if (action === 'block') pn_jid = jidNormalizedUser(normalizedJid)
253
+ } else {
254
+ throw new Boom(`Invalid jid: ${jid}`, { statusCode: 400 })
255
+ }
256
+ const itemAttrs = { action, jid: lid }
257
+ if (action === 'block') {
258
+ if (!pn_jid) throw new Boom(`pn_jid required for block: ${jid}`, { statusCode: 400 })
259
+ itemAttrs.pn_jid = pn_jid
260
+ }
261
+ await query({ tag: 'iq', attrs: { xmlns: 'blocklist', to: S_WHATSAPP_NET, type: 'set' }, content: [{ tag: 'item', attrs: itemAttrs }] })
295
262
  }
296
263
 
297
- // ─── Business ────────────────────────────────────────────────────────────────
264
+ // ─── Business ─────────────────────────────────────────────────────────────
298
265
 
299
266
  const getBusinessProfile = async (jid) => {
300
- const results = await query({
301
- tag: 'iq',
302
- attrs: { to: 's.whatsapp.net', xmlns: 'w:biz', type: 'get' },
303
- content: [{ tag: 'business_profile', attrs: { v: '244' }, content: [{ tag: 'profile', attrs: { jid } }] }]
304
- })
267
+ const results = await query({ tag: 'iq', attrs: { to: 's.whatsapp.net', xmlns: 'w:biz', type: 'get' }, content: [{ tag: 'business_profile', attrs: { v: '244' }, content: [{ tag: 'profile', attrs: { jid } }] }] })
305
268
  const profileNode = getBinaryNodeChild(results, 'business_profile')
306
269
  const profiles = getBinaryNodeChild(profileNode, 'profile')
307
270
  if (profiles) {
@@ -325,15 +288,11 @@ export const makeChatsSocket = (config) => {
325
288
  }
326
289
  }
327
290
 
328
- // ─── App state ────────────────────────────────────────────────────────────────
291
+ // ─── App state ────────────────────────────────────────────────────────────
329
292
 
330
293
  const cleanDirtyBits = async (type, fromTimestamp) => {
331
294
  logger.info({ fromTimestamp }, 'clean dirty bits ' + type)
332
- await sendNode({
333
- tag: 'iq',
334
- attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'urn:xmpp:whatsapp:dirty', id: generateMessageTag() },
335
- content: [{ tag: 'clean', attrs: { type, ...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null) } }]
336
- })
295
+ await sendNode({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'urn:xmpp:whatsapp:dirty', id: generateMessageTag() }, content: [{ tag: 'clean', attrs: { type, ...(fromTimestamp ? { timestamp: fromTimestamp.toString() } : null) } }] })
337
296
  }
338
297
 
339
298
  const newAppStateChunkHandler = (isInitialSync) => ({
@@ -347,7 +306,6 @@ export const makeChatsSocket = (config) => {
347
306
  const collectionsToSync = collections.filter(name => (appStateResyncCooldown.get(name) || 0) <= now)
348
307
  if (!collectionsToSync.length) return
349
308
 
350
- // Per-invocation cache to avoid redundant key fetches within a single resync
351
309
  const appStateSyncKeyCache = new Map()
352
310
  const getCachedAppStateSyncKey = async (keyId) => {
353
311
  if (appStateSyncKeyCache.has(keyId)) return appStateSyncKeyCache.get(keyId) ?? undefined
@@ -378,24 +336,15 @@ export const makeChatsSocket = (config) => {
378
336
  state = newLTHashState()
379
337
  }
380
338
  states[name] = state
381
-
382
339
  const shouldForceSnapshot = forceSnapshotCollections.has(name)
383
340
  if (shouldForceSnapshot) forceSnapshotCollections.delete(name)
384
-
385
341
  logger.info(`resyncing ${name} from v${state.version}${shouldForceSnapshot ? ' (forcing snapshot)' : ''}`)
386
- nodes.push({
387
- tag: 'collection',
388
- attrs: { name, version: state.version.toString(), return_snapshot: (shouldForceSnapshot || !state.version).toString() }
389
- })
342
+ nodes.push({ tag: 'collection', attrs: { name, version: state.version.toString(), return_snapshot: (shouldForceSnapshot || !state.version).toString() } })
390
343
  }
391
344
 
392
- const result = await query({
393
- tag: 'iq',
394
- attrs: { to: S_WHATSAPP_NET, xmlns: 'w:sync:app:state', type: 'set' },
395
- content: [{ tag: 'sync', attrs: {}, content: nodes }]
396
- })
397
-
345
+ const result = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, xmlns: 'w:sync:app:state', type: 'set' }, content: [{ tag: 'sync', attrs: {}, content: nodes }] })
398
346
  const decoded = await extractSyncdPatches(result, config?.options)
347
+
399
348
  for (const key in decoded) {
400
349
  const name = key
401
350
  const { patches, hasMorePatches, snapshot } = decoded[name]
@@ -421,25 +370,20 @@ export const makeChatsSocket = (config) => {
421
370
  }
422
371
  } catch (error) {
423
372
  attemptsMap[name] = (attemptsMap[name] || 0) + 1
424
- const isMissingKey = error.output?.statusCode === 404
425
- const isIrrecoverable = attemptsMap[name] >= MAX_SYNC_ATTEMPTS || error.name === 'TypeError'
426
-
427
- logger.info({ name, error: error.stack, attempt: attemptsMap[name] }, `failed to sync ${name} from v${states[name].version}`)
428
- if (!isMissingKey) {
429
- await authState.keys.set({ 'app-state-sync-version': { [name]: null } })
430
- }
431
-
432
- if (isMissingKey) appStateResyncCooldown.set(name, Date.now() + APP_STATE_RESYNC_COOLDOWN_MS)
433
-
434
- if (isMissingKey && attemptsMap[name] >= MAX_SYNC_ATTEMPTS) {
435
- logger.warn({ name }, `${name} blocked on missing key, parking until key arrives`)
373
+ const logData = { name, attempt: attemptsMap[name], version: states[name].version, statusCode: error.output?.statusCode, errorType: error.name, error: error.stack }
374
+ if (isMissingKeyError(error) && attemptsMap[name] >= MAX_SYNC_ATTEMPTS) {
375
+ logger.warn(logData, `${name} blocked on missing key from v${states[name].version}, parking after ${attemptsMap[name]} attempts`)
436
376
  blockedCollections.add(name)
437
377
  collectionsToHandle.delete(name)
438
- } else if (isMissingKey) {
378
+ appStateResyncCooldown.set(name, Date.now() + APP_STATE_RESYNC_COOLDOWN_MS)
379
+ } else if (isMissingKeyError(error)) {
380
+ logger.info(logData, `${name} blocked on missing key from v${states[name].version}, retrying with snapshot`)
439
381
  forceSnapshotCollections.add(name)
440
- } else if (isIrrecoverable) {
382
+ } else if (isAppStateSyncIrrecoverable(error, attemptsMap[name])) {
383
+ logger.warn(logData, `failed to sync ${name} from v${states[name].version}, giving up`)
441
384
  collectionsToHandle.delete(name)
442
385
  } else {
386
+ logger.info(logData, `failed to sync ${name} from v${states[name].version}, forcing snapshot retry`)
443
387
  forceSnapshotCollections.add(name)
444
388
  }
445
389
  }
@@ -451,33 +395,33 @@ export const makeChatsSocket = (config) => {
451
395
  for (const key in globalMutationMap) onMutation(globalMutationMap[key])
452
396
  })
453
397
 
454
- // ─── Presence ─────────────────────────────────────────────────────────────────
398
+ // ─── Presence ─────────────────────────────────────────────────────────────
455
399
 
456
400
  const sendPresenceUpdate = async (type, toJid) => {
457
401
  const me = authState.creds.me
458
- if (type === 'available' || type === 'unavailable') {
459
- if (!me.name) {
460
- logger.warn('no name present, ignoring presence update request...')
461
- return
462
- }
463
- ev.emit('connection.update', { isOnline: type === 'available' })
402
+ const isAvailableType = type === 'available'
403
+ if (isAvailableType || type === 'unavailable') {
404
+ if (!me.name) { logger.warn('no name present, ignoring presence update request...'); return }
405
+ ev.emit('connection.update', { isOnline: isAvailableType })
406
+ if (isAvailableType) void sendUnifiedSession()
464
407
  await sendNode({ tag: 'presence', attrs: { name: me.name.replace(/@/g, ''), type } })
465
408
  } else {
466
409
  const { server } = jidDecode(toJid)
467
410
  const isLid = server === 'lid'
468
- await sendNode({
469
- tag: 'chatstate',
470
- attrs: { from: isLid ? me.lid : me.id, to: toJid },
471
- content: [{ tag: type === 'recording' ? 'composing' : type, attrs: type === 'recording' ? { media: 'audio' } : {} }]
472
- })
411
+ await sendNode({ tag: 'chatstate', attrs: { from: isLid ? me.lid : me.id, to: toJid }, content: [{ tag: type === 'recording' ? 'composing' : type, attrs: type === 'recording' ? { media: 'audio' } : {} }] })
473
412
  }
474
413
  }
475
414
 
476
- const presenceSubscribe = (toJid, tcToken) => sendNode({
477
- tag: 'presence',
478
- attrs: { to: toJid, id: generateMessageTag(), type: 'subscribe' },
479
- content: tcToken ? [{ tag: 'tctoken', attrs: {}, content: tcToken }] : undefined
480
- })
415
+ /**
416
+ * @param toJid the jid to subscribe to
417
+ * @param tcToken token for subscription, use if present
418
+ */
419
+ const presenceSubscribe = async (toJid) => {
420
+ const normalizedToJid = jidNormalizedUser(toJid)
421
+ const isUserJid = isPnUser(normalizedToJid) || isLidUser(normalizedToJid)
422
+ const tcTokenContent = isUserJid ? await buildTcTokenFromJid({ authState, jid: normalizedToJid, getLIDForPN }) : undefined
423
+ return sendNode({ tag: 'presence', attrs: { to: toJid, id: generateMessageTag(), type: 'subscribe' }, content: tcTokenContent })
424
+ }
481
425
 
482
426
  const handlePresenceUpdate = ({ tag, attrs, content }) => {
483
427
  let presence
@@ -485,10 +429,7 @@ export const makeChatsSocket = (config) => {
485
429
  const participant = attrs.participant || attrs.from
486
430
  if (shouldIgnoreJid(jid) && jid !== S_WHATSAPP_NET) return
487
431
  if (tag === 'presence') {
488
- presence = {
489
- lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
490
- lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
491
- }
432
+ presence = { lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available', lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined, groupOnlineCount: attrs.count ? +attrs.count : undefined }
492
433
  } else if (Array.isArray(content)) {
493
434
  const [firstChild] = content
494
435
  let type = firstChild.tag
@@ -501,7 +442,7 @@ export const makeChatsSocket = (config) => {
501
442
  if (presence) ev.emit('presence.update', { id: jid, presences: { [participant]: presence } })
502
443
  }
503
444
 
504
- // ─── App patch ────────────────────────────────────────────────────────────────
445
+ // ─── App patch ────────────────────────────────────────────────────────────
505
446
 
506
447
  const appPatch = async (patchCreate) => {
507
448
  const name = patchCreate.type
@@ -519,17 +460,7 @@ export const makeChatsSocket = (config) => {
519
460
  initial = currentSyncVersion ? ensureLTHashStateVersion(currentSyncVersion) : newLTHashState()
520
461
  encodeResult = await encodeSyncdPatch(patchCreate, myAppStateKeyId, initial, getAppStateSyncKey)
521
462
  const { patch, state } = encodeResult
522
- await query({
523
- tag: 'iq',
524
- attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:sync:app:state' },
525
- content: [{
526
- tag: 'sync', attrs: {}, content: [{
527
- tag: 'collection',
528
- attrs: { name, version: (state.version - 1).toString(), return_snapshot: 'false' },
529
- content: [{ tag: 'patch', attrs: {}, content: proto.SyncdPatch.encode(patch).finish() }]
530
- }]
531
- }]
532
- })
463
+ await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, type: 'set', xmlns: 'w:sync:app:state' }, content: [{ tag: 'sync', attrs: {}, content: [{ tag: 'collection', attrs: { name, version: (state.version - 1).toString(), return_snapshot: 'false' }, content: [{ tag: 'patch', attrs: {}, content: proto.SyncdPatch.encode(patch).finish() }] }] }] })
533
464
  await authState.keys.set({ 'app-state-sync-version': { [name]: state } })
534
465
  }, authState?.creds?.me?.id || 'app-patch')
535
466
  })
@@ -541,14 +472,11 @@ export const makeChatsSocket = (config) => {
541
472
  }
542
473
  }
543
474
 
544
- // ─── Props ────────────────────────────────────────────────────────────────────
475
+ // ─── Props ────────────────────────────────────────────────────────────────
545
476
 
477
+ /** fetch AB props */
546
478
  const fetchProps = async () => {
547
- const resultNode = await query({
548
- tag: 'iq',
549
- attrs: { to: S_WHATSAPP_NET, xmlns: 'w', type: 'get' },
550
- content: [{ tag: 'props', attrs: { protocol: '2', hash: authState?.creds?.lastPropHash || '' } }]
551
- })
479
+ const resultNode = await query({ tag: 'iq', attrs: { to: S_WHATSAPP_NET, xmlns: 'abt', type: 'get' }, content: [{ tag: 'props', attrs: { protocol: '1', ...(authState?.creds?.lastPropHash ? { hash: authState.creds.lastPropHash } : {}) } }] })
552
480
  const propsNode = getBinaryNodeChild(resultNode, 'props')
553
481
  let props = {}
554
482
  if (propsNode) {
@@ -558,41 +486,63 @@ export const makeChatsSocket = (config) => {
558
486
  }
559
487
  props = reduceBinaryNodeToDictionary(propsNode, 'prop')
560
488
  }
561
- logger.debug('fetched props')
489
+ const privacyTokenProp = props['10518'] ?? props['privacy_token_sending_on_all_1_on_1_messages']
490
+ if (privacyTokenProp !== undefined) serverProps.privacyTokenOn1to1 = privacyTokenProp === 'true' || privacyTokenProp === '1'
491
+ const profilePicProp = props['9666'] ?? props['profile_scraping_privacy_token_in_photo_iq']
492
+ if (profilePicProp !== undefined) serverProps.profilePicPrivacyToken = profilePicProp === 'true' || profilePicProp === '1'
493
+ const lidIssueProp = props['14303'] ?? props['lid_trusted_token_issue_to_lid']
494
+ if (lidIssueProp !== undefined) serverProps.lidTrustedTokenIssueToLid = lidIssueProp === 'true' || lidIssueProp === '1'
495
+ logger.debug({ serverProps }, 'fetched props')
562
496
  return props
563
497
  }
564
498
 
565
- // ─── Chat modification helpers ────────────────────────────────────────────────
499
+ // ─── Chat modification helpers ────────────────────────────────────────────
566
500
 
501
+ /**
502
+ * modify a chat -- mark unread, read etc.
503
+ * lastMessages must be sorted in reverse chronologically
504
+ * requires the last messages till the last message received; required for archive & unread
505
+ */
567
506
  const chatModify = (mod, jid) => appPatch(chatModificationToAppPatch(mod, jid))
507
+ /** Enable/Disable link preview privacy, not related to baileys link preview generation */
568
508
  const updateDisableLinkPreviewsPrivacy = (isPreviewsDisabled) => chatModify({ disableLinkPreviews: { isPreviewsDisabled } }, '')
509
+ /** Star or Unstar a message */
569
510
  const star = (jid, messages, star) => chatModify({ star: { messages, star } }, jid)
511
+ /** Add or Edit Contact */
570
512
  const addOrEditContact = (jid, contact) => chatModify({ contact }, jid)
513
+ /** Remove Contact */
571
514
  const removeContact = (jid) => chatModify({ contact: null }, jid)
515
+ /** Adds label */
572
516
  const addLabel = (jid, labels) => chatModify({ addLabel: { ...labels } }, jid)
517
+ /** Adds label for the chats */
573
518
  const addChatLabel = (jid, labelId) => chatModify({ addChatLabel: { labelId } }, jid)
519
+ /** Removes label for the chat */
574
520
  const removeChatLabel = (jid, labelId) => chatModify({ removeChatLabel: { labelId } }, jid)
521
+ /** Adds label for the message */
575
522
  const addMessageLabel = (jid, messageId, labelId) => chatModify({ addMessageLabel: { messageId, labelId } }, jid)
523
+ /** Removes label for the message */
576
524
  const removeMessageLabel = (jid, messageId, labelId) => chatModify({ removeMessageLabel: { messageId, labelId } }, jid)
525
+ /** Add or Edit Quick Reply */
577
526
  const addOrEditQuickReply = (quickReply) => chatModify({ quickReply }, '')
527
+ /** Remove Quick Reply */
578
528
  const removeQuickReply = (timestamp) => chatModify({ quickReply: { timestamp, deleted: true } }, '')
579
529
 
580
- // ─── Call link ────────────────────────────────────────────────────────────────
530
+ // ─── Call link ────────────────────────────────────────────────────────────
581
531
 
582
532
  const createCallLink = async (type, event, timeoutMs) => {
583
- const result = await query({
584
- tag: 'call',
585
- attrs: { id: generateMessageTag(), to: '@call' },
586
- content: [{ tag: 'link_create', attrs: { media: type }, content: event ? [{ tag: 'event', attrs: { start_time: String(event.startTime) } }] : undefined }]
587
- }, timeoutMs)
533
+ const result = await query({ tag: 'call', attrs: { id: generateMessageTag(), to: '@call' }, content: [{ tag: 'link_create', attrs: { media: type }, content: event ? [{ tag: 'event', attrs: { start_time: String(event.startTime) } }] : undefined }] }, timeoutMs)
588
534
  return getBinaryNodeChild(result, 'link_create')?.attrs?.token
589
535
  }
590
536
 
591
- // ─── Init ─────────────────────────────────────────────────────────────────────
537
+ // ─── Init ─────────────────────────────────────────────────────────────────
592
538
 
539
+ /**
540
+ * queries need to be fired on connection open
541
+ * help ensure parity with WA Web
542
+ */
593
543
  const executeInitQueries = () => Promise.all([fetchProps(), fetchBlocklist(), fetchPrivacySettings()])
594
544
 
595
- // ─── Message upsert ───────────────────────────────────────────────────────────
545
+ // ─── Message upsert ───────────────────────────────────────────────────────
596
546
 
597
547
  const upsertMessage = ev.createBufferedFunction(async (msg, type) => {
598
548
  ev.emit('messages.upsert', { messages: [msg], type })
@@ -600,35 +550,27 @@ export const makeChatsSocket = (config) => {
600
550
  if (msg.pushName) {
601
551
  let jid = msg.key.fromMe ? authState.creds.me.id : msg.key.participant || msg.key.remoteJid
602
552
  jid = jidNormalizedUser(jid)
603
- if (!msg.key.fromMe) {
604
- ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }])
605
- }
606
- if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) {
607
- ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } })
608
- }
553
+ if (!msg.key.fromMe) ev.emit('contacts.update', [{ id: jid, notify: msg.pushName, verifiedName: msg.verifiedBizName }])
554
+ if (msg.key.fromMe && msg.pushName && authState.creds.me?.name !== msg.pushName) ev.emit('creds.update', { me: { ...authState.creds.me, name: msg.pushName } })
609
555
  }
610
556
 
611
557
  const historyMsg = getHistoryMsg(msg.message)
612
558
  const shouldProcessHistoryMsg = historyMsg
613
- ? shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
559
+ ? !!historyMsg.mediaKey?.length && shouldSyncHistoryMessage(historyMsg) && PROCESSABLE_HISTORY_TYPES.includes(historyMsg.syncType)
614
560
  : false
615
561
 
616
- // History sync progress tracking
617
562
  if (historyMsg && shouldProcessHistoryMsg) {
618
563
  const syncType = historyMsg.syncType
619
-
620
564
  if (syncType === proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP && !historySyncStatus.initialBootstrapComplete) {
621
565
  historySyncStatus.initialBootstrapComplete = true
622
566
  ev.emit('messaging-history.status', { syncType, status: 'complete', explicit: true })
623
567
  }
624
-
625
568
  if (syncType === proto.HistorySync.HistorySyncType.RECENT && historyMsg.progress === 100 && !historySyncStatus.recentSyncComplete) {
626
569
  historySyncStatus.recentSyncComplete = true
627
570
  clearTimeout(historySyncPausedTimeout)
628
571
  historySyncPausedTimeout = undefined
629
572
  ev.emit('messaging-history.status', { syncType, status: 'complete', explicit: true })
630
573
  }
631
-
632
574
  if (syncType === proto.HistorySync.HistorySyncType.RECENT && !historySyncStatus.recentSyncComplete) {
633
575
  clearTimeout(historySyncPausedTimeout)
634
576
  historySyncPausedTimeout = setTimeout(() => {
@@ -641,12 +583,8 @@ export const makeChatsSocket = (config) => {
641
583
  }
642
584
  }
643
585
 
644
- // SyncState machine
645
586
  if (historyMsg && syncState === SyncState.AwaitingInitialSync) {
646
- if (awaitingSyncTimeout) {
647
- clearTimeout(awaitingSyncTimeout)
648
- awaitingSyncTimeout = undefined
649
- }
587
+ if (awaitingSyncTimeout) { clearTimeout(awaitingSyncTimeout); awaitingSyncTimeout = undefined }
650
588
  if (shouldProcessHistoryMsg) {
651
589
  syncState = SyncState.Syncing
652
590
  logger.info('Transitioned to Syncing state')
@@ -672,17 +610,7 @@ export const makeChatsSocket = (config) => {
672
610
 
673
611
  await Promise.all([
674
612
  (async () => { if (shouldProcessHistoryMsg) await doAppStateSync() })(),
675
- processMessage(msg, {
676
- signalRepository,
677
- shouldProcessHistoryMsg,
678
- placeholderResendCache,
679
- ev,
680
- creds: authState.creds,
681
- keyStore: authState.keys,
682
- logger,
683
- options: config.options,
684
- getMessage
685
- })
613
+ processMessage(msg, { signalRepository, shouldProcessHistoryMsg, placeholderResendCache, ev, creds: authState.creds, keyStore: authState.keys, logger, options: config.options, getMessage })
686
614
  ])
687
615
 
688
616
  if (msg.message?.protocolMessage?.appStateSyncKeyShare) {
@@ -698,7 +626,7 @@ export const makeChatsSocket = (config) => {
698
626
  }
699
627
  })
700
628
 
701
- // ─── WS handlers ──────────────────────────────────────────────────────────────
629
+ // ─── WS handlers ──────────────────────────────────────────────────────────
702
630
 
703
631
  ws.on('CB:presence', handlePresenceUpdate)
704
632
  ws.on('CB:chatstate', handlePresenceUpdate)
@@ -723,7 +651,7 @@ export const makeChatsSocket = (config) => {
723
651
  }
724
652
  })
725
653
 
726
- // ─── Event listeners ──────────────────────────────────────────────────────────
654
+ // ─── Event listeners ──────────────────────────────────────────────────────
727
655
 
728
656
  ev.on('connection.update', ({ connection, receivedPendingNotifications }) => {
729
657
  if (connection === 'close') {
@@ -748,9 +676,7 @@ export const makeChatsSocket = (config) => {
748
676
  logger.info('Connection is now AwaitingInitialSync, buffering events')
749
677
  ev.buffer()
750
678
 
751
- const willSyncHistory = shouldSyncHistoryMessage(proto.Message.HistorySyncNotification.create({
752
- syncType: proto.HistorySync.HistorySyncType.RECENT
753
- }))
679
+ const willSyncHistory = shouldSyncHistoryMessage(proto.Message.HistorySyncNotification.create({ syncType: proto.HistorySync.HistorySyncType.RECENT }))
754
680
 
755
681
  if (!willSyncHistory) {
756
682
  logger.info('History sync is disabled by config, not waiting for notification. Transitioning to Online.')
@@ -759,7 +685,6 @@ export const makeChatsSocket = (config) => {
759
685
  return
760
686
  }
761
687
 
762
- // On reconnection the server won't push history sync again — skip the wait
763
688
  if (authState.creds.accountSyncCounter > 0) {
764
689
  logger.info('Reconnection with existing sync data, skipping history sync wait. Transitioning to Online.')
765
690
  syncState = SyncState.Online
@@ -780,13 +705,9 @@ export const makeChatsSocket = (config) => {
780
705
  }, 20_000)
781
706
  })
782
707
 
783
- // Re-sync collections that were parked due to a missing app state key
784
708
  ev.on('creds.update', ({ myAppStateKeyId }) => {
785
709
  if (!myAppStateKeyId || blockedCollections.size === 0) return
786
- if (syncState === SyncState.Syncing) {
787
- blockedCollections.clear()
788
- return
789
- }
710
+ if (syncState === SyncState.Syncing) { blockedCollections.clear(); return }
790
711
  const collections = [...blockedCollections]
791
712
  blockedCollections.clear()
792
713
  logger.info({ collections }, 'app state sync key arrived, re-syncing blocked collections')
@@ -801,10 +722,18 @@ export const makeChatsSocket = (config) => {
801
722
  }
802
723
  })
803
724
 
804
- // ─── Return ───────────────────────────────────────────────────────────────────
725
+ registerSocketEndHandler(() => {
726
+ if (awaitingSyncTimeout) { clearTimeout(awaitingSyncTimeout); awaitingSyncTimeout = undefined }
727
+ if (!config.placeholderResendCache && placeholderResendCache.close) placeholderResendCache.close()
728
+ syncState = SyncState.Connecting
729
+ privacySettings = undefined
730
+ })
731
+
732
+ // ─── Return ───────────────────────────────────────────────────────────────
805
733
 
806
734
  return {
807
735
  ...sock,
736
+ serverProps,
808
737
  createCallLink,
809
738
  getBotListV2,
810
739
  processingMutex,
@@ -842,9 +771,9 @@ export const makeChatsSocket = (config) => {
842
771
  cleanDirtyBits,
843
772
  addOrEditContact,
844
773
  removeContact,
774
+ placeholderResendCache,
845
775
  addLabel,
846
776
  onWhatsApp,
847
- checkStatusWA,
848
777
  addChatLabel,
849
778
  removeChatLabel,
850
779
  addMessageLabel,
@@ -854,6 +783,7 @@ export const makeChatsSocket = (config) => {
854
783
  removeQuickReply
855
784
  }
856
785
  }
786
+
857
787
  export const checkStatusWA = async (phoneNumber) => {
858
788
  if (!phoneNumber) throw new Error('Please provide a phone number')
859
789
 
@@ -872,40 +802,29 @@ export const checkStatusWA = async (phoneNumber) => {
872
802
 
873
803
  const state = {
874
804
  creds: initAuthCreds(),
875
- keys: {
876
- get: async () => ({}),
877
- set: async () => { },
878
- transaction: async (fn) => fn(),
879
- }
805
+ keys: { get: async () => ({}), set: async () => { }, transaction: async (fn) => fn() }
880
806
  }
881
807
 
882
- const build = (status, isBanned, isNeedOfficialWa, banInfo = null) => ({
883
- number: formattedNumber, status, isBanned, isNeedOfficialWa, banInfo
884
- })
808
+ const build = (status, isBanned, isNeedOfficialWa, banInfo = null) => ({ number: formattedNumber, status, isBanned, isNeedOfficialWa, banInfo })
885
809
 
886
810
  try {
887
- await mobileRegisterExists({
888
- ...state.creds,
889
- phoneNumberCountryCode: countryCode,
890
- phoneNumberNationalNumber: nationalNumber,
891
- })
811
+ await mobileRegisterExists({ ...state.creds, phoneNumberCountryCode: countryCode, phoneNumberNationalNumber: nationalNumber })
892
812
  return build('active', false, false)
893
813
  } catch (err) {
894
814
  if (err?.appeal_token) {
895
815
  const banDetails = await getBanDetails(err.appeal_token)
896
816
  const appealStatus = banDetails?.status || null
897
817
  const banType = appealStatus === 'BANNED' ? 'permanent' : 'temporary'
898
-
899
818
  return build('banned', true, false, {
900
819
  banType,
901
820
  violationType: err.violation_type || null,
902
821
  violationReason: err.violation_type ? `Type ${err.violation_type}` : 'Unknown',
903
- canAppeal: banType !== 'permanent', // ✅ temporary = can appeal, permanent = cannot
822
+ canAppeal: banType !== 'permanent',
904
823
  appealToken: err.appeal_token,
905
824
  banTime: banDetails?.ban_time || null,
906
825
  banDate: banDetails?.ban_time ? new Date(banDetails.ban_time * 1000).toISOString() : null,
907
826
  appealStatus,
908
- appealCreatedAt: banDetails?.appeal_creation_time ? new Date(banDetails.appeal_creation_time * 1000).toISOString() : null,
827
+ appealCreatedAt: banDetails?.appeal_creation_time ? new Date(banDetails.appeal_creation_time * 1000).toISOString() : null
909
828
  })
910
829
  }
911
830
  if (err?.custom_block_screen) return build('blocked', false, true)
@@ -913,4 +832,4 @@ export const checkStatusWA = async (phoneNumber) => {
913
832
  if (err?.reason === 'temporarily_unavailable') return build('rate_limited', false, false)
914
833
  return build('error', false, false)
915
834
  }
916
- }
835
+ }