@pney/whatsapp-web 1.34.6-3 → 1.34.7-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.
Files changed (59) hide show
  1. package/.env.example +0 -1
  2. package/.gitattributes +4 -0
  3. package/.husky/commit-msg +4 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.lintstagedrc.json +6 -0
  6. package/.prettierignore +8 -0
  7. package/.prettierrc.json +10 -0
  8. package/README.md +83 -80
  9. package/commitlint.config.js +29 -0
  10. package/eslint.config.mjs +67 -0
  11. package/example.js +151 -71
  12. package/index.d.ts +982 -734
  13. package/index.js +4 -4
  14. package/package.json +1 -1
  15. package/shell.js +4 -4
  16. package/src/Client.js +1860 -921
  17. package/src/authStrategies/BaseAuthStrategy.js +4 -2
  18. package/src/authStrategies/LocalAuth.js +25 -12
  19. package/src/authStrategies/NoAuth.js +3 -4
  20. package/src/authStrategies/RemoteAuth.js +92 -43
  21. package/src/factories/ChatFactory.js +1 -1
  22. package/src/factories/ContactFactory.js +2 -2
  23. package/src/structures/Base.js +5 -3
  24. package/src/structures/Broadcast.js +1 -2
  25. package/src/structures/BusinessContact.js +1 -2
  26. package/src/structures/Buttons.js +14 -10
  27. package/src/structures/Call.js +10 -6
  28. package/src/structures/Channel.js +171 -91
  29. package/src/structures/Chat.js +57 -41
  30. package/src/structures/ClientInfo.js +1 -1
  31. package/src/structures/Contact.js +37 -16
  32. package/src/structures/GroupChat.js +425 -228
  33. package/src/structures/GroupNotification.js +21 -12
  34. package/src/structures/Label.js +6 -6
  35. package/src/structures/List.js +22 -14
  36. package/src/structures/Location.js +5 -4
  37. package/src/structures/Message.js +412 -160
  38. package/src/structures/MessageMedia.js +31 -18
  39. package/src/structures/Order.js +4 -4
  40. package/src/structures/Payment.js +6 -3
  41. package/src/structures/Poll.js +2 -2
  42. package/src/structures/PollVote.js +9 -6
  43. package/src/structures/PrivateChat.js +2 -4
  44. package/src/structures/PrivateContact.js +2 -4
  45. package/src/structures/Product.js +1 -1
  46. package/src/structures/ProductMetadata.js +1 -2
  47. package/src/structures/Reaction.js +2 -4
  48. package/src/structures/ScheduledEvent.js +22 -10
  49. package/src/util/Constants.js +8 -6
  50. package/src/util/Injected/AuthStore/AuthStore.js +7 -3
  51. package/src/util/Injected/Utils.js +753 -345
  52. package/src/util/InterfaceController.js +72 -25
  53. package/src/util/Puppeteer.js +1 -1
  54. package/src/util/Util.js +28 -15
  55. package/src/webCache/LocalWebCache.js +7 -5
  56. package/src/webCache/RemoteWebCache.js +10 -4
  57. package/src/webCache/WebCache.js +8 -5
  58. package/src/webCache/WebCacheFactory.js +9 -9
  59. package/CODE_OF_CONDUCT.md +0 -133
package/src/Client.js CHANGED
@@ -5,15 +5,37 @@ const puppeteer = require('puppeteer');
5
5
 
6
6
  const Util = require('./util/Util');
7
7
  const InterfaceController = require('./util/InterfaceController');
8
- const { WhatsWebURL, DefaultOptions, Events, WAState, MessageTypes } = require('./util/Constants');
8
+ const {
9
+ WhatsWebURL,
10
+ DefaultOptions,
11
+ Events,
12
+ WAState,
13
+ MessageTypes,
14
+ } = require('./util/Constants');
9
15
  const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore');
10
16
  const { LoadUtils } = require('./util/Injected/Utils');
11
17
  const ChatFactory = require('./factories/ChatFactory');
12
18
  const ContactFactory = require('./factories/ContactFactory');
13
19
  const WebCacheFactory = require('./webCache/WebCacheFactory');
14
- const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction, Broadcast, ScheduledEvent } = require('./structures');
20
+ const {
21
+ ClientInfo,
22
+ Message,
23
+ MessageMedia,
24
+ Contact,
25
+ Location,
26
+ Poll,
27
+ PollVote,
28
+ GroupNotification,
29
+ Label,
30
+ Call,
31
+ Buttons,
32
+ List,
33
+ Reaction,
34
+ Broadcast,
35
+ ScheduledEvent,
36
+ } = require('./structures');
15
37
  const NoAuth = require('./authStrategies/NoAuth');
16
- const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
38
+ const { exposeFunctionIfAbsent } = require('./util/Puppeteer');
17
39
 
18
40
  /**
19
41
  * Starting point for interacting with the WhatsApp Web API
@@ -29,12 +51,12 @@ const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
29
51
  * @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser
30
52
  * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session
31
53
  * @param {string} options.userAgent - User agent to use in puppeteer
32
- * @param {string} options.ffmpegPath - Ffmpeg path to use when formatting videos to webp while sending stickers
54
+ * @param {string} options.ffmpegPath - Ffmpeg path to use when formatting videos to webp while sending stickers
33
55
  * @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy.
34
56
  * @param {string} options.deviceName - Sets the device name of a current linked device., i.e.: 'TEST'.
35
57
  * @param {string} options.browserName - Sets the browser name of a current linked device, i.e.: 'Firefox'.
36
58
  * @param {object} options.proxyAuthentication - Proxy Authentication object.
37
- *
59
+ *
38
60
  * @fires Client#qr
39
61
  * @fires Client#authenticated
40
62
  * @fires Client#auth_failure
@@ -62,8 +84,8 @@ class Client extends EventEmitter {
62
84
  super();
63
85
 
64
86
  this.options = Util.mergeDefault(DefaultOptions, options);
65
-
66
- if(!this.options.authStrategy) {
87
+
88
+ if (!this.options.authStrategy) {
67
89
  this.authStrategy = new NoAuth();
68
90
  } else {
69
91
  this.authStrategy = this.options.authStrategy;
@@ -85,26 +107,37 @@ class Client extends EventEmitter {
85
107
 
86
108
  Util.setFfmpegPath(this.options.ffmpegPath);
87
109
  }
110
+
88
111
  /**
89
112
  * Injection logic
90
113
  * Private function
91
114
  */
92
115
  async inject() {
93
- if(this.options.authTimeoutMs === undefined || this.options.authTimeoutMs==0){
116
+ if (
117
+ this.options.authTimeoutMs === undefined ||
118
+ this.options.authTimeoutMs == 0
119
+ ) {
94
120
  this.options.authTimeoutMs = 30000;
95
121
  }
96
122
  let start = Date.now();
97
123
  let timeout = this.options.authTimeoutMs;
98
124
  let res = false;
99
- while(start > (Date.now() - timeout)){
100
- res = await this.pupPage.evaluate('window.Debug?.VERSION != undefined');
101
- if(res){break;}
102
- await new Promise(r => setTimeout(r, 200));
125
+ while (start > Date.now() - timeout) {
126
+ res = await this.pupPage.evaluate(
127
+ 'window.Debug?.VERSION != undefined',
128
+ );
129
+ if (res) {
130
+ break;
131
+ }
132
+ await new Promise((r) => setTimeout(r, 200));
103
133
  }
104
- if(!res){
134
+ if (!res) {
105
135
  throw 'auth timeout';
106
- }
107
- await this.setDeviceName(this.options.deviceName, this.options.browserName);
136
+ }
137
+ await this.setDeviceName(
138
+ this.options.deviceName,
139
+ this.options.browserName,
140
+ );
108
141
  const pairWithPhoneNumber = this.options.pairWithPhoneNumber;
109
142
  const version = await this.getWWebVersion();
110
143
 
@@ -113,25 +146,44 @@ class Client extends EventEmitter {
113
146
  const needAuthentication = await this.pupPage.evaluate(async () => {
114
147
  let state = window.require('WAWebSocketModel').Socket.state;
115
148
 
116
- if (state === 'OPENING' || state === 'UNLAUNCHED' || state === 'PAIRING') {
149
+ if (
150
+ state === 'OPENING' ||
151
+ state === 'UNLAUNCHED' ||
152
+ state === 'PAIRING'
153
+ ) {
117
154
  // wait till state changes
118
- await new Promise(r => {
119
- window.require('WAWebSocketModel').Socket.on('change:state', function waitTillInit(_AppState, state) {
120
- if (state !== 'OPENING' && state !== 'UNLAUNCHED' && state !== 'PAIRING') {
121
- window.require('WAWebSocketModel').Socket.off('change:state', waitTillInit);
122
- r();
123
- }
124
- });
125
- });
155
+ await new Promise((r) => {
156
+ window
157
+ .require('WAWebSocketModel')
158
+ .Socket.on(
159
+ 'change:state',
160
+ function waitTillInit(_AppState, state) {
161
+ if (
162
+ state !== 'OPENING' &&
163
+ state !== 'UNLAUNCHED' &&
164
+ state !== 'PAIRING'
165
+ ) {
166
+ window
167
+ .require('WAWebSocketModel')
168
+ .Socket.off(
169
+ 'change:state',
170
+ waitTillInit,
171
+ );
172
+ r();
173
+ }
174
+ },
175
+ );
176
+ });
126
177
  }
127
178
  state = window.require('WAWebSocketModel').Socket.state;
128
179
  return state == 'UNPAIRED' || state == 'UNPAIRED_IDLE';
129
180
  });
130
181
 
131
182
  if (needAuthentication) {
132
- const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded();
183
+ const { failed, failureEventPayload, restart } =
184
+ await this.authStrategy.onAuthenticationNeeded();
133
185
 
134
- if(failed) {
186
+ if (failed) {
135
187
  /**
136
188
  * Emitted when there has been an error while trying to restore an existing session
137
189
  * @event Client#auth_failure
@@ -148,129 +200,219 @@ class Client extends EventEmitter {
148
200
 
149
201
  // Register qr/code events
150
202
  if (pairWithPhoneNumber.phoneNumber) {
151
- await exposeFunctionIfAbsent(this.pupPage, 'onCodeReceivedEvent', async (code) => {
152
- /**
153
- * Emitted when a pairing code is received
154
- * @event Client#code
155
- * @param {string} code Code
156
- * @returns {string} Code that was just received
157
- */
158
- this.emit(Events.CODE_RECEIVED, code);
159
- return code;
160
- });
161
- this.requestPairingCode(pairWithPhoneNumber.phoneNumber, pairWithPhoneNumber.showNotification, pairWithPhoneNumber.intervalMs);
203
+ await exposeFunctionIfAbsent(
204
+ this.pupPage,
205
+ 'onCodeReceivedEvent',
206
+ async (code) => {
207
+ /**
208
+ * Emitted when a pairing code is received
209
+ * @event Client#code
210
+ * @param {string} code Code
211
+ * @returns {string} Code that was just received
212
+ */
213
+ this.emit(Events.CODE_RECEIVED, code);
214
+ return code;
215
+ },
216
+ );
217
+ this.requestPairingCode(
218
+ pairWithPhoneNumber.phoneNumber,
219
+ pairWithPhoneNumber.showNotification,
220
+ pairWithPhoneNumber.intervalMs,
221
+ );
162
222
  } else {
163
223
  let qrRetries = 0;
164
- await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => {
165
- /**
166
- * Emitted when a QR code is received
167
- * @event Client#qr
168
- * @param {string} qr QR Code
169
- */
170
- this.emit(Events.QR_RECEIVED, qr);
171
- if (this.options.qrMaxRetries > 0) {
172
- qrRetries++;
173
- if (qrRetries > this.options.qrMaxRetries) {
174
- this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
175
- await this.destroy();
224
+ await exposeFunctionIfAbsent(
225
+ this.pupPage,
226
+ 'onQRChangedEvent',
227
+ async (qr) => {
228
+ /**
229
+ * Emitted when a QR code is received
230
+ * @event Client#qr
231
+ * @param {string} qr QR Code
232
+ */
233
+ this.emit(Events.QR_RECEIVED, qr);
234
+ if (this.options.qrMaxRetries > 0) {
235
+ qrRetries++;
236
+ if (qrRetries > this.options.qrMaxRetries) {
237
+ this.emit(
238
+ Events.DISCONNECTED,
239
+ 'Max qrcode retries reached',
240
+ );
241
+ await this.destroy();
242
+ }
176
243
  }
177
- }
178
- });
179
-
244
+ },
245
+ );
180
246
 
181
247
  await this.pupPage.evaluate(async () => {
182
- const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
183
- const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
184
- const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey);
185
- const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey);
186
- const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey();
187
- const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
188
- const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform;
189
-
248
+ const registrationInfo =
249
+ await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
250
+ const noiseKeyPair =
251
+ await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
252
+ const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(
253
+ noiseKeyPair.staticKeyPair.pubKey,
254
+ );
255
+ const identityKeyB64 =
256
+ window.AuthStore.Base64Tools.encodeB64(
257
+ registrationInfo.identityKeyPair.pubKey,
258
+ );
259
+ const platform =
260
+ window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
261
+ const getQR = (ref) =>
262
+ ref +
263
+ ',' +
264
+ staticKeyB64 +
265
+ ',' +
266
+ identityKeyB64 +
267
+ ',' +
268
+ window
269
+ .require('WAWebUserPrefsMultiDevice')
270
+ .getADVSecretKey() +
271
+ ',' +
272
+ platform;
190
273
  window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
191
- window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes
274
+ window.AuthStore.Conn.on('change:ref', (_, ref) => {
275
+ window.onQRChangedEvent(getQR(ref));
276
+ }); // future QR changes
192
277
  });
193
278
  }
194
279
  }
195
280
 
196
- await exposeFunctionIfAbsent(this.pupPage, 'onAuthAppStateChangedEvent', async (state) => {
197
- if (state == 'UNPAIRED_IDLE' && !pairWithPhoneNumber.phoneNumber) {
198
- // refresh qr code
199
- window.require('WAWebCmd').Cmd.refreshQR();
200
- }
201
- });
202
-
203
- await exposeFunctionIfAbsent(this.pupPage, 'onAppStateHasSyncedEvent', async () => {
204
- const authEventPayload = await this.authStrategy.getAuthEventPayload();
205
- /**
281
+ await exposeFunctionIfAbsent(
282
+ this.pupPage,
283
+ 'onAuthAppStateChangedEvent',
284
+ async (state) => {
285
+ if (
286
+ state == 'UNPAIRED_IDLE' &&
287
+ !pairWithPhoneNumber.phoneNumber
288
+ ) {
289
+ // refresh qr code
290
+ window.require('WAWebCmd').Cmd.refreshQR();
291
+ }
292
+ },
293
+ );
294
+
295
+ await exposeFunctionIfAbsent(
296
+ this.pupPage,
297
+ 'onAppStateHasSyncedEvent',
298
+ async () => {
299
+ const authEventPayload =
300
+ await this.authStrategy.getAuthEventPayload();
301
+ /**
206
302
  * Emitted when authentication is successful
207
303
  * @event Client#authenticated
208
304
  */
209
- this.emit(Events.AUTHENTICATED, authEventPayload);
305
+ this.emit(Events.AUTHENTICATED, authEventPayload);
210
306
 
211
- const injected = await this.pupPage.evaluate(async () => {
212
- return typeof window.WWebJS !== 'undefined';
213
- });
307
+ const injected = await this.pupPage.evaluate(async () => {
308
+ return typeof window.WWebJS !== 'undefined';
309
+ });
214
310
 
215
- if (!injected) {
216
- if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) {
217
- const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
218
- const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
219
-
220
- await webCache.persist(this.currentIndexHtml, version);
221
- }
311
+ if (!injected) {
312
+ if (
313
+ this.options.webVersionCache.type === 'local' &&
314
+ this.currentIndexHtml
315
+ ) {
316
+ const { type: webCacheType, ...webCacheOptions } =
317
+ this.options.webVersionCache;
318
+ const webCache = WebCacheFactory.createWebCache(
319
+ webCacheType,
320
+ webCacheOptions,
321
+ );
322
+
323
+ await webCache.persist(this.currentIndexHtml, version);
324
+ }
222
325
 
223
- //Load util functions (serializers, helper functions)
224
- await this.pupPage.evaluate(LoadUtils);
225
-
226
- let start = Date.now();
227
- let res = false;
228
- while(start > (Date.now() - 30000)){
229
- // Check window.WWebJS Injection
230
- res = await this.pupPage.evaluate('window.WWebJS != undefined');
231
- if(res){break;}
232
- await new Promise(r => setTimeout(r, 200));
233
- }
234
- if(!res){
235
- throw 'ready timeout';
236
- }
237
-
238
- /**
326
+ // Load util functions (serializers, helper functions)
327
+ await this.pupPage.evaluate(LoadUtils);
328
+
329
+ let start = Date.now();
330
+ let res = false;
331
+ while (start > Date.now() - 30000) {
332
+ // Check window.WWebJS Injection
333
+ res = await this.pupPage.evaluate(
334
+ 'window.WWebJS != undefined',
335
+ );
336
+ if (res) {
337
+ break;
338
+ }
339
+ await new Promise((r) => setTimeout(r, 200));
340
+ }
341
+ if (!res) {
342
+ throw 'ready timeout';
343
+ }
344
+
345
+ /**
239
346
  * Current connection information
240
347
  * @type {ClientInfo}
241
348
  */
242
- this.info = new ClientInfo(this, await this.pupPage.evaluate(() => {
243
- return { ...(window.require('WAWebConnModel').Conn).serialize(), wid: (window.require('WAWebUserPrefsMeUser')).getMaybeMePnUser() || (window.require('WAWebUserPrefsMeUser')).getMaybeMeLidUser() };
244
- }));
349
+ this.info = new ClientInfo(
350
+ this,
351
+ await this.pupPage.evaluate(() => {
352
+ return {
353
+ ...window
354
+ .require('WAWebConnModel')
355
+ .Conn.serialize(),
356
+ wid:
357
+ window
358
+ .require('WAWebUserPrefsMeUser')
359
+ .getMaybeMePnUser() ||
360
+ window
361
+ .require('WAWebUserPrefsMeUser')
362
+ .getMaybeMeLidUser(),
363
+ };
364
+ }),
365
+ );
245
366
 
246
- this.interface = new InterfaceController(this);
367
+ this.interface = new InterfaceController(this);
247
368
 
248
- await this.attachEventListeners();
249
- }
250
- /**
369
+ await this.attachEventListeners();
370
+ }
371
+ /**
251
372
  * Emitted when the client has initialized and is ready to receive messages.
252
373
  * @event Client#ready
253
374
  */
254
- this.emit(Events.READY);
255
- this.authStrategy.afterAuthReady();
256
- });
375
+ this.emit(Events.READY);
376
+ this.authStrategy.afterAuthReady();
377
+ },
378
+ );
257
379
  let lastPercent = null;
258
- await exposeFunctionIfAbsent(this.pupPage, 'onOfflineProgressUpdateEvent', async (percent) => {
259
- if (lastPercent !== percent) {
260
- lastPercent = percent;
261
- this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now
262
- }
263
- });
264
- await exposeFunctionIfAbsent(this.pupPage, 'onLogoutEvent', async () => {
265
- this.lastLoggedOut = true;
266
- await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _);
267
- });
380
+ await exposeFunctionIfAbsent(
381
+ this.pupPage,
382
+ 'onOfflineProgressUpdateEvent',
383
+ async (percent) => {
384
+ if (lastPercent !== percent) {
385
+ lastPercent = percent;
386
+ this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now
387
+ }
388
+ },
389
+ );
390
+ await exposeFunctionIfAbsent(
391
+ this.pupPage,
392
+ 'onLogoutEvent',
393
+ async () => {
394
+ this.lastLoggedOut = true;
395
+ await this.pupPage
396
+ .waitForNavigation({ waitUntil: 'load', timeout: 5000 })
397
+ .catch((_) => _);
398
+ },
399
+ );
268
400
  await this.pupPage.evaluate(() => {
269
- window.require('WAWebSocketModel').Socket.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); });
270
- window.require('WAWebSocketModel').Socket.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); });
401
+ window
402
+ .require('WAWebSocketModel')
403
+ .Socket.on('change:state', (_AppState, state) => {
404
+ window.onAuthAppStateChangedEvent(state);
405
+ });
406
+ window
407
+ .require('WAWebSocketModel')
408
+ .Socket.on('change:hasSynced', () => {
409
+ window.onAppStateHasSyncedEvent();
410
+ });
271
411
  const Cmd = window.require('WAWebCmd').Cmd;
