@pney/whatsapp-web 1.34.6 → 1.34.7-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +3 -3
  15. package/shell.js +4 -4
  16. package/src/Client.js +1860 -920
  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 -168
  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,18 +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
- return window.WWebJS.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0')
1515
- ? await window.require('WAWebContactProfilePicThumbBridge').profilePicFind(chatWid)
1516
- : await window.require('WAWebContactProfilePicThumbBridge').requestProfilePicFromServer(chatWid);
2148
+ const chat = await window.WWebJS.getChat(contactId);
2149
+ return await window
2150
+ .require('WAWebContactProfilePicThumbBridge')
2151
+ .requestProfilePicFromServer(chat);
1517
2152
  } catch (err) {
1518
- if(err.name === 'ServerStatusCodeError') return undefined;
2153
+ if (err.name === 'ServerStatusCodeError') return undefined;
1519
2154
  throw err;
1520
2155
  }
1521
2156
  }, contactId);
1522
-
2157
+
1523
2158
  return profilePic ? profilePic.eurl : undefined;
1524
2159
  }
1525
2160
 
@@ -1530,17 +2165,26 @@ class Client extends EventEmitter {
1530
2165
  */
1531
2166
  async getCommonGroups(contactId) {
1532
2167
  const commonGroups = await this.pupPage.evaluate(async (contactId) => {
1533
- let contact = (window.require('WAWebCollections')).Contact.get(contactId);
2168
+ let contact = window
2169
+ .require('WAWebCollections')
2170
+ .Contact.get(contactId);
1534
2171
  if (!contact) {
1535
- const wid = window.require('WAWebWidFactory').createWid(contactId);
1536
- const chatConstructor = (window.require('WAWebCollections')).Contact.getModelsArray().find(c=>!c.isGroup).constructor;
1537
- 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 });
1538
2180
  }
1539
2181
 
1540
2182
  if (contact.commonGroups) {
1541
2183
  return contact.commonGroups.serialize();
1542
2184
  }
1543
- const status = await (window.require('WAWebFindCommonGroupsContactAction').findCommonGroups)(contact);
2185
+ const status = await window
2186
+ .require('WAWebFindCommonGroupsContactAction')
2187
+ .findCommonGroups(contact);
1544
2188
  if (status) {
1545
2189
  return contact.commonGroups.serialize();
1546
2190
  }
@@ -1555,10 +2199,10 @@ class Client extends EventEmitter {
1555
2199
 
1556
2200
  /**
1557
2201
  * Force reset of connection state for the client
1558
- */
2202
+ */
1559
2203
  async resetState() {
1560
2204
  await this.pupPage.evaluate(() => {
1561
- window.require('WAWebSocketModel').Socket.reconnect();
2205
+ window.require('WAWebSocketModel').Socket.reconnect();
1562
2206
  });
1563
2207
  }
1564
2208
 
@@ -1572,7 +2216,7 @@ class Client extends EventEmitter {
1572
2216
  }
1573
2217
 
1574
2218
  /**
1575
- * Get the registered WhatsApp ID for a number.
2219
+ * Get the registered WhatsApp ID for a number.
1576
2220
  * Will return null if the number is not registered on WhatsApp.
1577
2221
  * @param {string} number Number or ID ("@c.us" will be automatically appended if not specified)
1578
2222
  * @returns {Promise<Object|null>}
@@ -1582,9 +2226,11 @@ class Client extends EventEmitter {
1582
2226
  number += '@c.us';
1583
2227
  }
1584
2228
 
1585
- return await this.pupPage.evaluate(async number => {
2229
+ return await this.pupPage.evaluate(async (number) => {
1586
2230
  const wid = window.require('WAWebWidFactory').createWid(number);
1587
- const result = await (window.require('WAWebQueryExistsJob').queryWidExists)(wid);
2231
+ const result = await window
2232
+ .require('WAWebQueryExistsJob')
2233
+ .queryWidExists(wid);
1588
2234
  if (!result || result.wid === undefined) return null;
1589
2235
  return result.wid;
1590
2236
  }, number);
@@ -1596,11 +2242,15 @@ class Client extends EventEmitter {
1596
2242
  * @returns {Promise<string>}
1597
2243
  */
1598
2244
  async getFormattedNumber(number) {
1599
- if (!number.endsWith('@s.whatsapp.net')) number = number.replace('c.us', 's.whatsapp.net');
1600
- if (!number.includes('@s.whatsapp.net')) number = `${number}@s.whatsapp.net`;
1601
-
1602
- return await this.pupPage.evaluate(async numberId => {
1603
- 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);
1604
2254
  }, number);
1605
2255
  }
1606
2256
 
@@ -1612,7 +2262,7 @@ class Client extends EventEmitter {
1612
2262
  async getCountryCode(number) {
1613
2263
  number = number.replace(' ', '').replace('+', '').replace('@c.us', '');
1614
2264
 
1615
- return await this.pupPage.evaluate(async numberId => {
2265
+ return await this.pupPage.evaluate(async (numberId) => {
1616
2266
  return window.require('WAPhoneFindCC').findCC(numberId);
1617
2267
  }, number);
1618
2268
  }
@@ -1659,92 +2309,137 @@ class Client extends EventEmitter {
1659
2309
  */
1660
2310
  async createGroup(title, participants = [], options = {}) {
1661
2311
  !Array.isArray(participants) && (participants = [participants]);
1662
- participants.map(p => (p instanceof Contact) ? p.id._serialized : p);
1663
-
1664
- return await this.pupPage.evaluate(async (title, participants, options) => {
1665
- const {
1666
- messageTimer = 0,
1667
- parentGroupId,
1668
- autoSendInviteV4 = true,
1669
- comment = '',
1670
- } = options;
1671
- const participantData = {}, participantWids = [], failedParticipants = [];
1672
- let createGroupResult, parentGroupWid;
1673
-
1674
- const addParticipantResultCodes = {
1675
- default: 'An unknown error occupied while adding a participant',
1676
- 200: 'The participant was added successfully',
1677
- 403: 'The participant can be added by sending private invitation only',
1678
- 404: 'The phone number is not registered on WhatsApp'
1679
- };
1680
-
1681
- for (const participant of participants) {
1682
- const pWid = window.require('WAWebWidFactory').createWid(participant);
1683
- if ((await (window.require('WAWebQueryExistsJob').queryWidExists)(pWid))?.wid) {
1684
- 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);
1685
2348
  }
1686
- else failedParticipants.push(participant);
1687
- }
1688
2349
 
1689
- 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
+ }
1690
2378
 
1691
- try {
1692
- createGroupResult = await (window.require('WAWebGroupCreateJob')).createGroup(
1693
- {
1694
- 'addressingModeOverride': 'lid',
1695
- 'memberAddMode': options.memberAddMode ?? false,
1696
- 'membershipApprovalMode': options.membershipApprovalMode ?? false,
1697
- 'announce': options.announce ?? false,
1698
- 'restrict': options.isRestrict !== undefined ? !options.isRestrict : false,
1699
- 'ephemeralDuration': messageTimer,
1700
- 'parentGroupId': parentGroupWid,
1701
- 'title': title,
1702
- },
1703
- participantWids
1704
- );
1705
- } catch (err) {
1706
- return 'CreateGroupError: An unknown error occupied while creating a group';
1707
- }
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
+ }
1708
2413
 
1709
- for (const participant of createGroupResult.participants) {
1710
- let isInviteV4Sent = false;
1711
- participant.wid.server == 'lid' && (participant.wid = window.require('WAWebApiContact').getPhoneNumber(participant.wid));
1712
- const participantId = participant.wid._serialized;
1713
- const statusCode = participant.error || 200;
1714
-
1715
- if (autoSendInviteV4 && statusCode === 403) {
1716
- (window.require('WAWebCollections')).Contact.gadd(participant.wid, { silent: true });
1717
- const addParticipantResult = await (window.require('WAWebChatSendMessages')).sendGroupInviteMessage(
1718
- (window.require('WAWebCollections')).Chat.get(participant.wid) || await (window.require('WAWebCollections')).Chat.find(participant.wid),
1719
- createGroupResult.wid._serialized,
1720
- createGroupResult.subject,
1721
- participant.invite_code,
1722
- participant.invite_code_exp,
1723
- comment,
1724
- await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid)
1725
- );
1726
- 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
+ };
1727
2422
  }
1728
2423
 
1729
- participantData[participantId] = {
1730
- statusCode: statusCode,
1731
- message: addParticipantResultCodes[statusCode] || addParticipantResultCodes.default,
1732
- isGroupCreator: participant.type === 'superadmin',
1733
- isInviteV4Sent: isInviteV4Sent
1734
- };
1735
- }
2424
+ for (const f of failedParticipants) {
2425
+ participantData[f] = {
2426
+ statusCode: 404,
2427
+ message: addParticipantResultCodes[404],
2428
+ isGroupCreator: false,
2429
+ isInviteV4Sent: false,
2430
+ };
2431
+ }
1736
2432
 
1737
- for (const f of failedParticipants) {
1738
- participantData[f] = {
1739
- statusCode: 404,
1740
- message: addParticipantResultCodes[404],
1741
- isGroupCreator: false,
1742
- isInviteV4Sent: false
2433
+ return {
2434
+ title: title,
2435
+ gid: createGroupResult.wid,
2436
+ participants: participantData,
1743
2437
  };
1744
- }
1745
-
1746
- return { title: title, gid: createGroupResult.wid, participants: participantData };
1747
- }, title, participants, options);
2438
+ },
2439
+ title,
2440
+ participants,
2441
+ options,
2442
+ );
1748
2443
  }
