whatsapp-web-sj.js 1.26.0

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 (52) hide show
  1. package/.env.example +3 -0
  2. package/CODE_OF_CONDUCT.md +133 -0
  3. package/LICENSE +201 -0
  4. package/README.md +185 -0
  5. package/example.js +634 -0
  6. package/index.d.ts +1842 -0
  7. package/index.js +32 -0
  8. package/package.json +55 -0
  9. package/shell.js +36 -0
  10. package/src/Client.js +1747 -0
  11. package/src/authStrategies/BaseAuthStrategy.js +27 -0
  12. package/src/authStrategies/LocalAuth.js +56 -0
  13. package/src/authStrategies/NoAuth.js +12 -0
  14. package/src/authStrategies/RemoteAuth.js +204 -0
  15. package/src/factories/ChatFactory.js +16 -0
  16. package/src/factories/ContactFactory.js +16 -0
  17. package/src/structures/Base.js +22 -0
  18. package/src/structures/BusinessContact.js +21 -0
  19. package/src/structures/Buttons.js +82 -0
  20. package/src/structures/Call.js +76 -0
  21. package/src/structures/Chat.js +275 -0
  22. package/src/structures/ClientInfo.js +71 -0
  23. package/src/structures/Contact.js +208 -0
  24. package/src/structures/GroupChat.js +475 -0
  25. package/src/structures/GroupNotification.js +104 -0
  26. package/src/structures/Label.js +50 -0
  27. package/src/structures/List.js +79 -0
  28. package/src/structures/Location.js +61 -0
  29. package/src/structures/Message.js +711 -0
  30. package/src/structures/MessageMedia.js +111 -0
  31. package/src/structures/Order.js +52 -0
  32. package/src/structures/Payment.js +79 -0
  33. package/src/structures/Poll.js +44 -0
  34. package/src/structures/PollVote.js +61 -0
  35. package/src/structures/PrivateChat.js +13 -0
  36. package/src/structures/PrivateContact.js +13 -0
  37. package/src/structures/Product.js +68 -0
  38. package/src/structures/ProductMetadata.js +25 -0
  39. package/src/structures/Reaction.js +69 -0
  40. package/src/structures/index.js +24 -0
  41. package/src/util/Constants.js +176 -0
  42. package/src/util/Injected/AuthStore/AuthStore.js +17 -0
  43. package/src/util/Injected/AuthStore/LegacyAuthStore.js +22 -0
  44. package/src/util/Injected/LegacyStore.js +146 -0
  45. package/src/util/Injected/Store.js +167 -0
  46. package/src/util/Injected/Utils.js +1017 -0
  47. package/src/util/InterfaceController.js +127 -0
  48. package/src/util/Util.js +186 -0
  49. package/src/webCache/LocalWebCache.js +40 -0
  50. package/src/webCache/RemoteWebCache.js +40 -0
  51. package/src/webCache/WebCache.js +14 -0
  52. package/src/webCache/WebCacheFactory.js +20 -0