272
412
  Cmd.on('offline_progress_update_from_bridge', () => {
273
- window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress());
413
+ window.onOfflineProgressUpdateEvent(
414
+ window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress(),
415
+ );
274
416
  });
275
417
  Cmd.on('logout', async () => {
276
418
  await window.onLogoutEvent();
@@ -285,12 +427,10 @@ class Client extends EventEmitter {
285
427
  * Sets up events and requirements, kicks off authentication request
286
428
  */
287
429
  async initialize() {
288
-
289
- let
290
- /**
430
+ let /**
291
431
  * @type {puppeteer.Browser}
292
432
  */
293
- browser,
433
+ browser,
294
434
  /**
295
435
  * @type {puppeteer.Page}
296
436
  */
@@ -302,25 +442,34 @@ class Client extends EventEmitter {
302
442
  await this.authStrategy.beforeBrowserInitialized();
303
443
 
304
444
  const puppeteerOpts = this.options.puppeteer;
305
- if (puppeteerOpts && (puppeteerOpts.browserWSEndpoint || puppeteerOpts.browserURL)) {
445
+ if (
446
+ puppeteerOpts &&
447
+ (puppeteerOpts.browserWSEndpoint || puppeteerOpts.browserURL)
448
+ ) {
306
449
  browser = await puppeteer.connect(puppeteerOpts);
307
450
  page = await browser.newPage();
308
451
  } else {
309
452
  const browserArgs = [...(puppeteerOpts.args || [])];
310
- if(this.options.userAgent !== false && !browserArgs.find(arg => arg.includes('--user-agent'))) {
453
+ if (
454
+ this.options.userAgent !== false &&
455
+ !browserArgs.find((arg) => arg.includes('--user-agent'))
456
+ ) {
311
457
  browserArgs.push(`--user-agent=${this.options.userAgent}`);
312
458
  }
313
459
  // navigator.webdriver fix
314
460
  browserArgs.push('--disable-blink-features=AutomationControlled');
315
461
 
316
- browser = await puppeteer.launch({...puppeteerOpts, args: browserArgs});
462
+ browser = await puppeteer.launch({
463
+ ...puppeteerOpts,
464
+ args: browserArgs,
465
+ });
317
466
  page = (await browser.pages())[0];
318
467
  }
319
468
 
320
469
  if (this.options.proxyAuthentication !== undefined) {
321
470
  await page.authenticate(this.options.proxyAuthentication);
322
471
  }
323
- if(this.options.userAgent !== false) {
472
+ if (this.options.userAgent !== false) {
324
473
  await page.setUserAgent(this.options.userAgent);
325
474
  }
326
475
  if (this.options.bypassCSP) await page.setBypassCSP(true);
@@ -330,21 +479,21 @@ class Client extends EventEmitter {
330
479
 
331
480
  await this.authStrategy.afterBrowserInitialized();
332
481
  await this.initWebVersionCache();
333
-
482
+
334
483
  if (this.options.evalOnNewDoc !== undefined) {
335
484
  await page.evaluateOnNewDocument(this.options.evalOnNewDoc);
336
485
  }
337
-
486
+
338
487
  await page.goto(WhatsWebURL, {
339
488
  waitUntil: 'load',
340
489
  timeout: 0,
341
- referer: 'https://whatsapp.com/'
490
+ referer: 'https://whatsapp.com/',
342
491
  });
343
492
 
344
493
  await this.inject();
345
494
 
346
495
  this.pupPage.on('framenavigated', async (frame) => {
347
- if(frame.url().includes('post_logout=1') || this.lastLoggedOut) {
496
+ if (frame.url().includes('post_logout=1') || this.lastLoggedOut) {
348
497
  this.emit(Events.DISCONNECTED, 'LOGOUT');
349
498
  await this.authStrategy.logout();
350
499
  await this.authStrategy.beforeBrowserInitialized();
@@ -362,29 +511,70 @@ class Client extends EventEmitter {
362
511
  * @param {number} [intervalMs = 180000] - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes)
363
512
  * @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH"
364
513
  */
365
- async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) {
366
- return await this.pupPage.evaluate(async (phoneNumber, showNotification, intervalMs) => {
367
- const getCode = async () => {
368
- while (!window.AuthStore.PairingCodeLinkUtils) {
369
- await new Promise(resolve => setTimeout(resolve, 250));
514
+ async requestPairingCode(
515
+ phoneNumber,
516
+ showNotification = true,
517
+ intervalMs = 180000,
518
+ ) {
519
+ await exposeFunctionIfAbsent(
520
+ this.pupPage,
521
+ 'onCodeReceivedEvent',
522
+ async (code) => {
523
+ this.emit(Events.CODE_RECEIVED, code);
524
+ return code;
525
+ },
526
+ );
527
+ return await this.pupPage.evaluate(
528
+ async (phoneNumber, showNotification, intervalMs) => {
529
+ const getCode = async () => {
530
+ while (!window.AuthStore.PairingCodeLinkUtils) {
531
+ await new Promise((resolve) =>
532
+ setTimeout(resolve, 250),
533
+ );
534
+ }
535
+ window.AuthStore.PairingCodeLinkUtils.setPairingType(
536
+ 'ALT_DEVICE_LINKING',
537
+ );
538
+ await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
539
+ return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(
540
+ phoneNumber,
541
+ showNotification,
542
+ );
543
+ };
544
+ if (window.codeInterval) {
545
+ clearInterval(window.codeInterval); // remove existing interval
370
546
  }
371
- window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
372
- await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
373
- return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
374
- };
547
+ window.codeInterval = setInterval(async () => {
548
+ const state =
549
+ window.require('WAWebSocketModel').Socket.state;
550
+ if (state != 'UNPAIRED' && state != 'UNPAIRED_IDLE') {
551
+ clearInterval(window.codeInterval);
552
+ return;
553
+ }
554
+ window.onCodeReceivedEvent(await getCode());
555
+ }, intervalMs);
556
+ return window.onCodeReceivedEvent(await getCode());
557
+ },
558
+ phoneNumber,
559
+ showNotification,
560
+ intervalMs,
561
+ );
562
+ }
563
+
564
+ /**
565
+ * Cancels an active pairing code session and returns to QR code mode
566
+ */
567
+ async cancelPairingCode() {
568
+ await this.pupPage.evaluate(async () => {
375
569
  if (window.codeInterval) {
376
- clearInterval(window.codeInterval); // remove existing interval
570
+ clearInterval(window.codeInterval);
571
+ window.codeInterval = undefined;
377
572
  }
378
- window.codeInterval = setInterval(async () => {
379
- const state = window.require('WAWebSocketModel').Socket.state;
380
- if (state != 'UNPAIRED' && state != 'UNPAIRED_IDLE') {
381
- clearInterval(window.codeInterval);
382
- return;
383
- }
384
- window.onCodeReceivedEvent(await getCode());
385
- }, intervalMs);
386
- return window.onCodeReceivedEvent(await getCode());
387
- }, phoneNumber, showNotification, intervalMs);
573
+ window.require('WAWebLaunchSocketUtils').refreshQR();
574
+ await window
575
+ .require('WAWebAltDeviceLinkingApi')
576
+ .initializeQRLinking();
577
+ });
388
578
  }
389
579
 
390
580
  /**
@@ -393,32 +583,45 @@ class Client extends EventEmitter {
393
583
  * @property {boolean} reinject is this a reinject?
394
584
  */
395
585
  async attachEventListeners() {
396
- await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageEvent', msg => {
397
- if (msg.type === 'gp2') {
398
- const notification = new GroupNotification(this, msg);
399
- if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
400
- /**
586
+ await exposeFunctionIfAbsent(
587
+ this.pupPage,
588
+ 'onAddMessageEvent',
589
+ (msg) => {
590
+ if (msg.type === 'gp2') {
591
+ const notification = new GroupNotification(this, msg);
592
+ if (
593
+ ['add', 'invite', 'linked_group_join'].includes(
594
+ msg.subtype,
595
+ )
596
+ ) {
597
+ /**
401
598
  * Emitted when a user joins the chat via invite link or is added by an admin.
402
599
  * @event Client#group_join
403
600
  * @param {GroupNotification} notification GroupNotification with more information about the action
404
601
  */
405
- this.emit(Events.GROUP_JOIN, notification);
406
- } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
407
- /**
602
+ this.emit(Events.GROUP_JOIN, notification);
603
+ } else if (
604
+ msg.subtype === 'remove' ||
605
+ msg.subtype === 'leave'
606
+ ) {
607
+ /**
408
608
  * Emitted when a user leaves the chat or is removed by an admin.
409
609
  * @event Client#group_leave
410
610
  * @param {GroupNotification} notification GroupNotification with more information about the action
411
611
  */
412
- this.emit(Events.GROUP_LEAVE, notification);
413
- } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
414
- /**
612
+ this.emit(Events.GROUP_LEAVE, notification);
613
+ } else if (
614
+ msg.subtype === 'promote' ||
615
+ msg.subtype === 'demote'
616
+ ) {
617
+ /**
415
618
  * Emitted when a current user is promoted to an admin or demoted to a regular user.
416
619
  * @event Client#group_admin_changed
417
620
  * @param {GroupNotification} notification GroupNotification with more information about the action
418
621
  */
419
- this.emit(Events.GROUP_ADMIN_CHANGED, notification);
420
- } else if (msg.subtype === 'membership_approval_request') {
421
- /**
622
+ this.emit(Events.GROUP_ADMIN_CHANGED, notification);
623
+ } else if (msg.subtype === 'membership_approval_request') {
624
+ /**
422
625
  * Emitted when some user requested to join the group
423
626
  * that has the membership approval mode turned on
424
627
  * @event Client#group_membership_request
@@ -427,89 +630,106 @@ class Client extends EventEmitter {
427
630
  * @param {string} notification.author The user ID that made a request
428
631
  * @param {number} notification.timestamp The timestamp the request was made at
429
632
  */
430
- this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
431
- } else {
432
- /**
633
+ this.emit(
634
+ Events.GROUP_MEMBERSHIP_REQUEST,
635
+ notification,
636
+ );
637
+ } else {
638
+ /**
433
639
  * Emitted when group settings are updated, such as subject, description or picture.
434
640
  * @event Client#group_update
435
641
  * @param {GroupNotification} notification GroupNotification with more information about the action
436
642
  */
437
- this.emit(Events.GROUP_UPDATE, notification);
643
+ this.emit(Events.GROUP_UPDATE, notification);
644
+ }
645
+ return;
438
646
  }
439
- return;
440
- }
441
647
 
442
- const message = new Message(this, msg);
648
+ const message = new Message(this, msg);
443
649
 
444
- /**
650
+ /**
445
651
  * Emitted when a new message is created, which may include the current user's own messages.
446
652
  * @event Client#message_create
447
653
  * @param {Message} message The message that was created
448
654
  */
449
- this.emit(Events.MESSAGE_CREATE, message);
655
+ this.emit(Events.MESSAGE_CREATE, message);
450
656
 
451
- if (msg.id.fromMe) return;
657
+ if (msg.id.fromMe) return;
452
658
 
453
- /**
659
+ /**
454
660
  * Emitted when a new message is received.
455
661
  * @event Client#message
456
662
  * @param {Message} message The message that was received
457
663
  */
458
- this.emit(Events.MESSAGE_RECEIVED, message);
459
- });
664
+ this.emit(Events.MESSAGE_RECEIVED, message);
665
+ },
666
+ );
460
667
 
461
668
  let last_message;
462
669
 
463
- await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageTypeEvent', (msg) => {
464
-
465
- if (msg.type === 'revoked') {
466
- const message = new Message(this, msg);
467
- let revoked_msg;
468
- if (last_message && msg.id.id === last_message.id.id) {
469
- revoked_msg = new Message(this, last_message);
470
-
471
- if (message.protocolMessageKey)
472
- revoked_msg.id = { ...message.protocolMessageKey };
473
- }
670
+ await exposeFunctionIfAbsent(
671
+ this.pupPage,
672
+ 'onChangeMessageTypeEvent',
673
+ (msg) => {
674
+ if (msg.type === 'revoked') {
675
+ const message = new Message(this, msg);
676
+ let revoked_msg;
677
+ if (last_message && msg.id.id === last_message.id.id) {
678
+ revoked_msg = new Message(this, last_message);
679
+
680
+ if (message.protocolMessageKey)
681
+ revoked_msg.id = { ...message.protocolMessageKey };
682
+ }
474
683
 
475
- /**
684
+ /**
476
685
  * Emitted when a message is deleted for everyone in the chat.
477
686
  * @event Client#message_revoke_everyone
478
687
  * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
479
- * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
688
+ * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
480
689
  * Note that due to the way this data is captured, it may be possible that this param will be undefined.
481
690
  */
482
- this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
483
- }
484
-
485
- });
486
-
487
- await exposeFunctionIfAbsent(this.pupPage, 'onChangeMessageEvent', (msg) => {
488
-
489
- if (msg.type !== 'revoked') {
490
- last_message = msg;
491
- }
691
+ this.emit(
692
+ Events.MESSAGE_REVOKED_EVERYONE,
693
+ message,
694
+ revoked_msg,
695
+ );
696
+ }
697
+ },
698
+ );
699
+
700
+ await exposeFunctionIfAbsent(
701
+ this.pupPage,
702
+ 'onChangeMessageEvent',
703
+ (msg) => {
704
+ if (msg.type !== 'revoked') {
705
+ last_message = msg;
706
+ }
492
707
 
493
- /**
708
+ /**
494
709
  * The event notification that is received when one of
495
710
  * the group participants changes their phone number.
496
711
  */
497
- const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
712
+ const isParticipant =
713
+ msg.type === 'gp2' && msg.subtype === 'modify';
498
714
 
499
- /**
715
+ /**
500
716
  * The event notification that is received when one of
501
717
  * the contacts changes their phone number.
502
718
  */
503
- const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
719
+ const isContact =
720
+ msg.type === 'notification_template' &&
721
+ msg.subtype === 'change_number';
504
722
 
505
- if (isParticipant || isContact) {
506
- /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
507
- const message = new Message(this, msg);
723
+ if (isParticipant || isContact) {
724
+ /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
725
+ const message = new Message(this, msg);
508
726
 
509
- const newId = isParticipant ? msg.recipients[0] : msg.to;
510
- const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
727
+ const newId = isParticipant ? msg.recipients[0] : msg.to;
728
+ const oldId = isParticipant
729
+ ? msg.author
730
+ : msg.templateParams.find((id) => id !== newId);
511
731
 
512
- /**
732
+ /**
513
733
  * Emitted when a contact or a group participant changes their phone number.
514
734
  * @event Client#contact_changed
515
735
  * @param {Message} message Message with more information about the event.
@@ -518,98 +738,132 @@ class Client extends EventEmitter {
518
738
  * @param {String} newId The user's new id after the change.
519
739
  * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
520
740
  */
521
- this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
522
- }
523
- });
524
-
525
- await exposeFunctionIfAbsent(this.pupPage, 'onRemoveMessageEvent', (msg) => {
741
+ this.emit(
742
+ Events.CONTACT_CHANGED,
743
+ message,
744
+ oldId,
745
+ newId,
746
+ isContact,
747
+ );
748
+ }
749
+ },
750
+ );
526
751
 
527
- if (!msg.isNewMsg) return;
752
+ await exposeFunctionIfAbsent(
753
+ this.pupPage,
754
+ 'onRemoveMessageEvent',
755
+ (msg) => {
756
+ if (!msg.isNewMsg) return;
528
757
 
529
- const message = new Message(this, msg);
758
+ const message = new Message(this, msg);
530
759
 
531
- /**
760
+ /**
532
761
  * Emitted when a message is deleted by the current user.
533
762
  * @event Client#message_revoke_me
534
763
  * @param {Message} message The message that was revoked
535
764
  */
536
- this.emit(Events.MESSAGE_REVOKED_ME, message);
537
-
538
- });
539
-
540
- await exposeFunctionIfAbsent(this.pupPage, 'onMessageAckEvent', (msg, ack) => {
541
-
542
- const message = new Message(this, msg);
765
+ this.emit(Events.MESSAGE_REVOKED_ME, message);
766
+ },
767
+ );
768
+
769
+ await exposeFunctionIfAbsent(
770
+ this.pupPage,
771
+ 'onMessageAckEvent',
772
+ (msg, ack) => {
773
+ const message = new Message(this, msg);
543
774
 
544
- /**
775
+ /**
545
776
  * Emitted when an ack event occurrs on message type.
546
777
  * @event Client#message_ack
547
778
  * @param {Message} message The message that was affected
548
779
  * @param {MessageAck} ack The new ACK value
549
780
  */
550
- this.emit(Events.MESSAGE_ACK, message, ack);
781
+ this.emit(Events.MESSAGE_ACK, message, ack);
782
+ },
783
+ );
551
784
 
552
- });
785
+ await exposeFunctionIfAbsent(
786
+ this.pupPage,
787
+ 'onChatUnreadCountEvent',
788
+ async (data) => {
789
+ const chat = await this.getChatById(data.id);
553
790
 
554
- await exposeFunctionIfAbsent(this.pupPage, 'onChatUnreadCountEvent', async (data) =>{
555
- const chat = await this.getChatById(data.id);
556
-
557
- /**
791
+ /**
558
792
  * Emitted when the chat unread count changes
559
793
  */
560
- this.emit(Events.UNREAD_COUNT, chat);
561
- });
562
-
563
- await exposeFunctionIfAbsent(this.pupPage, 'onMessageMediaUploadedEvent', (msg) => {
564
-
565
- const message = new Message(this, msg);
794
+ this.emit(Events.UNREAD_COUNT, chat);
795
+ },
796
+ );
797
+
798
+ await exposeFunctionIfAbsent(
799
+ this.pupPage,
800
+ 'onMessageMediaUploadedEvent',
801
+ (msg) => {
802
+ const message = new Message(this, msg);
566
803
 
567
- /**
804
+ /**
568
805
  * Emitted when media has been uploaded for a message sent by the client.
569
806
  * @event Client#media_uploaded
570
807
  * @param {Message} message The message with media that was uploaded
571
808
  */
572
- this.emit(Events.MEDIA_UPLOADED, message);
573
- });
574
-
575
- await exposeFunctionIfAbsent(this.pupPage, 'onAppStateChangedEvent', async (state) => {
576
- /**
809
+ this.emit(Events.MEDIA_UPLOADED, message);
810
+ },
811
+ );
812
+
813
+ await exposeFunctionIfAbsent(
814
+ this.pupPage,
815
+ 'onAppStateChangedEvent',
816
+ async (state) => {
817
+ /**
577
818
  * Emitted when the connection state changes
578
819
  * @event Client#change_state
579
820
  * @param {WAState} state the new connection state
580
821
  */
581
- this.emit(Events.STATE_CHANGED, state);
582
-
583
- const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
584
-
585
- if (this.options.takeoverOnConflict) {
586
- ACCEPTED_STATES.push(WAState.CONFLICT);
587
-
588
- if (state === WAState.CONFLICT) {
589
- setTimeout(() => {
590
- this.pupPage.evaluate(() => window.require('WAWebSocketModel').Socket.takeover());
591
- }, this.options.takeoverTimeoutMs);
822
+ this.emit(Events.STATE_CHANGED, state);
823
+
824
+ const ACCEPTED_STATES = [
825
+ WAState.CONNECTED,
826
+ WAState.OPENING,
827
+ WAState.PAIRING,
828
+ WAState.TIMEOUT,
829
+ ];
830
+
831
+ if (this.options.takeoverOnConflict) {
832
+ ACCEPTED_STATES.push(WAState.CONFLICT);
833
+
834
+ if (state === WAState.CONFLICT) {
835
+ setTimeout(() => {
836
+ this.pupPage.evaluate(() =>
837
+ window
838
+ .require('WAWebSocketModel')
839
+ .Socket.takeover(),
840
+ );
841
+ }, this.options.takeoverTimeoutMs);
842
+ }
592
843
  }
593
- }
594
844
 
595
- if (!ACCEPTED_STATES.includes(state)) {
596
- /**
845
+ if (!ACCEPTED_STATES.includes(state)) {
846
+ /**
597
847
  * Emitted when the client has been disconnected
598
848
  * @event Client#disconnected
599
849
  * @param {WAState|"LOGOUT"} reason reason that caused the disconnect
600
850
  */
601
- await this.authStrategy.disconnect();
602
- this.emit(Events.DISCONNECTED, state);
603
- this.destroy();
604
- }
605
- });
851
+ await this.authStrategy.disconnect();
852
+ this.emit(Events.DISCONNECTED, state);
853
+ this.destroy();
854
+ }
855
+ },
856
+ );
606
857
 
607
- await exposeFunctionIfAbsent(this.pupPage, 'onBatteryStateChangedEvent', (state) => {
608
- const { battery, plugged } = state;
858
+ await exposeFunctionIfAbsent(
859
+ this.pupPage,
860
+ 'onBatteryStateChangedEvent',
861
+ (state) => {
862
+ const { battery, plugged } = state;
609
863
 
610
- if (battery === undefined) return;
864
+ if (battery === undefined) return;
611
865
 
612
- /**
866
+ /**
613
867
  * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
614
868
  * @event Client#change_battery
615
869
  * @param {object} batteryInfo
@@ -617,30 +871,34 @@ class Client extends EventEmitter {
617
871
  * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
618
872
  * @deprecated
619
873
  */
620
- this.emit(Events.BATTERY_CHANGED, { battery, plugged });
621
- });
874
+ this.emit(Events.BATTERY_CHANGED, { battery, plugged });
875
+ },
876
+ );
622
877
 
623
878
  await exposeFunctionIfAbsent(this.pupPage, 'onIncomingCall', (call) => {
624
879
  /**
625
- * Emitted when a call is received
626
- * @event Client#incoming_call
627
- * @param {object} call
628
- * @param {number} call.id - Call id
629
- * @param {string} call.peerJid - Who called
630
- * @param {boolean} call.isVideo - if is video
631
- * @param {boolean} call.isGroup - if is group
632
- * @param {boolean} call.canHandleLocally - if we can handle in waweb
633
- * @param {boolean} call.outgoing - if is outgoing
634
- * @param {boolean} call.webClientShouldHandle - If Waweb should handle
635
- * @param {object} call.participants - Participants
636
- */
880
+ * Emitted when a call is received
881
+ * @event Client#incoming_call
882
+ * @param {object} call
883
+ * @param {number} call.id - Call id
884
+ * @param {string} call.peerJid - Who called
885
+ * @param {boolean} call.isVideo - if is video
886
+ * @param {boolean} call.isGroup - if is group
887
+ * @param {boolean} call.canHandleLocally - if we can handle in waweb
888
+ * @param {boolean} call.outgoing - if is outgoing
889
+ * @param {boolean} call.webClientShouldHandle - If Waweb should handle
890
+ * @param {object} call.participants - Participants
891
+ */
637
892
  const cll = new Call(this, call);
638
893
  this.emit(Events.INCOMING_CALL, cll);
639
894
  });
640
895
 
641
- await exposeFunctionIfAbsent(this.pupPage, 'onReaction', (reactions) => {
642
- for (const reaction of reactions) {
643
- /**
896
+ await exposeFunctionIfAbsent(
897
+ this.pupPage,
898
+ 'onReaction',
899
+ (reactions) => {
900
+ for (const reaction of reactions) {
901
+ /**
644
902
  * Emitted when a reaction is sent, received, updated or removed
645
903
  * @event Client#message_reaction
646
904
  * @param {object} reaction
@@ -655,168 +913,357 @@ class Client extends EventEmitter {
655
913
  * @param {?number} reaction.ack - Ack
656
914
  */
657
915
 
658
- this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
659
- }
660
- });
916
+ this.emit(
917
+ Events.MESSAGE_REACTION,
918
+ new Reaction(this, reaction),
919
+ );
920
+ }
921
+ },
922
+ );
661
923
 
662
- await exposeFunctionIfAbsent(this.pupPage, 'onRemoveChatEvent', async (chat) => {
663
- const _chat = await this.getChatById(chat.id);
924
+ await exposeFunctionIfAbsent(
925
+ this.pupPage,
926
+ 'onRemoveChatEvent',
927
+ async (chat) => {
928
+ const _chat = await this.getChatById(chat.id);
664
929
 
665
- /**
930
+ /**
666
931
  * Emitted when a chat is removed
667
932
  * @event Client#chat_removed
668
933
  * @param {Chat} chat
669
934
  */
670
- this.emit(Events.CHAT_REMOVED, _chat);
671
- });
672
-
673
- await exposeFunctionIfAbsent(this.pupPage, 'onArchiveChatEvent', async (chat, currState, prevState) => {
674
- const _chat = await this.getChatById(chat.id);
675
-
676
- /**
935
+ this.emit(Events.CHAT_REMOVED, _chat);
936
+ },
937
+ );
938
+
939
+ await exposeFunctionIfAbsent(
940
+ this.pupPage,
941
+ 'onArchiveChatEvent',
942
+ async (chat, currState, prevState) => {
943
+ const _chat = await this.getChatById(chat.id);
944
+
945
+ /**
677
946
  * Emitted when a chat is archived/unarchived
678
947
  * @event Client#chat_archived
679
948
  * @param {Chat} chat
680
949
  * @param {boolean} currState
681
950
  * @param {boolean} prevState
682
951
  */
683
- this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
684
- });
685
-
686
- await exposeFunctionIfAbsent(this.pupPage, 'onEditMessageEvent', (msg, newBody, prevBody) => {
687
-
688
- if(msg.type === 'revoked'){
689
- return;
690
- }
691
- /**
952
+ this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
953
+ },
954
+ );
955
+
956
+ await exposeFunctionIfAbsent(
957
+ this.pupPage,
958
+ 'onEditMessageEvent',
959
+ (msg, newBody, prevBody) => {
960
+ if (msg.type === 'revoked') {
961
+ return;
962
+ }
963
+ /**
692
964
  * Emitted when messages are edited
693
965
  * @event Client#message_edit
694
966
  * @param {Message} message
695
967
  * @param {string} newBody
696
968
  * @param {string} prevBody
697
969
  */
698
- this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
699
- });
700
-
701
- await exposeFunctionIfAbsent(this.pupPage, 'onAddMessageCiphertextEvent', msg => {
702
-
703
- /**
704
- * Emitted when messages are edited
970
+ this.emit(
971
+ Events.MESSAGE_EDIT,
972
+ new Message(this, msg),
973
+ newBody,
974
+ prevBody,
975
+ );
976
+ },
977
+ );
978
+
979
+ await exposeFunctionIfAbsent(
980
+ this.pupPage,
981
+ 'onAddMessageCiphertextEvent',
982
+ (msg) => {
983
+ /**
984
+ * Emitted when a message is received as ciphertext (not yet decrypted)
705
985
  * @event Client#message_ciphertext
706
986
  * @param {Message} message
707
987
  */
708
- this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
709
- });
710
-
711
- await exposeFunctionIfAbsent(this.pupPage, 'onPollVoteEvent', (votes) => {
712
- for (const vote of votes) {
988
+ this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
989
+ },
990
+ );
991
+
992
+ await exposeFunctionIfAbsent(
993
+ this.pupPage,
994
+ 'onCiphertextFailedEvent',
995
+ (msg) => {
713
996
  /**
714
- * Emitted when some poll option is selected or deselected,
715
- * shows a user's current selected option(s) on the poll
716
- * @event Client#vote_update
997
+ * Emitted when a ciphertext message failed to decrypt after recovery attempt
998
+ * @event Client#message_ciphertext_failed
999
+ * @param {Message} message
717
1000
  */
718
- this.emit(Events.VOTE_UPDATE, new PollVote(this, vote));
719
- }
720
- });
1001
+ this.emit(
1002
+ Events.MESSAGE_CIPHERTEXT_FAILED,
1003
+ new Message(this, msg),
1004
+ );
1005
+ },
1006
+ );
1007
+
1008
+ await exposeFunctionIfAbsent(
1009
+ this.pupPage,
1010
+ 'onPollVoteEvent',
1011
+ (votes) => {
1012
+ for (const vote of votes) {
1013
+ /**
1014
+ * Emitted when some poll option is selected or deselected,
1015
+ * shows a user's current selected option(s) on the poll
1016
+ * @event Client#vote_update
1017
+ */
1018
+ this.emit(Events.VOTE_UPDATE, new PollVote(this, vote));
1019
+ }
1020
+ },
1021
+ );
721
1022
 
722
1023
  await this.pupPage.evaluate(() => {
723
- const { Msg, Chat, WAWebCallCollection } = window.require('WAWebCollections');
1024
+ const { Msg, Chat } = window.require('WAWebCollections');
724
1025
  const AppState = window.require('WAWebSocketModel').Socket;
725
- Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
726
- Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
727
- Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
728
- Msg.on('change:isUnsentMedia', (msg, unsent) => { if (msg.id.fromMe && !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); });
729
- Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); });
730
- Msg.on('change:body change:caption', (msg, newBody, prevBody) => { window.onEditMessageEvent(window.WWebJS.getMessageModel(msg), newBody, prevBody); });
731
- AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); });
732
- (window.require('WAWebConnModel').Conn).on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); });
733
- if (WAWebCallCollection && typeof WAWebCallCollection.on === 'function') {
734
- WAWebCallCollection.on('add', (call) => { window.onIncomingCall(call); });
1026
+
1027
+ // Enable placeholder message resend (recovery for ciphertext messages)
1028
+ const gatingUtils = window.require('WAWebSyncGatingUtils');
1029
+ gatingUtils.isPlaceholderMessageResendEnabled = () => true;
1030
+
1031
+ Msg.on('change', (msg) => {
1032
+ window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg));
1033
+ });
1034
+ Msg.on('change:type', (msg) => {
1035
+ window.onChangeMessageTypeEvent(
1036
+ window.WWebJS.getMessageModel(msg),
1037
+ );
1038
+ });
1039
+ Msg.on('change:ack', (msg, ack) => {
1040
+ window.onMessageAckEvent(
1041
+ window.WWebJS.getMessageModel(msg),
1042
+ ack,
1043
+ );
1044
+ });
1045
+ Msg.on('change:isUnsentMedia', (msg, unsent) => {
1046
+ if (msg.id.fromMe && !unsent)
1047
+ window.onMessageMediaUploadedEvent(
1048
+ window.WWebJS.getMessageModel(msg),
1049
+ );
1050
+ });
1051
+ Msg.on('remove', (msg) => {
1052
+ if (msg.isNewMsg)
1053
+ window.onRemoveMessageEvent(
1054
+ window.WWebJS.getMessageModel(msg),
1055
+ );
1056
+ });
1057
+ Msg.on('change:body change:caption', (msg, newBody, prevBody) => {
1058
+ window.onEditMessageEvent(
1059
+ window.WWebJS.getMessageModel(msg),
1060
+ newBody,
1061
+ prevBody,
1062
+ );
1063
+ });
1064
+ AppState.on('change:state', (_AppState, state) => {
1065
+ window.onAppStateChangedEvent(state);
1066
+ });
1067
+ window
1068
+ .require('WAWebConnModel')
1069
+ .Conn.on('change:battery', (state) => {
1070
+ window.onBatteryStateChangedEvent(state);
1071
+ });
1072
+ const WAWebCallCollection = window.require('WAWebCallCollection');
1073
+ if (
1074
+ WAWebCallCollection &&
1075
+ typeof WAWebCallCollection.on === 'function'
1076
+ ) {
1077
+ const mapKey = Object.keys(WAWebCallCollection).find(
1078
+ (k) => WAWebCallCollection[k] instanceof Map,
1079
+ );
1080
+ const internalCallMap = WAWebCallCollection[mapKey];
1081
+ const originalMapSet =
1082
+ internalCallMap.set.bind(internalCallMap);
1083
+
1084
+ internalCallMap.set = function (key, value) {
1085
+ window.onIncomingCall({
1086
+ id: value.id,
1087
+ peerJid: value.peerJid,
1088
+ isVideo: value.isVideo,
1089
+ isGroup: value.isGroup,
1090
+ canHandleLocally: value.canHandleLocally,
1091
+ outgoing: value.outgoing,
1092
+ webClientShouldHandle: value.webClientShouldHandle,
1093
+ participants: value.participants,
1094
+ });
1095
+ return originalMapSet(key, value);
1096
+ };
735
1097
  }