1749
2444
 
1750
2445
  /**
@@ -1769,46 +2464,61 @@ class Client extends EventEmitter {
1769
2464
  /**
1770
2465
  * Creates a new channel
1771
2466
  * @param {string} title The channel name
1772
- * @param {CreateChannelOptions} options
2467
+ * @param {CreateChannelOptions} options
1773
2468
  * @returns {Promise<CreateChannelResult|string>} Returns an object that handles the result for the channel creation or an error message as a string
1774
2469
  */
1775
2470
  async createChannel(title, options = {}) {
1776
- return await this.pupPage.evaluate(async (title, options) => {
1777
- let response, { description = null, picture = null } = options;
1778
-
1779
- if (!(window.require('WAWebNewsletterGatingUtils')).isNewsletterCreationEnabled()) {
1780
- return 'CreateChannelError: A channel creation is not enabled';
1781
- }
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
+ }
1782
2483
 
1783
- if (picture) {
1784
- picture = await window.WWebJS.cropAndResizeImage(picture, {
1785
- asDataUrl: true,
1786
- mimetype: 'image/jpeg',
1787
- size: 640,
1788
- quality: 1
1789
- });
1790
- }
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
+ }
1791
2492
 
1792
- try {
1793
- response = await (window.require('WAWebNewsletterCreateQueryJob')).createNewsletterQuery({
1794
- name: title,
1795
- description: description,
1796
- picture: picture,
1797
- });
1798
- } catch (err) {
1799
- if (err.name === 'ServerStatusCodeError') {
1800
- 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;
1801
2506
  }
1802
- throw err;
1803
- }
1804
2507
 
1805
- return {
1806
- title: title,
1807
- nid: window.require('WAWebJidToWid').newsletterJidToWid(response.idJid),
1808
- inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`,
1809
- createdAtTs: response.newsletterCreationTimeMetadataMixin.creationTimeValue
1810
- };
1811
- }, 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
+ );
1812
2522
  }
1813
2523
 
1814
2524
  /**
@@ -1818,7 +2528,10 @@ class Client extends EventEmitter {
1818
2528
  */
1819
2529
  async subscribeToChannel(channelId) {
1820
2530
  return await this.pupPage.evaluate(async (channelId) => {
1821
- return await window.WWebJS.subscribeToUnsubscribeFromChannel(channelId, 'Subscribe');
2531
+ return await window.WWebJS.subscribeToUnsubscribeFromChannel(
2532
+ channelId,
2533
+ 'Subscribe',
2534
+ );
1822
2535
  }, channelId);
1823
2536
  }
1824
2537
 
@@ -1835,9 +2548,17 @@ class Client extends EventEmitter {
1835
2548
  * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
1836
2549
  */
1837
2550
  async unsubscribeFromChannel(channelId, options) {
1838
- return await this.pupPage.evaluate(async (channelId, options) => {
1839
- return await window.WWebJS.subscribeToUnsubscribeFromChannel(channelId, 'Unsubscribe', options);
1840
- }, 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
+ );
1841
2562
  }
1842
2563
 
1843
2564
  /**
@@ -1855,26 +2576,51 @@ class Client extends EventEmitter {
1855
2576
  * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
1856
2577
  */
1857
2578
  async transferChannelOwnership(channelId, newOwnerId, options = {}) {
1858
- return await this.pupPage.evaluate(async (channelId, newOwnerId, options) => {
1859
- const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
1860
- const newOwner = (window.require('WAWebCollections')).Contact.get(newOwnerId) || (await (window.require('WAWebCollections')).Contact.find(newOwnerId));
1861
- if (!channel.newsletterMetadata) {
1862
- await (window.require('WAWebCollections')).NewsletterMetadataCollection.update(channel.id);
1863
- }
1864
-
1865
- try {
1866
- 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
+ }
1867
2596
 
1868
- if (options.shouldDismissSelfAsAdmin) {
1869
- const meContact = window.require('WAWebContactCollection').getMeContact();
1870
- 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;
1871
2616
  }
1872
- } catch (error) {
1873
- return false;
1874
- }
1875
2617
 
1876
- return true;
1877
- }, channelId, newOwnerId, options);
2618
+ return true;
2619
+ },
2620
+ channelId,
2621
+ newOwnerId,
2622
+ options,
2623
+ );
1878
2624
  }
1879
2625
 
1880
2626
  /**
@@ -1897,51 +2643,78 @@ class Client extends EventEmitter {
1897
2643
  * @returns {Promise<Array<Channel>>} Returns an array of Channel objects or an empty array if no channels were found
1898
2644
  */
1899
2645
  async searchChannels(searchOptions = {}) {
1900
- return await this.pupPage.evaluate(async ({
1901
- searchText = '',
1902
- countryCodes = [],
1903
- skipSubscribedNewsletters = false,
1904
- view = 0,
1905
- limit = 50
1906
- }) => {
1907
- searchText = searchText.trim();
1908
- const currentRegion = window.require('WAWebL10N').getRegion();
1909
- if (countryCodes.length === 0) countryCodes[0] = currentRegion;
1910
- if (![0, 1, 2, 3].includes(view)) view = 0;
1911
-
1912
- const { countryCodesIso } = window.require('WAWebCountriesNativeCountryNames');
1913
-
1914
- countryCodes = countryCodes.length === 1 && countryCodes[0] === currentRegion
1915
- ? countryCodes
1916
- : countryCodes.filter((code) => Object.keys(countryCodesIso).includes(code));
1917
-
1918
- const viewTypeMapping = {
1919
- 0: 'RECOMMENDED',
1920
- 1: 'TRENDING',
1921
- 2: 'POPULAR',
1922
- 3: 'NEW'
1923
- };
1924
-
1925
- searchOptions = {
1926
- searchText: searchText,
1927
- countryCodes: countryCodes,
1928
- skipSubscribedNewsletters: skipSubscribedNewsletters,
1929
- view: viewTypeMapping[view],
1930
- categories: [],
1931
- cursorToken: ''
1932
- };
1933
-
1934
- const originalFunction = window.require('WAWebNewsletterGatingUtils').getNewsletterDirectoryPageSize;
1935
- limit !== 50 && (window.require('WAWebNewsletterGatingUtils').getNewsletterDirectoryPageSize = () => limit);
1936
-
1937
- const channels = (await (window.require('WAWebNewsletterDirectorySearchAction')).fetchNewsletterDirectories(searchOptions)).newsletters;
1938
-
1939
- limit !== 50 && (window.require('WAWebNewsletterGatingUtils').getNewsletterDirectoryPageSize = originalFunction);
1940
-
1941
- return channels
1942
- ? await Promise.all(channels.map((channel) => window.WWebJS.getChatModel(channel, { isChannel: true })))
1943
- : [];
1944
- }, 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
+ );
1945
2718
  }
1946
2719
 
1947
2720
  /**
@@ -1951,10 +2724,14 @@ class Client extends EventEmitter {
1951
2724
  */
1952
2725
  async deleteChannel(channelId) {
1953
2726
  return await this.pupPage.evaluate(async (channelId) => {
1954
- const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
2727
+ const channel = await window.WWebJS.getChat(channelId, {
2728
+ getAsModel: false,
2729
+ });
1955
2730
  if (!channel) return false;
1956
2731
  try {
1957
- await (window.require('WAWebNewsletterDeleteAction')).deleteNewsletterAction(channel);
2732
+ await window
2733
+ .require('WAWebNewsletterDeleteAction')
2734
+ .deleteNewsletterAction(channel);
1958
2735
  return true;
1959
2736
  } catch (err) {
1960
2737
  if (err.name === 'ServerStatusCodeError') return false;
@@ -1972,7 +2749,7 @@ class Client extends EventEmitter {
1972
2749
  return window.WWebJS.getLabels();
1973
2750
  });
1974
2751
 
1975
- return labels.map(data => new Label(this, data));
2752
+ return labels.map((data) => new Label(this, data));
1976
2753
  }
1977
2754
 
1978
2755
  /**
@@ -1983,7 +2760,7 @@ class Client extends EventEmitter {
1983
2760
  const broadcasts = await this.pupPage.evaluate(async () => {
1984
2761
  return window.WWebJS.getAllStatuses();
1985
2762
  });
1986
- return broadcasts.map(data => new Broadcast(this, data));
2763
+ return broadcasts.map((data) => new Broadcast(this, data));
1987
2764
  }
1988
2765
 
1989
2766
  /**
@@ -1995,9 +2772,11 @@ class Client extends EventEmitter {
1995
2772
  const broadcast = await this.pupPage.evaluate(async (userId) => {
1996
2773
  let status;
1997
2774
  try {
1998
- status = (window.require('WAWebCollections')).Status.get(userId);
2775
+ status = window.require('WAWebCollections').Status.get(userId);
1999
2776
  if (!status) {
2000
- status = await (window.require('WAWebCollections')).Status.find(userId);
2777
+ status = await window
2778
+ .require('WAWebCollections')
2779
+ .Status.find(userId);
2001
2780
  }
2002
2781
  } catch {
2003
2782
  status = null;
@@ -2015,17 +2794,26 @@ class Client extends EventEmitter {
2015
2794
  */
2016
2795
  async revokeStatusMessage(messageId) {
2017
2796
  return await this.pupPage.evaluate(async (msgId) => {
2018
- const status = (window.require('WAWebCollections')).Status.getMyStatus();
2797
+ const status = window
2798
+ .require('WAWebCollections')
2799
+ .Status.getMyStatus();
2019
2800
  if (!status) return;
2020
2801
 
2021
2802
  const msg =
2022
- (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];
2023
2809
  if (!msg) return;
2024
2810
 
2025
2811
  if (!msg.id.fromMe || !msg.id.remote.isStatus())
2026
2812
  throw 'Invalid usage! Can only revoke the message its from own status broadcast';
2027
2813
 
2028
- return await (window.require('WAWebRevokeStatusAction')).sendStatusRevokeMsgAction(status, msg);
2814
+ return await window
2815
+ .require('WAWebRevokeStatusAction')
2816
+ .sendStatusRevokeMsgAction(status, msg);
2029
2817
  }, messageId);
2030
2818
  }
2031
2819
 
@@ -2043,7 +2831,7 @@ class Client extends EventEmitter {
2043
2831
  }
2044
2832
 
2045
2833
  /**
2046
- * Get all Labels assigned to a chat
2834
+ * Get all Labels assigned to a chat
2047
2835
  * @param {string} chatId
2048
2836
  * @returns {Promise<Array<Label>>}
2049
2837
  */
@@ -2052,7 +2840,7 @@ class Client extends EventEmitter {
2052
2840
  return window.WWebJS.getChatLabels(chatId);
2053
2841
  }, chatId);
2054
2842
 
2055
- return labels.map(data => new Label(this, data));
2843
+ return labels.map((data) => new Label(this, data));
2056
2844
  }
2057
2845
 
2058
2846
  /**
@@ -2062,7 +2850,7 @@ class Client extends EventEmitter {
2062
2850
  */
2063
2851
  async getChatsByLabelId(labelId) {
2064
2852
  const chatIds = await this.pupPage.evaluate(async (labelId) => {
2065
- const label = (window.require('WAWebCollections')).Label.get(labelId);
2853
+ const label = window.require('WAWebCollections').Label.get(labelId);
2066
2854
  const labelItems = label.labelItemCollection.getModelsArray();
2067
2855
  return labelItems.reduce((result, item) => {
2068
2856
  if (item.parentType === 'Chat') {
@@ -2072,7 +2860,7 @@ class Client extends EventEmitter {
2072
2860
  }, []);
2073
2861
  }, labelId);
2074
2862
 
2075
- return Promise.all(chatIds.map(id => this.getChatById(id)));
2863
+ return Promise.all(chatIds.map((id) => this.getChatById(id)));
2076
2864
  }
2077
2865
 
2078
2866
  /**
@@ -2081,11 +2869,18 @@ class Client extends EventEmitter {
2081
2869
  */
2082
2870
  async getBlockedContacts() {
2083
2871
  const blockedContacts = await this.pupPage.evaluate(() => {
2084
- let chatIds = (window.require('WAWebCollections')).Blocklist.getModelsArray().map(a => a.id._serialized);
2085
- 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
+ );
2086
2879
  });
2087
2880
 
2088
- return blockedContacts.map(contact => ContactFactory.create(this.client, contact));
2881
+ return blockedContacts.map((contact) =>
2882
+ ContactFactory.create(this.client, contact),
2883
+ );
2089
2884
  }
2090
2885
 
2091
2886
  /**
@@ -2094,9 +2889,13 @@ class Client extends EventEmitter {
2094
2889
  * @returns {Promise<boolean>} Returns true if the picture was properly updated.
2095
2890
  */
2096
2891
  async setProfilePicture(media) {
2097
- const success = await this.pupPage.evaluate((chatid, media) => {
2098
- return window.WWebJS.setPicture(chatid, media);
2099
- }, 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
+ );
2100
2899
 
2101
2900
  return success;
2102
2901
  }
@@ -2112,7 +2911,7 @@ class Client extends EventEmitter {
2112
2911
 
2113
2912
  return success;
2114
2913
  }
2115
-
2914
+
2116
2915
  /**
2117
2916
  * Change labels in chats
2118
2917
  * @param {Array<number|string>} labelIds
@@ -2120,26 +2919,42 @@ class Client extends EventEmitter {
2120
2919
  * @returns {Promise<void>}
2121
2920
  */
2122
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));
2123
2937
 
2124
- return this.pupPage.evaluate(async (labelIds, chatIds) => {
2125
- if (['smba', 'smbi'].indexOf((window.require('WAWebConnModel').Conn).platform) === -1) {
2126
- throw '[LT01] Only Whatsapp business';
2127
- }
2128
- const labels = window.WWebJS.getLabels().filter(e => labelIds.find(l => l == e.id) !== undefined);
2129
- const chats = (window.require('WAWebCollections')).Chat.filter(e => chatIds.includes(e.id._serialized));
2130
-
2131
- let actions = labels.map(label => ({id: label.id, type: 'add'}));
2938
+ let actions = labels.map((label) => ({
2939
+ id: label.id,
2940
+ type: 'add',
2941
+ }));
2132
2942
 
2133
- chats.forEach(chat => {
2134
- (chat.labels || []).forEach(n => {
2135
- if (!actions.find(e => e.id == n)) {
2136
- actions.push({id: n, type: 'remove'});
2137
- }
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
+ });
2138
2949
  });
2139
- });
2140
2950
 
2141
- return await (window.require('WAWebCollections')).Label.addOrRemoveLabels(actions, chats);
2142
- }, labelIds, chatIds);
2951
+ return await window
2952
+ .require('WAWebCollections')
2953
+ .Label.addOrRemoveLabels(actions, chats);
2954
+ },
2955
+ labelIds,
2956
+ chatIds,
2957
+ );
2143
2958
  }
2144
2959
 
2145
2960
  /**
@@ -2159,8 +2974,12 @@ class Client extends EventEmitter {
2159
2974
  */
2160
2975
  async getGroupMembershipRequests(groupId) {
2161
2976
  return await this.pupPage.evaluate(async (groupId) => {
2162
- const groupWid = window.require('WAWebWidFactory').createWid(groupId);
2163
- 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);
2164
2983
  }, groupId);
2165
2984
  }
2166
2985
 
@@ -2186,10 +3005,19 @@ class Client extends EventEmitter {
2186
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
2187
3006
  */
2188
3007
  async approveGroupMembershipRequests(groupId, options = {}) {
2189
- return await this.pupPage.evaluate(async (groupId, options) => {
2190
- const { requesterIds = null, sleep = [250, 500] } = options;
2191
- return await window.WWebJS.membershipRequestAction(groupId, 'Approve', requesterIds, sleep);
2192
- }, 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
+ );
2193
3021
  }
2194
3022
 
2195
3023
  /**
@@ -2199,24 +3027,36 @@ class Client extends EventEmitter {
2199
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
2200
3028
  */
2201
3029
  async rejectGroupMembershipRequests(groupId, options = {}) {
2202
- return await this.pupPage.evaluate(async (groupId, options) => {
2203
- const { requesterIds = null, sleep = [250, 500] } = options;
2204
- return await window.WWebJS.membershipRequestAction(groupId, 'Reject', requesterIds, sleep);
2205
- }, 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
+ );
2206
3043
  }
2207
3044
 
2208
-
2209
3045
  /**
2210
3046
  * Setting autoload download audio
2211
3047
  * @param {boolean} flag true/false
2212
3048
  */
2213
3049
  async setAutoDownloadAudio(flag) {
2214
- await this.pupPage.evaluate(async flag => {
2215
- const autoDownload = (window.require('WAWebUserPrefsGeneral')).getAutoDownloadAudio();
3050
+ await this.pupPage.evaluate(async (flag) => {
3051
+ const autoDownload = window
3052
+ .require('WAWebUserPrefsGeneral')
3053
+ .getAutoDownloadAudio();
2216
3054
  if (autoDownload === flag) {
2217
3055
  return flag;
2218
3056
  }
2219
- await (window.require('WAWebUserPrefsGeneral')).setAutoDownloadAudio(flag);
3057
+ await window
3058
+ .require('WAWebUserPrefsGeneral')
3059
+ .setAutoDownloadAudio(flag);
2220
3060
  return flag;
2221
3061
  }, flag);
2222
3062
  }
@@ -2226,12 +3066,16 @@ class Client extends EventEmitter {
2226
3066
  * @param {boolean} flag true/false
2227
3067
  */
2228
3068
  async setAutoDownloadDocuments(flag) {
2229
- await this.pupPage.evaluate(async flag => {
2230
- const autoDownload = (window.require('WAWebUserPrefsGeneral')).getAutoDownloadDocuments();
3069
+ await this.pupPage.evaluate(async (flag) => {
3070
+ const autoDownload = window
3071
+ .require('WAWebUserPrefsGeneral')
3072
+ .getAutoDownloadDocuments();
2231
3073
  if (autoDownload === flag) {
2232
3074
  return flag;
2233
3075
  }
2234
- await (window.require('WAWebUserPrefsGeneral')).setAutoDownloadDocuments(flag);
3076
+ await window
3077
+ .require('WAWebUserPrefsGeneral')
3078
+ .setAutoDownloadDocuments(flag);
2235
3079
  return flag;
2236
3080
  }, flag);
2237
3081
  }
@@ -2241,12 +3085,16 @@ class Client extends EventEmitter {
2241
3085
  * @param {boolean} flag true/false
2242
3086
  */
2243
3087
  async setAutoDownloadPhotos(flag) {
2244
- await this.pupPage.evaluate(async flag => {
2245
- const autoDownload = (window.require('WAWebUserPrefsGeneral')).getAutoDownloadPhotos();
3088
+ await this.pupPage.evaluate(async (flag) => {
3089
+ const autoDownload = window
3090
+ .require('WAWebUserPrefsGeneral')
3091
+ .getAutoDownloadPhotos();
2246
3092
  if (autoDownload === flag) {
2247
3093
  return flag;
2248
3094
  }
2249
- await (window.require('WAWebUserPrefsGeneral')).setAutoDownloadPhotos(flag);
3095
+ await window
3096
+ .require('WAWebUserPrefsGeneral')
3097
+ .setAutoDownloadPhotos(flag);
2250
3098
  return flag;
2251
3099
  }, flag);
2252
3100
  }
@@ -2256,12 +3104,16 @@ class Client extends EventEmitter {
2256
3104
  * @param {boolean} flag true/false
2257
3105
  */
2258
3106
  async setAutoDownloadVideos(flag) {
2259
- await this.pupPage.evaluate(async flag => {
2260
- const autoDownload = (window.require('WAWebUserPrefsGeneral')).getAutoDownloadVideos();
3107
+ await this.pupPage.evaluate(async (flag) => {
3108
+ const autoDownload = window
3109
+ .require('WAWebUserPrefsGeneral')
3110
+ .getAutoDownloadVideos();
2261
3111
  if (autoDownload === flag) {
2262
3112
  return flag;
2263
3113
  }
2264
- await (window.require('WAWebUserPrefsGeneral')).setAutoDownloadVideos(flag);
3114
+ await window
3115
+ .require('WAWebUserPrefsGeneral')
3116
+ .setAutoDownloadVideos(flag);
2265
3117
  return flag;
2266
3118
  }, flag);
2267
3119
  }
@@ -2273,16 +3125,20 @@ class Client extends EventEmitter {
2273
3125
  * @returns {Promise<boolean>}
2274
3126
  */
2275
3127
  async setBackgroundSync(flag) {
2276
- return await this.pupPage.evaluate(async flag => {
2277
- const backSync = (window.require('WAWebUserPrefsNotifications')).getGlobalOfflineNotifications();
3128
+ return await this.pupPage.evaluate(async (flag) => {
3129
+ const backSync = window
3130
+ .require('WAWebUserPrefsNotifications')
3131
+ .getGlobalOfflineNotifications();
2278
3132
  if (backSync === flag) {
2279
3133
  return flag;
2280
3134
  }
2281
- await (window.require('WAWebUserPrefsNotifications')).setGlobalOfflineNotifications(flag);
3135
+ await window
3136
+ .require('WAWebUserPrefsNotifications')
3137
+ .setGlobalOfflineNotifications(flag);
2282
3138
  return flag;
2283
3139
  }, flag);
2284
3140
  }
2285
-
3141
+
2286
3142
  /**
2287
3143
  * Get user device count by ID
2288
3144
  * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
@@ -2292,8 +3148,17 @@ class Client extends EventEmitter {
2292
3148
  */
2293
3149
  async getContactDeviceCount(userId) {
2294
3150
  return await this.pupPage.evaluate(async (userId) => {
2295
- const devices = await (window.require('WAWebApiDeviceList')).getDeviceIds([window.require('WAWebWidFactory').createWid(userId)]);
2296
- 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
+ ) {
2297
3162
  return devices[0].devices.length;
2298
3163
  }
2299
3164
  return 0;
@@ -2308,17 +3173,21 @@ class Client extends EventEmitter {
2308
3173
  async syncHistory(chatId) {
2309
3174
  return await this.pupPage.evaluate(async (chatId) => {
2310
3175
  const chatWid = window.require('WAWebWidFactory').createWid(chatId);
2311
- 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));
2312
3179
  if (chat?.endOfHistoryTransferType === 0) {
2313
- await (window.require('WAWebSendNonMessageDataRequest')).sendPeerDataOperationRequest(3, {
2314
- chatId: chat.id
2315
- });
3180
+ await window
3181
+ .require('WAWebSendNonMessageDataRequest')
3182
+ .sendPeerDataOperationRequest(3, {
3183
+ chatId: chat.id,
3184
+ });
2316
3185
  return true;
2317
3186
  }
2318
3187
  return false;
2319
3188
  }, chatId);
2320
3189
  }
2321
-
3190
+
2322
3191
  /**
2323
3192
  * Generates a WhatsApp call link (video call or voice call)
2324
3193
  * @param {Date} startTime The start time of the call
@@ -2327,17 +3196,27 @@ class Client extends EventEmitter {
2327
3196
  */
2328
3197
  async createCallLink(startTime, callType) {
2329
3198
  if (!['video', 'voice'].includes(callType)) {
2330
- throw new class CreateCallLinkError extends Error {
2331
- constructor(m) { super(m); }
2332
- }('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
+ );
2333
3206
  }
2334
3207
 
2335
3208
  startTime = Math.floor(startTime.getTime() / 1000);
2336
-
2337
- return await this.pupPage.evaluate(async (startTimeTs, callType) => {
2338
- const response = await (window.require('WAWebGenerateEventCallLink')).createEventCallLink(startTimeTs, callType);
2339
- return response ?? '';
2340
- }, 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
+ );
2341
3220
  }
2342
3221
 
2343
3222
  /**
@@ -2349,35 +3228,59 @@ class Client extends EventEmitter {
2349
3228
  async sendResponseToScheduledEvent(response, eventMessageId) {
2350
3229
  if (![0, 1, 2, 3].includes(response)) return false;
2351
3230
 
2352
- return await this.pupPage.evaluate(async (response, msgId) => {
2353
- const eventMsg = (window.require('WAWebCollections')).Msg.get(msgId) || (await (window.require('WAWebCollections')).Msg.getMessagesById([msgId]))?.messages?.[0];
2354
- if (!eventMsg) return false;
2355
-
2356
- await (window.require('WAWebSendEventResponseMsgAction')).sendEventResponseMsg(response, eventMsg);
2357
- return true;
2358
- }, 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
+ );
2359
3250
  }
2360
-
3251
+
2361
3252
  /**
2362
3253
  * Save new contact to user's addressbook or edit the existing one
2363
3254
  * @param {string} phoneNumber The contact's phone number in a format "17182222222", where "1" is a country code
2364
- * @param {string} firstName
2365
- * @param {string} lastName
3255
+ * @param {string} firstName
3256
+ * @param {string} lastName
2366
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
2367
3258
  * @returns {Promise<void>}
2368
3259
  */
2369
- async saveOrEditAddressbookContact(phoneNumber, firstName, lastName, syncToAddressbook = false)
2370
- {
2371
- return await this.pupPage.evaluate(async (phoneNumber, firstName, lastName, syncToAddressbook) => {
2372
- return await (window.require('WAWebSaveContactAction')).saveContactAction({
2373
- 'firstName' : firstName,
2374
- 'lastName' : lastName,
2375
- 'phoneNumber' : phoneNumber,
2376
- 'prevPhoneNumber' : phoneNumber,
2377
- 'syncToAddressbook': syncToAddressbook,
2378
- 'username' : undefined
2379
- });
2380
- }, 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
+ );
2381
3284
  }
2382
3285
 
2383
3286
  /**
@@ -2385,11 +3288,14 @@ class Client extends EventEmitter {
2385
3288
  * @param {string} phoneNumber The contact's phone number in a format "17182222222", where "1" is a country code
2386
3289
  * @returns {Promise<void>}
2387
3290
  */
2388
- async deleteAddressbookContact(phoneNumber)
2389
- {
3291
+ async deleteAddressbookContact(phoneNumber) {
2390
3292
  return await this.pupPage.evaluate(async (phoneNumber) => {
2391
- const wid = window.require('WAWebWidFactory').createWid(phoneNumber);
2392
- 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 });
2393
3299
  }, phoneNumber);
2394
3300
  }
2395
3301
 
@@ -2402,14 +3308,17 @@ class Client extends EventEmitter {
2402
3308
  return await this.pupPage.evaluate(async (userIds) => {
2403
3309
  if (!Array.isArray(userIds)) userIds = [userIds];
2404
3310
 
2405
- return await Promise.all(userIds.map(async (userId) => {
2406
- 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);
2407
3315
 
2408
- return {
2409
- lid: lid?._serialized,
2410
- pn: phone?._serialized
2411
- };
2412
- }));
3316
+ return {
3317
+ lid: lid?._serialized,
3318
+ pn: phone?._serialized,
3319
+ };
3320
+ }),
3321
+ );
2413
3322
  }, userIds);
2414
3323
  }
2415
3324
 
@@ -2421,15 +3330,28 @@ class Client extends EventEmitter {
2421
3330
  * @returns {Promise<void>}
2422
3331
  */
2423
3332
  async addOrEditCustomerNote(userId, note) {
2424
- return await this.pupPage.evaluate(async (userId, note) => {
2425
- if (!(window.require('WAWebBizGatingUtils')).smbNotesV1Enabled()) return;
3333
+ return await this.pupPage.evaluate(
3334
+ async (userId, note) => {
3335
+ if (!window.require('WAWebBizGatingUtils').smbNotesV1Enabled())
3336
+ return;
2426
3337
 
2427
- return (window.require('WAWebNoteAction')).noteAddAction(
2428
- 'unstructured',
2429
- window.require('WAWebWidToJid').widToUserJid(window.require('WAWebWidFactory').createWid(userId)),
2430
- note
2431
- );
2432
- }, 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
+ );
2433
3355
  }
2434
3356
 
2435
3357
  /**
@@ -2447,47 +3369,65 @@ class Client extends EventEmitter {
2447
3369
  */
2448
3370
  async getCustomerNote(userId) {
2449
3371
  return await this.pupPage.evaluate(async (userId) => {
2450
- if (!(window.require('WAWebBizGatingUtils')).smbNotesV1Enabled()) return null;
2451
-
2452
- const note = await (window.require('WAWebNoteAction')).retrieveOnlyNoteForChatJid(
2453
- window.require('WAWebWidToJid').widToUserJid(window.require('WAWebWidFactory').createWid(userId))
2454
- );
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
+ );
2455
3384
 
2456
3385
  let serialized = note?.serialize();
2457
3386
 
2458
3387
  if (!serialized) return null;
2459
3388
 
2460
- serialized.chatId = window.require('WAWebJidToWid').userJidToUserWid(serialized.chatJid)._serialized;
3389
+ serialized.chatId = window
3390
+ .require('WAWebJidToWid')
3391
+ .userJidToUserWid(serialized.chatJid)._serialized;
2461
3392
  delete serialized.chatJid;
2462
3393
 
2463
3394
  return serialized;
2464
3395
  }, userId);
2465
3396
  }
2466
-
3397
+
2467
3398
  /**
2468
3399
  * Get Poll Votes
2469
3400
  * @param {string} messageId
2470
- * @return {Promise<Array<PollVote>>}
3401
+ * @return {Promise<Array<PollVote>>}
2471
3402
  */
2472
3403
  async getPollVotes(messageId) {
2473
3404
  const msg = await this.getMessageById(messageId);
2474
3405
  if (!msg) return [];
2475
- if (msg.type != MessageTypes.POLL_CREATION) throw 'Invalid usage! Can only be used with a pollCreation message';
2476
-
2477
- const pollVotes = await this.pupPage.evaluate( async (msg) => {
2478
- const msgKey = (window.require('WAWebMsgKey')).fromString(msg.id._serialized);
2479
- let pollVotes = await (window.require('WAWebPollsVotesSchema')).getTable().equals(['parentMsgKey'], msgKey.toString());
2480
-
2481
- 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) => {
2482
3419
  const typedArray = new Uint8Array(item.selectedOptionLocalIds);
2483
3420
  return {
2484
3421
  ...item,
2485
- selectedOptionLocalIds: Array.from(typedArray)
3422
+ selectedOptionLocalIds: Array.from(typedArray),
2486
3423
  };
2487
3424
  });
2488
3425
  }, msg);
2489
3426
 
2490
- 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
+ );
2491
3431
  }
2492
3432
  }
2493
3433