package/src/Client.js ADDED
@@ -0,0 +1,1747 @@
1
+ 'use strict';
2
+
3
+ const EventEmitter = require('events');
4
+ const puppeteer = require('puppeteer');
5
+ const moduleRaid = require('@pedroslopez/moduleraid/moduleraid');
6
+
7
+ const Util = require('./util/Util');
8
+ const InterfaceController = require('./util/InterfaceController');
9
+ const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants');
10
+ const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore');
11
+ const { ExposeStore } = require('./util/Injected/Store');
12
+ const { ExposeLegacyAuthStore } = require('./util/Injected/AuthStore/LegacyAuthStore');
13
+ const { ExposeLegacyStore } = require('./util/Injected/LegacyStore');
14
+ const { LoadUtils } = require('./util/Injected/Utils');
15
+ const ChatFactory = require('./factories/ChatFactory');
16
+ const ContactFactory = require('./factories/ContactFactory');
17
+ const WebCacheFactory = require('./webCache/WebCacheFactory');
18
+ const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
19
+ const NoAuth = require('./authStrategies/NoAuth');
20
+ const pie = require('puppeteer-in-electron');
21
+
22
+ /**
23
+ * Starting point for interacting with the WhatsApp Web API
24
+ * @extends {EventEmitter}
25
+ * @param {object} options - Client options
26
+ * @param {AuthStrategy} options.authStrategy - Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used.
27
+ * @param {string} options.webVersion - The version of WhatsApp Web to use. Use options.webVersionCache to configure how the version is retrieved.
28
+ * @param {object} options.webVersionCache - Determines how to retrieve the WhatsApp Web version. Defaults to a local cache (LocalWebCache) that falls back to latest if the requested version is not found.
29
+ * @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer
30
+ * @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/
31
+ * @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up
32
+ * @param {string} options.restartOnAuthFail - @deprecated This option should be set directly on the LegacySessionAuth.
33
+ * @param {object} options.session - @deprecated Only here for backwards-compatibility. You should move to using LocalAuth, or set the authStrategy to LegacySessionAuth explicitly.
34
+ * @param {number} options.takeoverOnConflict - If another whatsapp web session is detected (another browser), take over the session in the current browser
35
+ * @param {number} options.takeoverTimeoutMs - How much time to wait before taking over the session
36
+ * @param {string} options.userAgent - User agent to use in puppeteer
37
+ * @param {string} options.ffmpegPath - Ffmpeg path to use when formatting videos to webp while sending stickers
38
+ * @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy.
39
+ * @param {object} options.proxyAuthentication - Proxy Authentication object.
40
+ *
41
+ * @fires Client#qr
42
+ * @fires Client#authenticated
43
+ * @fires Client#auth_failure
44
+ * @fires Client#ready
45
+ * @fires Client#message
46
+ * @fires Client#message_ack
47
+ * @fires Client#message_create
48
+ * @fires Client#message_revoke_me
49
+ * @fires Client#message_revoke_everyone
50
+ * @fires Client#message_ciphertext
51
+ * @fires Client#message_edit
52
+ * @fires Client#media_uploaded
53
+ * @fires Client#group_join
54
+ * @fires Client#group_leave
55
+ * @fires Client#group_update
56
+ * @fires Client#disconnected
57
+ * @fires Client#change_state
58
+ * @fires Client#contact_changed
59
+ * @fires Client#group_admin_changed
60
+ * @fires Client#group_membership_request
61
+ * @fires Client#vote_update
62
+ */
63
+ class Client extends EventEmitter {
64
+ constructor(puppeteerBrowser, browserWindow, options = {}) {
65
+ super();
66
+
67
+ this.options = Util.mergeDefault(DefaultOptions, options);
68
+
69
+ if(!this.options.authStrategy) {
70
+ this.authStrategy = new NoAuth();
71
+ } else {
72
+ this.authStrategy = this.options.authStrategy;
73
+ }
74
+
75
+ this.authStrategy.setup(this);
76
+
77
+ /**
78
+ * @type {puppeteer.Browser}
79
+ */
80
+ this.pupBrowser = puppeteerBrowser;
81
+ /**
82
+ * @type {puppeteer.Page}
83
+ */
84
+ this.browserWindow = browserWindow;
85
+
86
+ this.currentIndexHtml = null;
87
+ this.lastLoggedOut = false;
88
+
89
+ Util.setFfmpegPath(this.options.ffmpegPath);
90
+ }
91
+ /**
92
+ * Injection logic
93
+ * Private function
94
+ * @property {boolean} reinject is this a reinject?
95
+ */
96
+ async inject(reinject = false) {
97
+ await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs});
98
+
99
+ const version = await this.getWWebVersion();
100
+ const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000;
101
+
102
+ if (isCometOrAbove) {
103
+ await this.pupPage.evaluate(ExposeAuthStore);
104
+ } else {
105
+ await this.pupPage.evaluate(ExposeLegacyAuthStore, moduleRaid.toString());
106
+ }
107
+
108
+ const needAuthentication = await this.pupPage.evaluate(async () => {
109
+ let state = window.AuthStore.AppState.state;
110
+
111
+ if (state === 'OPENING' || state === 'UNLAUNCHED' || state === 'PAIRING') {
112
+ // wait till state changes
113
+ await new Promise(r => {
114
+ window.AuthStore.AppState.on('change:state', function waitTillInit(_AppState, state) {
115
+ if (state !== 'OPENING' && state !== 'UNLAUNCHED' && state !== 'PAIRING') {
116
+ window.AuthStore.AppState.off('change:state', waitTillInit);
117
+ r();
118
+ }
119
+ });
120
+ });
121
+ }
122
+ state = window.AuthStore.AppState.state;
123
+ return state == 'UNPAIRED' || state == 'UNPAIRED_IDLE';
124
+ });
125
+
126
+ if (needAuthentication) {
127
+ const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded();
128
+
129
+ if(failed) {
130
+ /**
131
+ * Emitted when there has been an error while trying to restore an existing session
132
+ * @event Client#auth_failure
133
+ * @param {string} message
134
+ */
135
+ this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
136
+ await this.destroy();
137
+ if (restart) {
138
+ // session restore failed so try again but without session to force new authentication
139
+ return this.initialize();
140
+ }
141
+ return;
142
+ }
143
+
144
+ // Register qr events
145
+ let qrRetries = 0;
146
+ const injected = await this.pupPage.evaluate(() => {
147
+ return typeof window.onQRChangedEvent !== 'undefined';
148
+ });
149
+ if (!injected) {
150
+ await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => {
151
+ /**
152
+ * Emitted when a QR code is received
153
+ * @event Client#qr
154
+ * @param {string} qr QR Code
155
+ */
156
+ this.emit(Events.QR_RECEIVED, qr);
157
+ if (this.options.qrMaxRetries > 0) {
158
+ qrRetries++;
159
+ if (qrRetries > this.options.qrMaxRetries) {
160
+ this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
161
+ await this.destroy();
162
+ }
163
+ }
164
+ });
165
+ }
166
+
167
+
168
+ await this.pupPage.evaluate(async () => {
169
+ const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
170
+ const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
171
+ const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey);
172
+ const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey);
173
+ const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey();
174
+ const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
175
+ const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform;
176
+
177
+ window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
178
+ window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes
179
+ });
180
+ }
181
+
182
+ if (!reinject) {
183
+ await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => {
184
+ if (state == 'UNPAIRED_IDLE') {
185
+ // refresh qr code
186
+ window.Store.Cmd.refreshQR();
187
+ }
188
+ });
189
+
190
+ await this.pupPage.exposeFunction('onAppStateHasSyncedEvent', async () => {
191
+ const authEventPayload = await this.authStrategy.getAuthEventPayload();
192
+ /**
193
+ * Emitted when authentication is successful
194
+ * @event Client#authenticated
195
+ */
196
+ this.emit(Events.AUTHENTICATED, authEventPayload);
197
+
198
+ const injected = await this.pupPage.evaluate(async () => {
199
+ return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined';
200
+ });
201
+
202
+ if (!injected) {
203
+ if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) {
204
+ const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
205
+ const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
206
+
207
+ await webCache.persist(this.currentIndexHtml, version);
208
+ }
209
+
210
+ if (isCometOrAbove) {
211
+ await this.pupPage.evaluate(ExposeStore);
212
+ } else {
213
+ // make sure all modules are ready before injection
214
+ // 2 second delay after authentication makes sense and does not need to be made dyanmic or removed
215
+ await new Promise(r => setTimeout(r, 2000));
216
+ await this.pupPage.evaluate(ExposeLegacyStore);
217
+ }
218
+
219
+ // Check window.Store Injection
220
+ await this.pupPage.waitForFunction('window.Store != undefined');
221
+
222
+ /**
223
+ * Current connection information
224
+ * @type {ClientInfo}
225
+ */
226
+ this.info = new ClientInfo(this, await this.pupPage.evaluate(() => {
227
+ return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
228
+ }));
229
+
230
+ this.interface = new InterfaceController(this);
231
+
232
+ //Load util functions (serializers, helper functions)
233
+ await this.pupPage.evaluate(LoadUtils);
234
+
235
+ await this.attachEventListeners(reinject);
236
+ reinject = true;
237
+ }
238
+ /**
239
+ * Emitted when the client has initialized and is ready to receive messages.
240
+ * @event Client#ready
241
+ */
242
+ this.emit(Events.READY);
243
+ this.authStrategy.afterAuthReady();
244
+ });
245
+ let lastPercent = null;
246
+ await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => {
247
+ if (lastPercent !== percent) {
248
+ lastPercent = percent;
249
+ this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now
250
+ }
251
+ });
252
+ }
253
+ const logoutCatchInjected = await this.pupPage.evaluate(() => {
254
+ return typeof window.onLogoutEvent !== 'undefined';
255
+ });
256
+ if (!logoutCatchInjected) {
257
+ await this.pupPage.exposeFunction('onLogoutEvent', async () => {
258
+ this.lastLoggedOut = true;
259
+ await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _);
260
+ });
261
+ }
262
+ await this.pupPage.evaluate(() => {
263
+ window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); });
264
+ window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); });
265
+ window.AuthStore.Cmd.on('offline_progress_update', () => {
266
+ window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress());
267
+ });
268
+ window.AuthStore.Cmd.on('logout', async () => {
269
+ await window.onLogoutEvent();
270
+ });
271
+ });
272
+ }
273
+
274
+ /**
275
+ * Sets up events and requirements, kicks off authentication request
276
+ */
277
+ async initialize() {
278
+
279
+ // let
280
+ // /**
281
+ // * @type {puppeteer.Browser}
282
+ // */
283
+ // browser,
284
+ // /**
285
+ // * @type {puppeteer.Page}
286
+ // */
287
+ // page;
288
+
289
+ // browser = null;
290
+ // page = null;
291
+
292
+ // await this.authStrategy.beforeBrowserInitialized();
293
+
294
+ // const puppeteerOpts = this.options.puppeteer;
295
+ // if (puppeteerOpts && puppeteerOpts.browserWSEndpoint) {
296
+ // browser = await puppeteer.connect(puppeteerOpts);
297
+ // page = await browser.newPage();
298
+ // } else {
299
+ // const browserArgs = [...(puppeteerOpts.args || [])];
300
+ // if(!browserArgs.find(arg => arg.includes('--user-agent'))) {
301
+ // browserArgs.push(`--user-agent=${this.options.userAgent}`);
302
+ // }
303
+ // // navigator.webdriver fix
304
+ // browserArgs.push('--disable-blink-features=AutomationControlled');
305
+
306
+ // browser = await puppeteer.launch({...puppeteerOpts, args: browserArgs});
307
+ // page = (await browser.pages())[0];
308
+ // }
309
+
310
+ let page = await pie.getPage(this.pupBrowser, this.browserWindow);
311
+
312
+ if (this.options.proxyAuthentication !== undefined) {
313
+ await page.authenticate(this.options.proxyAuthentication);
314
+ }
315
+
316
+ await page.setUserAgent(this.options.userAgent);
317
+ if (this.options.bypassCSP) await page.setBypassCSP(true);
318
+
319
+ // this.pupBrowser = browser;
320
+ this.pupPage = page;
321
+
322
+ await this.authStrategy.afterBrowserInitialized();
323
+ await this.initWebVersionCache();
324
+
325
+ // ocVersion (isOfficialClient patch)
326
+ // remove after 2.3000.x hard release
327
+ await page.evaluateOnNewDocument(() => {
328
+ const originalError = Error;
329
+ window.originalError = originalError;
330
+ //eslint-disable-next-line no-global-assign
331
+ Error = function (message) {
332
+ const error = new originalError(message);
333
+ const originalStack = error.stack;
334
+ if (error.stack.includes('moduleRaid')) error.stack = originalStack + '\n at https://web.whatsapp.com/vendors~lazy_loaded_low_priority_components.05e98054dbd60f980427.js:2:44';
335
+ return error;
336
+ };
337
+ });
338
+
339
+ await page.goto(WhatsWebURL, {
340
+ waitUntil: 'load',
341
+ timeout: 0,
342
+ referer: 'https://whatsapp.com/'
343
+ });
344
+
345
+ await this.inject();
346
+
347
+ this.pupPage.on('framenavigated', async (frame) => {
348
+ if(frame.url().includes('post_logout=1') || this.lastLoggedOut) {
349
+ this.emit(Events.DISCONNECTED, 'LOGOUT');
350
+ await this.authStrategy.logout();
351
+ await this.authStrategy.beforeBrowserInitialized();
352
+ await this.authStrategy.afterBrowserInitialized();
353
+ this.lastLoggedOut = false;
354
+ }
355
+ await this.inject(true);
356
+ });
357
+ }
358
+
359
+ /**
360
+ * Request authentication via pairing code instead of QR code
361
+ * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil)
362
+ * @param {boolean} showNotification - Show notification to pair on phone number
363
+ * @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH"
364
+ */
365
+ async requestPairingCode(phoneNumber, showNotification = true) {
366
+ return await this.pupPage.evaluate(async (phoneNumber, showNotification) => {
367
+ window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
368
+ await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
369
+ return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
370
+ }, phoneNumber, showNotification);
371
+ }
372
+
373
+ /**
374
+ * Attach event listeners to WA Web
375
+ * Private function
376
+ * @property {boolean} reinject is this a reinject?
377
+ */
378
+ async attachEventListeners(reinject = false) {
379
+ if (!reinject) {
380
+ await this.pupPage.exposeFunction('onAddMessageEvent', msg => {
381
+ if (msg.type === 'gp2') {
382
+ const notification = new GroupNotification(this, msg);
383
+ if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
384
+ /**
385
+ * Emitted when a user joins the chat via invite link or is added by an admin.
386
+ * @event Client#group_join
387
+ * @param {GroupNotification} notification GroupNotification with more information about the action
388
+ */
389
+ this.emit(Events.GROUP_JOIN, notification);
390
+ } else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
391
+ /**
392
+ * Emitted when a user leaves the chat or is removed by an admin.
393
+ * @event Client#group_leave
394
+ * @param {GroupNotification} notification GroupNotification with more information about the action
395
+ */
396
+ this.emit(Events.GROUP_LEAVE, notification);
397
+ } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
398
+ /**
399
+ * Emitted when a current user is promoted to an admin or demoted to a regular user.
400
+ * @event Client#group_admin_changed
401
+ * @param {GroupNotification} notification GroupNotification with more information about the action
402
+ */
403
+ this.emit(Events.GROUP_ADMIN_CHANGED, notification);
404
+ } else if (msg.subtype === 'membership_approval_request') {
405
+ /**
406
+ * Emitted when some user requested to join the group
407
+ * that has the membership approval mode turned on
408
+ * @event Client#group_membership_request
409
+ * @param {GroupNotification} notification GroupNotification with more information about the action
410
+ * @param {string} notification.chatId The group ID the request was made for
411
+ * @param {string} notification.author The user ID that made a request
412
+ * @param {number} notification.timestamp The timestamp the request was made at
413
+ */
414
+ this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
415
+ } else {
416
+ /**
417
+ * Emitted when group settings are updated, such as subject, description or picture.
418
+ * @event Client#group_update
419
+ * @param {GroupNotification} notification GroupNotification with more information about the action
420
+ */
421
+ this.emit(Events.GROUP_UPDATE, notification);
422
+ }
423
+ return;
424
+ }
425
+
426
+ const message = new Message(this, msg);
427
+
428
+ /**
429
+ * Emitted when a new message is created, which may include the current user's own messages.
430
+ * @event Client#message_create
431
+ * @param {Message} message The message that was created
432
+ */
433
+ this.emit(Events.MESSAGE_CREATE, message);
434
+
435
+ if (msg.id.fromMe) return;
436
+
437
+ /**
438
+ * Emitted when a new message is received.
439
+ * @event Client#message
440
+ * @param {Message} message The message that was received
441
+ */
442
+ this.emit(Events.MESSAGE_RECEIVED, message);
443
+ });
444
+
445
+ let last_message;
446
+
447
+ await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => {
448
+
449
+ if (msg.type === 'revoked') {
450
+ const message = new Message(this, msg);
451
+ let revoked_msg;
452
+ if (last_message && msg.id.id === last_message.id.id) {
453
+ revoked_msg = new Message(this, last_message);
454
+ }
455
+
456
+ /**
457
+ * Emitted when a message is deleted for everyone in the chat.
458
+ * @event Client#message_revoke_everyone
459
+ * @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
460
+ * @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
461
+ * Note that due to the way this data is captured, it may be possible that this param will be undefined.
462
+ */
463
+ this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
464
+ }
465
+
466
+ });
467
+
468
+ await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => {
469
+
470
+ if (msg.type !== 'revoked') {
471
+ last_message = msg;
472
+ }
473
+
474
+ /**
475
+ * The event notification that is received when one of
476
+ * the group participants changes their phone number.
477
+ */
478
+ const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
479
+
480
+ /**
481
+ * The event notification that is received when one of
482
+ * the contacts changes their phone number.
483
+ */
484
+ const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
485
+
486
+ if (isParticipant || isContact) {
487
+ /** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
488
+ const message = new Message(this, msg);
489
+
490
+ const newId = isParticipant ? msg.recipients[0] : msg.to;
491
+ const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
492
+
493
+ /**
494
+ * Emitted when a contact or a group participant changes their phone number.
495
+ * @event Client#contact_changed
496
+ * @param {Message} message Message with more information about the event.
497
+ * @param {String} oldId The user's id (an old one) who changed their phone number
498
+ * and who triggered the notification.
499
+ * @param {String} newId The user's new id after the change.
500
+ * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
501
+ */
502
+ this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
503
+ }
504
+ });
505
+
506
+ await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => {
507
+
508
+ if (!msg.isNewMsg) return;
509
+
510
+ const message = new Message(this, msg);
511
+
512
+ /**
513
+ * Emitted when a message is deleted by the current user.
514
+ * @event Client#message_revoke_me
515
+ * @param {Message} message The message that was revoked
516
+ */
517
+ this.emit(Events.MESSAGE_REVOKED_ME, message);
518
+
519
+ });
520
+
521
+ await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => {
522
+
523
+ const message = new Message(this, msg);
524
+
525
+ /**
526
+ * Emitted when an ack event occurrs on message type.
527
+ * @event Client#message_ack
528
+ * @param {Message} message The message that was affected
529
+ * @param {MessageAck} ack The new ACK value
530
+ */
531
+ this.emit(Events.MESSAGE_ACK, message, ack);
532
+
533
+ });
534
+
535
+ await this.pupPage.exposeFunction('onChatUnreadCountEvent', async (data) =>{
536
+ const chat = await this.getChatById(data.id);
537
+
538
+ /**
539
+ * Emitted when the chat unread count changes
540
+ */
541
+ this.emit(Events.UNREAD_COUNT, chat);
542
+ });
543
+
544
+ await this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
545
+
546
+ const message = new Message(this, msg);
547
+
548
+ /**
549
+ * Emitted when media has been uploaded for a message sent by the client.
550
+ * @event Client#media_uploaded
551
+ * @param {Message} message The message with media that was uploaded
552
+ */
553
+ this.emit(Events.MEDIA_UPLOADED, message);
554
+ });
555
+
556
+ await this.pupPage.exposeFunction('onAppStateChangedEvent', async (state) => {
557
+ /**
558
+ * Emitted when the connection state changes
559
+ * @event Client#change_state
560
+ * @param {WAState} state the new connection state
561
+ */
562
+ this.emit(Events.STATE_CHANGED, state);
563
+
564
+ const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
565
+
566
+ if (this.options.takeoverOnConflict) {
567
+ ACCEPTED_STATES.push(WAState.CONFLICT);
568
+
569
+ if (state === WAState.CONFLICT) {
570
+ setTimeout(() => {
571
+ this.pupPage.evaluate(() => window.Store.AppState.takeover());
572
+ }, this.options.takeoverTimeoutMs);
573
+ }
574
+ }
575
+
576
+ if (!ACCEPTED_STATES.includes(state)) {
577
+ /**
578
+ * Emitted when the client has been disconnected
579
+ * @event Client#disconnected
580
+ * @param {WAState|"LOGOUT"} reason reason that caused the disconnect
581
+ */
582
+ await this.authStrategy.disconnect();
583
+ this.emit(Events.DISCONNECTED, state);
584
+ this.destroy();
585
+ }
586
+ });
587
+
588
+ await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => {
589
+ const { battery, plugged } = state;
590
+
591
+ if (battery === undefined) return;
592
+
593
+ /**
594
+ * Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
595
+ * @event Client#change_battery
596
+ * @param {object} batteryInfo
597
+ * @param {number} batteryInfo.battery - The current battery percentage
598
+ * @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
599
+ * @deprecated
600
+ */
601
+ this.emit(Events.BATTERY_CHANGED, { battery, plugged });
602
+ });
603
+
604
+ await this.pupPage.exposeFunction('onIncomingCall', (call) => {
605
+ /**
606
+ * Emitted when a call is received
607
+ * @event Client#incoming_call
608
+ * @param {object} call
609
+ * @param {number} call.id - Call id
610
+ * @param {string} call.peerJid - Who called
611
+ * @param {boolean} call.isVideo - if is video
612
+ * @param {boolean} call.isGroup - if is group
613
+ * @param {boolean} call.canHandleLocally - if we can handle in waweb
614
+ * @param {boolean} call.outgoing - if is outgoing
615
+ * @param {boolean} call.webClientShouldHandle - If Waweb should handle
616
+ * @param {object} call.participants - Participants
617
+ */
618
+ const cll = new Call(this, call);
619
+ this.emit(Events.INCOMING_CALL, cll);
620
+ });
621
+
622
+ await this.pupPage.exposeFunction('onReaction', (reactions) => {
623
+ for (const reaction of reactions) {
624
+ /**
625
+ * Emitted when a reaction is sent, received, updated or removed
626
+ * @event Client#message_reaction
627
+ * @param {object} reaction
628
+ * @param {object} reaction.id - Reaction id
629
+ * @param {number} reaction.orphan - Orphan
630
+ * @param {?string} reaction.orphanReason - Orphan reason
631
+ * @param {number} reaction.timestamp - Timestamp
632
+ * @param {string} reaction.reaction - Reaction
633
+ * @param {boolean} reaction.read - Read
634
+ * @param {object} reaction.msgId - Parent message id
635
+ * @param {string} reaction.senderId - Sender id
636
+ * @param {?number} reaction.ack - Ack
637
+ */
638
+
639
+ this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
640
+ }
641
+ });
642
+
643
+ await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => {
644
+ const _chat = await this.getChatById(chat.id);
645
+
646
+ /**
647
+ * Emitted when a chat is removed
648
+ * @event Client#chat_removed
649
+ * @param {Chat} chat
650
+ */
651
+ this.emit(Events.CHAT_REMOVED, _chat);
652
+ });
653
+
654
+ await this.pupPage.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => {
655
+ const _chat = await this.getChatById(chat.id);
656
+
657
+ /**
658
+ * Emitted when a chat is archived/unarchived
659
+ * @event Client#chat_archived
660
+ * @param {Chat} chat
661
+ * @param {boolean} currState
662
+ * @param {boolean} prevState
663
+ */
664
+ this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
665
+ });
666
+
667
+ await this.pupPage.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => {
668
+
669
+ if(msg.type === 'revoked'){
670
+ return;
671
+ }
672
+ /**
673
+ * Emitted when messages are edited
674
+ * @event Client#message_edit
675
+ * @param {Message} message
676
+ * @param {string} newBody
677
+ * @param {string} prevBody
678
+ */
679
+ this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
680
+ });
681
+
682
+ await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => {
683
+
684
+ /**
685
+ * Emitted when messages are edited
686
+ * @event Client#message_ciphertext
687
+ * @param {Message} message
688
+ */
689
+ this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
690
+ });
691
+ }
692
+
693
+ await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => {
694
+ const _vote = new PollVote(this, vote);
695
+ /**
696
+ * Emitted when some poll option is selected or deselected,
697
+ * shows a user's current selected option(s) on the poll
698
+ * @event Client#vote_update
699
+ */
700
+ this.emit(Events.VOTE_UPDATE, _vote);
701
+ });
702
+
703
+ await this.pupPage.evaluate(() => {
704
+ window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
705
+ window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
706
+ window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
707
+ window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if (msg.id.fromMe && !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); });
708
+ window.Store.Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); });
709
+ window.Store.Msg.on('change:body change:caption', (msg, newBody, prevBody) => { window.onEditMessageEvent(window.WWebJS.getMessageModel(msg), newBody, prevBody); });
710
+ window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); });
711
+ window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); });
712
+ window.Store.Call.on('add', (call) => { window.onIncomingCall(call); });
713
+ window.Store.Chat.on('remove', async (chat) => { window.onRemoveChatEvent(await window.WWebJS.getChatModel(chat)); });
714
+ window.Store.Chat.on('change:archive', async (chat, currState, prevState) => { window.onArchiveChatEvent(await window.WWebJS.getChatModel(chat), currState, prevState); });
715
+ window.Store.Msg.on('add', (msg) => {
716
+ if (msg.isNewMsg) {
717
+ if(msg.type === 'ciphertext') {
718
+ // defer message event until ciphertext is resolved (type changed)
719
+ msg.once('change:type', (_msg) => window.onAddMessageEvent(window.WWebJS.getMessageModel(_msg)));
720
+ window.onAddMessageCiphertextEvent(window.WWebJS.getMessageModel(msg));
721
+ } else {
722
+ window.onAddMessageEvent(window.WWebJS.getMessageModel(msg));
723
+ }
724
+ }
725
+ });
726
+ window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);});
727
+ window.Store.PollVote.on('add', async (vote) => {
728
+ const pollVoteModel = await window.WWebJS.getPollVoteModel(vote);
729
+ pollVoteModel && window.onPollVoteEvent(pollVoteModel);
730
+ });
731
+
732
+ if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) {
733
+ const module = window.Store.AddonReactionTable;
734
+ const ogMethod = module.bulkUpsert;
735
+ module.bulkUpsert = ((...args) => {
736
+ window.onReaction(args[0].map(reaction => {
737
+ const msgKey = reaction.id;
738
+ const parentMsgKey = reaction.reactionParentKey;
739
+ const timestamp = reaction.reactionTimestamp / 1000;
740
+ const sender = reaction.author ?? reaction.from;
741
+ const senderUserJid = sender._serialized;
742
+
743
+ return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp };
744
+ }));
745
+
746
+ return ogMethod(...args);
747
+ }).bind(module);
748
+ } else {
749
+ const module = window.Store.createOrUpdateReactionsModule;
750
+ const ogMethod = module.createOrUpdateReactions;
751
+ module.createOrUpdateReactions = ((...args) => {
752
+ window.onReaction(args[0].map(reaction => {
753
+ const msgKey = window.Store.MsgKey.fromString(reaction.msgKey);
754
+ const parentMsgKey = window.Store.MsgKey.fromString(reaction.parentMsgKey);
755
+ const timestamp = reaction.timestamp / 1000;
756
+
757
+ return {...reaction, msgKey, parentMsgKey, timestamp };
758
+ }));
759
+
760
+ return ogMethod(...args);
761
+ }).bind(module);
762
+ }
763
+ });
764
+ }
765
+
766
+ async initWebVersionCache() {
767
+ const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
768
+ const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
769
+
770
+ const requestedVersion = this.options.webVersion;
771
+ const versionContent = await webCache.resolve(requestedVersion);
772
+
773
+ if(versionContent) {
774
+ await this.pupPage.setRequestInterception(true);
775
+ this.pupPage.on('request', async (req) => {
776
+ if(req.url() === WhatsWebURL) {
777
+ req.respond({
778
+ status: 200,
779
+ contentType: 'text/html',
780
+ body: versionContent
781
+ });
782
+ } else {
783
+ req.continue();
784
+ }
785
+ });
786
+ } else {
787
+ this.pupPage.on('response', async (res) => {
788
+ if(res.ok() && res.url() === WhatsWebURL) {
789
+ const indexHtml = await res.text();
790
+ this.currentIndexHtml = indexHtml;
791
+ }
792
+ });
793
+ }
794
+ }
795
+
796
+ /**
797
+ * Closes the client
798
+ */
799
+ async destroy() {
800
+ await this.pupBrowser.close();
801
+ await this.authStrategy.destroy();
802
+ }
803
+
804
+ /**
805
+ * Logs out the client, closing the current session
806
+ */
807
+ async logout() {
808
+ await this.pupPage.evaluate(() => {
809
+ if (window.Store && window.Store.AppState && typeof window.Store.AppState.logout === 'function') {
810
+ return window.Store.AppState.logout();
811
+ }
812
+ });
813
+ // await this.pupBrowser.close();
814
+
815
+ // let maxDelay = 0;
816
+ // while (this.pupBrowser.isConnected() && (maxDelay < 10)) { // waits a maximum of 1 second before calling the AuthStrategy
817
+ // await new Promise(resolve => setTimeout(resolve, 100));
818
+ // maxDelay++;
819
+ // }
820
+
821
+ // await this.authStrategy.logout();
822
+ }
823
+
824
+ /**
825
+ * Returns the version of WhatsApp Web currently being run
826
+ * @returns {Promise<string>}
827
+ */
828
+ async getWWebVersion() {
829
+ return await this.pupPage.evaluate(() => {
830
+ return window.Debug.VERSION;
831
+ });
832
+ }
833
+
834
+ /**
835
+ * Mark as seen for the Chat
836
+ * @param {string} chatId
837
+ * @returns {Promise<boolean>} result
838
+ *
839
+ */
840
+ async sendSeen(chatId) {
841
+ const result = await this.pupPage.evaluate(async (chatId) => {
842
+ return window.WWebJS.sendSeen(chatId);
843
+
844
+ }, chatId);
845
+ return result;
846
+ }
847
+
848
+ /**
849
+ * An object representing mentions of groups
850
+ * @typedef {Object} GroupMention
851
+ * @property {string} subject - The name of a group to mention (can be custom)
852
+ * @property {string} id - The group ID, e.g.: 'XXXXXXXXXX@g.us'
853
+ */
854
+
855
+ /**
856
+ * Message options.
857
+ * @typedef {Object} MessageSendOptions
858
+ * @property {boolean} [linkPreview=true] - Show links preview. Has no effect on multi-device accounts.
859
+ * @property {boolean} [sendAudioAsVoice=false] - Send audio as voice message with a generated waveform
860
+ * @property {boolean} [sendVideoAsGif=false] - Send video as gif
861
+ * @property {boolean} [sendMediaAsSticker=false] - Send media as a sticker
862
+ * @property {boolean} [sendMediaAsDocument=false] - Send media as a document
863
+ * @property {boolean} [isViewOnce=false] - Send photo/video as a view once message
864
+ * @property {boolean} [parseVCards=true] - Automatically parse vCards and send them as contacts
865
+ * @property {string} [caption] - Image or video caption
866
+ * @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to)
867
+ * @property {GroupMention[]} [groupMentions] - An array of object that handle group mentions
868
+ * @property {string[]} [mentions] - User IDs to mention in the message
869
+ * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message
870
+ * @property {string} [invokedBotWid=undefined] - Bot Wid when doing a bot mention like @Meta AI
871
+ * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true).
872
+ * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
873
+ * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
874
+ * @property {MessageMedia} [media] - Media to be sent
875
+ */
876
+
877
+ /**
878
+ * Send a message to a specific chatId
879
+ * @param {string} chatId
880
+ * @param {string|MessageMedia|Location|Poll|Contact|Array<Contact>|Buttons|List} content
881
+ * @param {MessageSendOptions} [options] - Options used when sending the message
882
+ *
883
+ * @returns {Promise<Message>} Message that was just sent
884
+ */
885
+ async sendMessage(chatId, content, options = {}) {
886
+ if (options.mentions) {
887
+ !Array.isArray(options.mentions) && (options.mentions = [options.mentions]);
888
+ if (options.mentions.some((possiblyContact) => possiblyContact instanceof Contact)) {
889
+ console.warn('Mentions with an array of Contact are now deprecated. See more at https://github.com/pedroslopez/whatsapp-web.js/pull/2166.');
890
+ options.mentions = options.mentions.map((a) => a.id._serialized);
891
+ }
892
+ }
893
+
894
+ options.groupMentions && !Array.isArray(options.groupMentions) && (options.groupMentions = [options.groupMentions]);
895
+
896
+ let internalOptions = {
897
+ linkPreview: options.linkPreview === false ? undefined : true,
898
+ sendAudioAsVoice: options.sendAudioAsVoice,
899
+ sendVideoAsGif: options.sendVideoAsGif,
900
+ sendMediaAsSticker: options.sendMediaAsSticker,
901
+ sendMediaAsDocument: options.sendMediaAsDocument,
902
+ caption: options.caption,
903
+ quotedMessageId: options.quotedMessageId,
904
+ parseVCards: options.parseVCards !== false,
905
+ mentionedJidList: options.mentions || [],
906
+ groupMentions: options.groupMentions,
907
+ invokedBotWid: options.invokedBotWid,
908
+ extraOptions: options.extra
909
+ };
910
+
911
+ const sendSeen = typeof options.sendSeen === 'undefined' ? true : options.sendSeen;
912
+
913
+ if (content instanceof MessageMedia) {
914
+ internalOptions.attachment = content;
915
+ internalOptions.isViewOnce = options.isViewOnce,
916
+ content = '';
917
+ } else if (options.media instanceof MessageMedia) {
918
+ internalOptions.attachment = options.media;
919
+ internalOptions.caption = content;
920
+ internalOptions.isViewOnce = options.isViewOnce,
921
+ content = '';
922
+ } else if (content instanceof Location) {
923
+ internalOptions.location = content;
924
+ content = '';
925
+ } else if (content instanceof Poll) {
926
+ internalOptions.poll = content;
927
+ content = '';
928
+ } else if (content instanceof Contact) {
929
+ internalOptions.contactCard = content.id._serialized;
930
+ content = '';
931
+ } else if (Array.isArray(content) && content.length > 0 && content[0] instanceof Contact) {
932
+ internalOptions.contactCardList = content.map(contact => contact.id._serialized);
933
+ content = '';
934
+ } else if (content instanceof Buttons) {
935
+ if (content.type !== 'chat') { internalOptions.attachment = content.body; }
936
+ internalOptions.buttons = content;
937
+ content = '';
938
+ } else if (content instanceof List) {
939
+ internalOptions.list = content;
940
+ content = '';
941
+ }
942
+
943
+ if (internalOptions.sendMediaAsSticker && internalOptions.attachment) {
944
+ internalOptions.attachment = await Util.formatToWebpSticker(
945
+ internalOptions.attachment, {
946
+ name: options.stickerName,
947
+ author: options.stickerAuthor,
948
+ categories: options.stickerCategories
949
+ }, this.pupPage
950
+ );
951
+ }
952
+
953
+ const newMessage = await this.pupPage.evaluate(async (chatId, message, options, sendSeen) => {
954
+ const chatWid = window.Store.WidFactory.createWid(chatId);
955
+ const chat = await window.Store.Chat.find(chatWid);
956
+
957
+
958
+ if (sendSeen) {
959
+ await window.WWebJS.sendSeen(chatId);
960
+ }
961
+
962
+ const msg = await window.WWebJS.sendMessage(chat, message, options, sendSeen);
963
+ return window.WWebJS.getMessageModel(msg);
964
+ }, chatId, content, internalOptions, sendSeen);
965
+
966
+ return new Message(this, newMessage);
967
+ }
968
+
969
+ /**
970
+ * Searches for messages
971
+ * @param {string} query
972
+ * @param {Object} [options]
973
+ * @param {number} [options.page]
974
+ * @param {number} [options.limit]
975
+ * @param {string} [options.chatId]
976
+ * @returns {Promise<Message[]>}
977
+ */
978
+ async searchMessages(query, options = {}) {
979
+ const messages = await this.pupPage.evaluate(async (query, page, count, remote) => {
980
+ const { messages } = await window.Store.Msg.search(query, page, count, remote);
981
+ return messages.map(msg => window.WWebJS.getMessageModel(msg));
982
+ }, query, options.page, options.limit, options.chatId);
983
+
984
+ return messages.map(msg => new Message(this, msg));
985
+ }
986
+
987
+ /**
988
+ * Get all current chat instances
989
+ * @returns {Promise<Array<Chat>>}
990
+ */
991
+ async getChats() {
992
+ let chats = await this.pupPage.evaluate(async () => {
993
+ return await window.WWebJS.getChats();
994
+ });
995
+
996
+ return chats.map(chat => ChatFactory.create(this, chat));
997
+ }
998
+
999
+ /**
1000
+ * Get chat instance by ID
1001
+ * @param {string} chatId
1002
+ * @returns {Promise<Chat>}
1003
+ */
1004
+ async getChatById(chatId) {
1005
+ let chat = await this.pupPage.evaluate(async chatId => {
1006
+ return await window.WWebJS.getChat(chatId);
1007
+ }, chatId);
1008
+
1009
+ return ChatFactory.create(this, chat);
1010
+ }
1011
+
1012
+ /**
1013
+ * Get all current contact instances
1014
+ * @returns {Promise<Array<Contact>>}
1015
+ */
1016
+ async getContacts() {
1017
+ let contacts = await this.pupPage.evaluate(() => {
1018
+ return window.WWebJS.getContacts();
1019
+ });
1020
+
1021
+ return contacts.map(contact => ContactFactory.create(this, contact));
1022
+ }
1023
+
1024
+ /**
1025
+ * Get contact instance by ID
1026
+ * @param {string} contactId
1027
+ * @returns {Promise<Contact>}
1028
+ */
1029
+ async getContactById(contactId) {
1030
+ let contact = await this.pupPage.evaluate(contactId => {
1031
+ return window.WWebJS.getContact(contactId);
1032
+ }, contactId);
1033
+
1034
+ return ContactFactory.create(this, contact);
1035
+ }
1036
+
1037
+ async getMessageById(messageId) {
1038
+ const msg = await this.pupPage.evaluate(async messageId => {
1039
+ let msg = window.Store.Msg.get(messageId);
1040
+ if(msg) return window.WWebJS.getMessageModel(msg);
1041
+
1042
+ const params = messageId.split('_');
1043
+ if (params.length !== 3 && params.length !== 4) throw new Error('Invalid serialized message id specified');
1044
+
1045
+ let messagesObject = await window.Store.Msg.getMessagesById([messageId]);
1046
+ if (messagesObject && messagesObject.messages.length) msg = messagesObject.messages[0];
1047
+
1048
+ if(msg) return window.WWebJS.getMessageModel(msg);
1049
+ }, messageId);
1050
+
1051
+ if(msg) return new Message(this, msg);
1052
+ return null;
1053
+ }
1054
+
1055
+ /**
1056
+ * Returns an object with information about the invite code's group
1057
+ * @param {string} inviteCode
1058
+ * @returns {Promise<object>} Invite information
1059
+ */
1060
+ async getInviteInfo(inviteCode) {
1061
+ return await this.pupPage.evaluate(inviteCode => {
1062
+ return window.Store.GroupInvite.queryGroupInvite(inviteCode);
1063
+ }, inviteCode);
1064
+ }
1065
+
1066
+ /**
1067
+ * Accepts an invitation to join a group
1068
+ * @param {string} inviteCode Invitation code
1069
+ * @returns {Promise<string>} Id of the joined Chat
1070
+ */
1071
+ async acceptInvite(inviteCode) {
1072
+ const res = await this.pupPage.evaluate(async inviteCode => {
1073
+ return await window.Store.GroupInvite.joinGroupViaInvite(inviteCode);
1074
+ }, inviteCode);
1075
+
1076
+ return res.gid._serialized;
1077
+ }
1078
+
1079
+ /**
1080
+ * Accepts a private invitation to join a group
1081
+ * @param {object} inviteInfo Invite V4 Info
1082
+ * @returns {Promise<Object>}
1083
+ */
1084
+ async acceptGroupV4Invite(inviteInfo) {
1085
+ if (!inviteInfo.inviteCode) throw 'Invalid invite code, try passing the message.inviteV4 object';
1086
+ if (inviteInfo.inviteCodeExp == 0) throw 'Expired invite code';
1087
+ return this.pupPage.evaluate(async inviteInfo => {
1088
+ let { groupId, fromId, inviteCode, inviteCodeExp } = inviteInfo;
1089
+ let userWid = window.Store.WidFactory.createWid(fromId);
1090
+ return await window.Store.GroupInviteV4.joinGroupViaInviteV4(inviteCode, String(inviteCodeExp), groupId, userWid);
1091
+ }, inviteInfo);
1092
+ }
1093
+
1094
+ /**
1095
+ * Sets the current user's status message
1096
+ * @param {string} status New status message
1097
+ */
1098
+ async setStatus(status) {
1099
+ await this.pupPage.evaluate(async status => {
1100
+ return await window.Store.StatusUtils.setMyStatus(status);
1101
+ }, status);
1102
+ }
1103
+
1104
+ /**
1105
+ * Sets the current user's display name.
1106
+ * 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.
1107
+ * @param {string} displayName New display name
1108
+ * @returns {Promise<Boolean>}
1109
+ */
1110
+ async setDisplayName(displayName) {
1111
+ const couldSet = await this.pupPage.evaluate(async displayName => {
1112
+ if(!window.Store.Conn.canSetMyPushname()) return false;
1113
+ await window.Store.Settings.setPushname(displayName);
1114
+ return true;
1115
+ }, displayName);
1116
+
1117
+ return couldSet;
1118
+ }
1119
+
1120
+ /**
1121
+ * Gets the current connection state for the client
1122
+ * @returns {WAState}
1123
+ */
1124
+ async getState() {
1125
+ return await this.pupPage.evaluate(() => {
1126
+ if(!window.Store) return null;
1127
+ return window.Store.AppState.state;
1128
+ });
1129
+ }
1130
+
1131
+ /**
1132
+ * Marks the client as online
1133
+ */
1134
+ async sendPresenceAvailable() {
1135
+ return await this.pupPage.evaluate(() => {
1136
+ return window.Store.PresenceUtils.sendPresenceAvailable();
1137
+ });
1138
+ }
1139
+
1140
+ /**
1141
+ * Marks the client as unavailable
1142
+ */
1143
+ async sendPresenceUnavailable() {
1144
+ return await this.pupPage.evaluate(() => {
1145
+ return window.Store.PresenceUtils.sendPresenceUnavailable();
1146
+ });
1147
+ }
1148
+
1149
+ /**
1150
+ * Enables and returns the archive state of the Chat
1151
+ * @returns {boolean}
1152
+ */
1153
+ async archiveChat(chatId) {
1154
+ return await this.pupPage.evaluate(async chatId => {
1155
+ let chat = await window.Store.Chat.get(chatId);
1156
+ await window.Store.Cmd.archiveChat(chat, true);
1157
+ return true;
1158
+ }, chatId);
1159
+ }
1160
+
1161
+ /**
1162
+ * Changes and returns the archive state of the Chat
1163
+ * @returns {boolean}
1164
+ */
1165
+ async unarchiveChat(chatId) {
1166
+ return await this.pupPage.evaluate(async chatId => {
1167
+ let chat = await window.Store.Chat.get(chatId);
1168
+ await window.Store.Cmd.archiveChat(chat, false);
1169
+ return false;
1170
+ }, chatId);
1171
+ }
1172
+
1173
+ /**
1174
+ * Pins the Chat
1175
+ * @returns {Promise<boolean>} New pin state. Could be false if the max number of pinned chats was reached.
1176
+ */
1177
+ async pinChat(chatId) {
1178
+ return this.pupPage.evaluate(async chatId => {
1179
+ let chat = window.Store.Chat.get(chatId);
1180
+ if (chat.pin) {
1181
+ return true;
1182
+ }
1183
+ const MAX_PIN_COUNT = 3;
1184
+ const chatModels = window.Store.Chat.getModelsArray();
1185
+ if (chatModels.length > MAX_PIN_COUNT) {
1186
+ let maxPinned = chatModels[MAX_PIN_COUNT - 1].pin;
1187
+ if (maxPinned) {
1188
+ return false;
1189
+ }
1190
+ }
1191
+ await window.Store.Cmd.pinChat(chat, true);
1192
+ return true;
1193
+ }, chatId);
1194
+ }
1195
+
1196
+ /**
1197
+ * Unpins the Chat
1198
+ * @returns {Promise<boolean>} New pin state
1199
+ */
1200
+ async unpinChat(chatId) {
1201
+ return this.pupPage.evaluate(async chatId => {
1202
+ let chat = window.Store.Chat.get(chatId);
1203
+ if (!chat.pin) {
1204
+ return false;
1205
+ }
1206
+ await window.Store.Cmd.pinChat(chat, false);
1207
+ return false;
1208
+ }, chatId);
1209
+ }
1210
+
1211
+ /**
1212
+ * Mutes this chat forever, unless a date is specified
1213
+ * @param {string} chatId ID of the chat that will be muted
1214
+ * @param {?Date} unmuteDate Date when the chat will be unmuted, leave as is to mute forever
1215
+ */
1216
+ async muteChat(chatId, unmuteDate) {
1217
+ unmuteDate = unmuteDate ? unmuteDate.getTime() / 1000 : -1;
1218
+ await this.pupPage.evaluate(async (chatId, timestamp) => {
1219
+ let chat = await window.Store.Chat.get(chatId);
1220
+ await chat.mute.mute({expiration: timestamp, sendDevice:!0});
1221
+ }, chatId, unmuteDate || -1);
1222
+ }
1223
+
1224
+ /**
1225
+ * Unmutes the Chat
1226
+ * @param {string} chatId ID of the chat that will be unmuted
1227
+ */
1228
+ async unmuteChat(chatId) {
1229
+ await this.pupPage.evaluate(async chatId => {
1230
+ let chat = await window.Store.Chat.get(chatId);
1231
+ await window.Store.Cmd.muteChat(chat, false);
1232
+ }, chatId);
1233
+ }
1234
+
1235
+ /**
1236
+ * Mark the Chat as unread
1237
+ * @param {string} chatId ID of the chat that will be marked as unread
1238
+ */
1239
+ async markChatUnread(chatId) {
1240
+ await this.pupPage.evaluate(async chatId => {
1241
+ let chat = await window.Store.Chat.get(chatId);
1242
+ await window.Store.Cmd.markChatUnread(chat, true);
1243
+ }, chatId);
1244
+ }
1245
+
1246
+ /**
1247
+ * Returns the contact ID's profile picture URL, if privacy settings allow it
1248
+ * @param {string} contactId the whatsapp user's ID
1249
+ * @returns {Promise<string>}
1250
+ */
1251
+ async getProfilePicUrl(contactId) {
1252
+ const profilePic = await this.pupPage.evaluate(async contactId => {
1253
+ try {
1254
+ const chatWid = window.Store.WidFactory.createWid(contactId);
1255
+ return window.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0')
1256
+ ? await window.Store.ProfilePic.profilePicFind(chatWid)
1257
+ : await window.Store.ProfilePic.requestProfilePicFromServer(chatWid);
1258
+ } catch (err) {
1259
+ if(err.name === 'ServerStatusCodeError') return undefined;
1260
+ throw err;
1261
+ }
1262
+ }, contactId);
1263
+
1264
+ return profilePic ? profilePic.eurl : undefined;
1265
+ }
1266
+
1267
+ /**
1268
+ * Gets the Contact's common groups with you. Returns empty array if you don't have any common group.
1269
+ * @param {string} contactId the whatsapp user's ID (_serialized format)
1270
+ * @returns {Promise<WAWebJS.ChatId[]>}
1271
+ */
1272
+ async getCommonGroups(contactId) {
1273
+ const commonGroups = await this.pupPage.evaluate(async (contactId) => {
1274
+ let contact = window.Store.Contact.get(contactId);
1275
+ if (!contact) {
1276
+ const wid = window.Store.WidFactory.createUserWid(contactId);
1277
+ const chatConstructor = window.Store.Contact.getModelsArray().find(c=>!c.isGroup).constructor;
1278
+ contact = new chatConstructor({id: wid});
1279
+ }
1280
+
1281
+ if (contact.commonGroups) {
1282
+ return contact.commonGroups.serialize();
1283
+ }
1284
+ const status = await window.Store.findCommonGroups(contact);
1285
+ if (status) {
1286
+ return contact.commonGroups.serialize();
1287
+ }
1288
+ return [];
1289
+ }, contactId);
1290
+ const chats = [];
1291
+ for (const group of commonGroups) {
1292
+ chats.push(group.id);
1293
+ }
1294
+ return chats;
1295
+ }
1296
+
1297
+ /**
1298
+ * Force reset of connection state for the client
1299
+ */
1300
+ async resetState() {
1301
+ await this.pupPage.evaluate(() => {
1302
+ window.Store.AppState.phoneWatchdog.shiftTimer.forceRunNow();
1303
+ });
1304
+ }
1305
+
1306
+ /**
1307
+ * Check if a given ID is registered in whatsapp
1308
+ * @param {string} id the whatsapp user's ID
1309
+ * @returns {Promise<Boolean>}
1310
+ */
1311
+ async isRegisteredUser(id) {
1312
+ return Boolean(await this.getNumberId(id));
1313
+ }
1314
+
1315
+ /**
1316
+ * Get the registered WhatsApp ID for a number.
1317
+ * Will return null if the number is not registered on WhatsApp.
1318
+ * @param {string} number Number or ID ("@c.us" will be automatically appended if not specified)
1319
+ * @returns {Promise<Object|null>}
1320
+ */
1321
+ async getNumberId(number) {
1322
+ if (!number.endsWith('@c.us')) {
1323
+ number += '@c.us';
1324
+ }
1325
+
1326
+ return await this.pupPage.evaluate(async number => {
1327
+ const wid = window.Store.WidFactory.createWid(number);
1328
+ const result = await window.Store.QueryExist(wid);
1329
+ if (!result || result.wid === undefined) return null;
1330
+ return result.wid;
1331
+ }, number);
1332
+ }
1333
+
1334
+ /**
1335
+ * Get the formatted number of a WhatsApp ID.
1336
+ * @param {string} number Number or ID
1337
+ * @returns {Promise<string>}
1338
+ */
1339
+ async getFormattedNumber(number) {
1340
+ if (!number.endsWith('@s.whatsapp.net')) number = number.replace('c.us', 's.whatsapp.net');
1341
+ if (!number.includes('@s.whatsapp.net')) number = `${number}@s.whatsapp.net`;
1342
+
1343
+ return await this.pupPage.evaluate(async numberId => {
1344
+ return window.Store.NumberInfo.formattedPhoneNumber(numberId);
1345
+ }, number);
1346
+ }
1347
+
1348
+ /**
1349
+ * Get the country code of a WhatsApp ID.
1350
+ * @param {string} number Number or ID
1351
+ * @returns {Promise<string>}
1352
+ */
1353
+ async getCountryCode(number) {
1354
+ number = number.replace(' ', '').replace('+', '').replace('@c.us', '');
1355
+
1356
+ return await this.pupPage.evaluate(async numberId => {
1357
+ return window.Store.NumberInfo.findCC(numberId);
1358
+ }, number);
1359
+ }
1360
+
1361
+ /**
1362
+ * An object that represents the result for a participant added to a group
1363
+ * @typedef {Object} ParticipantResult
1364
+ * @property {number} statusCode The status code of the result
1365
+ * @property {string} message The result message
1366
+ * @property {boolean} isGroupCreator Indicates if the participant is a group creator
1367
+ * @property {boolean} isInviteV4Sent Indicates if the inviteV4 was sent to the participant
1368
+ */
1369
+
1370
+ /**
1371
+ * An object that handles the result for {@link createGroup} method
1372
+ * @typedef {Object} CreateGroupResult
1373
+ * @property {string} title A group title
1374
+ * @property {Object} gid An object that handles the newly created group ID
1375
+ * @property {string} gid.server
1376
+ * @property {string} gid.user
1377
+ * @property {string} gid._serialized
1378
+ * @property {Object.<string, ParticipantResult>} participants An object that handles the result value for each added to the group participant
1379
+ */
1380
+
1381
+ /**
1382
+ * An object that handles options for group creation
1383
+ * @typedef {Object} CreateGroupOptions
1384
+ * @property {number} [messageTimer = 0] The number of seconds for the messages to disappear in the group (0 by default, won't take an effect if the group is been creating with myself only)
1385
+ * @property {string|undefined} parentGroupId The ID of a parent community group to link the newly created group with (won't take an effect if the group is been creating with myself only)
1386
+ * @property {boolean} [autoSendInviteV4 = true] If true, the inviteV4 will be sent to those participants who have restricted others from being automatically added to groups, otherwise the inviteV4 won't be sent (true by default)
1387
+ * @property {string} [comment = ''] The comment to be added to an inviteV4 (empty string by default)
1388
+ */
1389
+
1390
+ /**
1391
+ * Creates a new group
1392
+ * @param {string} title Group title
1393
+ * @param {string|Contact|Array<Contact|string>|undefined} participants A single Contact object or an ID as a string or an array of Contact objects or contact IDs to add to the group
1394
+ * @param {CreateGroupOptions} options An object that handles options for group creation
1395
+ * @returns {Promise<CreateGroupResult|string>} Object with resulting data or an error message as a string
1396
+ */
1397
+ async createGroup(title, participants = [], options = {}) {
1398
+ !Array.isArray(participants) && (participants = [participants]);
1399
+ participants.map(p => (p instanceof Contact) ? p.id._serialized : p);
1400
+
1401
+ return await this.pupPage.evaluate(async (title, participants, options) => {
1402
+ const { messageTimer = 0, parentGroupId, autoSendInviteV4 = true, comment = '' } = options;
1403
+ const participantData = {}, participantWids = [], failedParticipants = [];
1404
+ let createGroupResult, parentGroupWid;
1405
+
1406
+ const addParticipantResultCodes = {
1407
+ default: 'An unknown error occupied while adding a participant',
1408
+ 200: 'The participant was added successfully',
1409
+ 403: 'The participant can be added by sending private invitation only',
1410
+ 404: 'The phone number is not registered on WhatsApp'
1411
+ };
1412
+
1413
+ for (const participant of participants) {
1414
+ const pWid = window.Store.WidFactory.createWid(participant);
1415
+ if ((await window.Store.QueryExist(pWid))?.wid) participantWids.push(pWid);
1416
+ else failedParticipants.push(participant);
1417
+ }
1418
+
1419
+ parentGroupId && (parentGroupWid = window.Store.WidFactory.createWid(parentGroupId));
1420
+
1421
+ try {
1422
+ createGroupResult = await window.Store.GroupUtils.createGroup(
1423
+ {
1424
+ 'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode,
1425
+ 'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode,
1426
+ 'announce': options.announce === undefined ? true : options.announce,
1427
+ 'ephemeralDuration': messageTimer,
1428
+ 'full': undefined,
1429
+ 'parentGroupId': parentGroupWid,
1430
+ 'restrict': options.restrict === undefined ? true : options.restrict,
1431
+ 'thumb': undefined,
1432
+ 'title': title,
1433
+ },
1434
+ participantWids
1435
+ );
1436
+ } catch (err) {
1437
+ return 'CreateGroupError: An unknown error occupied while creating a group';
1438
+ }
1439
+
1440
+ for (const participant of createGroupResult.participants) {
1441
+ let isInviteV4Sent = false;
1442
+ const participantId = participant.wid._serialized;
1443
+ const statusCode = participant.error ?? 200;
1444
+
1445
+ if (autoSendInviteV4 && statusCode === 403) {
1446
+ window.Store.Contact.gadd(participant.wid, { silent: true });
1447
+ const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage(
1448
+ await window.Store.Chat.find(participant.wid),
1449
+ createGroupResult.wid._serialized,
1450
+ createGroupResult.subject,
1451
+ participant.invite_code,
1452
+ participant.invite_code_exp,
1453
+ comment,
1454
+ await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid)
1455
+ );
1456
+ isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6')
1457
+ ? addParticipantResult === 'OK'
1458
+ : addParticipantResult.messageSendResult === 'OK';
1459
+ }
1460
+
1461
+ participantData[participantId] = {
1462
+ statusCode: statusCode,
1463
+ message: addParticipantResultCodes[statusCode] || addParticipantResultCodes.default,
1464
+ isGroupCreator: participant.type === 'superadmin',
1465
+ isInviteV4Sent: isInviteV4Sent
1466
+ };
1467
+ }
1468
+
1469
+ for (const f of failedParticipants) {
1470
+ participantData[f] = {
1471
+ statusCode: 404,
1472
+ message: addParticipantResultCodes[404],
1473
+ isGroupCreator: false,
1474
+ isInviteV4Sent: false
1475
+ };
1476
+ }
1477
+
1478
+ return { title: title, gid: createGroupResult.wid, participants: participantData };
1479
+ }, title, participants, options);
1480
+ }
1481
+
1482
+ /**
1483
+ * Get all current Labels
1484
+ * @returns {Promise<Array<Label>>}
1485
+ */
1486
+ async getLabels() {
1487
+ const labels = await this.pupPage.evaluate(async () => {
1488
+ return window.WWebJS.getLabels();
1489
+ });
1490
+
1491
+ return labels.map(data => new Label(this, data));
1492
+ }
1493
+
1494
+ /**
1495
+ * Get Label instance by ID
1496
+ * @param {string} labelId
1497
+ * @returns {Promise<Label>}
1498
+ */
1499
+ async getLabelById(labelId) {
1500
+ const label = await this.pupPage.evaluate(async (labelId) => {
1501
+ return window.WWebJS.getLabel(labelId);
1502
+ }, labelId);
1503
+
1504
+ return new Label(this, label);
1505
+ }
1506
+
1507
+ /**
1508
+ * Get all Labels assigned to a chat
1509
+ * @param {string} chatId
1510
+ * @returns {Promise<Array<Label>>}
1511
+ */
1512
+ async getChatLabels(chatId) {
1513
+ const labels = await this.pupPage.evaluate(async (chatId) => {
1514
+ return window.WWebJS.getChatLabels(chatId);
1515
+ }, chatId);
1516
+
1517
+ return labels.map(data => new Label(this, data));
1518
+ }
1519
+
1520
+ /**
1521
+ * Get all Chats for a specific Label
1522
+ * @param {string} labelId
1523
+ * @returns {Promise<Array<Chat>>}
1524
+ */
1525
+ async getChatsByLabelId(labelId) {
1526
+ const chatIds = await this.pupPage.evaluate(async (labelId) => {
1527
+ const label = window.Store.Label.get(labelId);
1528
+ const labelItems = label.labelItemCollection.getModelsArray();
1529
+ return labelItems.reduce((result, item) => {
1530
+ if (item.parentType === 'Chat') {
1531
+ result.push(item.parentId);
1532
+ }
1533
+ return result;
1534
+ }, []);
1535
+ }, labelId);
1536
+
1537
+ return Promise.all(chatIds.map(id => this.getChatById(id)));
1538
+ }
1539
+
1540
+ /**
1541
+ * Gets all blocked contacts by host account
1542
+ * @returns {Promise<Array<Contact>>}
1543
+ */
1544
+ async getBlockedContacts() {
1545
+ const blockedContacts = await this.pupPage.evaluate(() => {
1546
+ let chatIds = window.Store.Blocklist.getModelsArray().map(a => a.id._serialized);
1547
+ return Promise.all(chatIds.map(id => window.WWebJS.getContact(id)));
1548
+ });
1549
+
1550
+ return blockedContacts.map(contact => ContactFactory.create(this.client, contact));
1551
+ }
1552
+
1553
+ /**
1554
+ * Sets the current user's profile picture.
1555
+ * @param {MessageMedia} media
1556
+ * @returns {Promise<boolean>} Returns true if the picture was properly updated.
1557
+ */
1558
+ async setProfilePicture(media) {
1559
+ const success = await this.pupPage.evaluate((chatid, media) => {
1560
+ return window.WWebJS.setPicture(chatid, media);
1561
+ }, this.info.wid._serialized, media);
1562
+
1563
+ return success;
1564
+ }
1565
+
1566
+ /**
1567
+ * Deletes the current user's profile picture.
1568
+ * @returns {Promise<boolean>} Returns true if the picture was properly deleted.
1569
+ */
1570
+ async deleteProfilePicture() {
1571
+ const success = await this.pupPage.evaluate((chatid) => {
1572
+ return window.WWebJS.deletePicture(chatid);
1573
+ }, this.info.wid._serialized);
1574
+
1575
+ return success;
1576
+ }
1577
+
1578
+ /**
1579
+ * Change labels in chats
1580
+ * @param {Array<number|string>} labelIds
1581
+ * @param {Array<string>} chatIds
1582
+ * @returns {Promise<void>}
1583
+ */
1584
+ async addOrRemoveLabels(labelIds, chatIds) {
1585
+
1586
+ return this.pupPage.evaluate(async (labelIds, chatIds) => {
1587
+ if (['smba', 'smbi'].indexOf(window.Store.Conn.platform) === -1) {
1588
+ throw '[LT01] Only Whatsapp business';
1589
+ }
1590
+ const labels = window.WWebJS.getLabels().filter(e => labelIds.find(l => l == e.id) !== undefined);
1591
+ const chats = window.Store.Chat.filter(e => chatIds.includes(e.id._serialized));
1592
+
1593
+ let actions = labels.map(label => ({id: label.id, type: 'add'}));
1594
+
1595
+ chats.forEach(chat => {
1596
+ (chat.labels || []).forEach(n => {
1597
+ if (!actions.find(e => e.id == n)) {
1598
+ actions.push({id: n, type: 'remove'});
1599
+ }
1600
+ });
1601
+ });
1602
+
1603
+ return await window.Store.Label.addOrRemoveLabels(actions, chats);
1604
+ }, labelIds, chatIds);
1605
+ }
1606
+
1607
+ /**
1608
+ * An object that handles the information about the group membership request
1609
+ * @typedef {Object} GroupMembershipRequest
1610
+ * @property {Object} id The wid of a user who requests to enter the group
1611
+ * @property {Object} addedBy The wid of a user who created that request
1612
+ * @property {Object|null} parentGroupId The wid of a community parent group to which the current group is linked
1613
+ * @property {string} requestMethod The method used to create the request: NonAdminAdd/InviteLink/LinkedGroupJoin
1614
+ * @property {number} t The timestamp the request was created at
1615
+ */
1616
+
1617
+ /**
1618
+ * Gets an array of membership requests
1619
+ * @param {string} groupId The ID of a group to get membership requests for
1620
+ * @returns {Promise<Array<GroupMembershipRequest>>} An array of membership requests
1621
+ */
1622
+ async getGroupMembershipRequests(groupId) {
1623
+ return await this.pupPage.evaluate(async (groupId) => {
1624
+ const groupWid = window.Store.WidFactory.createWid(groupId);
1625
+ return await window.Store.MembershipRequestUtils.getMembershipApprovalRequests(groupWid);
1626
+ }, groupId);
1627
+ }
1628
+
1629
+ /**
1630
+ * An object that handles the result for membership request action
1631
+ * @typedef {Object} MembershipRequestActionResult
1632
+ * @property {string} requesterId User ID whos membership request was approved/rejected
1633
+ * @property {number|undefined} error An error code that occurred during the operation for the participant
1634
+ * @property {string} message A message with a result of membership request action
1635
+ */
1636
+
1637
+ /**
1638
+ * An object that handles options for {@link approveGroupMembershipRequests} and {@link rejectGroupMembershipRequests} methods
1639
+ * @typedef {Object} MembershipRequestActionOptions
1640
+ * @property {Array<string>|string|null} requesterIds User ID/s who requested to join the group, if no value is provided, the method will search for all membership requests for that group
1641
+ * @property {Array<number>|number|null} sleep The number of milliseconds to wait before performing an operation for the next requester. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500]
1642
+ */
1643
+
1644
+ /**
1645
+ * Approves membership requests if any
1646
+ * @param {string} groupId The group ID to get the membership request for
1647
+ * @param {MembershipRequestActionOptions} options Options for performing a membership request action
1648
+ * @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
1649
+ */
1650
+ async approveGroupMembershipRequests(groupId, options = {}) {
1651
+ return await this.pupPage.evaluate(async (groupId, options) => {
1652
+ const { requesterIds = null, sleep = [250, 500] } = options;
1653
+ return await window.WWebJS.membershipRequestAction(groupId, 'Approve', requesterIds, sleep);
1654
+ }, groupId, options);
1655
+ }
1656
+
1657
+ /**
1658
+ * Rejects membership requests if any
1659
+ * @param {string} groupId The group ID to get the membership request for
1660
+ * @param {MembershipRequestActionOptions} options Options for performing a membership request action
1661
+ * @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
1662
+ */
1663
+ async rejectGroupMembershipRequests(groupId, options = {}) {
1664
+ return await this.pupPage.evaluate(async (groupId, options) => {
1665
+ const { requesterIds = null, sleep = [250, 500] } = options;
1666
+ return await window.WWebJS.membershipRequestAction(groupId, 'Reject', requesterIds, sleep);
1667
+ }, groupId, options);
1668
+ }
1669
+
1670
+
1671
+ /**
1672
+ * Setting autoload download audio
1673
+ * @param {boolean} flag true/false
1674
+ */
1675
+ async setAutoDownloadAudio(flag) {
1676
+ await this.pupPage.evaluate(async flag => {
1677
+ const autoDownload = window.Store.Settings.getAutoDownloadAudio();
1678
+ if (autoDownload === flag) {
1679
+ return flag;
1680
+ }
1681
+ await window.Store.Settings.setAutoDownloadAudio(flag);
1682
+ return flag;
1683
+ }, flag);
1684
+ }
1685
+
1686
+ /**
1687
+ * Setting autoload download documents
1688
+ * @param {boolean} flag true/false
1689
+ */
1690
+ async setAutoDownloadDocuments(flag) {
1691
+ await this.pupPage.evaluate(async flag => {
1692
+ const autoDownload = window.Store.Settings.getAutoDownloadDocuments();
1693
+ if (autoDownload === flag) {
1694
+ return flag;
1695
+ }
1696
+ await window.Store.Settings.setAutoDownloadDocuments(flag);
1697
+ return flag;
1698
+ }, flag);
1699
+ }
1700
+
1701
+ /**
1702
+ * Setting autoload download photos
1703
+ * @param {boolean} flag true/false
1704
+ */
1705
+ async setAutoDownloadPhotos(flag) {
1706
+ await this.pupPage.evaluate(async flag => {
1707
+ const autoDownload = window.Store.Settings.getAutoDownloadPhotos();
1708
+ if (autoDownload === flag) {
1709
+ return flag;
1710
+ }
1711
+ await window.Store.Settings.setAutoDownloadPhotos(flag);
1712
+ return flag;
1713
+ }, flag);
1714
+ }
1715
+
1716
+ /**
1717
+ * Setting autoload download videos
1718
+ * @param {boolean} flag true/false
1719
+ */
1720
+ async setAutoDownloadVideos(flag) {
1721
+ await this.pupPage.evaluate(async flag => {
1722
+ const autoDownload = window.Store.Settings.getAutoDownloadVideos();
1723
+ if (autoDownload === flag) {
1724
+ return flag;
1725
+ }
1726
+ await window.Store.Settings.setAutoDownloadVideos(flag);
1727
+ return flag;
1728
+ }, flag);
1729
+ }
1730
+
1731
+ /**
1732
+ * Get user device count by ID
1733
+ * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one
1734
+ * So for a non-enterprise user with one WaWeb connection it should return "2"
1735
+ * @param {string} contactId
1736
+ * @returns {number}
1737
+ */
1738
+ async getContactDeviceCount(contactId) {
1739
+ let devices = await window.Store.DeviceList.getDeviceIds([window.Store.WidFactory.createWid(contactId)]);
1740
+ if(devices && devices.length && devices[0] != null && typeof devices[0].devices == 'object'){
1741
+ return devices[0].devices.length;
1742
+ }
1743
+ return 0;
1744
+ }
1745
+ }
1746
+
1747
+ module.exports = Client;