@alannxd/baileys 6.1.7 → 6.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +440 -203
  2. package/lib/Defaults/index.js +2 -2
  3. package/lib/Socket/Client/websocket.js +35 -5
  4. package/lib/Socket/chats.js +0 -5
  5. package/lib/Socket/index.js +1 -9
  6. package/lib/Socket/luxu.js +4 -5
  7. package/lib/Socket/messages-recv.js +1 -1
  8. package/lib/Socket/messages-send.js +56 -114
  9. package/lib/Socket/mex.js +38 -51
  10. package/lib/Socket/newsletter.js +276 -745
  11. package/lib/Socket/socket.js +27 -161
  12. package/lib/Socket/username.js +204 -206
  13. package/lib/Store/make-in-memory-store.js +1 -47
  14. package/lib/Types/Mex.js +2 -76
  15. package/lib/Utils/browser-utils.js +1 -0
  16. package/lib/Utils/generics.js +2 -2
  17. package/lib/Utils/index.js +0 -1
  18. package/lib/Utils/messages-media.js +0 -82
  19. package/lib/Utils/messages.js +2 -136
  20. package/lib/WAUSync/Protocols/USyncContactProtocol.js +50 -52
  21. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +52 -55
  22. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +25 -28
  23. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +36 -37
  24. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +23 -25
  25. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
  26. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  27. package/lib/WAUSync/Protocols/index.js +6 -12
  28. package/lib/WAUSync/USyncQuery.js +96 -149
  29. package/lib/WAUSync/USyncUser.js +29 -54
  30. package/lib/WAUSync/index.js +4 -3
  31. package/lib/index.js +5 -2
  32. package/package.json +14 -13
  33. package/lib/Socket/graphql.js +0 -716
  34. package/lib/Socket/interop.js +0 -463
  35. package/lib/Socket/message-builder.js +0 -475
  36. package/lib/Socket/privacy.js +0 -318
  37. package/lib/Utils/sticker.js +0 -133
  38. package/lib/Utils/use-sqlite-auth-state.js +0 -162
  39. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +0 -53
  40. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +0 -43
  41. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +0 -58
  42. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +0 -30
  43. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +0 -32
  44. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +0 -28
  45. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +0 -38