736
- Chat.on('remove', async (chat) => { window.onRemoveChatEvent(await window.WWebJS.getChatModel(chat)); });
737
- Chat.on('change:archive', async (chat, currState, prevState) => { window.onArchiveChatEvent(await window.WWebJS.getChatModel(chat), currState, prevState); });
738
- Msg.on('add', (msg) => {
739
- if (msg.isNewMsg) {
740
- if(msg.type === 'ciphertext') {
741
- // defer message event until ciphertext is resolved (type changed)
742
- msg.once('change:type', (_msg) => window.onAddMessageEvent(window.WWebJS.getMessageModel(_msg)));
743
- window.onAddMessageCiphertextEvent(window.WWebJS.getMessageModel(msg));
744
- } else {
745
- window.onAddMessageEvent(window.WWebJS.getMessageModel(msg));
746
- }
747
- }
1098
+ Chat.on('remove', async (chat) => {
1099
+ window.onRemoveChatEvent(
1100
+ await window.WWebJS.getChatModel(chat),
1101
+ );
1102
+ });
1103
+ Chat.on('change:archive', async (chat, currState, prevState) => {
1104
+ window.onArchiveChatEvent(
1105
+ await window.WWebJS.getChatModel(chat),
1106
+ currState,
1107
+ prevState,
1108
+ );
748
1109
  });
749
- Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);});
1110
+ const pendingResend = new Set();
1111
+ let resendFlush = null;
1112
+
1113
+ function requestResend(msg) {
1114
+ pendingResend.add(msg);
1115
+ if (resendFlush) return;
1116
+ resendFlush = setTimeout(() => {
1117
+ resendFlush = null;
1118
+ const msgs = [...pendingResend];
1119
+ pendingResend.clear();
1120
+ if (msgs.length === 0) return;
1121
+ window
1122
+ .require(
1123
+ 'WAWebNonMessageDataRequestPlaceholderMessageResendUtils',
1124
+ )
1125
+ .handlePlaceholderMsgsSeen(msgs, true);
1126
+ }, 5000);
1127
+ }
750
1128
 
751
- window.WWebJS.injectToFunction({ module: 'WAWebAddonReactionTableMode', function: 'reactionTableMode.bulkUpsert'}, (module, origFunction, ...args) => {
752
- window.onReaction(args[0].map(reaction => {
753
- const msgKey = reaction.id;
754
- const parentMsgKey = reaction.reactionParentKey;
755
- const timestamp = reaction.reactionTimestamp / 1000;
756
- const sender = reaction.author ?? reaction.from;
757
- const senderUserJid = sender._serialized;
1129
+ Msg.on('add', (msg) => {
1130
+ if (!msg.isNewMsg) return;
758
1131
 
759
- return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp };
760
- }));
1132
+ if (msg.type !== 'ciphertext') {
1133
+ window.onAddMessageEvent(
1134
+ window.WWebJS.getMessageModel(msg),
1135
+ );
1136
+ return;
1137
+ }
761
1138
 
762
- return origFunction.apply(module, args);
763
- });
764
-
765
- window.WWebJS.injectToFunction({ module: 'WAWebAddonPollVoteTableMode', function: 'pollVoteTableMode.bulkUpsert'}, async (module, origFunction, ...args) => {
766
- const votes = await Promise.all(args[0].map(async vote => {
767
- const msgKey = vote.id;
768
- const parentMsgKey = vote.pollUpdateParentKey;
769
- const timestamp = vote.t / 1000;
770
- const sender = vote.author ?? vote.from;
771
- const senderUserJid = sender._serialized;
772
-
773
- let parentMessage = Msg.get(parentMsgKey._serialized);
774
- if (!parentMessage) {
775
- const fetched = await Msg.getMessagesById([parentMsgKey._serialized]);
776
- parentMessage = fetched?.messages?.[0] || null;
777
- }
1139
+ window.onAddMessageCiphertextEvent(
1140
+ window.WWebJS.getMessageModel(msg),
1141
+ );
778
1142
 
779
- return {
780
- ...vote,
781
- msgKey,
782
- sender,
783
- parentMsgKey,
784
- senderUserJid,
785
- timestamp,
786
- parentMessage
787
- };
788
- }));
1143
+ if (msg.subtype && msg.subtype.endsWith('_unavailable_fanout'))
1144
+ return;
789
1145
 
790
- window.onPollVoteEvent(votes);
1146
+ requestResend(msg);
791
1147
 
792
- return origFunction.apply(module, args);
1148
+ const failTimer = setTimeout(() => {
1149
+ if (msg.type !== 'ciphertext') return;
1150
+ window.onCiphertextFailedEvent(
1151
+ window.WWebJS.getMessageModel(msg),
1152
+ );
1153
+ }, 15000);
1154
+
1155
+ msg.once('change:type', (_msg) => {
1156
+ clearTimeout(failTimer);
1157
+ pendingResend.delete(_msg);
1158
+ if (_msg.type === 'revoked') return;
1159
+ window.onAddMessageEvent(
1160
+ window.WWebJS.getMessageModel(_msg),
1161
+ );
1162
+ });
1163
+ });
1164
+ Chat.on('change:unreadCount', (chat) => {
1165
+ window.onChatUnreadCountEvent(chat);
793
1166
  });
1167
+
1168
+ window.WWebJS.injectToFunction(
1169
+ {
1170
+ module: 'WAWebAddonReactionTableMode',
1171
+ function: 'reactionTableMode.bulkUpsert',
1172
+ },
1173
+ (module, origFunction, ...args) => {
1174
+ window.onReaction(
1175
+ args[0].map((reaction) => {
1176
+ const msgKey = reaction.id;
1177
+ const parentMsgKey = reaction.reactionParentKey;
1178
+ const timestamp = reaction.reactionTimestamp / 1000;
1179
+ const sender = reaction.author ?? reaction.from;
1180
+ const senderUserJid = sender._serialized;
1181
+
1182
+ return {
1183
+ ...reaction,
1184
+ msgKey,
1185
+ parentMsgKey,
1186
+ senderUserJid,
1187
+ timestamp,
1188
+ };
1189
+ }),
1190
+ );
1191
+
1192
+ return origFunction.apply(module, args);
1193
+ },
1194
+ );
1195
+
1196
+ window.WWebJS.injectToFunction(
1197
+ {
1198
+ module: 'WAWebAddonPollVoteTableMode',
1199
+ function: 'pollVoteTableMode.bulkUpsert',
1200
+ },
1201
+ async (module, origFunction, ...args) => {
1202
+ const votes = await Promise.all(
1203
+ args[0].map(async (vote) => {
1204
+ const msgKey = vote.id;
1205
+ const parentMsgKey = vote.pollUpdateParentKey;
1206
+ const timestamp = vote.t / 1000;
1207
+ const sender = vote.author ?? vote.from;
1208
+ const senderUserJid = sender._serialized;
1209
+
1210
+ let parentMessage = Msg.get(
1211
+ parentMsgKey._serialized,
1212
+ );
1213
+ if (!parentMessage) {
1214
+ const fetched = await Msg.getMessagesById([
1215
+ parentMsgKey._serialized,
1216
+ ]);
1217
+ parentMessage = fetched?.messages?.[0] || null;
1218
+ }
1219
+
1220
+ return {
1221
+ ...vote,
1222
+ msgKey,
1223
+ sender,
1224
+ parentMsgKey,
1225
+ senderUserJid,
1226
+ timestamp,
1227
+ parentMessage,
1228
+ };
1229
+ }),
1230
+ );
1231
+
1232
+ window.onPollVoteEvent(votes);
1233
+
1234
+ return origFunction.apply(module, args);
1235
+ },
1236
+ );
794
1237
  });
795
- }
1238
+ }
796
1239
 
797
1240
  async initWebVersionCache() {
798
- const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
799
- const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
1241
+ const { type: webCacheType, ...webCacheOptions } =
1242
+ this.options.webVersionCache;
1243
+ const webCache = WebCacheFactory.createWebCache(
1244
+ webCacheType,
1245
+ webCacheOptions,
1246
+ );
800
1247
 
801
1248
  const requestedVersion = this.options.webVersion;
802
1249
  const versionContent = await webCache.resolve(requestedVersion);
803
1250
 
804
- if(versionContent) {
1251
+ if (versionContent) {
805
1252
  await this.pupPage.setRequestInterception(true);
806
1253
  this.pupPage.on('request', async (req) => {
807
- if(req.url() === WhatsWebURL) {
1254
+ if (req.url() === WhatsWebURL) {
808
1255
  req.respond({
809
1256
  status: 200,
810
1257
  contentType: 'text/html',
811
- body: versionContent
812
- });
1258
+ body: versionContent,
1259
+ });
813
1260
  } else {
814
1261
  req.continue();
815
1262
  }
816
1263
  });
817
1264
  } else {
818
1265
  this.pupPage.on('response', async (res) => {
819
- if(res.ok() && res.url() === WhatsWebURL) {
1266
+ if (res.ok() && res.url() === WhatsWebURL) {
820
1267
  const indexHtml = await res.text();
821
1268
  this.currentIndexHtml = indexHtml;
822
1269
  }
@@ -844,13 +1291,14 @@ class Client extends EventEmitter {
844
1291
  return window.require('WAWebSocketModel').Socket.logout();
845
1292
  });
846
1293
  await this.pupBrowser.close();
847
-
1294
+
848
1295
  let maxDelay = 0;
849
- while (this.pupBrowser.isConnected() && (maxDelay < 10)) { // waits a maximum of 1 second before calling the AuthStrategy
850
- await new Promise(resolve => setTimeout(resolve, 100));
851
- maxDelay++;
1296
+ while (this.pupBrowser.isConnected() && maxDelay < 10) {
1297
+ // waits a maximum of 1 second before calling the AuthStrategy
1298
+ await new Promise((resolve) => setTimeout(resolve, 100));
1299
+ maxDelay++;
852
1300
  }
853
-
1301
+
854
1302
  await this.authStrategy.logout();
855
1303
  }
856
1304
 
@@ -865,23 +1313,28 @@ class Client extends EventEmitter {
865
1313
  }
866
1314
 
867
1315
  async setDeviceName(deviceName, browserName) {
868
- (deviceName || browserName) && await this.pupPage.evaluate((deviceName, browserName) => {
869
- const func = window.require('WAWebMiscBrowserUtils').info;
870
- window.require('WAWebMiscBrowserUtils').info = () => {
871
- return {
872
- ...func(),
873
- ...(deviceName ? { os: deviceName } : {}),
874
- ...(browserName ? { name: browserName } : {})
875
- };
876
- };
877
- }, deviceName, browserName);
1316
+ (deviceName || browserName) &&
1317
+ (await this.pupPage.evaluate(
1318
+ (deviceName, browserName) => {
1319
+ const func = window.require('WAWebMiscBrowserUtils').info;
1320
+ window.require('WAWebMiscBrowserUtils').info = () => {
1321
+ return {
1322
+ ...func(),
1323
+ ...(deviceName ? { os: deviceName } : {}),
1324
+ ...(browserName ? { name: browserName } : {}),
1325
+ };
1326
+ };
1327
+ },
1328
+ deviceName,
1329
+ browserName,
1330
+ ));
878
1331
  }
879
1332
 
880
1333
  /**
881
1334
  * Mark as seen for the Chat
882
1335
  * @param {string} chatId
883
1336
  * @returns {Promise<boolean>} result
884
- *
1337
+ *
885
1338
  */
886
1339
  async sendSeen(chatId) {
887
1340
  return await this.pupPage.evaluate(async (chatId) => {
@@ -921,50 +1374,84 @@ class Client extends EventEmitter {
921
1374
  * @property {MessageMedia} [media] - Media to be sent
922
1375
  * @property {any} [extra] - Extra options
923
1376
  */
924
-
1377
+
925
1378
  /**
926
1379
  * Send a message to a specific chatId
927
1380
  * @param {string} chatId
928
1381
  * @param {string|MessageMedia|Location|Poll|Contact|Array<Contact>|Buttons|List} content
929
1382
  * @param {MessageSendOptions} [options] - Options used when sending the message
930
- *
1383
+ *
931
1384
  * @returns {Promise<Message>} Message that was just sent
932
1385
  */
933
1386
  async sendMessage(chatId, content, options = {}) {
934
1387
  const isChannel = /@\w*newsletter\b/.test(chatId);
935
1388
  const isStatus = /@\w*broadcast\b/.test(chatId);
936
1389
 
937
- if (isChannel && [
938
- options.sendMediaAsDocument, options.quotedMessageId,
939
- options.parseVCards, options.isViewOnce,
940
- content instanceof Location, content instanceof Contact,
941
- content instanceof Buttons, content instanceof List,
942
- Array.isArray(content) && content.length > 0 && content[0] instanceof Contact
943
- ].includes(true)) {
944
- console.warn('The message type is currently not supported for sending in channels,\nthe supported message types are: text, image, sticker, gif, video, voice and poll.');
1390
+ if (
1391
+ isChannel &&
1392
+ [
1393
+ options.sendMediaAsDocument,
1394
+ options.quotedMessageId,
1395
+ options.parseVCards,
1396
+ options.isViewOnce,
1397
+ content instanceof Location,
1398
+ content instanceof Contact,
1399
+ content instanceof Buttons,
1400
+ content instanceof List,
1401
+ Array.isArray(content) &&
1402
+ content.length > 0 &&
1403
+ content[0] instanceof Contact,
1404
+ ].includes(true)
1405
+ ) {
1406
+ console.warn(
1407
+ 'The message type is currently not supported for sending in channels,\nthe supported message types are: text, image, sticker, gif, video, voice and poll.',
1408
+ );
945
1409
  return null;
946
-
947
- } else if (isStatus && [
948
- options.sendMediaAsDocument, options.quotedMessageId,
949
- options.parseVCards, options.isViewOnce, options.sendMediaAsSticker,
950
- content instanceof Location, content instanceof Contact,
951
- content instanceof Poll, content instanceof Buttons, content instanceof List,
952
- Array.isArray(content) && content.length > 0 && content[0] instanceof Contact
953
- ].includes(true)) {
954
- console.warn('The message type is currently not supported for sending in status broadcast,\nthe supported message types are: text, image, gif, audio and video.');
1410
+ } else if (
1411
+ isStatus &&
1412
+ [
1413
+ options.sendMediaAsDocument,
1414
+ options.quotedMessageId,
1415
+ options.parseVCards,
1416
+ options.isViewOnce,
1417
+ options.sendMediaAsSticker,
1418
+ content instanceof Location,
1419
+ content instanceof Contact,
1420
+ content instanceof Poll,
1421
+ content instanceof Buttons,
1422
+ content instanceof List,
1423
+ Array.isArray(content) &&
1424
+ content.length > 0 &&
1425
+ content[0] instanceof Contact,
1426
+ ].includes(true)
1427
+ ) {
1428
+ console.warn(
1429
+ 'The message type is currently not supported for sending in status broadcast,\nthe supported message types are: text, image, gif, audio and video.',
1430
+ );
955
1431
  return null;
956
1432
  }
957
-
1433
+
958
1434
  if (options.mentions) {
959
- !Array.isArray(options.mentions) && (options.mentions = [options.mentions]);
960
- if (options.mentions.some((possiblyContact) => possiblyContact instanceof Contact)) {
961
- console.warn('Mentions with an array of Contact are now deprecated. See more at https://github.com/pedroslopez/whatsapp-web.js/pull/2166.');
962
- options.mentions = options.mentions.map((a) => a.id._serialized);
1435
+ !Array.isArray(options.mentions) &&
1436
+ (options.mentions = [options.mentions]);
1437
+ if (
1438
+ options.mentions.some(
1439
+ (possiblyContact) => possiblyContact instanceof Contact,
1440
+ )
1441
+ ) {
1442
+ console.warn(
1443
+ 'Mentions with an array of Contact are now deprecated. See more at https://github.com/wwebjssapp-web.js/pull/2166.',
1444
+ );
1445
+ options.mentions = options.mentions.map(
1446
+ (a) => a.id._serialized,
1447
+ );
963
1448
  }
964
1449
  }
965
1450
 
966
- options.groupMentions && !Array.isArray(options.groupMentions) && (options.groupMentions = [options.groupMentions]);
967
-
1451
+ options.groupMentions &&
1452
+ !Array.isArray(options.groupMentions) &&
1453
+ (options.groupMentions = [options.groupMentions]);
1454
+
968
1455
  let internalOptions = {
969
1456
  linkPreview: options.linkPreview === false ? undefined : true,
970
1457
  sendAudioAsVoice: options.sendAudioAsVoice,
@@ -981,20 +1468,18 @@ class Client extends EventEmitter {
981
1468
  invokedBotWid: options.invokedBotWid,
982
1469
  ignoreQuoteErrors: options.ignoreQuoteErrors !== false,
983
1470
  waitUntilMsgSent: options.waitUntilMsgSent || false,
984
- extraOptions: options.extra
1471
+ extraOptions: options.extra,
985
1472
  };
986
1473
 
987
1474
  const sendSeen = options.sendSeen !== false;
988
1475
 
989
1476
  if (content instanceof MessageMedia) {
990
1477
  internalOptions.media = content;
991
- internalOptions.isViewOnce = options.isViewOnce,
992
- content = '';
1478
+ ((internalOptions.isViewOnce = options.isViewOnce), (content = ''));
993
1479
  } else if (options.media instanceof MessageMedia) {
994
1480
  internalOptions.media = options.media;
995
1481
  internalOptions.caption = content;
996
- internalOptions.isViewOnce = options.isViewOnce,
997
- content = '';
1482
+ ((internalOptions.isViewOnce = options.isViewOnce), (content = ''));
998
1483
  } else if (content instanceof Location) {
999
1484
  internalOptions.location = content;
1000
1485
  content = '';
@@ -1007,48 +1492,97 @@ class Client extends EventEmitter {
1007
1492
  } else if (content instanceof Contact) {
1008
1493
  internalOptions.contactCard = content.id._serialized;
1009
1494
  content = '';
1010
- } else if (Array.isArray(content) && content.length > 0 && content[0] instanceof Contact) {
1011
- internalOptions.contactCardList = content.map(contact => contact.id._serialized);
1495
+ } else if (
1496
+ Array.isArray(content) &&
1497
+ content.length > 0 &&
1498
+ content[0] instanceof Contact
1499
+ ) {
1500
+ internalOptions.contactCardList = content.map(
1501
+ (contact) => contact.id._serialized,
1502
+ );
1012
1503
  content = '';
1013
1504
  } else if (content instanceof Buttons) {
1014
- console.warn('Buttons are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.');
1015
- if (content.type !== 'chat') { internalOptions.attachment = content.body; }
1505
+ console.warn(
1506
+ 'Buttons are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.',
1507
+ );
1508
+ if (content.type !== 'chat') {
1509
+ internalOptions.attachment = content.body;
1510
+ }
1016
1511
  internalOptions.buttons = content;
1017
1512
  content = '';
1018
1513
  } else if (content instanceof List) {
1019
- console.warn('Lists are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.');
1514
+ console.warn(
1515
+ 'Lists are now deprecated. See more at https://www.youtube.com/watch?v=hv1R1rLeVVE.',
1516
+ );
1020
1517
  internalOptions.list = content;
1021
1518
  content = '';
1022
1519
  }
1023
1520
 
1024
1521
  if (internalOptions.sendMediaAsSticker && internalOptions.media) {
1025
1522
  internalOptions.media = await Util.formatToWebpSticker(
1026
- internalOptions.media, {
1523
+ internalOptions.media,
1524
+ {
1027
1525
  name: options.stickerName,
1028
1526
  author: options.stickerAuthor,
1029
- categories: options.stickerCategories
1030
- }, this.pupPage
1527
+ categories: options.stickerCategories,
1528
+ },
1529
+ this.pupPage,
1031
1530
  );
1032
1531
  }
1033
1532
 
1034
- const sentMsg = await this.pupPage.evaluate(async (chatId, content, options, sendSeen) => {
1035
- const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
1036
-
1037
- if (!chat) return null;
1533
+ const sentMsg = await this.pupPage.evaluate(
1534
+ async (chatId, content, options, sendSeen) => {
1535
+ const chat = await window.WWebJS.getChat(chatId, {
1536
+ getAsModel: false,
1537
+ });
1038
1538
 
1039
- if (sendSeen) {
1040
- await window.WWebJS.sendSeen(chatId);
1041
- }
1539
+ if (!chat) return null;
1042
1540
 
1043
- const msg = await window.WWebJS.sendMessage(chat, content, options);
1044
- return msg
1045
- ? window.WWebJS.getMessageModel(msg)
1046
- : undefined;
1047
- }, chatId, content, internalOptions, sendSeen);
1541
+ if (sendSeen) {
1542
+ await window.WWebJS.sendSeen(chatId);
1543
+ }
1048
1544
 
1049
- return sentMsg
1050
- ? new Message(this, sentMsg)
1051
- : undefined;
1545
+ const msg = await window.WWebJS.sendMessage(
1546
+ chat,
1547
+ content,
1548
+ options,
1549
+ );
1550
+ return msg ? window.WWebJS.getMessageModel(msg) : undefined;
1551
+ },
1552
+ chatId,
1553
+ content,
1554
+ internalOptions,
1555
+ sendSeen,
1556
+ );
1557
+
1558
+ return sentMsg ? new Message(this, sentMsg) : undefined;
1559
+ }
1560
+
1561
+ /**
1562
+ * Send an emoji reaction to a specific message
1563
+ * @param {string} messageId - Id of the message to add the reaction.
1564
+ * @param {string} reaction - Emoji to react with. Send an empty string to remove the reaction.
1565
+ * @return {Promise}
1566
+ */
1567
+ async sendReaction(messageId, reaction) {
1568
+ await this.pupPage.evaluate(
1569
+ async (messageId, reaction) => {
1570
+ if (!messageId) return null;
1571
+ const msg =
1572
+ window.require('WAWebCollections').Msg.get(messageId) ||
1573
+ (
1574
+ await window
1575
+ .require('WAWebCollections')
1576
+ .Msg.getMessagesById([messageId])
1577
+ )?.messages?.[0];
1578
+ if (!msg) return null;
1579
+ await window
1580
+ .require('WAWebSendReactionMsgAction')
1581
+ .sendReactionToMsg(msg, reaction);
1582
+ },
1583
+ messageId,
1584
+ reaction,
1585
+ );
1052
1586
  }
1053
1587
 
1054
1588
  /**
@@ -1060,34 +1594,45 @@ class Client extends EventEmitter {
1060
1594
  * Sends a channel admin invitation to a user, allowing them to become an admin of the channel
1061
1595
  * @param {string} chatId The ID of a user to send the channel admin invitation to
1062
1596
  * @param {string} channelId The ID of a channel for which the invitation is being sent
1063
- * @param {SendChannelAdminInviteOptions} options
1597
+ * @param {SendChannelAdminInviteOptions} options
1064
1598
  * @returns {Promise<boolean>} Returns true if an invitation was sent successfully, false otherwise
1065
1599
  */
1066
1600
  async sendChannelAdminInvite(chatId, channelId, options = {}) {
1067
- const response = await this.pupPage.evaluate(async (chatId, channelId, options) => {
1068
- const { createWid } = window.require('WAWebWidFactory');
1069
- const channelWid = createWid(channelId);
1070
- const chatWid = createWid(chatId);
1071
- const chat = window.require('WAWebCollections').Chat.get(chatWid) || (await (window.require('WAWebCollections')).Chat.find(chatWid));
1072
-
1073
- if (!chatWid.isUser()) {
1074
- return false;
1075
- }
1076
-
1077
- return await (window.require('WAWebNewsletterSendMsgAction')).sendNewsletterAdminInviteMessage(
1078
- chat,
1079
- {
1080
- newsletterWid: channelWid,
1081
- invitee: chatWid,
1082
- inviteMessage: options.comment,
1083
- base64Thumb: await window.WWebJS.getProfilePicThumbToBase64(channelWid)
1601
+ const response = await this.pupPage.evaluate(
1602
+ async (chatId, channelId, options) => {
1603
+ const { createWid } = window.require('WAWebWidFactory');
1604
+ const channelWid = createWid(channelId);
1605
+ const chatWid = createWid(chatId);
1606
+ const chat =
1607
+ window.require('WAWebCollections').Chat.get(chatWid) ||
1608
+ (await window
1609
+ .require('WAWebCollections')
1610
+ .Chat.find(chatWid));
1611
+
1612
+ if (!chatWid.isUser()) {
1613
+ return false;
1084
1614
  }
1085
- );
1086
- }, chatId, channelId, options);
1615
+
1616
+ return await window
1617
+ .require('WAWebNewsletterSendMsgAction')
1618
+ .sendNewsletterAdminInviteMessage(chat, {
1619
+ newsletterWid: channelWid,
1620
+ invitee: chatWid,
1621
+ inviteMessage: options.comment,
1622
+ base64Thumb:
1623
+ await window.WWebJS.getProfilePicThumbToBase64(
1624
+ channelWid,
1625
+ ),
1626
+ });
1627
+ },
1628
+ chatId,
1629
+ channelId,
1630
+ options,
1631
+ );
1087
1632
 
1088
1633
  return response.messageSendResult === 'OK';
1089
1634
  }
1090
-
1635
+
1091
1636
  /**
1092
1637
  * Searches for messages
1093
1638
  * @param {string} query
@@ -1098,12 +1643,22 @@ class Client extends EventEmitter {
1098
1643
  * @returns {Promise<Message[]>}
1099
1644
  */
1100
1645
  async searchMessages(query, options = {}) {
1101
- const messages = await this.pupPage.evaluate(async (query, page, count, remote) => {
1102
- const { messages } = await (window.require('WAWebCollections')).Msg.search(query, page, count, remote);
1103
- return messages.map(msg => window.WWebJS.getMessageModel(msg));
1104
- }, query, options.page, options.limit, options.chatId);
1646
+ const messages = await this.pupPage.evaluate(
1647
+ async (query, page, count, remote) => {
1648
+ const { messages } = await window
1649
+ .require('WAWebCollections')
1650
+ .Msg.search(query, page, count, remote);
1651
+ return messages.map((msg) =>
1652
+ window.WWebJS.getMessageModel(msg),
1653
+ );
1654
+ },
1655
+ query,
1656
+ options.page,
1657
+ options.limit,
1658
+ options.chatId,
1659
+ );
1105
1660
 
1106
- return messages.map(msg => new Message(this, msg));
1661
+ return messages.map((msg) => new Message(this, msg));
1107
1662
  }
1108
1663
 
1109
1664
  /**
@@ -1115,7 +1670,7 @@ class Client extends EventEmitter {
1115
1670
  return await window.WWebJS.getChats();
1116
1671
  });
1117
1672
 
1118
- return chats.map(chat => ChatFactory.create(this, chat));
1673
+ return chats.map((chat) => ChatFactory.create(this, chat));
1119
1674
  }
1120
1675
 
1121
1676
  /**
@@ -1132,16 +1687,14 @@ class Client extends EventEmitter {
1132
1687
 
1133
1688
  /**
1134
1689
  * Gets chat or channel instance by ID
1135
- * @param {string} chatId
1690
+ * @param {string} chatId
1136
1691
  * @returns {Promise<Chat|Channel>}
1137
1692
  */
1138
1693
  async getChatById(chatId) {
1139
- const chat = await this.pupPage.evaluate(async chatId => {
1694
+ const chat = await this.pupPage.evaluate(async (chatId) => {
1140
1695
  return await window.WWebJS.getChat(chatId);
1141
1696
  }, chatId);
1142
- return chat
1143
- ? ChatFactory.create(this, chat)
1144
- : undefined;
1697
+ return chat ? ChatFactory.create(this, chat) : undefined;
1145
1698
  }
1146
1699
 
1147
1700
  /**
@@ -1153,7 +1706,8 @@ class Client extends EventEmitter {
1153
1706
  const channel = await this.pupPage.evaluate(async (inviteCode) => {
1154
1707
  let channelMetadata;
1155
1708
  try {
1156
- channelMetadata = await window.WWebJS.getChannelMetadata(inviteCode);
1709
+ channelMetadata =
1710
+ await window.WWebJS.getChannelMetadata(inviteCode);
1157
1711
  } catch (err) {
1158
1712
  if (err.name === 'ServerStatusCodeError') return null;
1159
1713
  throw err;
@@ -1161,9 +1715,7 @@ class Client extends EventEmitter {
1161
1715
  return await window.WWebJS.getChat(channelMetadata.id);
1162
1716
  }, inviteCode);
1163
1717
 
1164
- return channel
1165
- ? ChatFactory.create(this, channel)
1166
- : undefined;
1718
+ return channel ? ChatFactory.create(this, channel) : undefined;
1167
1719
  }
1168
1720
 
1169
1721
  /**
@@ -1175,7 +1727,7 @@ class Client extends EventEmitter {
1175
1727
  return window.WWebJS.getContacts();
1176
1728
  });
1177
1729
 
1178
- return contacts.map(contact => ContactFactory.create(this, contact));
1730
+ return contacts.map((contact) => ContactFactory.create(this, contact));
1179
1731
  }
1180
1732
 
1181
1733
  /**
@@ -1184,7 +1736,7 @@ class Client extends EventEmitter {
1184
1736
  * @returns {Promise<Contact>}
1185
1737
  */
1186
1738
  async getContactById(contactId) {
1187
- let contact = await this.pupPage.evaluate(contactId => {
1739
+ let contact = await this.pupPage.evaluate((contactId) => {
1188
1740
  return window.WWebJS.getContact(contactId);
1189
1741
  }, contactId);
1190
1742
 
@@ -1197,20 +1749,24 @@ class Client extends EventEmitter {
1197
1749
  * @returns {Promise<Message>}
1198
1750
  */
1199
1751
  async getMessageById(messageId) {
1200
- const msg = await this.pupPage.evaluate(async messageId => {
1201
- let msg = (window.require('WAWebCollections')).Msg.get(messageId);
1202
- if(msg) return window.WWebJS.getMessageModel(msg);
1752
+ const msg = await this.pupPage.evaluate(async (messageId) => {
1753
+ let msg = window.require('WAWebCollections').Msg.get(messageId);
1754
+ if (msg) return window.WWebJS.getMessageModel(msg);
1203
1755
 
1204
1756
  const params = messageId.split('_');
1205
- if (params.length !== 3 && params.length !== 4) throw new Error('Invalid serialized message id specified');
1757
+ if (params.length !== 3 && params.length !== 4)
1758
+ throw new Error('Invalid serialized message id specified');
1759
+
1760
+ let messagesObject = await window
1761
+ .require('WAWebCollections')
1762
+ .Msg.getMessagesById([messageId]);
1763
+ if (messagesObject && messagesObject.messages.length)
1764
+ msg = messagesObject.messages[0];
1206
1765
 
1207
- let messagesObject = await (window.require('WAWebCollections')).Msg.getMessagesById([messageId]);
1208
- if (messagesObject && messagesObject.messages.length) msg = messagesObject.messages[0];
1209
-
1210
- if(msg) return window.WWebJS.getMessageModel(msg);
1766
+ if (msg) return window.WWebJS.getMessageModel(msg);
1211
1767
  }, messageId);
1212
1768
 
1213
- if(msg) return new Message(this, msg);
1769
+ if (msg) return new Message(this, msg);
1214
1770
  return null;
1215
1771
  }
1216
1772
 
@@ -1222,23 +1778,36 @@ class Client extends EventEmitter {
1222
1778
  async getPinnedMessages(chatId) {
1223
1779
  const pinnedMsgs = await this.pupPage.evaluate(async (chatId) => {
1224
1780
  const chatWid = window.require('WAWebWidFactory').createWid(chatId);
1225
- const chat = (window.require('WAWebCollections')).Chat.get(chatWid) ?? await (window.require('WAWebCollections')).Chat.find(chatWid);
1781
+ const chat =
1782
+ window.require('WAWebCollections').Chat.get(chatWid) ??
1783
+ (await window.require('WAWebCollections').Chat.find(chatWid));
1226
1784
  if (!chat) return [];
1227
-
1228
- const msgs = await (window.require('WAWebPinInChatSchema')).getTable().equals(['chatId'], chatWid.toString());
1785
+
1786
+ const msgs = await window
1787
+ .require('WAWebPinInChatSchema')
1788
+ .getTable()
1789
+ .equals(['chatId'], chatWid.toString());
1229
1790
 
1230
1791
  const pinnedMsgs = (
1231
1792
  await Promise.all(
1232
- msgs.filter(msg => msg.pinType == 1).map(async (msg) => {
1233
- const res = await (window.require('WAWebCollections')).Msg.getMessagesById([msg.parentMsgKey]);
1234
- return res?.messages?.[0];
1235
- })
1793
+ msgs
1794
+ .filter((msg) => msg.pinType == 1)
1795
+ .map(async (msg) => {
1796
+ const res = await window
1797
+ .require('WAWebCollections')
1798
+ .Msg.getMessagesById([msg.parentMsgKey]);
1799
+ return res?.messages?.[0];
1800
+ }),
1236
1801
  )
1237
1802
  ).filter(Boolean);
1238
1803
 
1239
1804
  return !pinnedMsgs.length
1240
1805
  ? []
1241
- : await Promise.all(pinnedMsgs.map((msg) => window.WWebJS.getMessageModel(msg)));
1806
+ : await Promise.all(
1807
+ pinnedMsgs.map((msg) =>
1808
+ window.WWebJS.getMessageModel(msg),
1809
+ ),
1810
+ );
1242
1811
  }, chatId);
1243
1812
 
1244
1813
  return pinnedMsgs.map((msg) => new Message(this, msg));
@@ -1246,12 +1815,14 @@ class Client extends EventEmitter {
1246
1815
 
1247
1816
  /**
1248
1817
  * Returns an object with information about the invite code's group
1249
- * @param {string} inviteCode
1818
+ * @param {string} inviteCode
1250
1819
  * @returns {Promise<object>} Invite information
1251
1820
  */
1252
1821
  async getInviteInfo(inviteCode) {
1253
- return await this.pupPage.evaluate(inviteCode => {
1254
- return (window.require('WAWebGroupQueryJob')).queryGroupInvite(inviteCode);
1822
+ return await this.pupPage.evaluate((inviteCode) => {
1823
+ return window
1824
+ .require('WAWebGroupQueryJob')
1825
+ .queryGroupInvite(inviteCode);
1255
1826
  }, inviteCode);
1256
1827
  }
1257
1828
 
@@ -1261,8 +1832,10 @@ class Client extends EventEmitter {
1261
1832
  * @returns {Promise<string>} Id of the joined Chat
1262
1833
  */
1263
1834
  async acceptInvite(inviteCode) {
1264
- const res = await this.pupPage.evaluate(async inviteCode => {
1265
- return await (window.require('WAWebGroupQueryJob')).joinGroupViaInvite(inviteCode);
1835
+ const res = await this.pupPage.evaluate(async (inviteCode) => {
1836
+ return await window
1837
+ .require('WAWebGroupInviteJob')
1838
+ .joinGroupViaInvite(inviteCode);
1266
1839
  }, inviteCode);
1267
1840
 
1268
1841
  return res.gid._serialized;
@@ -1276,7 +1849,9 @@ class Client extends EventEmitter {
1276
1849
  async acceptChannelAdminInvite(channelId) {
1277
1850
  return await this.pupPage.evaluate(async (channelId) => {
1278
1851
  try {
1279
- await (window.require('WAWebMexAcceptNewsletterAdminInviteJob')).acceptNewsletterAdminInvite(channelId);
1852
+ await window
1853
+ .require('WAWebMexAcceptNewsletterAdminInviteJob')
1854
+ .acceptNewsletterAdminInvite(channelId);
1280
1855
  return true;
1281
1856
  } catch (err) {
1282
1857
  if (err.name === 'ServerStatusCodeError') return false;
@@ -1292,16 +1867,24 @@ class Client extends EventEmitter {
1292
1867
  * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
1293
1868
  */
1294
1869
  async revokeChannelAdminInvite(channelId, userId) {
1295
- return await this.pupPage.evaluate(async (channelId, userId) => {
1296
- try {
1297
- const userWid = window.require('WAWebWidFactory').createWid(userId);
1298
- await (window.require('WAWebMexRevokeNewsletterAdminInviteJob')).revokeNewsletterAdminInvite(channelId, userWid);
1299
- return true;
1300
- } catch (err) {
1301
- if (err.name === 'ServerStatusCodeError') return false;
1302
- throw err;
1303
- }
1304
- }, channelId, userId);
1870
+ return await this.pupPage.evaluate(
1871
+ async (channelId, userId) => {
1872
+ try {
1873
+ const userWid = window
1874
+ .require('WAWebWidFactory')
1875
+ .createWid(userId);
1876
+ await window
1877
+ .require('WAWebMexRevokeNewsletterAdminInviteJob')
1878
+ .revokeNewsletterAdminInvite(channelId, userWid);
1879
+ return true;
1880
+ } catch (err) {
1881
+ if (err.name === 'ServerStatusCodeError') return false;
1882
+ throw err;
1883
+ }
1884
+ },
1885
+ channelId,
1886
+ userId,
1887
+ );
1305
1888
  }
1306
1889
 
1307
1890
  /**
@@ -1311,16 +1894,24 @@ class Client extends EventEmitter {
1311
1894
  * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
1312
1895
  */
1313
1896
  async demoteChannelAdmin(channelId, userId) {
1314
- return await this.pupPage.evaluate(async (channelId, userId) => {
1315
- try {
1316
- const userWid = window.require('WAWebWidFactory').createWid(userId);
1317
- await (window.require('WAWebDemoteNewsletterAdminAction')).demoteNewsletterAdmin(channelId, userWid);
1318
- return true;
1319
- } catch (err) {
1320
- if (err.name === 'ServerStatusCodeError') return false;
1321
- throw err;
1322
- }
1323
- }, channelId, userId);
1897
+ return await this.pupPage.evaluate(
1898
+ async (channelId, userId) => {
1899
+ try {
1900
+ const userWid = window
1901
+ .require('WAWebWidFactory')
1902
+ .createWid(userId);
1903
+ await window
1904
+ .require('WAWebDemoteNewsletterAdminAction')
1905
+ .demoteNewsletterAdmin(channelId, userWid);
1906
+ return true;
1907
+ } catch (err) {
1908
+ if (err.name === 'ServerStatusCodeError') return false;
1909
+ throw err;
1910
+ }
1911
+ },
1912
+ channelId,
1913
+ userId,
1914
+ );
1324
1915
  }
1325
1916
 
1326
1917
  /**
@@ -1329,12 +1920,20 @@ class Client extends EventEmitter {
1329
1920
  * @returns {Promise<Object>}
1330
1921
  */
1331
1922
  async acceptGroupV4Invite(inviteInfo) {
1332
- if (!inviteInfo.inviteCode) throw 'Invalid invite code, try passing the message.inviteV4 object';
1923
+ if (!inviteInfo.inviteCode)
1924
+ throw 'Invalid invite code, try passing the message.inviteV4 object';
1333
1925
  if (inviteInfo.inviteCodeExp == 0) throw 'Expired invite code';
1334
- return this.pupPage.evaluate(async inviteInfo => {
1926
+ return this.pupPage.evaluate(async (inviteInfo) => {
1335
1927
  let { groupId, fromId, inviteCode, inviteCodeExp } = inviteInfo;
1336
1928
  let userWid = window.require('WAWebWidFactory').createWid(fromId);
1337
- return await (window.require('WAWebGroupInviteV4Job')).joinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, userWid);
1929
+ return await window
1930
+ .require('WAWebGroupInviteV4Job')
1931
+ .joinGroupViaInviteV4(
1932
+ inviteCode,
1933
+ String(inviteCodeExp),
1934
+ groupId,
1935
+ userWid,
1936
+ );
1338
1937
  }, inviteInfo);
1339
1938
  }
1340
1939
 
@@ -1343,30 +1942,35 @@ class Client extends EventEmitter {
1343
1942
  * @param {string} status New status message
1344
1943
  */
1345
1944
  async setStatus(status) {
1346
- await this.pupPage.evaluate(async status => {
1347
- return await (window.require('WAWebContactStatusBridge')).setMyStatus(status);
1945
+ await this.pupPage.evaluate(async (status) => {
1946
+ return await window
1947
+ .require('WAWebContactStatusBridge')
1948
+ .setMyStatus(status);
1348
1949
  }, status);
1349
1950
  }
1350
1951
 
1351
1952
  /**
1352
- * Sets the current user's display name.
1953
+ * Sets the current user's display name.
1353
1954
  * This is the name shown to WhatsApp users that have not added you as a contact beside your number in groups and in your profile.
1354
1955
  * @param {string} displayName New display name
1355
1956
  * @returns {Promise<Boolean>}
1356
1957
  */
1357
1958
  async setDisplayName(displayName) {
1358
- const couldSet = await this.pupPage.evaluate(async displayName => {
1359
- if(!(window.require('WAWebConnModel').Conn).canSetMyPushname()) return false;
1360
- await (window.require('WAWebSetPushnameConnAction')).setPushname(displayName);
1959
+ const couldSet = await this.pupPage.evaluate(async (displayName) => {
1960
+ if (!window.require('WAWebConnModel').Conn.canSetMyPushname())
1961
+ return false;
1962
+ await window
1963
+ .require('WAWebSetPushnameConnAction')
1964
+ .setPushname(displayName);
1361
1965
  return true;
1362
1966
  }, displayName);
1363
1967
 
1364
1968
  return couldSet;
1365
1969
  }
1366
-
1970
+
1367
1971
  /**
1368
1972
  * Gets the current connection state for the client
1369
- * @returns {WAState}
1973
+ * @returns {WAState}
1370
1974
  */
1371
1975
  async getState() {
1372
1976
  return await this.pupPage.evaluate(() => {
@@ -1379,7 +1983,9 @@ class Client extends EventEmitter {
1379
1983
  */
1380
1984
  async sendPresenceAvailable() {
1381
1985
  return await this.pupPage.evaluate(() => {
1382
- return window.require('WAWebPresenceChatAction').sendPresenceAvailable();
1986
+ return window
1987
+ .require('WAWebPresenceChatAction')
1988
+ .sendPresenceAvailable();
1383
1989
  });
1384
1990
  }
1385
1991
 
@@ -1388,7 +1994,9 @@ class Client extends EventEmitter {
1388
1994
  */
1389
1995
  async sendPresenceUnavailable() {
1390
1996
  return await this.pupPage.evaluate(() => {
1391
- return window.require('WAWebPresenceChatAction').sendPresenceUnavailable();
1997
+ return window
1998
+ .require('WAWebPresenceChatAction')
1999
+ .sendPresenceUnavailable();
1392
2000
  });
1393
2001
  }
1394
2002
 
@@ -1397,9 +2005,11 @@ class Client extends EventEmitter {
1397
2005
  * @returns {boolean}
1398
2006
  */
1399
2007
  async archiveChat(chatId) {
1400
- return await this.pupPage.evaluate(async chatId => {
1401
- let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
1402
- await (window.require('WAWebCmd').Cmd).archiveChat(chat, true);
2008
+ return await this.pupPage.evaluate(async (chatId) => {
2009
+ let chat = await window.WWebJS.getChat(chatId, {
2010
+ getAsModel: false,
2011
+ });
2012
+ await window.require('WAWebCmd').Cmd.archiveChat(chat, true);
1403
2013
  return true;
1404
2014
  }, chatId);
1405
2015
  }
@@ -1409,9 +2019,11 @@ class Client extends EventEmitter {
1409
2019
  * @returns {boolean}
1410
2020
  */
1411
2021
  async unarchiveChat(chatId) {
1412
- return await this.pupPage.evaluate(async chatId => {
1413
- let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
1414
- await (window.require('WAWebCmd').Cmd).archiveChat(chat, false);
2022
+ return await this.pupPage.evaluate(async (chatId) => {
2023
+ let chat = await window.WWebJS.getChat(chatId, {
2024
+ getAsModel: false,
2025
+ });
2026
+ await window.require('WAWebCmd').Cmd.archiveChat(chat, false);
1415
2027
  return false;
1416
2028
  }, chatId);
1417
2029
  }
@@ -1421,20 +2033,24 @@ class Client extends EventEmitter {
1421
2033
  * @returns {Promise<boolean>} New pin state. Could be false if the max number of pinned chats was reached.
1422
2034
  */
1423
2035
  async pinChat(chatId) {
1424
- return this.pupPage.evaluate(async chatId => {
1425
- let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
2036
+ return this.pupPage.evaluate(async (chatId) => {
2037
+ let chat = await window.WWebJS.getChat(chatId, {
2038
+ getAsModel: false,
2039
+ });
1426
2040
  if (chat.pin) {
1427
2041
  return true;
1428
2042
  }
1429
2043
  const MAX_PIN_COUNT = 3;
1430
- const chatModels = (window.require('WAWebCollections')).Chat.getModelsArray();
2044
+ const chatModels = window
2045
+ .require('WAWebCollections')
2046
+ .Chat.getModelsArray();
1431
2047
  if (chatModels.length > MAX_PIN_COUNT) {
1432
2048
  let maxPinned = chatModels[MAX_PIN_COUNT - 1].pin;
1433
2049
  if (maxPinned) {
1434
2050
  return false;
1435
2051
  }
1436
2052
  }
1437
- await (window.require('WAWebCmd').Cmd).pinChat(chat, true);
2053
+ await window.require('WAWebCmd').Cmd.pinChat(chat, true);
1438
2054
  return true;
1439
2055
  }, chatId);
1440
2056
  }
@@ -1444,12 +2060,14 @@ class Client extends EventEmitter {
1444
2060
  * @returns {Promise<boolean>} New pin state
1445
2061
  */
1446
2062
  async unpinChat(chatId) {
1447
- return this.pupPage.evaluate(async chatId => {
1448
- let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
2063
+ return this.pupPage.evaluate(async (chatId) => {
2064
+ let chat = await window.WWebJS.getChat(chatId, {
2065
+ getAsModel: false,
2066
+ });
1449
2067
  if (!chat.pin) {
1450
2068
  return false;
1451
2069
  }
1452
- await (window.require('WAWebCmd').Cmd).pinChat(chat, false);
2070
+ await window.require('WAWebCmd').Cmd.pinChat(chat, false);
1453
2071
  return false;
1454
2072
  }, chatId);
1455
2073
  }
@@ -1481,14 +2099,29 @@ class Client extends EventEmitter {
1481
2099
  * @param {number} unmuteDateTs Timestamp at which the chat will be unmuted
1482
2100
  * @returns {Promise<{isMuted: boolean, muteExpiration: number}>}
1483
2101
  */
1484
- async _muteUnmuteChat (chatId, action, unmuteDateTs) {
1485
- return this.pupPage.evaluate(async (chatId, action, unmuteDateTs) => {
1486
- const chat = (window.require('WAWebCollections')).Chat.get(chatId) ?? await (window.require('WAWebCollections')).Chat.find(chatId);
1487
- action === 'MUTE'
1488
- ? await chat.mute.mute({ expiration: unmuteDateTs, sendDevice: true })
1489
- : await chat.mute.unmute({ sendDevice: true });
1490
- return { isMuted: chat.mute.expiration !== 0, muteExpiration: chat.mute.expiration };
1491
- }, chatId, action, unmuteDateTs || -1);
2102
+ async _muteUnmuteChat(chatId, action, unmuteDateTs) {
2103
+ return this.pupPage.evaluate(
2104
+ async (chatId, action, unmuteDateTs) => {
2105
+ const chat =
2106
+ window.require('WAWebCollections').Chat.get(chatId) ??
2107
+ (await window
2108
+ .require('WAWebCollections')
2109
+ .Chat.find(chatId));
2110
+ action === 'MUTE'
2111
+ ? await chat.mute.mute({
2112
+ expiration: unmuteDateTs,
2113
+ sendDevice: true,
2114
+ })
2115
+ : await chat.mute.unmute({ sendDevice: true });
2116
+ return {
2117
+ isMuted: chat.mute.expiration !== 0,
2118
+ muteExpiration: chat.mute.expiration,
2119
+ };
2120
+ },
2121
+ chatId,
2122
+ action,
2123
+ unmuteDateTs || -1,
2124
+ );
1492
2125
  }
1493
2126
 
1494
2127
  /**
@@ -1496,9 +2129,11 @@ class Client extends EventEmitter {
1496
2129
  * @param {string} chatId ID of the chat that will be marked as unread
1497
2130
  */
1498
2131
  async markChatUnread(chatId) {
1499
- await this.pupPage.evaluate(async chatId => {
1500
- let chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
1501
- await (window.require('WAWebCmd').Cmd).markChatUnread(chat, true);
2132
+ await this.pupPage.evaluate(async (chatId) => {
2133
+ let chat = await window.WWebJS.getChat(chatId, {
2134
+ getAsModel: false,
2135
+ });
2136
+ await window.require('WAWebCmd').Cmd.markChatUnread(chat, true);
1502
2137
  }, chatId);
1503
2138
  }
1504
2139
 
@@ -1508,19 +2143,18 @@ class Client extends EventEmitter {
1508
2143
  * @returns {Promise<string>}
1509
2144
  */
1510
2145
  async getProfilePicUrl(contactId) {
1511
- const profilePic = await this.pupPage.evaluate(async contactId => {
2146
+ const profilePic = await this.pupPage.evaluate(async (contactId) => {
1512
2147
  try {
1513
- const chatWid = window.require('WAWebWidFactory').createWid(contactId);
1514
- const chat = await window.WWebJS.getChat(contactId, { getAsModel: false });
1515
- return window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0')
1516
- ? await window.require('WAWebContactProfilePicThumbBridge').profilePicFind(chatWid)
1517
- : await window.require('WAWebContactProfilePicThumbBridge').requestProfilePicFromServer(chat);
2148
+ const chat = await window.WWebJS.getChat(contactId);
2149
+ return await window
2150
+ .require('WAWebContactProfilePicThumbBridge')
2151
+ .requestProfilePicFromServer(chat);
1518
2152
  } catch (err) {
1519
- if(err.name === 'ServerStatusCodeError') return undefined;
2153
+ if (err.name === 'ServerStatusCodeError') return undefined;
1520
2154
  throw err;
1521
2155
  }
1522
2156
  }, contactId);
1523
-
2157
+
1524
2158
  return profilePic ? profilePic.eurl : undefined;
1525
2159
  }
1526
2160
 
@@ -1531,17 +2165,26 @@ class Client extends EventEmitter {
1531
2165
  */
1532
2166
  async getCommonGroups(contactId) {
1533
2167
  const commonGroups = await this.pupPage.evaluate(async (contactId) => {
1534
- let contact = (window.require('WAWebCollections')).Contact.get(contactId);
2168
+ let contact = window
2169
+ .require('WAWebCollections')
2170
+ .Contact.get(contactId);
1535
2171
  if (!contact) {
1536
- const wid = window.require('WAWebWidFactory').createWid(contactId);
1537
- const chatConstructor = (window.require('WAWebCollections')).Contact.getModelsArray().find(c=>!c.isGroup).constructor;
1538
- contact = new chatConstructor({id: wid});
2172
+ const wid = window
2173
+ .require('WAWebWidFactory')
2174
+ .createWid(contactId);
2175
+ const chatConstructor = window
2176
+ .require('WAWebCollections')
2177
+ .Contact.getModelsArray()
2178
+ .find((c) => !c.isGroup).constructor;
2179
+ contact = new chatConstructor({ id: wid });
1539
2180
  }
1540
2181
 
1541
2182
  if (contact.commonGroups) {
1542
2183
  return contact.commonGroups.serialize();
1543
2184
  }
1544
- const status = await (window.require('WAWebFindCommonGroupsContactAction').findCommonGroups)(contact);
2185
+ const status = await window
2186
+ .require('WAWebFindCommonGroupsContactAction')
2187
+ .findCommonGroups(contact);
1545
2188
  if (status) {
1546
2189
  return contact.commonGroups.serialize();
1547
2190
  }
@@ -1556,10 +2199,10 @@ class Client extends EventEmitter {
1556
2199
 
1557
2200
  /**
1558
2201
  * Force reset of connection state for the client
1559
- */
2202
+ */
1560
2203
  async resetState() {
1561
2204
  await this.pupPage.evaluate(() => {
1562
- window.require('WAWebSocketModel').Socket.reconnect();
2205
+ window.require('WAWebSocketModel').Socket.reconnect();
1563
2206
  });
1564
2207
  }
1565
2208
 
@@ -1573,7 +2216,7 @@ class Client extends EventEmitter {
1573
2216
  }
1574
2217
 
1575
2218
  /**
1576
- * Get the registered WhatsApp ID for a number.
2219
+ * Get the registered WhatsApp ID for a number.
1577
2220
  * Will return null if the number is not registered on WhatsApp.
1578
2221
  * @param {string} number Number or ID ("@c.us" will be automatically appended if not specified)
1579
2222
  * @returns {Promise<Object|null>}
@@ -1583,9 +2226,11 @@ class Client extends EventEmitter {
1583
2226
  number += '@c.us';
1584
2227
  }
1585
2228
 
1586
- return await this.pupPage.evaluate(async number => {
2229
+ return await this.pupPage.evaluate(async (number) => {
1587
2230
  const wid = window.require('WAWebWidFactory').createWid(number);
1588
- const result = await (window.require('WAWebQueryExistsJob').queryWidExists)(wid);
2231
+ const result = await window
2232
+ .require('WAWebQueryExistsJob')
2233
+ .queryWidExists(wid);
1589
2234
  if (!result || result.wid === undefined) return null;
1590
2235
  return result.wid;
1591
2236
  }, number);
@@ -1597,11 +2242,15 @@ class Client extends EventEmitter {
1597
2242
  * @returns {Promise<string>}
1598
2243
  */
1599
2244
  async getFormattedNumber(number) {
1600
- if (!number.endsWith('@s.whatsapp.net')) number = number.replace('c.us', 's.whatsapp.net');
1601
- if (!number.includes('@s.whatsapp.net')) number = `${number}@s.whatsapp.net`;
1602
-
1603
- return await this.pupPage.evaluate(async numberId => {
1604
- return window.require('WAWebPhoneUtils').formattedPhoneNumber(numberId);
2245
+ if (!number.endsWith('@s.whatsapp.net'))
2246
+ number = number.replace('c.us', 's.whatsapp.net');
2247
+ if (!number.includes('@s.whatsapp.net'))
2248
+ number = `${number}@s.whatsapp.net`;
2249
+
2250
+ return await this.pupPage.evaluate(async (numberId) => {
2251
+ return window
2252
+ .require('WAWebPhoneUtils')
2253
+ .formattedPhoneNumber(numberId);
1605
2254
  }, number);
1606
2255
  }
1607
2256
 
@@ -1613,7 +2262,7 @@ class Client extends EventEmitter {
1613
2262
  async getCountryCode(number) {
1614
2263
  number = number.replace(' ', '').replace('+', '').replace('@c.us', '');
1615
2264
 
1616
- return await this.pupPage.evaluate(async numberId => {
2265
+ return await this.pupPage.evaluate(async (numberId) => {
1617
2266
  return window.require('WAPhoneFindCC').findCC(numberId);
1618
2267
  }, number);
1619
2268
  }
@@ -1660,92 +2309,137 @@ class Client extends EventEmitter {
1660
2309
  */
1661
2310
  async createGroup(title, participants = [], options = {}) {
1662
2311
  !Array.isArray(participants) && (participants = [participants]);
1663
- participants.map(p => (p instanceof Contact) ? p.id._serialized : p);
1664
-
1665
- return await this.pupPage.evaluate(async (title, participants, options) => {
1666
- const {
1667
- messageTimer = 0,
1668
- parentGroupId,
1669
- autoSendInviteV4 = true,
1670
- comment = '',
1671
- } = options;
1672
- const participantData = {}, participantWids = [], failedParticipants = [];
1673
- let createGroupResult, parentGroupWid;
1674
-
1675
- const addParticipantResultCodes = {
1676
- default: 'An unknown error occupied while adding a participant',
1677
- 200: 'The participant was added successfully',
1678
- 403: 'The participant can be added by sending private invitation only',
1679
- 404: 'The phone number is not registered on WhatsApp'
1680
- };
1681
-
1682
- for (const participant of participants) {
1683
- const pWid = window.require('WAWebWidFactory').createWid(participant);
1684
- if ((await (window.require('WAWebQueryExistsJob').queryWidExists)(pWid))?.wid) {
1685
- participantWids.push({ phoneNumber: pWid });
2312
+ participants.map((p) => (p instanceof Contact ? p.id._serialized : p));
2313
+
2314
+ return await this.pupPage.evaluate(
2315
+ async (title, participants, options) => {
2316
+ const {
2317
+ messageTimer = 0,
2318
+ parentGroupId,
2319
+ autoSendInviteV4 = true,
2320
+ comment = '',
2321
+ } = options;
2322
+ const participantData = {},
2323
+ participantWids = [],
2324
+ failedParticipants = [];
2325
+ let createGroupResult, parentGroupWid;
2326
+
2327
+ const addParticipantResultCodes = {
2328
+ default:
2329
+ 'An unknown error occupied while adding a participant',
2330
+ 200: 'The participant was added successfully',
2331
+ 403: 'The participant can be added by sending private invitation only',
2332
+ 404: 'The phone number is not registered on WhatsApp',
2333
+ };
2334
+
2335
+ for (const participant of participants) {
2336
+ const pWid = window
2337
+ .require('WAWebWidFactory')
2338
+ .createWid(participant);
2339
+ if (
2340
+ (
2341
+ await window
2342
+ .require('WAWebQueryExistsJob')
2343
+ .queryWidExists(pWid)
2344
+ )?.wid
2345
+ ) {
2346
+ participantWids.push({ phoneNumber: pWid });
2347
+ } else failedParticipants.push(participant);
1686
2348
  }
1687
- else failedParticipants.push(participant);
1688
- }
1689
2349
 
1690
- parentGroupId && (parentGroupWid = window.require('WAWebWidFactory').createWid(parentGroupId));
2350
+ parentGroupId &&
2351
+ (parentGroupWid = window
2352
+ .require('WAWebWidFactory')
2353
+ .createWid(parentGroupId));
2354
+
2355
+ try {
2356
+ createGroupResult = await window
2357
+ .require('WAWebGroupCreateJob')
2358
+ .createGroup(
2359
+ {
2360
+ addressingModeOverride: 'lid',
2361
+ memberAddMode: options.memberAddMode ?? false,
2362
+ membershipApprovalMode:
2363
+ options.membershipApprovalMode ?? false,
2364
+ announce: options.announce ?? false,
2365
+ restrict:
2366
+ options.isRestrict !== undefined
2367
+ ? !options.isRestrict
2368
+ : false,
2369
+ ephemeralDuration: messageTimer,
2370
+ parentGroupId: parentGroupWid,
2371
+ title: title,
2372
+ },
2373
+ participantWids,
2374
+ );
2375
+ } catch (ignoredError) {
2376
+ return 'CreateGroupError: An unknown error occupied while creating a group';
2377
+ }
1691
2378
 
1692
- try {
1693
- createGroupResult = await (window.require('WAWebGroupCreateJob')).createGroup(
1694
- {
1695
- 'addressingModeOverride': 'lid',
1696
- 'memberAddMode': options.memberAddMode ?? false,
1697
- 'membershipApprovalMode': options.membershipApprovalMode ?? false,
1698
- 'announce': options.announce ?? false,
1699
- 'restrict': options.isRestrict !== undefined ? !options.isRestrict : false,
1700
- 'ephemeralDuration': messageTimer,
1701
- 'parentGroupId': parentGroupWid,
1702
- 'title': title,
1703
- },
1704
- participantWids
1705
- );
1706
- } catch (err) {
1707
- return 'CreateGroupError: An unknown error occupied while creating a group';
1708
- }
2379
+ for (const participant of createGroupResult.participants) {
2380
+ let isInviteV4Sent = false;
2381
+ participant.wid.server == 'lid' &&
2382
+ (participant.wid = window
2383
+ .require('WAWebApiContact')
2384
+ .getPhoneNumber(participant.wid));
2385
+ const participantId = participant.wid._serialized;
2386
+ const statusCode = participant.error || 200;
2387
+
2388
+ if (autoSendInviteV4 && statusCode === 403) {
2389
+ window
2390
+ .require('WAWebCollections')
2391
+ .Contact.gadd(participant.wid, { silent: true });
2392
+ const addParticipantResult = await window
2393
+ .require('WAWebChatSendMessages')
2394
+ .sendGroupInviteMessage(
2395
+ window
2396
+ .require('WAWebCollections')
2397
+ .Chat.get(participant.wid) ||
2398
+ (await window
2399
+ .require('WAWebCollections')
2400
+ .Chat.find(participant.wid)),
2401
+ createGroupResult.wid._serialized,
2402
+ createGroupResult.subject,
2403
+ participant.invite_code,
2404
+ participant.invite_code_exp,
2405
+ comment,
2406
+ await window.WWebJS.getProfilePicThumbToBase64(
2407
+ createGroupResult.wid,
2408
+ ),
2409
+ );
2410
+ isInviteV4Sent =
2411
+ addParticipantResult.messageSendResult === 'OK';
2412
+ }
1709
2413
 
1710
- for (const participant of createGroupResult.participants) {
1711
- let isInviteV4Sent = false;
1712
- participant.wid.server == 'lid' && (participant.wid = window.require('WAWebApiContact').getPhoneNumber(participant.wid));
1713
- const participantId = participant.wid._serialized;
1714
- const statusCode = participant.error || 200;
1715
-
1716
- if (autoSendInviteV4 && statusCode === 403) {
1717
- (window.require('WAWebCollections')).Contact.gadd(participant.wid, { silent: true });
1718
- const addParticipantResult = await (window.require('WAWebChatSendMessages')).sendGroupInviteMessage(
1719
- (window.require('WAWebCollections')).Chat.get(participant.wid) || await (window.require('WAWebCollections')).Chat.find(participant.wid),
1720
- createGroupResult.wid._serialized,
1721
- createGroupResult.subject,
1722
- participant.invite_code,
1723
- participant.invite_code_exp,
1724
- comment,
1725
- await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid)
1726
- );
1727
- isInviteV4Sent = addParticipantResult.messageSendResult === 'OK';
2414
+ participantData[participantId] = {
2415
+ statusCode: statusCode,
2416
+ message:
2417
+ addParticipantResultCodes[statusCode] ||
2418
+ addParticipantResultCodes.default,
2419
+ isGroupCreator: participant.type === 'superadmin',
2420
+ isInviteV4Sent: isInviteV4Sent,
2421
+ };
1728
2422
  }
1729
2423
 
1730
- participantData[participantId] = {
1731
- statusCode: statusCode,
1732
- message: addParticipantResultCodes[statusCode] || addParticipantResultCodes.default,
1733
- isGroupCreator: participant.type === 'superadmin',
1734
- isInviteV4Sent: isInviteV4Sent
1735
- };
1736
- }
2424
+ for (const f of failedParticipants) {
2425
+ participantData[f] = {
2426
+ statusCode: 404,
2427
+ message: addParticipantResultCodes[404],
2428
+ isGroupCreator: false,
2429
+ isInviteV4Sent: false,
2430
+ };
2431
+ }
1737
2432
 
1738
- for (const f of failedParticipants) {
1739
- participantData[f] = {
1740
- statusCode: 404,
1741
- message: addParticipantResultCodes[404],
1742
- isGroupCreator: false,
1743
- isInviteV4Sent: false
2433
+ return {
2434
+ title: title,
2435
+ gid: createGroupResult.wid,
2436
+ participants: participantData,
1744
2437
  };
1745
- }
1746
-
1747
- return { title: title, gid: createGroupResult.wid, participants: participantData };
1748
- }, title, participants, options);
2438
+ },
2439
+ title,
2440
+ participants,
2441
+ options,
2442
+ );
1749
2443
  }
1750
2444
 
1751
2445
  /**
@@ -1770,46 +2464,61 @@ class Client extends EventEmitter {
1770
2464
  /**
1771
2465
  * Creates a new channel
1772
2466
  * @param {string} title The channel name
1773
- * @param {CreateChannelOptions} options
2467
+ * @param {CreateChannelOptions} options
1774
2468
  * @returns {Promise<CreateChannelResult|string>} Returns an object that handles the result for the channel creation or an error message as a string
1775
2469
  */
1776
2470
  async createChannel(title, options = {}) {
1777
- return await this.pupPage.evaluate(async (title, options) => {
1778
- let response, { description = null, picture = null } = options;
1779
-
1780
- if (!(window.require('WAWebNewsletterGatingUtils')).isNewsletterCreationEnabled()) {
1781
- return 'CreateChannelError: A channel creation is not enabled';
1782
- }
2471
+ return await this.pupPage.evaluate(
2472
+ async (title, options) => {
2473
+ let response,
2474
+ { description = null, picture = null } = options;
2475
+
2476
+ if (
2477
+ !window
2478
+ .require('WAWebNewsletterGatingUtils')
2479
+ .isNewsletterCreationEnabled()
2480
+ ) {
2481
+ return 'CreateChannelError: A channel creation is not enabled';
2482
+ }
1783
2483
 
1784
- if (picture) {
1785
- picture = await window.WWebJS.cropAndResizeImage(picture, {
1786
- asDataUrl: true,
1787
- mimetype: 'image/jpeg',
1788
- size: 640,
1789
- quality: 1
1790
- });
1791
- }
2484
+ if (picture) {
2485
+ picture = await window.WWebJS.cropAndResizeImage(picture, {
2486
+ asDataUrl: true,
2487
+ mimetype: 'image/jpeg',
2488
+ size: 640,
2489
+ quality: 1,
2490
+ });
2491
+ }
1792
2492
 
1793
- try {
1794
- response = await (window.require('WAWebNewsletterCreateQueryJob')).createNewsletterQuery({
1795
- name: title,
1796
- description: description,
1797
- picture: picture,
1798
- });
1799
- } catch (err) {
1800
- if (err.name === 'ServerStatusCodeError') {
1801
- return 'CreateChannelError: An error occupied while creating a channel';
2493
+ try {
2494
+ response = await window
2495
+ .require('WAWebNewsletterCreateQueryJob')
2496
+ .createNewsletterQuery({
2497
+ name: title,
2498
+ description: description,
2499
+ picture: picture,
2500
+ });
2501
+ } catch (err) {
2502
+ if (err.name === 'ServerStatusCodeError') {
2503
+ return 'CreateChannelError: An error occupied while creating a channel';
2504
+ }
2505
+ throw err;
1802
2506
  }
1803
- throw err;
1804
- }
1805
2507
 
1806
- return {
1807
- title: title,
1808
- nid: window.require('WAWebJidToWid').newsletterJidToWid(response.idJid),
1809
- inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`,
1810
- createdAtTs: response.newsletterCreationTimeMetadataMixin.creationTimeValue
1811
- };
1812
- }, title, options);
2508
+ return {
2509
+ title: title,
2510
+ nid: window
2511
+ .require('WAWebJidToWid')
2512
+ .newsletterJidToWid(response.idJid),
2513
+ inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`,
2514
+ createdAtTs:
2515
+ response.newsletterCreationTimeMetadataMixin
2516
+ .creationTimeValue,
2517
+ };
2518
+ },
2519
+ title,
2520
+ options,
2521
+ );
1813
2522
  }
1814
2523
 
1815
2524
  /**
@@ -1819,7 +2528,10 @@ class Client extends EventEmitter {
1819
2528
  */
1820
2529
  async subscribeToChannel(channelId) {
1821
2530
  return await this.pupPage.evaluate(async (channelId) => {
1822
- return await window.WWebJS.subscribeToUnsubscribeFromChannel(channelId, 'Subscribe');
2531
+ return await window.WWebJS.subscribeToUnsubscribeFromChannel(
2532
+ channelId,
2533
+ 'Subscribe',
2534
+ );
1823
2535
  }, channelId);
1824
2536
  }
1825
2537
 
@@ -1836,9 +2548,17 @@ class Client extends EventEmitter {
1836
2548
  * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
1837
2549
  */
1838
2550
  async unsubscribeFromChannel(channelId, options) {
1839
- return await this.pupPage.evaluate(async (channelId, options) => {
1840
- return await window.WWebJS.subscribeToUnsubscribeFromChannel(channelId, 'Unsubscribe', options);
1841
- }, channelId, options);
2551
+ return await this.pupPage.evaluate(
2552
+ async (channelId, options) => {
2553
+ return await window.WWebJS.subscribeToUnsubscribeFromChannel(
2554
+ channelId,
2555
+ 'Unsubscribe',
2556
+ options,
2557
+ );
2558
+ },
2559
+ channelId,
2560
+ options,
2561
+ );
1842
2562
  }
1843
2563
 
1844
2564
  /**
@@ -1856,26 +2576,51 @@ class Client extends EventEmitter {
1856
2576
  * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
1857
2577
  */
1858
2578
  async transferChannelOwnership(channelId, newOwnerId, options = {}) {
1859
- return await this.pupPage.evaluate(async (channelId, newOwnerId, options) => {
1860
- const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
1861
- const newOwner = (window.require('WAWebCollections')).Contact.get(newOwnerId) || (await (window.require('WAWebCollections')).Contact.find(newOwnerId));
1862
- if (!channel.newsletterMetadata) {
1863
- await (window.require('WAWebCollections')).NewsletterMetadataCollection.update(channel.id);
1864
- }
1865
-
1866
- try {
1867
- await (window.require('WAWebChangeNewsletterOwnerAction')).changeNewsletterOwnerAction(channel, newOwner);
2579
+ return await this.pupPage.evaluate(
2580
+ async (channelId, newOwnerId, options) => {
2581
+ const channel = await window.WWebJS.getChat(channelId, {
2582
+ getAsModel: false,
2583
+ });
2584
+ const newOwner =
2585
+ window
2586
+ .require('WAWebCollections')
2587
+ .Contact.get(newOwnerId) ||
2588
+ (await window
2589
+ .require('WAWebCollections')
2590
+ .Contact.find(newOwnerId));
2591
+ if (!channel.newsletterMetadata) {
2592
+ await window
2593
+ .require('WAWebCollections')
2594
+ .NewsletterMetadataCollection.update(channel.id);
2595
+ }
1868
2596
 
1869
- if (options.shouldDismissSelfAsAdmin) {
1870
- const meContact = window.require('WAWebContactCollection').getMeContact();
1871
- meContact && (await (window.require('WAWebNewsletterDemoteAdminJob')).demoteNewsletterAdminAction(channel, meContact));
2597
+ try {
2598
+ await window
2599
+ .require('WAWebChangeNewsletterOwnerAction')
2600
+ .changeNewsletterOwnerAction(channel, newOwner);
2601
+
2602
+ if (options.shouldDismissSelfAsAdmin) {
2603
+ const meContact = window
2604
+ .require('WAWebContactCollection')
2605
+ .getMeContact();
2606
+ meContact &&
2607
+ (await window
2608
+ .require('WAWebNewsletterDemoteAdminJob')
2609
+ .demoteNewsletterAdminAction(
2610
+ channel,
2611
+ meContact,
2612
+ ));
2613
+ }
2614
+ } catch (ignoredError) {
2615
+ return false;
1872
2616
  }
1873
- } catch (error) {
1874
- return false;
1875
- }
1876
2617
 
1877
- return true;
1878
- }, channelId, newOwnerId, options);
2618
+ return true;
2619
+ },
2620
+ channelId,
2621
+ newOwnerId,
2622
+ options,
2623
+ );
1879
2624
  }
1880
2625
 
1881
2626
  /**
@@ -1898,51 +2643,78 @@ class Client extends EventEmitter {
1898
2643
  * @returns {Promise<Array<Channel>>} Returns an array of Channel objects or an empty array if no channels were found
1899
2644
  */
1900
2645
  async searchChannels(searchOptions = {}) {
1901
- return await this.pupPage.evaluate(async ({
1902
- searchText = '',
1903
- countryCodes = [],
1904
- skipSubscribedNewsletters = false,
1905
- view = 0,
1906
- limit = 50
1907
- }) => {
1908
- searchText = searchText.trim();
1909
- const currentRegion = window.require('WAWebL10N').getRegion();
1910
- if (countryCodes.length === 0) countryCodes[0] = currentRegion;
1911
- if (![0, 1, 2, 3].includes(view)) view = 0;
1912
-
1913
- const { countryCodesIso } = window.require('WAWebCountriesNativeCountryNames');
1914
-
1915
- countryCodes = countryCodes.length === 1 && countryCodes[0] === currentRegion
1916
- ? countryCodes
1917
- : countryCodes.filter((code) => Object.keys(countryCodesIso).includes(code));
1918
-
1919
- const viewTypeMapping = {
1920
- 0: 'RECOMMENDED',
1921
- 1: 'TRENDING',
1922
- 2: 'POPULAR',
1923
- 3: 'NEW'
1924
- };
1925
-
1926
- searchOptions = {
1927
- searchText: searchText,
1928
- countryCodes: countryCodes,
1929
- skipSubscribedNewsletters: skipSubscribedNewsletters,
1930
- view: viewTypeMapping[view],
1931
- categories: [],
1932
- cursorToken: ''
1933
- };
1934
-
1935
- const originalFunction = window.require('WAWebNewsletterGatingUtils').getNewsletterDirectoryPageSize;
1936
- limit !== 50 && (window.require('WAWebNewsletterGatingUtils').getNewsletterDirectoryPageSize = () => limit);
1937
-
1938
- const channels = (await (window.require('WAWebNewsletterDirectorySearchAction')).fetchNewsletterDirectories(searchOptions)).newsletters;
1939
-
1940
- limit !== 50 && (window.require('WAWebNewsletterGatingUtils').getNewsletterDirectoryPageSize = originalFunction);
1941
-
1942
- return channels
1943
- ? await Promise.all(channels.map((channel) => window.WWebJS.getChatModel(channel, { isChannel: true })))
1944
- : [];
1945
- }, searchOptions);
2646
+ return await this.pupPage.evaluate(
2647
+ async ({
2648
+ searchText = '',
2649
+ countryCodes = [],
2650
+ skipSubscribedNewsletters = false,
2651
+ view = 0,
2652
+ limit = 50,
2653
+ }) => {
2654
+ searchText = searchText.trim();
2655
+ const currentRegion = window.require('WAWebL10N').getRegion();
2656
+ if (countryCodes.length === 0) countryCodes[0] = currentRegion;
2657
+ if (![0, 1, 2, 3].includes(view)) view = 0;
2658
+
2659
+ const { countryCodesIso } = window.require(
2660
+ 'WAWebCountriesNativeCountryNames',
2661
+ );
2662
+
2663
+ countryCodes =
2664
+ countryCodes.length === 1 &&
2665
+ countryCodes[0] === currentRegion
2666
+ ? countryCodes
2667
+ : countryCodes.filter((code) =>
2668
+ Object.keys(countryCodesIso).includes(code),
2669
+ );
2670
+
2671
+ const viewTypeMapping = {
2672
+ 0: 'RECOMMENDED',
2673
+ 1: 'TRENDING',
2674
+ 2: 'POPULAR',
2675
+ 3: 'NEW',
2676
+ };
2677
+
2678
+ searchOptions = {
2679
+ searchText: searchText,
2680
+ countryCodes: countryCodes,
2681
+ skipSubscribedNewsletters: skipSubscribedNewsletters,
2682
+ view: viewTypeMapping[view],
2683
+ categories: [],
2684
+ cursorToken: '',
2685
+ };
2686
+
2687
+ const originalFunction = window.require(
2688
+ 'WAWebNewsletterGatingUtils',
2689
+ ).getNewsletterDirectoryPageSize;
2690
+ limit !== 50 &&
2691
+ (window.require(
2692
+ 'WAWebNewsletterGatingUtils',
2693
+ ).getNewsletterDirectoryPageSize = () => limit);
2694
+
2695
+ const channels = (
2696
+ await window
2697
+ .require('WAWebNewsletterDirectorySearchAction')
2698
+ .fetchNewsletterDirectories(searchOptions)
2699
+ ).newsletters;
2700
+
2701
+ limit !== 50 &&
2702
+ (window.require(
2703
+ 'WAWebNewsletterGatingUtils',
2704
+ ).getNewsletterDirectoryPageSize = originalFunction);
2705
+
2706
+ return channels
2707
+ ? await Promise.all(
2708
+ channels.map((channel) =>
2709
+ window.WWebJS.getChatModel(channel, {
2710
+ isChannel: true,
2711
+ }),
2712
+ ),
2713
+ )
2714
+ : [];
2715
+ },
2716
+ searchOptions,
2717
+ );
1946
2718
  }
1947
2719
 
1948
2720
  /**
@@ -1952,10 +2724,14 @@ class Client extends EventEmitter {
1952
2724
  */
1953
2725
  async deleteChannel(channelId) {
1954
2726
  return await this.pupPage.evaluate(async (channelId) => {
1955
- const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
2727
+ const channel = await window.WWebJS.getChat(channelId, {
2728
+ getAsModel: false,
2729
+ });
1956
2730
  if (!channel) return false;
1957
2731
  try {
1958
- await (window.require('WAWebNewsletterDeleteAction')).deleteNewsletterAction(channel);
2732
+ await window
2733
+ .require('WAWebNewsletterDeleteAction')
2734
+ .deleteNewsletterAction(channel);
1959
2735
  return true;
1960
2736
  } catch (err) {
1961
2737
  if (err.name === 'ServerStatusCodeError') return false;
@@ -1973,7 +2749,7 @@ class Client extends EventEmitter {
1973
2749
  return window.WWebJS.getLabels();
1974
2750
  });
1975
2751
 
1976
- return labels.map(data => new Label(this, data));
2752
+ return labels.map((data) => new Label(this, data));
1977
2753
  }
1978
2754
 
1979
2755
  /**
@@ -1984,7 +2760,7 @@ class Client extends EventEmitter {
1984
2760
  const broadcasts = await this.pupPage.evaluate(async () => {
1985
2761
  return window.WWebJS.getAllStatuses();
1986
2762
  });
1987
- return broadcasts.map(data => new Broadcast(this, data));
2763
+ return broadcasts.map((data) => new Broadcast(this, data));
1988
2764
  }
1989
2765
 
1990
2766
  /**
@@ -1996,9 +2772,11 @@ class Client extends EventEmitter {
1996
2772
  const broadcast = await this.pupPage.evaluate(async (userId) => {
1997
2773
  let status;
1998
2774
  try {
1999
- status = (window.require('WAWebCollections')).Status.get(userId);
2775
+ status = window.require('WAWebCollections').Status.get(userId);
2000
2776
  if (!status) {
2001
- status = await (window.require('WAWebCollections')).Status.find(userId);
2777
+ status = await window
2778
+ .require('WAWebCollections')
2779
+ .Status.find(userId);
2002
2780
  }
2003
2781
  } catch {
2004
2782
  status = null;
@@ -2016,17 +2794,26 @@ class Client extends EventEmitter {
2016
2794
  */
2017
2795
  async revokeStatusMessage(messageId) {
2018
2796
  return await this.pupPage.evaluate(async (msgId) => {
2019
- const status = (window.require('WAWebCollections')).Status.getMyStatus();
2797
+ const status = window
2798
+ .require('WAWebCollections')
2799
+ .Status.getMyStatus();
2020
2800
  if (!status) return;
2021
2801
 
2022
2802
  const msg =
2023
- (window.require('WAWebCollections')).Msg.get(msgId) || (await (window.require('WAWebCollections')).Msg.getMessagesById([msgId]))?.messages?.[0];
2803
+ window.require('WAWebCollections').Msg.get(msgId) ||
2804
+ (
2805
+ await window
2806
+ .require('WAWebCollections')
2807
+ .Msg.getMessagesById([msgId])
2808
+ )?.messages?.[0];
2024
2809
  if (!msg) return;
2025
2810
 
2026
2811
  if (!msg.id.fromMe || !msg.id.remote.isStatus())
2027
2812
  throw 'Invalid usage! Can only revoke the message its from own status broadcast';
2028
2813
 
2029
- return await (window.require('WAWebRevokeStatusAction')).sendStatusRevokeMsgAction(status, msg);
2814
+ return await window
2815
+ .require('WAWebRevokeStatusAction')
2816
+ .sendStatusRevokeMsgAction(status, msg);
2030
2817
  }, messageId);
2031
2818
  }
2032
2819
 
@@ -2044,7 +2831,7 @@ class Client extends EventEmitter {
2044
2831
  }
2045
2832
 
2046
2833
  /**
2047
- * Get all Labels assigned to a chat
2834
+ * Get all Labels assigned to a chat
2048
2835
  * @param {string} chatId
2049
2836
  * @returns {Promise<Array<Label>>}
2050
2837
  */
@@ -2053,7 +2840,7 @@ class Client extends EventEmitter {
2053
2840
  return window.WWebJS.getChatLabels(chatId);
2054
2841
  }, chatId);
2055
2842
 
2056
- return labels.map(data => new Label(this, data));
2843
+ return labels.map((data) => new Label(this, data));
2057
2844
  }
2058
2845
 
2059
2846
  /**
@@ -2063,7 +2850,7 @@ class Client extends EventEmitter {
2063
2850
  */
2064
2851
  async getChatsByLabelId(labelId) {
2065
2852
  const chatIds = await this.pupPage.evaluate(async (labelId) => {
2066
- const label = (window.require('WAWebCollections')).Label.get(labelId);
2853
+ const label = window.require('WAWebCollections').Label.get(labelId);
2067
2854
  const labelItems = label.labelItemCollection.getModelsArray();
2068
2855
  return labelItems.reduce((result, item) => {
2069
2856
  if (item.parentType === 'Chat') {
@@ -2073,7 +2860,7 @@ class Client extends EventEmitter {
2073
2860
  }, []);
2074
2861
  }, labelId);
2075
2862
 
2076
- return Promise.all(chatIds.map(id => this.getChatById(id)));
2863
+ return Promise.all(chatIds.map((id) => this.getChatById(id)));
2077
2864
  }
2078
2865
 
2079
2866
  /**
@@ -2082,11 +2869,18 @@ class Client extends EventEmitter {
2082
2869
  */
2083
2870
  async getBlockedContacts() {
2084
2871
  const blockedContacts = await this.pupPage.evaluate(() => {
2085
- let chatIds = (window.require('WAWebCollections')).Blocklist.getModelsArray().map(a => a.id._serialized);
2086
- return Promise.all(chatIds.map(id => window.WWebJS.getContact(id)));
2872
+ let chatIds = window
2873
+ .require('WAWebCollections')
2874
+ .Blocklist.getModelsArray()
2875
+ .map((a) => a.id._serialized);
2876
+ return Promise.all(
2877
+ chatIds.map((id) => window.WWebJS.getContact(id)),
2878
+ );
2087
2879
  });
2088
2880
 
2089
- return blockedContacts.map(contact => ContactFactory.create(this.client, contact));
2881
+ return blockedContacts.map((contact) =>
2882
+ ContactFactory.create(this.client, contact),
2883
+ );
2090
2884
  }
2091
2885
 
2092
2886
  /**
@@ -2095,9 +2889,13 @@ class Client extends EventEmitter {
2095
2889
  * @returns {Promise<boolean>} Returns true if the picture was properly updated.
2096
2890
  */
2097
2891
  async setProfilePicture(media) {
2098
- const success = await this.pupPage.evaluate((chatid, media) => {
2099
- return window.WWebJS.setPicture(chatid, media);
2100
- }, this.info.wid._serialized, media);
2892
+ const success = await this.pupPage.evaluate(
2893
+ (chatid, media) => {
2894
+ return window.WWebJS.setPicture(chatid, media);
2895
+ },
2896
+ this.info.wid._serialized,
2897
+ media,
2898
+ );
2101
2899
 
2102
2900
  return success;
2103
2901
  }
@@ -2113,7 +2911,7 @@ class Client extends EventEmitter {
2113
2911
 
2114
2912
  return success;
2115
2913
  }
2116
-
2914
+
2117
2915
  /**
2118
2916
  * Change labels in chats
2119
2917
  * @param {Array<number|string>} labelIds
@@ -2121,26 +2919,42 @@ class Client extends EventEmitter {
2121
2919
  * @returns {Promise<void>}
2122
2920
  */
2123
2921
  async addOrRemoveLabels(labelIds, chatIds) {
2922
+ return this.pupPage.evaluate(
2923
+ async (labelIds, chatIds) => {
2924
+ if (
2925
+ ['smba', 'smbi'].indexOf(
2926
+ window.require('WAWebConnModel').Conn.platform,
2927
+ ) === -1
2928
+ ) {
2929
+ throw '[LT01] Only Whatsapp business';
2930
+ }
2931
+ const labels = window.WWebJS.getLabels().filter(
2932
+ (e) => labelIds.find((l) => l == e.id) !== undefined,
2933
+ );
2934
+ const chats = window
2935
+ .require('WAWebCollections')
2936
+ .Chat.filter((e) => chatIds.includes(e.id._serialized));
2124
2937
 
2125
- return this.pupPage.evaluate(async (labelIds, chatIds) => {
2126
- if (['smba', 'smbi'].indexOf((window.require('WAWebConnModel').Conn).platform) === -1) {
2127
- throw '[LT01] Only Whatsapp business';
2128
- }
2129
- const labels = window.WWebJS.getLabels().filter(e => labelIds.find(l => l == e.id) !== undefined);
2130
- const chats = (window.require('WAWebCollections')).Chat.filter(e => chatIds.includes(e.id._serialized));
2131
-
2132
- let actions = labels.map(label => ({id: label.id, type: 'add'}));
2938
+ let actions = labels.map((label) => ({
2939
+ id: label.id,
2940
+ type: 'add',
2941
+ }));
2133
2942
 
2134
- chats.forEach(chat => {
2135
- (chat.labels || []).forEach(n => {
2136
- if (!actions.find(e => e.id == n)) {
2137
- actions.push({id: n, type: 'remove'});
2138
- }
2943
+ chats.forEach((chat) => {
2944
+ (chat.labels || []).forEach((n) => {
2945
+ if (!actions.find((e) => e.id == n)) {
2946
+ actions.push({ id: n, type: 'remove' });
2947
+ }
2948
+ });
2139
2949
  });
2140
- });
2141
2950
 
2142
- return await (window.require('WAWebCollections')).Label.addOrRemoveLabels(actions, chats);
2143
- }, labelIds, chatIds);
2951
+ return await window
2952
+ .require('WAWebCollections')
2953
+ .Label.addOrRemoveLabels(actions, chats);
2954
+ },
2955
+ labelIds,
2956
+ chatIds,
2957
+ );
2144
2958
  }
2145
2959
 
2146
2960
  /**
@@ -2160,8 +2974,12 @@ class Client extends EventEmitter {
2160
2974
  */
2161
2975
  async getGroupMembershipRequests(groupId) {
2162
2976
  return await this.pupPage.evaluate(async (groupId) => {
2163
- const groupWid = window.require('WAWebWidFactory').createWid(groupId);
2164
- return await (window.require('WAWebApiMembershipApprovalRequestStore')).getMembershipApprovalRequests(groupWid);
2977
+ const groupWid = window
2978
+ .require('WAWebWidFactory')
2979
+ .createWid(groupId);
2980
+ return await window
2981
+ .require('WAWebApiMembershipApprovalRequestStore')
2982
+ .getMembershipApprovalRequests(groupWid);
2165
2983
  }, groupId);
2166
2984
  }
2167
2985
 
@@ -2187,10 +3005,19 @@ class Client extends EventEmitter {
2187
3005
  * @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned
2188
3006
  */
2189
3007
  async approveGroupMembershipRequests(groupId, options = {}) {
2190
- return await this.pupPage.evaluate(async (groupId, options) => {
2191
- const { requesterIds = null, sleep = [250, 500] } = options;
2192
- return await window.WWebJS.membershipRequestAction(groupId, 'Approve', requesterIds, sleep);
2193
- }, groupId, options);
3008
+ return await this.pupPage.evaluate(
3009
+ async (groupId, options) => {
3010
+ const { requesterIds = null, sleep = [250, 500] } = options;
3011
+ return await window.WWebJS.membershipRequestAction(
3012
+ groupId,
3013
+ 'Approve',
3014
+ requesterIds,
3015
+ sleep,
3016
+ );
3017
+ },
3018
+ groupId,
3019
+ options,
3020
+ );
2194
3021
  }
2195
3022
 
2196
3023
  /**
@@ -2200,24 +3027,36 @@ class Client extends EventEmitter {
2200
3027
  * @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned
2201
3028
  */
2202
3029
  async rejectGroupMembershipRequests(groupId, options = {}) {
2203
- return await this.pupPage.evaluate(async (groupId, options) => {
2204
- const { requesterIds = null, sleep = [250, 500] } = options;
2205
- return await window.WWebJS.membershipRequestAction(groupId, 'Reject', requesterIds, sleep);
2206
- }, groupId, options);
3030
+ return await this.pupPage.evaluate(
3031
+ async (groupId, options) => {
3032
+ const { requesterIds = null, sleep = [250, 500] } = options;
3033
+ return await window.WWebJS.membershipRequestAction(
3034
+ groupId,
3035
+ 'Reject',
3036
+ requesterIds,
3037
+ sleep,
3038
+ );
3039
+ },
3040
+ groupId,
3041
+ options,
3042
+ );
2207
3043
  }
2208
3044
 
2209
-
2210
3045
  /**
2211
3046
  * Setting autoload download audio
2212
3047
  * @param {boolean} flag true/false
2213
3048
  */
2214
3049
  async setAutoDownloadAudio(flag) {
2215
- await this.pupPage.evaluate(async flag => {
2216
- const autoDownload = (window.require('WAWebUserPrefsGeneral')).getAutoDownloadAudio();
3050
+ await this.pupPage.evaluate(async (flag) => {
3051
+ const autoDownload = window
3052
+ .require('WAWebUserPrefsGeneral')
3053
+ .getAutoDownloadAudio();
2217
3054
  if (autoDownload === flag) {
2218
3055
  return flag;
2219
3056
  }
2220
- await (window.require('WAWebUserPrefsGeneral')).setAutoDownloadAudio(flag);
3057
+ await window
3058
+ .require('WAWebUserPrefsGeneral')
3059
+ .setAutoDownloadAudio(flag);
2221
3060
  return flag;
2222
3061
  }, flag);
2223
3062
  }
@@ -2227,12 +3066,16 @@ class Client extends EventEmitter {
2227
3066
  * @param {boolean} flag true/false
2228
3067
  */
2229
3068
  async setAutoDownloadDocuments(flag) {
2230
- await this.pupPage.evaluate(async flag => {
2231
- const autoDownload = (window.require('WAWebUserPrefsGeneral')).getAutoDownloadDocuments();
3069
+ await this.pupPage.evaluate(async (flag) => {
3070
+ const autoDownload = window
3071
+ .require('WAWebUserPrefsGeneral')
3072
+ .getAutoDownloadDocuments();
2232
3073
  if (autoDownload === flag) {
2233
3074
  return flag;
2234
3075
  }
2235
- await (window.require('WAWebUserPrefsGeneral')).setAutoDownloadDocuments(flag);
3076
+ await window
3077
+ .require('WAWebUserPrefsGeneral')
3078
+ .setAutoDownloadDocuments(flag);
2236
3079
  return flag;
2237
3080
  }, flag);
2238
3081
  }
@@ -2242,12 +3085,16 @@ class Client extends EventEmitter {
2242
3085
  * @param {boolean} flag true/false
2243
3086
  */
2244
3087
  async setAutoDownloadPhotos(flag) {
2245
- await this.pupPage.evaluate(async flag => {
2246
- const autoDownload = (window.require('WAWebUserPrefsGeneral')).getAutoDownloadPhotos();
3088
+ await this.pupPage.evaluate(async (flag) => {
3089
+ const autoDownload = window
3090
+ .require('WAWebUserPrefsGeneral')
3091
+ .getAutoDownloadPhotos();
2247
3092
  if (autoDownload === flag) {
2248
3093
  return flag;
2249
3094
  }
2250
- await (window.require('WAWebUserPrefsGeneral')).setAutoDownloadPhotos(flag);
3095
+ await window
3096
+ .require('WAWebUserPrefsGeneral')
3097
+ .setAutoDownloadPhotos(flag);
2251
3098
  return flag;
2252
3099
  }, flag);
2253
3100
  }
@@ -2257,12 +3104,16 @@ class Client extends EventEmitter {
2257
3104
  * @param {boolean} flag true/false
2258
3105
  */
2259
3106
  async setAutoDownloadVideos(flag) {
2260
- await this.pupPage.evaluate(async flag => {
2261
- const autoDownload = (window.require('WAWebUserPrefsGeneral')).getAutoDownloadVideos();
3107
+ await this.pupPage.evaluate(async (flag) => {
3108
+ const autoDownload = window
3109
+ .require('WAWebUserPrefsGeneral')
3110
+ .getAutoDownloadVideos();
2262
3111
  if (autoDownload === flag) {
2263
3112
  return flag;
2264
3113
  }
2265
- await (window.require('WAWebUserPrefsGeneral')).setAutoDownloadVideos(flag);
3114
+ await window
3115
+ .require('WAWebUserPrefsGeneral')
3116
+ .setAutoDownloadVideos(flag);
2266
3117
  return flag;
2267
3118
  }, flag);
2268
3119
  }
@@ -2274,16 +3125,20 @@ class Client extends EventEmitter {
2274
3125
  * @returns {Promise<boolean>}
2275
3126
  */
2276
3127
  async setBackgroundSync(flag) {
2277
- return await this.pupPage.evaluate(async flag => {
2278
- const backSync = (window.require('WAWebUserPrefsNotifications')).getGlobalOfflineNotifications();
3128
+ return await this.pupPage.evaluate(async (flag) => {
3129
+ const backSync = window
3130
+ .require('WAWebUserPrefsNotifications')
3131
+ .getGlobalOfflineNotifications();
2279
3132
  if (backSync === flag) {
2280
3133
  return flag;
2281
3134
  }
2282
- await (window.require('WAWebUserPrefsNotifications')).setGlobalOfflineNotifications(flag);
3135
+ await window
3136
+ .require('WAWebUserPrefsNotifications')
3137
+ .setGlobalOfflineNotifications(flag);
2283
3138
  return flag;
2284
3139
  }, flag);
2285
3140
  }
2286
-
3141
+
2287
3142
  /**
2288
3143
  * Get user device count by ID
2289
3144
  * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
@@ -2293,8 +3148,17 @@ class Client extends EventEmitter {
2293
3148
  */
2294
3149
  async getContactDeviceCount(userId) {
2295
3150
  return await this.pupPage.evaluate(async (userId) => {
2296
- const devices = await (window.require('WAWebApiDeviceList')).getDeviceIds([window.require('WAWebWidFactory').createWid(userId)]);
2297
- if (devices && devices.length && devices[0] != null && typeof devices[0].devices == 'object') {
3151
+ const devices = await window
3152
+ .require('WAWebApiDeviceList')
3153
+ .getDeviceIds([
3154
+ window.require('WAWebWidFactory').createWid(userId),
3155
+ ]);
3156
+ if (
3157
+ devices &&
3158
+ devices.length &&
3159
+ devices[0] != null &&
3160
+ typeof devices[0].devices == 'object'
3161
+ ) {
2298
3162
  return devices[0].devices.length;
2299
3163
  }
2300
3164
  return 0;
@@ -2309,17 +3173,21 @@ class Client extends EventEmitter {
2309
3173
  async syncHistory(chatId) {
2310
3174
  return await this.pupPage.evaluate(async (chatId) => {
2311
3175
  const chatWid = window.require('WAWebWidFactory').createWid(chatId);
2312
- const chat = (window.require('WAWebCollections')).Chat.get(chatWid) ?? (await (window.require('WAWebCollections')).Chat.find(chatWid));
3176
+ const chat =
3177
+ window.require('WAWebCollections').Chat.get(chatWid) ??
3178
+ (await window.require('WAWebCollections').Chat.find(chatWid));
2313
3179
  if (chat?.endOfHistoryTransferType === 0) {
2314
- await (window.require('WAWebSendNonMessageDataRequest')).sendPeerDataOperationRequest(3, {
2315
- chatId: chat.id
2316
- });
3180
+ await window
3181
+ .require('WAWebSendNonMessageDataRequest')
3182
+ .sendPeerDataOperationRequest(3, {
3183
+ chatId: chat.id,
3184
+ });
2317
3185
  return true;
2318
3186
  }
2319
3187
  return false;
2320
3188
  }, chatId);
2321
3189
  }
2322
-
3190
+
2323
3191
  /**
2324
3192
  * Generates a WhatsApp call link (video call or voice call)
2325
3193
  * @param {Date} startTime The start time of the call
@@ -2328,17 +3196,27 @@ class Client extends EventEmitter {
2328
3196
  */
2329
3197
  async createCallLink(startTime, callType) {
2330
3198
  if (!['video', 'voice'].includes(callType)) {
2331
- throw new class CreateCallLinkError extends Error {
2332
- constructor(m) { super(m); }
2333
- }('Invalid \'callType\' parameter value is provided. Valid values are: \'voice\' | \'video\'.');
3199
+ throw new (class CreateCallLinkError extends Error {
3200
+ constructor(m) {
3201
+ super(m);
3202
+ }
3203
+ })(
3204
+ "Invalid 'callType' parameter value is provided. Valid values are: 'voice' | 'video'.",
3205
+ );
2334
3206
  }
2335
3207
 
2336
3208
  startTime = Math.floor(startTime.getTime() / 1000);
2337
-
2338
- return await this.pupPage.evaluate(async (startTimeTs, callType) => {
2339
- const response = await (window.require('WAWebGenerateEventCallLink')).createEventCallLink(startTimeTs, callType);
2340
- return response ?? '';
2341
- }, startTime, callType);
3209
+
3210
+ return await this.pupPage.evaluate(
3211
+ async (startTimeTs, callType) => {
3212
+ const response = await window
3213
+ .require('WAWebGenerateEventCallLink')
3214
+ .createEventCallLink(startTimeTs, callType);
3215
+ return response ?? '';
3216
+ },
3217
+ startTime,
3218
+ callType,
3219
+ );
2342
3220
  }
2343
3221
 
2344
3222
  /**
@@ -2350,35 +3228,59 @@ class Client extends EventEmitter {
2350
3228
  async sendResponseToScheduledEvent(response, eventMessageId) {
2351
3229
  if (![0, 1, 2, 3].includes(response)) return false;
2352
3230
 
2353
- return await this.pupPage.evaluate(async (response, msgId) => {
2354
- const eventMsg = (window.require('WAWebCollections')).Msg.get(msgId) || (await (window.require('WAWebCollections')).Msg.getMessagesById([msgId]))?.messages?.[0];
2355
- if (!eventMsg) return false;
2356
-
2357
- await (window.require('WAWebSendEventResponseMsgAction')).sendEventResponseMsg(response, eventMsg);
2358
- return true;
2359
- }, response, eventMessageId);
3231
+ return await this.pupPage.evaluate(
3232
+ async (response, msgId) => {
3233
+ const eventMsg =
3234
+ window.require('WAWebCollections').Msg.get(msgId) ||
3235
+ (
3236
+ await window
3237
+ .require('WAWebCollections')
3238
+ .Msg.getMessagesById([msgId])
3239
+ )?.messages?.[0];
3240
+ if (!eventMsg) return false;
3241
+
3242
+ await window
3243
+ .require('WAWebSendEventResponseMsgAction')
3244
+ .sendEventResponseMsg(response, eventMsg);
3245
+ return true;
3246
+ },
3247
+ response,
3248
+ eventMessageId,
3249
+ );
2360
3250
  }
2361
-
3251
+
2362
3252
  /**
2363
3253
  * Save new contact to user's addressbook or edit the existing one
2364
3254
  * @param {string} phoneNumber The contact's phone number in a format "17182222222", where "1" is a country code
2365
- * @param {string} firstName
2366
- * @param {string} lastName
3255
+ * @param {string} firstName
3256
+ * @param {string} lastName
2367
3257
  * @param {boolean} [syncToAddressbook = false] If set to true, the contact will also be saved to the user's address book on their phone. False by default
2368
3258
  * @returns {Promise<void>}
2369
3259
  */
2370
- async saveOrEditAddressbookContact(phoneNumber, firstName, lastName, syncToAddressbook = false)
2371
- {
2372
- return await this.pupPage.evaluate(async (phoneNumber, firstName, lastName, syncToAddressbook) => {
2373
- return await (window.require('WAWebSaveContactAction')).saveContactAction({
2374
- 'firstName' : firstName,
2375
- 'lastName' : lastName,
2376
- 'phoneNumber' : phoneNumber,
2377
- 'prevPhoneNumber' : phoneNumber,
2378
- 'syncToAddressbook': syncToAddressbook,
2379
- 'username' : undefined
2380
- });
2381
- }, phoneNumber, firstName, lastName, syncToAddressbook);
3260
+ async saveOrEditAddressbookContact(
3261
+ phoneNumber,
3262
+ firstName,
3263
+ lastName,
3264
+ syncToAddressbook = false,
3265
+ ) {
3266
+ return await this.pupPage.evaluate(
3267
+ async (phoneNumber, firstName, lastName, syncToAddressbook) => {
3268
+ return await window
3269
+ .require('WAWebSaveContactAction')
3270
+ .saveContactAction({
3271
+ firstName: firstName,
3272
+ lastName: lastName,
3273
+ phoneNumber: phoneNumber,
3274
+ prevPhoneNumber: phoneNumber,
3275
+ syncToAddressbook: syncToAddressbook,
3276
+ username: undefined,
3277
+ });
3278
+ },
3279
+ phoneNumber,
3280
+ firstName,
3281
+ lastName,
3282
+ syncToAddressbook,
3283
+ );
2382
3284
  }
2383
3285
 
2384
3286
  /**
@@ -2386,11 +3288,14 @@ class Client extends EventEmitter {
2386
3288
  * @param {string} phoneNumber The contact's phone number in a format "17182222222", where "1" is a country code
2387
3289
  * @returns {Promise<void>}
2388
3290
  */
2389
- async deleteAddressbookContact(phoneNumber)
2390
- {
3291
+ async deleteAddressbookContact(phoneNumber) {
2391
3292
  return await this.pupPage.evaluate(async (phoneNumber) => {
2392
- const wid = window.require('WAWebWidFactory').createWid(phoneNumber);
2393
- return await (window.require('WAWebDeleteContactAction')).deleteContactAction({phoneNumber: wid});
3293
+ const wid = window
3294
+ .require('WAWebWidFactory')
3295
+ .createWid(phoneNumber);
3296
+ return await window
3297
+ .require('WAWebDeleteContactAction')
3298
+ .deleteContactAction({ phoneNumber: wid });
2394
3299
  }, phoneNumber);
2395
3300
  }
2396
3301
 
@@ -2403,14 +3308,17 @@ class Client extends EventEmitter {
2403
3308
  return await this.pupPage.evaluate(async (userIds) => {
2404
3309
  if (!Array.isArray(userIds)) userIds = [userIds];
2405
3310
 
2406
- return await Promise.all(userIds.map(async (userId) => {
2407
- const { lid, phone } = await window.WWebJS.enforceLidAndPnRetrieval(userId);
3311
+ return await Promise.all(
3312
+ userIds.map(async (userId) => {
3313
+ const { lid, phone } =
3314
+ await window.WWebJS.enforceLidAndPnRetrieval(userId);
2408
3315
 
2409
- return {
2410
- lid: lid?._serialized,
2411
- pn: phone?._serialized
2412
- };
2413
- }));
3316
+ return {
3317
+ lid: lid?._serialized,
3318
+ pn: phone?._serialized,
3319
+ };
3320
+ }),
3321
+ );
2414
3322
  }, userIds);
2415
3323
  }
2416
3324
 
@@ -2422,15 +3330,28 @@ class Client extends EventEmitter {
2422
3330
  * @returns {Promise<void>}
2423
3331
  */
2424
3332
  async addOrEditCustomerNote(userId, note) {
2425
- return await this.pupPage.evaluate(async (userId, note) => {
2426
- if (!(window.require('WAWebBizGatingUtils')).smbNotesV1Enabled()) return;
3333
+ return await this.pupPage.evaluate(
3334
+ async (userId, note) => {
3335
+ if (!window.require('WAWebBizGatingUtils').smbNotesV1Enabled())
3336
+ return;
2427
3337
 
2428
- return (window.require('WAWebNoteAction')).noteAddAction(
2429
- 'unstructured',
2430
- window.require('WAWebWidToJid').widToUserJid(window.require('WAWebWidFactory').createWid(userId)),
2431
- note
2432
- );
2433
- }, userId, note);
3338
+ return window
3339
+ .require('WAWebNoteAction')
3340
+ .noteAddAction(
3341
+ 'unstructured',
3342
+ window
3343
+ .require('WAWebWidToJid')
3344
+ .widToUserJid(
3345
+ window
3346
+ .require('WAWebWidFactory')
3347
+ .createWid(userId),
3348
+ ),
3349
+ note,
3350
+ );
3351
+ },
3352
+ userId,
3353
+ note,
3354
+ );
2434
3355
  }
2435
3356
 
2436
3357
  /**
@@ -2448,47 +3369,65 @@ class Client extends EventEmitter {
2448
3369
  */
2449
3370
  async getCustomerNote(userId) {
2450
3371
  return await this.pupPage.evaluate(async (userId) => {
2451
- if (!(window.require('WAWebBizGatingUtils')).smbNotesV1Enabled()) return null;
2452
-
2453
- const note = await (window.require('WAWebNoteAction')).retrieveOnlyNoteForChatJid(
2454
- window.require('WAWebWidToJid').widToUserJid(window.require('WAWebWidFactory').createWid(userId))
2455
- );
3372
+ if (!window.require('WAWebBizGatingUtils').smbNotesV1Enabled())
3373
+ return null;
3374
+
3375
+ const note = await window
3376
+ .require('WAWebNoteAction')
3377
+ .retrieveOnlyNoteForChatJid(
3378
+ window
3379
+ .require('WAWebWidToJid')
3380
+ .widToUserJid(
3381
+ window.require('WAWebWidFactory').createWid(userId),
3382
+ ),
3383
+ );
2456
3384
 
2457
3385
  let serialized = note?.serialize();
2458
3386
 
2459
3387
  if (!serialized) return null;
2460
3388
 
2461
- serialized.chatId = window.require('WAWebJidToWid').userJidToUserWid(serialized.chatJid)._serialized;
3389
+ serialized.chatId = window
3390
+ .require('WAWebJidToWid')
3391
+ .userJidToUserWid(serialized.chatJid)._serialized;
2462
3392
  delete serialized.chatJid;
2463
3393
 
2464
3394
  return serialized;
2465
3395
  }, userId);
2466
3396
  }
2467
-
3397
+
2468
3398
  /**
2469
3399
  * Get Poll Votes
2470
3400
  * @param {string} messageId
2471
- * @return {Promise<Array<PollVote>>}
3401
+ * @return {Promise<Array<PollVote>>}
2472
3402
  */
2473
3403
  async getPollVotes(messageId) {
2474
3404
  const msg = await this.getMessageById(messageId);
2475
3405
  if (!msg) return [];
2476
- if (msg.type != MessageTypes.POLL_CREATION) throw 'Invalid usage! Can only be used with a pollCreation message';
2477
-
2478
- const pollVotes = await this.pupPage.evaluate( async (msg) => {
2479
- const msgKey = (window.require('WAWebMsgKey')).fromString(msg.id._serialized);
2480
- let pollVotes = await (window.require('WAWebPollsVotesSchema')).getTable().equals(['parentMsgKey'], msgKey.toString());
2481
-
2482
- return pollVotes.map(item => {
3406
+ if (msg.type != MessageTypes.POLL_CREATION)
3407
+ throw 'Invalid usage! Can only be used with a pollCreation message';
3408
+
3409
+ const pollVotes = await this.pupPage.evaluate(async (msg) => {
3410
+ const msgKey = window
3411
+ .require('WAWebMsgKey')
3412
+ .fromString(msg.id._serialized);
3413
+ let pollVotes = await window
3414
+ .require('WAWebPollsVotesSchema')
3415
+ .getTable()
3416
+ .equals(['parentMsgKey'], msgKey.toString());
3417
+
3418
+ return pollVotes.map((item) => {
2483
3419
  const typedArray = new Uint8Array(item.selectedOptionLocalIds);
2484
3420
  return {
2485
3421
  ...item,
2486
- selectedOptionLocalIds: Array.from(typedArray)
3422
+ selectedOptionLocalIds: Array.from(typedArray),
2487
3423
  };
2488
3424
  });
2489
3425
  }, msg);
2490
3426
 
2491
- return pollVotes.map((pollVote) => new PollVote(this.client, {...pollVote, parentMessage: msg}));
3427
+ return pollVotes.map(
3428
+ (pollVote) =>
3429
+ new PollVote(this.client, { ...pollVote, parentMessage: msg }),
3430
+ );
2492
3431
  }
2493
3432
  }
2494
3433