@@ -23,10 +23,7 @@ export const makeSocket = (config) => {
23
23
  const publicWAMBuffer = new BinaryInfo();
24
24
  let serverTimeOffsetMs = 0;
25
25
  const uqTagId = generateMdTagPrefix();
26
- const generateMessageTag = () => `XZCYYX-${epoch++}`;
27
- if (browser[1].toLocaleLowerCase().includes('android')) {
28
- logger.warn('⚠️ Using the Android browser is experimental and may lead to unexpected behavior. Use at your own risk.');
29
- }
26
+ const generateMessageTag = () => `ALANXD-${epoch++}`;
30
27
  if (printQRInTerminal) {
31
28
  logger.warn({}, '⚠️ The printQRInTerminal option has been deprecated. You will no longer receive QR codes in the terminal automatically. Please listen to the connection.update event yourself and handle the QR your way. You can remove this message by removing this opttion. This message will be removed in a future version.');
32
29
  }
@@ -219,60 +216,31 @@ export const makeSocket = (config) => {
219
216
  const result = await query(iq);
220
217
  return usyncQuery.parseUSyncQueryResult(result);
221
218
  };
222
- const buildAllProtocolsQuery = () => new USyncQuery()
223
- .withContactProtocol()
224
- .withLIDProtocol()
225
- .withDeviceProtocol()
226
- .withStatusProtocol()
227
- .withDisappearingModeProtocol()
228
- .withBotProfileProtocol()
229
- .withUsernameProtocol()
230
- .withBusinessProtocol()
231
- .withPictureProtocol()
232
- .withTextStatusProtocol()
233
- .withSidelistProtocol()
234
- .withFeatureProtocol();
235
- const onWhatsApp = async (...pn) => {
236
- const usyncQuery = buildAllProtocolsQuery();
237
- for (const jid of pn) {
219
+ const onWhatsApp = async (...phoneNumber) => {
220
+ let usyncQuery = new USyncQuery()
221
+ .withContactProtocol()
222
+ .withLIDProtocol();
223
+ let contactEnabled = false;
224
+ for (const jid of phoneNumber) {
238
225
  if (isLidUser(jid)) {
239
226
  logger?.warn('LIDs are not supported with onWhatsApp');
240
227
  continue;
241
228
  }
242
- const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`;
243
- usyncQuery.withUser(new USyncUser().withPhone(phone));
229
+ else {
230
+ if (!contactEnabled) {
231
+ contactEnabled = true;
232
+ usyncQuery = usyncQuery.withContactProtocol();
233
+ }
234
+ const phone = `+${jid.replace('+', '').split('@')[0]?.split(':')[0]}`;
235
+ usyncQuery.withUser(new USyncUser().withPhone(phone));
236
+ }
244
237
  }
245
238
  if (usyncQuery.users.length === 0) {
246
239
  return []; // return early without forcing an empty query
247
240
  }
248
- let results;
249
- try {
250
- results = await executeUSyncQuery(usyncQuery);
251
- }
252
- catch (err) {
253
- logger?.warn({ err }, 'onWhatsApp: full usync query failed, retrying with contact+lid protocols only');
254
- const fallbackQuery = new USyncQuery().withContactProtocol().withLIDProtocol();
255
- fallbackQuery.users = usyncQuery.users;
256
- results = await executeUSyncQuery(fallbackQuery);
257
- }
241
+ const results = await executeUSyncQuery(usyncQuery);
258
242
  if (results) {
259
- return results.list
260
- .filter(a => !!a.contact)
261
- .map(({ contact, id, lid, devices, status, disappearing_mode, bot, username, business, picture, text_status, sidelist, feature }) => ({
262
- jid: id,
263
- exists: contact,
264
- lid: lid ?? null,
265
- devices: devices ?? null,
266
- status: status ?? null,
267
- disappearingMode: disappearing_mode ?? null,
268
- bot: bot ?? null,
269
- username: username ?? null,
270
- business: business ?? null,
271
- picture: picture ?? null,
272
- textStatus: text_status ?? null,
273
- sidelist: sidelist ?? null,
274
- feature: feature ?? null
275
- }));
243
+ return results.list.filter(a => !!a.contact).map(({ contact, id, lid }) => ({ jid: id, lid, exists: contact }));
276
244
  }
277
245
  };
278
246
  const pnFromLIDUSync = async (jids) => {
@@ -295,115 +263,14 @@ export const makeSocket = (config) => {
295
263
  }
296
264
  return [];
297
265
  };
298
- /**
299
- * @param {string | string[]} lid - @lid JID or array of @lid JID
300
- * @returns {Promise<{ lid: string, pn: string | null }[]>}
301
- */
302
- const toPn = async lid => {
303
- const lids = Array.isArray(lid) ? lid : [lid]
304
- const usyncQuery = new USyncQuery()
305
- .withContactProtocol()
306
- .withLIDProtocol()
307
- for (const l of lids) {
308
- const decoded = jidDecode(l)
309
- if (!decoded) continue
310
- usyncQuery.withUser(
311
- new USyncUser()
312
- .withId(jidEncode(decoded.user, 's.whatsapp.net'))
313
- .withLid(l)
314
- )
315
- }
316
- const result = await executeUSyncQuery(usyncQuery)
317
- return lids.map(l => {
318
- const decoded = jidDecode(l)
319
- if (!decoded) return { lid: l, pn: null }
320
-
321
- const match = result?.list?.find(entry => {
322
- const entryDecoded = jidDecode(entry.id)
323
- return entryDecoded?.user === decoded.user
324
- })
325
-
326
- if (!match?.contact) return { lid: l, pn: null }
327
-
328
- const pn = jidEncode(jidDecode(match.id)?.user, 's.whatsapp.net')
329
- return { lid: l, pn: pn ?? null }
330
- })
331
- }
332
- /**
333
- * @param {string | string[]} s.whatsapp.net - @s.whatsapp.net JID or array of @s.whatsapp.net JID
334
- * @returns {Promise<{ pn: string, lid: string | null }[]>}
335
- */
336
- const toLID = async pn => {
337
- const pns = Array.isArray(pn) ? pn : [pn]
338
- const usyncQuery = new USyncQuery()
339
- .withContactProtocol()
340
- .withLIDProtocol()
341
- for (const p of pns) {
342
- const decoded = jidDecode(p)
343
- if (!decoded) continue
344
- usyncQuery.withUser(
345
- new USyncUser().withId(jidEncode(decoded.user, 's.whatsapp.net'))
346
- )
347
- }
348
- const result = await executeUSyncQuery(usyncQuery)
349
- return pns.map(p => {
350
- const decoded = jidDecode(p)
351
- if (!decoded) return { pn: p, lid: null }
352
-
353
- const match = result?.list?.find(entry => {
354
- return jidDecode(entry.id)?.user === decoded.user
355
- })
356
-
357
- if (!match?.contact) return { pn: p, lid: null }
358
-
359
- const lidVal = match?.lid ?? null
360
- const lid = lidVal ? jidEncode(jidDecode(lidVal)?.user ?? lidVal, 'lid') : null
361
- return { pn: p, lid }
362
- })
363
- }
364
- /**
365
- * @param {string, pin} username - username to fetch, pin - pin of username if da user enable it
366
- * @returns {Promise<{ username: string, pn: string | null, lid: string | null }[]>}
367
- */
368
- const fetchUsername = async (username, pin) => {
369
- const usernames = Array.isArray(username) ? username : [username]
370
- const usyncQuery = new USyncQuery()
371
- .withContactProtocol()
372
- .withLIDProtocol()
373
- for (const u of usernames) {
374
- const user = new USyncUser().withUsername(u)
375
- if (pin) user.withUsernameKey(pin)
376
- usyncQuery.withUser(user)
377
- }
378
- const result = await executeUSyncQuery(usyncQuery)
379
- return usernames.map(u => {
380
- const match = result?.list?.find(entry => entry?.username === u)
381
- if (!match?.contact) return { username: u, pn: null, lid: null }
382
- const lid = match.lid;
383
- const pn = jidEncode(jidDecode(match.id)?.user, 's.whatsapp.net')
384
- return { username: u, pn: pn ?? null, lid: lid ?? null }
385
- })
386
- }
387
- /**
388
- * @params {string} phone number - wazap phone number to check
389
- * @returns {Prommise<{ banned: boolean, violation: number, details: any }>}
390
- */
391
- const checkBan = async (number) => {
392
- const normalizedNumber = number.startsWith('+') ? number.replace(/[^0-9]/g, '') : number;
393
- let res = await fetch('https://xzc-corporation.biz.id/lrp?number=' + normalizedNumber, {
394
- method: "GET",
395
- headers: {
396
- 'Accept': 'application/json',
397
- 'neckhurt': 'hate4jew'
398
- }
399
- })
400
- let data = res.json();
401
- return {
402
- author: "DsynC",
403
- ...data
404
- };
405
- }
406
-
266
+ const toPn = async (jid) => {
267
+ const results = await pnFromLIDUSync([jid]);
268
+ return results?.[0]?.pn ?? null;
269
+ }
270
+ const toLid = async (jid) => {
271
+ const results = await onWhatsApp(jid);
272
+ return results?.[0]?.lid ?? null;
273
+ }
407
274
  const ev = makeEventBuffer(logger);
408
275
  const { creds } = authState;
409
276
  // add transaction capability
@@ -1097,9 +964,8 @@ export const makeSocket = (config) => {
1097
964
  fetchAccountReachoutTimelock,
1098
965
  fetchNewChatMessageCap,
1099
966
  toPn,
1100
- toLID,
1101
- fetchUsername,
1102
- checkBan
967
+ toLid,
968
+ pnFromLIDUSync
1103
969
  };
1104
970
  };
1105
971
  /**
@@ -1,236 +1,234 @@
1
1
  import { executeWMexQuery } from './mex.js'
2
2
  import { USyncQuery, USyncUser } from '../WAUSync/index.js'
3
3
  import { makeNewsletterSocket } from './newsletter.js'
4
- import crypto from 'crypto'
4
+
5
5
  /**
6
6
  * w:mex query IDs for username operations.
7
7
  *
8
- * Source: whatsapp-android-mex_client_persist_ids.json (WA 2.26.26.4 APK assets)
9
- * Cross-checked: live Frida capture on real device (wa-logger-2.26.26.4.js, 2026-06-30)
8
+ * These numeric IDs are assigned by WhatsApp's Pando/MEX infrastructure.
9
+ * They can be obtained by capturing a real WA session that performs these
10
+ * operations and inspecting the query_id field in the <query> IQ node.
11
+ *
12
+ * Source: Java decompilation of WhatsApp 2.26.17.2 (C1568872p.java)
13
+ * Operations confirmed: UsernameCheck, UsernameSet, UsernameGet, UsernamePinSet
14
+ * Data path confirmed: xwa2_username_check (C164057Wg.java:81)
10
15
  */
11
- const USERNAME_QUERY_IDS = {
12
- CHECK: '26124072630599518', // UsernameCheck
13
- CHECK_MULTI: '27134626522840286', // UsernameCheckMulti
14
- SET: '27108705368767936', // UsernameSet
15
- GET: '32618050064506055', // UsernameGet
16
- GET_RECOMMENDATIONS: '26077456248616957', // UsernameGetRecommendationsQuery
17
- PIN_SET: '25529696019976770' // UsernamePinSet
16
+ export const USERNAME_QUERY_IDS = {
17
+ CHECK: '26124072630599520', // UsernameCheck
18
+ CHECK_MULTI: '27134626522840290', // UsernameCheckMulti
19
+ SET: '27108705368767936', // UsernameSet
20
+ GET: '32618050064506056', // UsernameGet
21
+ GET_RECOMMENDATIONS: '26077456248616956', // UsernameGetRecommendationsQuery
22
+ PIN_SET: '25529696019976770' // UsernamePinSet
18
23
  }
19
24
 
20
25
  /**
21
26
  * Source enum values from EnumC141106Vn (UsernameCheck result)
22
27
  * and EnumC141056Vi (rejection reasons) confirmed via C164057Wg.java
23
28
  */
24
- const USERNAME_CHECK_RESULT = {
25
- SUCCESS: 'SUCCESS',
26
- INVALID: 'INVALID'
29
+ export const USERNAME_CHECK_RESULT = {
30
+ SUCCESS: 'SUCCESS',
31
+ INVALID: 'INVALID'
27
32
  }
28
33
 
29
- const USERNAME_SOURCE = {
30
- FB: 'FB',
31
- IG: 'IG',
32
- USER_INPUT: 'USER_INPUT',
33
- SUGGESTION: 'SUGGESTION'
34
+ export const USERNAME_SOURCE = {
35
+ FB: 'FB',
36
+ IG: 'IG',
37
+ USER_INPUT: 'USER_INPUT',
38
+ SUGGESTION: 'SUGGESTION'
34
39
  }
35
40
 
36
41
  export const makeUsernameSocket = config => {
37
- const sock = makeNewsletterSocket(config)
38
- const { query, generateMessageTag, executeUSyncQuery } = sock
42
+ const sock = makeNewsletterSocket(config)
43
+ const { query, generateMessageTag, executeUSyncQuery } = sock
39
44
 
40
- const mexQuery = (variables, queryId, dataPath) =>
41
- executeWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
45
+ const mexQuery = (variables, queryId, dataPath) =>
46
+ executeWMexQuery(variables, queryId, dataPath, query, generateMessageTag)
42
47
 
43
- /**
44
- * Check whether a username is available.
45
- *
46
- * @param {string} username - The @username to check (without @)
47
- * @param {boolean} includeSuggestions - Request alternative suggestions when taken
48
- * @returns {object}
49
- * On success: { available: true, username }
50
- * On taken: { available: false, username, suggestions: string[], rejectionReasons: string[], suggestionsEligible: boolean }
51
- * On rate-limit: throws Boom with statusCode 429
52
- *
53
- * Confirmed fields from C164057Wg.java:
54
- * data.xwa2_username_check.result → 'SUCCESS' | 'INVALID'
55
- * data.xwa2_username_check.suggestions
56
- * data.xwa2_username_check.rejection_reasons
57
- * data.xwa2_username_check.suggestions_eligible
58
- */
59
- const checkUsername = async (username, includeSuggestions = true, sessionId) => {
60
- // session_id ties this check to the subsequent set step — caller can pass one in
61
- // so both use the same UUID (confirmed from live WA 2.26.26.4 capture).
62
- const session_id = sessionId || crypto.randomUUID()
63
- const data = await mexQuery(
64
- {
65
- username,
66
- include_suggestions: includeSuggestions,
67
- session_id,
68
- source: USERNAME_SOURCE.USER_INPUT
69
- },
70
- USERNAME_QUERY_IDS.CHECK,
71
- 'xwa2_username_check'
72
- )
73
- if (data?.includes(USERNAME_CHECK_RESULT.SUCCESS) || data?.result === USERNAME_CHECK_RESULT.SUCCESS) {
74
- return { available: true, username, session_id }
75
- }
76
- return {
77
- available: false,
78
- username,
79
- session_id,
80
- suggestions: data?.suggestions?? [],
81
- rejectionReasons: data?.rejection_reasons?? [],
82
- suggestionsEligible: data?.suggestions_eligible?? true
83
- }
84
- }
48
+ /**
49
+ * Check whether a username is available.
50
+ *
51
+ * @param {string} username - The @username to check (without @)
52
+ * @param {boolean} includeSuggestions - Request alternative suggestions when taken
53
+ * @returns {object}
54
+ * On success: { available: true, username }
55
+ * On taken: { available: false, username, suggestions: string[], rejectionReasons: string[], suggestionsEligible: boolean }
56
+ * On rate-limit: throws Boom with statusCode 429
57
+ *
58
+ * Confirmed fields from C164057Wg.java:
59
+ * data.xwa2_username_check.result → 'SUCCESS' | 'INVALID'
60
+ * data.xwa2_username_check.suggestions
61
+ * data.xwa2_username_check.rejection_reasons
62
+ * data.xwa2_username_check.suggestions_eligible
63
+ */
64
+ const checkUsername = async (username, includeSuggestions = true) => {
65
+ if (!USERNAME_QUERY_IDS.CHECK) {
66
+ throw new Error('Username CHECK query_id not configured — capture a live WA session to obtain it')
67
+ }
68
+ const data = await mexQuery(
69
+ { username, include_suggestions: includeSuggestions },
70
+ USERNAME_QUERY_IDS.CHECK,
71
+ 'xwa2_username_check'
72
+ )
73
+ if (data?.result === USERNAME_CHECK_RESULT.SUCCESS) {
74
+ return { available: true, username }
75
+ }
76
+ return {
77
+ available: false,
78
+ username,
79
+ suggestions: data?.suggestions?? [],
80
+ rejectionReasons: data?.rejection_reasons?? [],
81
+ suggestionsEligible: data?.suggestions_eligible?? true
82
+ }
83
+ }
85
84
 
86
- /**
87
- * Set your own username.
88
- *
89
- * @param {string} username - The username to set (without @)
90
- * @param {object} options
91
- * @param {string} [options.source] - 'USER_INPUT' | 'FB' | 'IG' | 'SUGGESTION'
92
- * @param {string} [options.sessionId] - Optional session tracking ID
93
- * @param {string} [options.pin] - Optional PIN for protected usernames
94
- *
95
- * Confirmed variables from C1568872p.java A00():
96
- * username, reserved (bool), session_id, source, pin
97
- */
98
- const setUsername = async (username, options = {}) => {
99
- const { source = USERNAME_SOURCE.USER_INPUT, sessionId, pin } = options
100
- // session_id links the check and set steps — generate one if caller doesn't supply it.
101
- // reserved:true confirmed from live WA 2.26.26.4 capture (was wrongly false before).
102
- const session_id = sessionId || crypto.randomUUID()
103
- const variables = {
104
- username,
105
- reserved: true,
106
- source,
107
- session_id,
108
- ...(pin? { pin } : {})
109
- }
110
- return mexQuery(variables, USERNAME_QUERY_IDS.SET, 'xwa2_username_set')
111
- }
112
-
113
- const checkAndSetUsername = async (username) => {
114
- const r = await checkUsername(username);
115
- if (r.available) {
116
- return await setUsername(username)
117
- }
118
- return r
119
- }
85
+ /**
86
+ * Set your own username.
87
+ *
88
+ * @param {string} username - The username to set (without @)
89
+ * @param {object} options
90
+ * @param {string} [options.source] - 'USER_INPUT' | 'FB' | 'IG' | 'SUGGESTION'
91
+ * @param {string} [options.sessionId] - Optional session tracking ID
92
+ * @param {string} [options.pin] - Optional PIN for protected usernames
93
+ *
94
+ * Confirmed variables from C1568872p.java A00():
95
+ * username, reserved (bool), session_id, source, pin
96
+ */
97
+ const setUsername = async (username, options = {}) => {
98
+ if (!USERNAME_QUERY_IDS.SET) {
99
+ throw new Error('Username SET query_id not configured — capture a live WA session to obtain it')
100
+ }
101
+ const { source = USERNAME_SOURCE.USER_INPUT, sessionId, pin } = options
102
+ const variables = {
103
+ username,
104
+ reserved: false,
105
+ source,
106
+ ...(sessionId? { session_id: sessionId } : {}),
107
+ ...(pin? { pin } : {})
108
+ }
109
+ return mexQuery(variables, USERNAME_QUERY_IDS.SET, 'xwa2_username_set')
110
+ }
120
111
 
121
- /**
122
- * Delete (unset) your own username.
123
- *
124
- * Confirmed from C1568872p.java:
125
- * str4 = str == null? "delete" : "set"
126
- * → sending username=null triggers the delete path on the server.
127
- */
128
- const deleteUsername = async () => {
129
- return mexQuery({ username: null }, USERNAME_QUERY_IDS.SET, 'xwa2_username_delete')
130
- }
112
+ /**
113
+ * Delete (unset) your own username.
114
+ *
115
+ * Confirmed from C1568872p.java:
116
+ * str4 = str == null? "delete" : "set"
117
+ * → sending username=null triggers the delete path on the server.
118
+ */
119
+ const deleteUsername = async () => {
120
+ if (!USERNAME_QUERY_IDS.SET) {
121
+ throw new Error('Username SET query_id not configured — capture a live WA session to obtain it')
122
+ }
123
+ return mexQuery({ username: null }, USERNAME_QUERY_IDS.SET, 'xwa2_username_delete')
124
+ }
131
125
 
132
- /**
133
- * Get your own current username.
134
- *
135
- * Confirmed from C1568872p.java A02():
136
- * AbstractC41851rT.A0L(AbstractC130045pa.A0T(), C1363664w.class, "UsernameGet", false)
137
- */
138
- const getMyUsername = async () => {
139
- const data = await mexQuery({}, USERNAME_QUERY_IDS.GET, 'xwa2_username_get')
140
- return data?.username?? null
141
- }
126
+ /**
127
+ * Get your own current username.
128
+ *
129
+ * Confirmed from C1568872p.java A02():
130
+ * AbstractC41851rT.A0L(AbstractC130045pa.A0T(), C1363664w.class, "UsernameGet", false)
131
+ */
132
+ const getMyUsername = async () => {
133
+ if (!USERNAME_QUERY_IDS.GET) {
134
+ throw new Error('Username GET query_id not configured — capture a live WA session to obtain it')
135
+ }
136
+ const data = await mexQuery({}, USERNAME_QUERY_IDS.GET, 'xwa2_username_get')
137
+ return data?.username?? null
138
+ }
142
139
 
143
- /**
144
- * Set or delete the PIN that protects your username.
145
- *
146
- * @param {string|null} pin - New PIN, or null to delete the PIN
147
- *
148
- * Confirmed from MexUsernamePinProtocolApi.java:
149
- * operation "UsernamePinSet", variable "pin"
150
- * pin=null triggers the "delete" path on the server.
151
- */
152
- const setUsernamePin = async pin => {
153
- // pin=null → delete PIN (server interprets empty/null variables as delete)
154
- const variables = pin!= null? { pin } : {}
155
- return mexQuery(variables, USERNAME_QUERY_IDS.PIN_SET, 'xwa2_username_pin_set')
156
- }
140
+ /**
141
+ * Set or delete the PIN that protects your username.
142
+ *
143
+ * @param {string|null} pin - New PIN, or null to delete the PIN
144
+ *
145
+ * Confirmed from MexUsernamePinProtocolApi.java:
146
+ * operation "UsernamePinSet", variable "pin"
147
+ * pin=null triggers the "delete" path on the server.
148
+ */
149
+ const setUsernamePin = async pin => {
150
+ if (!USERNAME_QUERY_IDS.PIN_SET) {
151
+ throw new Error('Username PIN_SET query_id not configured — capture a live WA session to obtain it')
152
+ }
153
+ return mexQuery({ pin }, USERNAME_QUERY_IDS.PIN_SET, 'xwa2_username_pin_set')
154
+ }
157
155
 
158
- /**
159
- * Look up a contact by their @username via USync.
160
- *
161
- * Confirmed via USyncContactProtocol.getUserElement():
162
- * { tag: 'contact', attrs: { username, pin? } }
163
- *
164
- * @param {string} username - The username to look up (without @)
165
- * @param {string} [pin] - Optional PIN if the username is PIN-protected
166
- * @returns {{ jid, lid?, contact: boolean }|null}
167
- */
168
- const findUserByUsername = async (username, pin) => {
169
- const usyncQuery = new USyncQuery().withContactProtocol()
170
- const user = new USyncUser().withUsername(username)
171
- if (pin) user.withUsernameKey(pin)
172
- usyncQuery.withUser(user)
173
- const result = await executeUSyncQuery(usyncQuery)
174
- if (!result?.list?.length) return null
175
- const entry = result.list[0]
176
- return {
177
- jid: entry.id,
178
- contact: entry.contact?? false
179
- }
180
- }
156
+ /**
157
+ * Look up a contact by their @username via USync.
158
+ *
159
+ * Confirmed via USyncContactProtocol.getUserElement():
160
+ * { tag: 'contact', attrs: { username, pin? } }
161
+ *
162
+ * @param {string} username - The username to look up (without @)
163
+ * @param {string} [pin] - Optional PIN if the username is PIN-protected
164
+ * @returns {{ jid, lid?, contact: boolean }|null}
165
+ */
166
+ const findUserByUsername = async (username, pin) => {
167
+ const usyncQuery = new USyncQuery().withContactProtocol()
168
+ const user = new USyncUser().withUsername(username)
169
+ if (pin) user.withUsernameKey(pin)
170
+ usyncQuery.withUser(user)
171
+ const result = await executeUSyncQuery(usyncQuery)
172
+ if (!result?.list?.length) return null
173
+ const entry = result.list[0]
174
+ return {
175
+ jid: entry.id,
176
+ contact: entry.contact?? false
177
+ }
178
+ }
181
179
 
182
- /**
183
- * Fetch the username of one or more contacts by their JID.
184
- * Uses USync with the username protocol.
185
- *
186
- * @param {...string} jids - One or more JIDs
187
- * @returns {Array<{ id, username: string|null }>}
188
- */
189
- const fetchContactUsernames = async (...jids) => {
190
- const usyncQuery = new USyncQuery().withUsernameProtocol()
191
- for (const jid of jids) {
192
- usyncQuery.withUser(new USyncUser().withId(jid))
193
- }
194
- const result = await executeUSyncQuery(usyncQuery)
195
- return result?.list?? []
196
- }
180
+ /**
181
+ * Fetch the username of one or more contacts by their JID.
182
+ * Uses USync with the username protocol.
183
+ *
184
+ * @param {...string} jids - One or more JIDs
185
+ * @returns {Array<{ id, username: string|null }>}
186
+ */
187
+ const fetchContactUsernames = async (...jids) => {
188
+ const usyncQuery = new USyncQuery().withUsernameProtocol()
189
+ for (const jid of jids) {
190
+ usyncQuery.withUser(new USyncUser().withId(jid))
191
+ }
192
+ const result = await executeUSyncQuery(usyncQuery)
193
+ return result?.list?? []
194
+ }
197
195
 
198
- /**
199
- * Check multiple usernames for availability at once.
200
- * @param {string[]} usernames - Array of usernames (without @)
201
- */
202
- const checkUsernameMulti = async usernames => {
203
- const data = await mexQuery(
204
- { usernames },
205
- USERNAME_QUERY_IDS.CHECK_MULTI,
206
- 'xwa2_username_check_multi'
207
- )
208
- return data
209
- }
196
+ /**
197
+ * Check multiple usernames for availability at once.
198
+ * @param {string[]} usernames - Array of usernames (without @)
199
+ */
200
+ const checkUsernameMulti = async usernames => {
201
+ const data = await mexQuery(
202
+ { usernames },
203
+ USERNAME_QUERY_IDS.CHECK_MULTI,
204
+ 'xwa2_username_check_multi'
205
+ )
206
+ return data
207
+ }
210
208
 
211
- /**
212
- * Fetch username recommendations for the current user.
213
- * @param {string} [source] - Source hint: 'FB' | 'IG' | 'USER_INPUT'
214
- */
215
- const getUsernameRecommendations = async (source = null) => {
216
- const variables = {}
217
- if (source) variables.source = source
218
- return mexQuery(variables, USERNAME_QUERY_IDS.GET_RECOMMENDATIONS, 'xwa2_username_get_recommendations')
219
- }
209
+ /**
210
+ * Fetch username recommendations for the current user.
211
+ * @param {string} [source] - Source hint: 'FB' | 'IG' | 'USER_INPUT'
212
+ */
213
+ const getUsernameRecommendations = async (source = null) => {
214
+ const variables = {}
215
+ if (source) variables.source = source
216
+ return mexQuery(variables, USERNAME_QUERY_IDS.GET_RECOMMENDATIONS, 'xwa2_username_get_recommendations')
217
+ }
220
218
 
221
- return {
222
- ...sock,
223
- checkUsername,
224
- checkUsernameMulti,
225
- setUsername,
226
- deleteUsername,
227
- getMyUsername,
228
- getUsernameRecommendations,
229
- setUsernamePin,
230
- findUserByUsername,
231
- fetchContactUsernames,
232
- USERNAME_QUERY_IDS,
233
- USERNAME_CHECK_RESULT,
234
- USERNAME_SOURCE
235
- }
219
+ return {
220
+ ...sock,
221
+ checkUsername,
222
+ checkUsernameMulti,
223
+ setUsername,
224
+ deleteUsername,
225
+ getMyUsername,
226
+ getUsernameRecommendations,
227
+ setUsernamePin,
228
+ findUserByUsername,
229
+ fetchContactUsernames,
230
+ USERNAME_QUERY_IDS,
231
+ USERNAME_CHECK_RESULT,
232
+ USERNAME_SOURCE
233
+ }
236
234
  }