whatsapp-web.js 1.24.0 → 1.25.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.
- package/example.js +29 -4
- package/index.d.ts +15 -1
- package/index.js +0 -1
- package/package.json +1 -1
- package/src/Client.js +535 -529
- package/src/authStrategies/LocalAuth.js +4 -1
- package/src/structures/GroupChat.js +12 -4
- package/src/structures/Message.js +6 -5
- package/src/util/Injected/AuthStore/AuthStore.js +17 -0
- package/src/util/Injected/AuthStore/LegacyAuthStore.js +22 -0
- package/src/util/Injected/LegacyStore.js +146 -0
- package/src/util/Injected/Store.js +166 -0
- package/src/util/{Injected.js → Injected/Utils.js} +31 -154
- package/src/webCache/LocalWebCache.js +3 -6
- package/src/authStrategies/LegacySessionAuth.js +0 -72
package/src/Client.js
CHANGED
|
@@ -7,12 +7,15 @@ const moduleRaid = require('@pedroslopez/moduleraid/moduleraid');
|
|
|
7
7
|
const Util = require('./util/Util');
|
|
8
8
|
const InterfaceController = require('./util/InterfaceController');
|
|
9
9
|
const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants');
|
|
10
|
-
const {
|
|
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');
|
|
11
15
|
const ChatFactory = require('./factories/ChatFactory');
|
|
12
16
|
const ContactFactory = require('./factories/ContactFactory');
|
|
13
17
|
const WebCacheFactory = require('./webCache/WebCacheFactory');
|
|
14
18
|
const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
|
|
15
|
-
const LegacySessionAuth = require('./authStrategies/LegacySessionAuth');
|
|
16
19
|
const NoAuth = require('./authStrategies/NoAuth');
|
|
17
20
|
|
|
18
21
|
/**
|
|
@@ -63,37 +66,227 @@ class Client extends EventEmitter {
|
|
|
63
66
|
this.options = Util.mergeDefault(DefaultOptions, options);
|
|
64
67
|
|
|
65
68
|
if(!this.options.authStrategy) {
|
|
66
|
-
|
|
67
|
-
process.emitWarning(
|
|
68
|
-
'options.session is deprecated and will be removed in a future release due to incompatibility with multi-device. ' +
|
|
69
|
-
'Use the LocalAuth authStrategy, don\'t pass in a session as an option, or suppress this warning by using the LegacySessionAuth strategy explicitly (see https://wwebjs.dev/guide/authentication.html#legacysessionauth-strategy).',
|
|
70
|
-
'DeprecationWarning'
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
this.authStrategy = new LegacySessionAuth({
|
|
74
|
-
session: this.options.session,
|
|
75
|
-
restartOnAuthFail: this.options.restartOnAuthFail
|
|
76
|
-
});
|
|
77
|
-
} else {
|
|
78
|
-
this.authStrategy = new NoAuth();
|
|
79
|
-
}
|
|
69
|
+
this.authStrategy = new NoAuth();
|
|
80
70
|
} else {
|
|
81
71
|
this.authStrategy = this.options.authStrategy;
|
|
82
72
|
}
|
|
83
73
|
|
|
84
74
|
this.authStrategy.setup(this);
|
|
85
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @type {puppeteer.Browser}
|
|
78
|
+
*/
|
|
86
79
|
this.pupBrowser = null;
|
|
80
|
+
/**
|
|
81
|
+
* @type {puppeteer.Page}
|
|
82
|
+
*/
|
|
87
83
|
this.pupPage = null;
|
|
88
84
|
|
|
85
|
+
this.currentIndexHtml = null;
|
|
86
|
+
this.lastLoggedOut = false;
|
|
87
|
+
|
|
89
88
|
Util.setFfmpegPath(this.options.ffmpegPath);
|
|
90
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Injection logic
|
|
92
|
+
* Private function
|
|
93
|
+
* @property {boolean} reinject is this a reinject?
|
|
94
|
+
*/
|
|
95
|
+
async inject(reinject = false) {
|
|
96
|
+
await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs});
|
|
97
|
+
|
|
98
|
+
const version = await this.getWWebVersion();
|
|
99
|
+
const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000;
|
|
100
|
+
|
|
101
|
+
if (isCometOrAbove) {
|
|
102
|
+
await this.pupPage.evaluate(ExposeAuthStore);
|
|
103
|
+
} else {
|
|
104
|
+
await this.pupPage.evaluate(ExposeLegacyAuthStore, moduleRaid.toString());
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const needAuthentication = await this.pupPage.evaluate(async () => {
|
|
108
|
+
let state = window.AuthStore.AppState.state;
|
|
109
|
+
|
|
110
|
+
if (state === 'OPENING' || state === 'UNLAUNCHED' || state === 'PAIRING') {
|
|
111
|
+
// wait till state changes
|
|
112
|
+
await new Promise(r => {
|
|
113
|
+
window.AuthStore.AppState.on('change:state', function waitTillInit(_AppState, state) {
|
|
114
|
+
if (state !== 'OPENING' && state !== 'UNLAUNCHED' && state !== 'PAIRING') {
|
|
115
|
+
window.AuthStore.AppState.off('change:state', waitTillInit);
|
|
116
|
+
r();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
state = window.AuthStore.AppState.state;
|
|
122
|
+
return state == 'UNPAIRED' || state == 'UNPAIRED_IDLE';
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (needAuthentication) {
|
|
126
|
+
const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded();
|
|
127
|
+
|
|
128
|
+
if(failed) {
|
|
129
|
+
/**
|
|
130
|
+
* Emitted when there has been an error while trying to restore an existing session
|
|
131
|
+
* @event Client#auth_failure
|
|
132
|
+
* @param {string} message
|
|
133
|
+
*/
|
|
134
|
+
this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
|
|
135
|
+
await this.destroy();
|
|
136
|
+
if (restart) {
|
|
137
|
+
// session restore failed so try again but without session to force new authentication
|
|
138
|
+
return this.initialize();
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Register qr events
|
|
144
|
+
let qrRetries = 0;
|
|
145
|
+
const injected = await this.pupPage.evaluate(() => {
|
|
146
|
+
return typeof window.onQRChangedEvent !== 'undefined';
|
|
147
|
+
});
|
|
148
|
+
if (!injected) {
|
|
149
|
+
await this.pupPage.exposeFunction('onQRChangedEvent', async (qr) => {
|
|
150
|
+
/**
|
|
151
|
+
* Emitted when a QR code is received
|
|
152
|
+
* @event Client#qr
|
|
153
|
+
* @param {string} qr QR Code
|
|
154
|
+
*/
|
|
155
|
+
this.emit(Events.QR_RECEIVED, qr);
|
|
156
|
+
if (this.options.qrMaxRetries > 0) {
|
|
157
|
+
qrRetries++;
|
|
158
|
+
if (qrRetries > this.options.qrMaxRetries) {
|
|
159
|
+
this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
|
|
160
|
+
await this.destroy();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
await this.pupPage.evaluate(async () => {
|
|
168
|
+
const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
|
|
169
|
+
const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
|
|
170
|
+
const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey);
|
|
171
|
+
const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey);
|
|
172
|
+
const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey();
|
|
173
|
+
const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
|
|
174
|
+
const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform;
|
|
175
|
+
|
|
176
|
+
window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
|
|
177
|
+
window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!reinject) {
|
|
182
|
+
await this.pupPage.exposeFunction('onAuthAppStateChangedEvent', async (state) => {
|
|
183
|
+
if (state == 'UNPAIRED_IDLE') {
|
|
184
|
+
// refresh qr code
|
|
185
|
+
window.Store.Cmd.refreshQR();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await this.pupPage.exposeFunction('onAppStateHasSyncedEvent', async () => {
|
|
190
|
+
const authEventPayload = await this.authStrategy.getAuthEventPayload();
|
|
191
|
+
/**
|
|
192
|
+
* Emitted when authentication is successful
|
|
193
|
+
* @event Client#authenticated
|
|
194
|
+
*/
|
|
195
|
+
this.emit(Events.AUTHENTICATED, authEventPayload);
|
|
196
|
+
|
|
197
|
+
const injected = await this.pupPage.evaluate(async () => {
|
|
198
|
+
return typeof window.Store !== 'undefined' && typeof window.WWebJS !== 'undefined';
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (!injected) {
|
|
202
|
+
if (this.options.webVersionCache.type === 'local' && this.currentIndexHtml) {
|
|
203
|
+
const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
|
|
204
|
+
const webCache = WebCacheFactory.createWebCache(webCacheType, webCacheOptions);
|
|
205
|
+
|
|
206
|
+
await webCache.persist(this.currentIndexHtml, version);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (isCometOrAbove) {
|
|
210
|
+
await this.pupPage.evaluate(ExposeStore);
|
|
211
|
+
} else {
|
|
212
|
+
// make sure all modules are ready before injection
|
|
213
|
+
// 2 second delay after authentication makes sense and does not need to be made dyanmic or removed
|
|
214
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
215
|
+
await this.pupPage.evaluate(ExposeLegacyStore);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check window.Store Injection
|
|
219
|
+
await this.pupPage.waitForFunction('window.Store != undefined');
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Current connection information
|
|
223
|
+
* @type {ClientInfo}
|
|
224
|
+
*/
|
|
225
|
+
this.info = new ClientInfo(this, await this.pupPage.evaluate(() => {
|
|
226
|
+
return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
this.interface = new InterfaceController(this);
|
|
230
|
+
|
|
231
|
+
//Load util functions (serializers, helper functions)
|
|
232
|
+
await this.pupPage.evaluate(LoadUtils);
|
|
233
|
+
|
|
234
|
+
await this.attachEventListeners(reinject);
|
|
235
|
+
reinject = true;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Emitted when the client has initialized and is ready to receive messages.
|
|
239
|
+
* @event Client#ready
|
|
240
|
+
*/
|
|
241
|
+
this.emit(Events.READY);
|
|
242
|
+
this.authStrategy.afterAuthReady();
|
|
243
|
+
});
|
|
244
|
+
let lastPercent = null;
|
|
245
|
+
await this.pupPage.exposeFunction('onOfflineProgressUpdateEvent', async (percent) => {
|
|
246
|
+
if (lastPercent !== percent) {
|
|
247
|
+
lastPercent = percent;
|
|
248
|
+
this.emit(Events.LOADING_SCREEN, percent, 'WhatsApp'); // Message is hardcoded as "WhatsApp" for now
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const logoutCatchInjected = await this.pupPage.evaluate(() => {
|
|
253
|
+
return typeof window.onLogoutEvent !== 'undefined';
|
|
254
|
+
});
|
|
255
|
+
if (!logoutCatchInjected) {
|
|
256
|
+
await this.pupPage.exposeFunction('onLogoutEvent', async () => {
|
|
257
|
+
this.lastLoggedOut = true;
|
|
258
|
+
await this.pupPage.waitForNavigation({waitUntil: 'load', timeout: 5000}).catch((_) => _);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
await this.pupPage.evaluate(() => {
|
|
262
|
+
window.AuthStore.AppState.on('change:state', (_AppState, state) => { window.onAuthAppStateChangedEvent(state); });
|
|
263
|
+
window.AuthStore.AppState.on('change:hasSynced', () => { window.onAppStateHasSyncedEvent(); });
|
|
264
|
+
window.AuthStore.Cmd.on('offline_progress_update', () => {
|
|
265
|
+
window.onOfflineProgressUpdateEvent(window.AuthStore.OfflineMessageHandler.getOfflineDeliveryProgress());
|
|
266
|
+
});
|
|
267
|
+
window.AuthStore.Cmd.on('logout', async () => {
|
|
268
|
+
await window.onLogoutEvent();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
}
|
|
91
272
|
|
|
92
273
|
/**
|
|
93
274
|
* Sets up events and requirements, kicks off authentication request
|
|
94
275
|
*/
|
|
95
276
|
async initialize() {
|
|
96
|
-
|
|
277
|
+
|
|
278
|
+
let
|
|
279
|
+
/**
|
|
280
|
+
* @type {puppeteer.Browser}
|
|
281
|
+
*/
|
|
282
|
+
browser,
|
|
283
|
+
/**
|
|
284
|
+
* @type {puppeteer.Page}
|
|
285
|
+
*/
|
|
286
|
+
page;
|
|
287
|
+
|
|
288
|
+
browser = null;
|
|
289
|
+
page = null;
|
|
97
290
|
|
|
98
291
|
await this.authStrategy.beforeBrowserInitialized();
|
|
99
292
|
|
|
@@ -126,7 +319,8 @@ class Client extends EventEmitter {
|
|
|
126
319
|
await this.authStrategy.afterBrowserInitialized();
|
|
127
320
|
await this.initWebVersionCache();
|
|
128
321
|
|
|
129
|
-
//
|
|
322
|
+
// ocVersion (isOfficialClient patch)
|
|
323
|
+
// remove after 2.3000.x hard release
|
|
130
324
|
await page.evaluateOnNewDocument(() => {
|
|
131
325
|
const originalError = Error;
|
|
132
326
|
//eslint-disable-next-line no-global-assign
|
|
@@ -144,549 +338,355 @@ class Client extends EventEmitter {
|
|
|
144
338
|
referer: 'https://whatsapp.com/'
|
|
145
339
|
});
|
|
146
340
|
|
|
147
|
-
await
|
|
148
|
-
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
149
|
-
}`);
|
|
150
|
-
|
|
151
|
-
let lastPercent = null,
|
|
152
|
-
lastPercentMessage = null;
|
|
341
|
+
await this.inject();
|
|
153
342
|
|
|
154
|
-
|
|
155
|
-
if
|
|
156
|
-
this.emit(Events.
|
|
157
|
-
|
|
158
|
-
|
|
343
|
+
this.pupPage.on('framenavigated', async (frame) => {
|
|
344
|
+
if(frame.url().includes('post_logout=1') || this.lastLoggedOut) {
|
|
345
|
+
this.emit(Events.DISCONNECTED, 'LOGOUT');
|
|
346
|
+
await this.authStrategy.logout();
|
|
347
|
+
await this.authStrategy.beforeBrowserInitialized();
|
|
348
|
+
await this.authStrategy.afterBrowserInitialized();
|
|
349
|
+
this.lastLoggedOut = false;
|
|
159
350
|
}
|
|
351
|
+
await this.inject(true);
|
|
160
352
|
});
|
|
353
|
+
}
|
|
161
354
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Request authentication via pairing code instead of QR code
|
|
357
|
+
* @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil)
|
|
358
|
+
* @param {boolean} showNotification - Show notification to pair on phone number
|
|
359
|
+
* @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH"
|
|
360
|
+
*/
|
|
361
|
+
async requestPairingCode(phoneNumber, showNotification = true) {
|
|
362
|
+
return await this.pupPage.evaluate(async (phoneNumber, showNotification) => {
|
|
363
|
+
window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
|
|
364
|
+
await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
|
|
365
|
+
return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
|
|
366
|
+
}, phoneNumber, showNotification);
|
|
367
|
+
}
|
|
171
368
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
369
|
+
/**
|
|
370
|
+
* Attach event listeners to WA Web
|
|
371
|
+
* Private function
|
|
372
|
+
* @property {boolean} reinject is this a reinject?
|
|
373
|
+
*/
|
|
374
|
+
async attachEventListeners(reinject = false) {
|
|
375
|
+
if (!reinject) {
|
|
376
|
+
await this.pupPage.exposeFunction('onAddMessageEvent', msg => {
|
|
377
|
+
if (msg.type === 'gp2') {
|
|
378
|
+
const notification = new GroupNotification(this, msg);
|
|
379
|
+
if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
|
|
380
|
+
/**
|
|
381
|
+
* Emitted when a user joins the chat via invite link or is added by an admin.
|
|
382
|
+
* @event Client#group_join
|
|
383
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
384
|
+
*/
|
|
385
|
+
this.emit(Events.GROUP_JOIN, notification);
|
|
386
|
+
} else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
|
|
387
|
+
/**
|
|
388
|
+
* Emitted when a user leaves the chat or is removed by an admin.
|
|
389
|
+
* @event Client#group_leave
|
|
390
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
391
|
+
*/
|
|
392
|
+
this.emit(Events.GROUP_LEAVE, notification);
|
|
393
|
+
} else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
|
|
394
|
+
/**
|
|
395
|
+
* Emitted when a current user is promoted to an admin or demoted to a regular user.
|
|
396
|
+
* @event Client#group_admin_changed
|
|
397
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
398
|
+
*/
|
|
399
|
+
this.emit(Events.GROUP_ADMIN_CHANGED, notification);
|
|
400
|
+
} else if (msg.subtype === 'membership_approval_request') {
|
|
401
|
+
/**
|
|
402
|
+
* Emitted when some user requested to join the group
|
|
403
|
+
* that has the membership approval mode turned on
|
|
404
|
+
* @event Client#group_membership_request
|
|
405
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
406
|
+
* @param {string} notification.chatId The group ID the request was made for
|
|
407
|
+
* @param {string} notification.author The user ID that made a request
|
|
408
|
+
* @param {number} notification.timestamp The timestamp the request was made at
|
|
409
|
+
*/
|
|
410
|
+
this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
|
|
411
|
+
} else {
|
|
412
|
+
/**
|
|
413
|
+
* Emitted when group settings are updated, such as subject, description or picture.
|
|
414
|
+
* @event Client#group_update
|
|
415
|
+
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
416
|
+
*/
|
|
417
|
+
this.emit(Events.GROUP_UPDATE, notification);
|
|
177
418
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
observer.observe(document, {
|
|
181
|
-
attributes: true,
|
|
182
|
-
childList: true,
|
|
183
|
-
characterData: true,
|
|
184
|
-
subtree: true,
|
|
185
|
-
});
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
PROGRESS: '//*[@id=\'app\']/div/div/div[2]/progress',
|
|
189
|
-
PROGRESS_MESSAGE: '//*[@id=\'app\']/div/div/div[3]',
|
|
190
|
-
}
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
const INTRO_IMG_SELECTOR = '[data-icon=\'search\']';
|
|
194
|
-
const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas';
|
|
195
|
-
|
|
196
|
-
// Checks which selector appears first
|
|
197
|
-
const needAuthentication = await Promise.race([
|
|
198
|
-
new Promise(resolve => {
|
|
199
|
-
page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: this.options.authTimeoutMs })
|
|
200
|
-
.then(() => resolve(false))
|
|
201
|
-
.catch((err) => resolve(err));
|
|
202
|
-
}),
|
|
203
|
-
new Promise(resolve => {
|
|
204
|
-
page.waitForSelector(INTRO_QRCODE_SELECTOR, { timeout: this.options.authTimeoutMs })
|
|
205
|
-
.then(() => resolve(true))
|
|
206
|
-
.catch((err) => resolve(err));
|
|
207
|
-
})
|
|
208
|
-
]);
|
|
209
|
-
|
|
210
|
-
// Checks if an error occurred on the first found selector. The second will be discarded and ignored by .race;
|
|
211
|
-
if (needAuthentication instanceof Error) throw needAuthentication;
|
|
212
|
-
|
|
213
|
-
// Scan-qrcode selector was found. Needs authentication
|
|
214
|
-
if (needAuthentication) {
|
|
215
|
-
const { failed, failureEventPayload, restart } = await this.authStrategy.onAuthenticationNeeded();
|
|
216
|
-
if(failed) {
|
|
217
|
-
/**
|
|
218
|
-
* Emitted when there has been an error while trying to restore an existing session
|
|
219
|
-
* @event Client#auth_failure
|
|
220
|
-
* @param {string} message
|
|
221
|
-
*/
|
|
222
|
-
this.emit(Events.AUTHENTICATION_FAILURE, failureEventPayload);
|
|
223
|
-
await this.destroy();
|
|
224
|
-
if (restart) {
|
|
225
|
-
// session restore failed so try again but without session to force new authentication
|
|
226
|
-
return this.initialize();
|
|
419
|
+
return;
|
|
227
420
|
}
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
421
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
let qrRetries = 0;
|
|
234
|
-
await page.exposeFunction('qrChanged', async (qr) => {
|
|
422
|
+
const message = new Message(this, msg);
|
|
423
|
+
|
|
235
424
|
/**
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
this.emit(Events.
|
|
241
|
-
if (this.options.qrMaxRetries > 0) {
|
|
242
|
-
qrRetries++;
|
|
243
|
-
if (qrRetries > this.options.qrMaxRetries) {
|
|
244
|
-
this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
|
|
245
|
-
await this.destroy();
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
});
|
|
425
|
+
* Emitted when a new message is created, which may include the current user's own messages.
|
|
426
|
+
* @event Client#message_create
|
|
427
|
+
* @param {Message} message The message that was created
|
|
428
|
+
*/
|
|
429
|
+
this.emit(Events.MESSAGE_CREATE, message);
|
|
249
430
|
|
|
250
|
-
|
|
251
|
-
const qr_container = document.querySelector(selectors.QR_CONTAINER);
|
|
252
|
-
window.qrChanged(qr_container.dataset.ref);
|
|
431
|
+
if (msg.id.fromMe) return;
|
|
253
432
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// Listens to retry button, when found, click it
|
|
261
|
-
else if (mut.type === 'childList') {
|
|
262
|
-
const retry_button = document.querySelector(selectors.QR_RETRY_BUTTON);
|
|
263
|
-
if (retry_button) retry_button.click();
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
obs.observe(qr_container.parentElement, {
|
|
268
|
-
subtree: true,
|
|
269
|
-
childList: true,
|
|
270
|
-
attributes: true,
|
|
271
|
-
attributeFilter: ['data-ref'],
|
|
272
|
-
});
|
|
273
|
-
}, {
|
|
274
|
-
QR_CONTAINER,
|
|
275
|
-
QR_RETRY_BUTTON
|
|
433
|
+
/**
|
|
434
|
+
* Emitted when a new message is received.
|
|
435
|
+
* @event Client#message
|
|
436
|
+
* @param {Message} message The message that was received
|
|
437
|
+
*/
|
|
438
|
+
this.emit(Events.MESSAGE_RECEIVED, message);
|
|
276
439
|
});
|
|
277
440
|
|
|
278
|
-
|
|
279
|
-
try {
|
|
280
|
-
await page.waitForSelector(INTRO_IMG_SELECTOR, { timeout: 0 });
|
|
281
|
-
} catch(error) {
|
|
282
|
-
if (
|
|
283
|
-
error.name === 'ProtocolError' &&
|
|
284
|
-
error.message &&
|
|
285
|
-
error.message.match(/Target closed/)
|
|
286
|
-
) {
|
|
287
|
-
// something has called .destroy() while waiting
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
}
|
|
441
|
+
let last_message;
|
|
295
442
|
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version.
|
|
299
|
-
* @param {string} lOperand The left operand for the WWeb version string to compare with
|
|
300
|
-
* @param {string} operator The comparison operator
|
|
301
|
-
* @param {string} rOperand The right operand for the WWeb version string to compare with
|
|
302
|
-
* @returns {boolean} Boolean value that indicates the result of the comparison
|
|
303
|
-
*/
|
|
304
|
-
window.compareWwebVersions = (lOperand, operator, rOperand) => {
|
|
305
|
-
if (!['>', '>=', '<', '<=', '='].includes(operator)) {
|
|
306
|
-
throw new class _ extends Error {
|
|
307
|
-
constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; }
|
|
308
|
-
}('Invalid comparison operator is provided');
|
|
443
|
+
await this.pupPage.exposeFunction('onChangeMessageTypeEvent', (msg) => {
|
|
309
444
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
lOperand = lOperand.replace(/-beta$/, '');
|
|
318
|
-
rOperand = rOperand.replace(/-beta$/, '');
|
|
445
|
+
if (msg.type === 'revoked') {
|
|
446
|
+
const message = new Message(this, msg);
|
|
447
|
+
let revoked_msg;
|
|
448
|
+
if (last_message && msg.id.id === last_message.id.id) {
|
|
449
|
+
revoked_msg = new Message(this, last_message);
|
|
450
|
+
}
|
|
319
451
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Emitted when a message is deleted for everyone in the chat.
|
|
454
|
+
* @event Client#message_revoke_everyone
|
|
455
|
+
* @param {Message} message The message that was revoked, in its current state. It will not contain the original message's data.
|
|
456
|
+
* @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
|
|
457
|
+
* Note that due to the way this data is captured, it may be possible that this param will be undefined.
|
|
458
|
+
*/
|
|
459
|
+
this.emit(Events.MESSAGE_REVOKED_EVERYONE, message, revoked_msg);
|
|
324
460
|
}
|
|
325
461
|
|
|
326
|
-
|
|
327
|
-
rOperand = Number(rOperand.replace(/\./g, ''));
|
|
462
|
+
});
|
|
328
463
|
|
|
329
|
-
|
|
330
|
-
operator === '>' ? lOperand > rOperand :
|
|
331
|
-
operator === '>=' ? lOperand >= rOperand :
|
|
332
|
-
operator === '<' ? lOperand < rOperand :
|
|
333
|
-
operator === '<=' ? lOperand <= rOperand :
|
|
334
|
-
operator === '=' ? lOperand === rOperand :
|
|
335
|
-
false
|
|
336
|
-
);
|
|
337
|
-
};
|
|
338
|
-
});
|
|
464
|
+
await this.pupPage.exposeFunction('onChangeMessageEvent', (msg) => {
|
|
339
465
|
|
|
340
|
-
|
|
341
|
-
|
|
466
|
+
if (msg.type !== 'revoked') {
|
|
467
|
+
last_message = msg;
|
|
468
|
+
}
|
|
342
469
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
470
|
+
/**
|
|
471
|
+
* The event notification that is received when one of
|
|
472
|
+
* the group participants changes their phone number.
|
|
473
|
+
*/
|
|
474
|
+
const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
|
|
348
475
|
|
|
349
|
-
|
|
350
|
-
|
|
476
|
+
/**
|
|
477
|
+
* The event notification that is received when one of
|
|
478
|
+
* the contacts changes their phone number.
|
|
479
|
+
*/
|
|
480
|
+
const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
|
|
351
481
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
for (let registration of registrations) {
|
|
356
|
-
registration.unregister();
|
|
357
|
-
}
|
|
358
|
-
});
|
|
482
|
+
if (isParticipant || isContact) {
|
|
483
|
+
/** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
|
|
484
|
+
const message = new Message(this, msg);
|
|
359
485
|
|
|
360
|
-
|
|
361
|
-
|
|
486
|
+
const newId = isParticipant ? msg.recipients[0] : msg.to;
|
|
487
|
+
const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
|
|
362
488
|
|
|
363
|
-
// Expose client info
|
|
364
|
-
/**
|
|
365
|
-
* Current connection information
|
|
366
|
-
* @type {ClientInfo}
|
|
367
|
-
*/
|
|
368
|
-
this.info = new ClientInfo(this, await page.evaluate(() => {
|
|
369
|
-
return { ...window.Store.Conn.serialize(), wid: window.Store.User.getMeUser() };
|
|
370
|
-
}));
|
|
371
|
-
|
|
372
|
-
// Add InterfaceController
|
|
373
|
-
this.interface = new InterfaceController(this);
|
|
374
|
-
|
|
375
|
-
// Register events
|
|
376
|
-
await page.exposeFunction('onAddMessageEvent', msg => {
|
|
377
|
-
if (msg.type === 'gp2') {
|
|
378
|
-
const notification = new GroupNotification(this, msg);
|
|
379
|
-
if (['add', 'invite', 'linked_group_join'].includes(msg.subtype)) {
|
|
380
489
|
/**
|
|
381
|
-
* Emitted when a
|
|
382
|
-
* @event Client#
|
|
383
|
-
* @param {
|
|
490
|
+
* Emitted when a contact or a group participant changes their phone number.
|
|
491
|
+
* @event Client#contact_changed
|
|
492
|
+
* @param {Message} message Message with more information about the event.
|
|
493
|
+
* @param {String} oldId The user's id (an old one) who changed their phone number
|
|
494
|
+
* and who triggered the notification.
|
|
495
|
+
* @param {String} newId The user's new id after the change.
|
|
496
|
+
* @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
|
|
384
497
|
*/
|
|
385
|
-
this.emit(Events.
|
|
386
|
-
} else if (msg.subtype === 'remove' || msg.subtype === 'leave') {
|
|
387
|
-
/**
|
|
388
|
-
* Emitted when a user leaves the chat or is removed by an admin.
|
|
389
|
-
* @event Client#group_leave
|
|
390
|
-
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
391
|
-
*/
|
|
392
|
-
this.emit(Events.GROUP_LEAVE, notification);
|
|
393
|
-
} else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
|
|
394
|
-
/**
|
|
395
|
-
* Emitted when a current user is promoted to an admin or demoted to a regular user.
|
|
396
|
-
* @event Client#group_admin_changed
|
|
397
|
-
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
398
|
-
*/
|
|
399
|
-
this.emit(Events.GROUP_ADMIN_CHANGED, notification);
|
|
400
|
-
} else if (msg.subtype === 'membership_approval_request') {
|
|
401
|
-
/**
|
|
402
|
-
* Emitted when some user requested to join the group
|
|
403
|
-
* that has the membership approval mode turned on
|
|
404
|
-
* @event Client#group_membership_request
|
|
405
|
-
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
406
|
-
* @param {string} notification.chatId The group ID the request was made for
|
|
407
|
-
* @param {string} notification.author The user ID that made a request
|
|
408
|
-
* @param {number} notification.timestamp The timestamp the request was made at
|
|
409
|
-
*/
|
|
410
|
-
this.emit(Events.GROUP_MEMBERSHIP_REQUEST, notification);
|
|
411
|
-
} else {
|
|
412
|
-
/**
|
|
413
|
-
* Emitted when group settings are updated, such as subject, description or picture.
|
|
414
|
-
* @event Client#group_update
|
|
415
|
-
* @param {GroupNotification} notification GroupNotification with more information about the action
|
|
416
|
-
*/
|
|
417
|
-
this.emit(Events.GROUP_UPDATE, notification);
|
|
498
|
+
this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
|
|
418
499
|
}
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const message = new Message(this, msg);
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Emitted when a new message is created, which may include the current user's own messages.
|
|
426
|
-
* @event Client#message_create
|
|
427
|
-
* @param {Message} message The message that was created
|
|
428
|
-
*/
|
|
429
|
-
this.emit(Events.MESSAGE_CREATE, message);
|
|
430
|
-
|
|
431
|
-
if (msg.id.fromMe) return;
|
|
432
|
-
|
|
433
|
-
/**
|
|
434
|
-
* Emitted when a new message is received.
|
|
435
|
-
* @event Client#message
|
|
436
|
-
* @param {Message} message The message that was received
|
|
437
|
-
*/
|
|
438
|
-
this.emit(Events.MESSAGE_RECEIVED, message);
|
|
439
|
-
});
|
|
500
|
+
});
|
|
440
501
|
|
|
441
|
-
|
|
502
|
+
await this.pupPage.exposeFunction('onRemoveMessageEvent', (msg) => {
|
|
442
503
|
|
|
443
|
-
|
|
504
|
+
if (!msg.isNewMsg) return;
|
|
444
505
|
|
|
445
|
-
if (msg.type === 'revoked') {
|
|
446
506
|
const message = new Message(this, msg);
|
|
447
|
-
let revoked_msg;
|
|
448
|
-
if (last_message && msg.id.id === last_message.id.id) {
|
|
449
|
-
revoked_msg = new Message(this, last_message);
|
|
450
|
-
}
|
|
451
507
|
|
|
452
508
|
/**
|
|
453
|
-
* Emitted when a message is deleted
|
|
454
|
-
* @event Client#
|
|
455
|
-
* @param {Message} message The message that was revoked
|
|
456
|
-
* @param {?Message} revoked_msg The message that was revoked, before it was revoked. It will contain the message's original data.
|
|
457
|
-
* Note that due to the way this data is captured, it may be possible that this param will be undefined.
|
|
509
|
+
* Emitted when a message is deleted by the current user.
|
|
510
|
+
* @event Client#message_revoke_me
|
|
511
|
+
* @param {Message} message The message that was revoked
|
|
458
512
|
*/
|
|
459
|
-
this.emit(Events.
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
});
|
|
513
|
+
this.emit(Events.MESSAGE_REVOKED_ME, message);
|
|
463
514
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (msg.type !== 'revoked') {
|
|
467
|
-
last_message = msg;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* The event notification that is received when one of
|
|
472
|
-
* the group participants changes their phone number.
|
|
473
|
-
*/
|
|
474
|
-
const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
|
|
515
|
+
});
|
|
475
516
|
|
|
476
|
-
|
|
477
|
-
* The event notification that is received when one of
|
|
478
|
-
* the contacts changes their phone number.
|
|
479
|
-
*/
|
|
480
|
-
const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
|
|
517
|
+
await this.pupPage.exposeFunction('onMessageAckEvent', (msg, ack) => {
|
|
481
518
|
|
|
482
|
-
if (isParticipant || isContact) {
|
|
483
|
-
/** @type {GroupNotification} object does not provide enough information about this event, so a @type {Message} object is used. */
|
|
484
519
|
const message = new Message(this, msg);
|
|
485
520
|
|
|
486
|
-
const newId = isParticipant ? msg.recipients[0] : msg.to;
|
|
487
|
-
const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
|
|
488
|
-
|
|
489
521
|
/**
|
|
490
|
-
* Emitted when
|
|
491
|
-
* @event Client#
|
|
492
|
-
* @param {Message} message
|
|
493
|
-
* @param {
|
|
494
|
-
* and who triggered the notification.
|
|
495
|
-
* @param {String} newId The user's new id after the change.
|
|
496
|
-
* @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
|
|
522
|
+
* Emitted when an ack event occurrs on message type.
|
|
523
|
+
* @event Client#message_ack
|
|
524
|
+
* @param {Message} message The message that was affected
|
|
525
|
+
* @param {MessageAck} ack The new ACK value
|
|
497
526
|
*/
|
|
498
|
-
this.emit(Events.
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
await page.exposeFunction('onRemoveMessageEvent', (msg) => {
|
|
503
|
-
|
|
504
|
-
if (!msg.isNewMsg) return;
|
|
505
|
-
|
|
506
|
-
const message = new Message(this, msg);
|
|
527
|
+
this.emit(Events.MESSAGE_ACK, message, ack);
|
|
507
528
|
|
|
508
|
-
|
|
509
|
-
* Emitted when a message is deleted by the current user.
|
|
510
|
-
* @event Client#message_revoke_me
|
|
511
|
-
* @param {Message} message The message that was revoked
|
|
512
|
-
*/
|
|
513
|
-
this.emit(Events.MESSAGE_REVOKED_ME, message);
|
|
514
|
-
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
await page.exposeFunction('onMessageAckEvent', (msg, ack) => {
|
|
518
|
-
|
|
519
|
-
const message = new Message(this, msg);
|
|
529
|
+
});
|
|
520
530
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
531
|
+
await this.pupPage.exposeFunction('onChatUnreadCountEvent', async (data) =>{
|
|
532
|
+
const chat = await this.getChatById(data.id);
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Emitted when the chat unread count changes
|
|
536
|
+
*/
|
|
537
|
+
this.emit(Events.UNREAD_COUNT, chat);
|
|
538
|
+
});
|
|
528
539
|
|
|
529
|
-
|
|
540
|
+
await this.pupPage.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
|
|
530
541
|
|
|
531
|
-
|
|
532
|
-
const chat = await this.getChatById(data.id);
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Emitted when the chat unread count changes
|
|
536
|
-
*/
|
|
537
|
-
this.emit(Events.UNREAD_COUNT, chat);
|
|
538
|
-
});
|
|
542
|
+
const message = new Message(this, msg);
|
|
539
543
|
|
|
540
|
-
|
|
544
|
+
/**
|
|
545
|
+
* Emitted when media has been uploaded for a message sent by the client.
|
|
546
|
+
* @event Client#media_uploaded
|
|
547
|
+
* @param {Message} message The message with media that was uploaded
|
|
548
|
+
*/
|
|
549
|
+
this.emit(Events.MEDIA_UPLOADED, message);
|
|
550
|
+
});
|
|
541
551
|
|
|
542
|
-
|
|
552
|
+
await this.pupPage.exposeFunction('onAppStateChangedEvent', async (state) => {
|
|
553
|
+
/**
|
|
554
|
+
* Emitted when the connection state changes
|
|
555
|
+
* @event Client#change_state
|
|
556
|
+
* @param {WAState} state the new connection state
|
|
557
|
+
*/
|
|
558
|
+
this.emit(Events.STATE_CHANGED, state);
|
|
543
559
|
|
|
544
|
-
|
|
545
|
-
* Emitted when media has been uploaded for a message sent by the client.
|
|
546
|
-
* @event Client#media_uploaded
|
|
547
|
-
* @param {Message} message The message with media that was uploaded
|
|
548
|
-
*/
|
|
549
|
-
this.emit(Events.MEDIA_UPLOADED, message);
|
|
550
|
-
});
|
|
560
|
+
const ACCEPTED_STATES = [WAState.CONNECTED, WAState.OPENING, WAState.PAIRING, WAState.TIMEOUT];
|
|
551
561
|
|
|
552
|
-
|
|
562
|
+
if (this.options.takeoverOnConflict) {
|
|
563
|
+
ACCEPTED_STATES.push(WAState.CONFLICT);
|
|
553
564
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
565
|
+
if (state === WAState.CONFLICT) {
|
|
566
|
+
setTimeout(() => {
|
|
567
|
+
this.pupPage.evaluate(() => window.Store.AppState.takeover());
|
|
568
|
+
}, this.options.takeoverTimeoutMs);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
560
571
|
|
|
561
|
-
|
|
572
|
+
if (!ACCEPTED_STATES.includes(state)) {
|
|
573
|
+
/**
|
|
574
|
+
* Emitted when the client has been disconnected
|
|
575
|
+
* @event Client#disconnected
|
|
576
|
+
* @param {WAState|"LOGOUT"} reason reason that caused the disconnect
|
|
577
|
+
*/
|
|
578
|
+
await this.authStrategy.disconnect();
|
|
579
|
+
this.emit(Events.DISCONNECTED, state);
|
|
580
|
+
this.destroy();
|
|
581
|
+
}
|
|
582
|
+
});
|
|
562
583
|
|
|
563
|
-
|
|
564
|
-
|
|
584
|
+
await this.pupPage.exposeFunction('onBatteryStateChangedEvent', (state) => {
|
|
585
|
+
const { battery, plugged } = state;
|
|
565
586
|
|
|
566
|
-
if (
|
|
567
|
-
setTimeout(() => {
|
|
568
|
-
this.pupPage.evaluate(() => window.Store.AppState.takeover());
|
|
569
|
-
}, this.options.takeoverTimeoutMs);
|
|
570
|
-
}
|
|
571
|
-
}
|
|
587
|
+
if (battery === undefined) return;
|
|
572
588
|
|
|
573
|
-
if (!ACCEPTED_STATES.includes(state)) {
|
|
574
589
|
/**
|
|
575
|
-
* Emitted when the
|
|
576
|
-
* @event Client#
|
|
577
|
-
* @param {
|
|
590
|
+
* Emitted when the battery percentage for the attached device changes. Will not be sent if using multi-device.
|
|
591
|
+
* @event Client#change_battery
|
|
592
|
+
* @param {object} batteryInfo
|
|
593
|
+
* @param {number} batteryInfo.battery - The current battery percentage
|
|
594
|
+
* @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
|
|
595
|
+
* @deprecated
|
|
578
596
|
*/
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
this.destroy();
|
|
582
|
-
}
|
|
583
|
-
});
|
|
597
|
+
this.emit(Events.BATTERY_CHANGED, { battery, plugged });
|
|
598
|
+
});
|
|
584
599
|
|
|
585
|
-
|
|
586
|
-
|
|
600
|
+
await this.pupPage.exposeFunction('onIncomingCall', (call) => {
|
|
601
|
+
/**
|
|
602
|
+
* Emitted when a call is received
|
|
603
|
+
* @event Client#incoming_call
|
|
604
|
+
* @param {object} call
|
|
605
|
+
* @param {number} call.id - Call id
|
|
606
|
+
* @param {string} call.peerJid - Who called
|
|
607
|
+
* @param {boolean} call.isVideo - if is video
|
|
608
|
+
* @param {boolean} call.isGroup - if is group
|
|
609
|
+
* @param {boolean} call.canHandleLocally - if we can handle in waweb
|
|
610
|
+
* @param {boolean} call.outgoing - if is outgoing
|
|
611
|
+
* @param {boolean} call.webClientShouldHandle - If Waweb should handle
|
|
612
|
+
* @param {object} call.participants - Participants
|
|
613
|
+
*/
|
|
614
|
+
const cll = new Call(this, call);
|
|
615
|
+
this.emit(Events.INCOMING_CALL, cll);
|
|
616
|
+
});
|
|
587
617
|
|
|
588
|
-
|
|
618
|
+
await this.pupPage.exposeFunction('onReaction', (reactions) => {
|
|
619
|
+
for (const reaction of reactions) {
|
|
620
|
+
/**
|
|
621
|
+
* Emitted when a reaction is sent, received, updated or removed
|
|
622
|
+
* @event Client#message_reaction
|
|
623
|
+
* @param {object} reaction
|
|
624
|
+
* @param {object} reaction.id - Reaction id
|
|
625
|
+
* @param {number} reaction.orphan - Orphan
|
|
626
|
+
* @param {?string} reaction.orphanReason - Orphan reason
|
|
627
|
+
* @param {number} reaction.timestamp - Timestamp
|
|
628
|
+
* @param {string} reaction.reaction - Reaction
|
|
629
|
+
* @param {boolean} reaction.read - Read
|
|
630
|
+
* @param {object} reaction.msgId - Parent message id
|
|
631
|
+
* @param {string} reaction.senderId - Sender id
|
|
632
|
+
* @param {?number} reaction.ack - Ack
|
|
633
|
+
*/
|
|
589
634
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
* @param {object} batteryInfo
|
|
594
|
-
* @param {number} batteryInfo.battery - The current battery percentage
|
|
595
|
-
* @param {boolean} batteryInfo.plugged - Indicates if the phone is plugged in (true) or not (false)
|
|
596
|
-
* @deprecated
|
|
597
|
-
*/
|
|
598
|
-
this.emit(Events.BATTERY_CHANGED, { battery, plugged });
|
|
599
|
-
});
|
|
635
|
+
this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
|
|
636
|
+
}
|
|
637
|
+
});
|
|
600
638
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
* Emitted when a call is received
|
|
604
|
-
* @event Client#incoming_call
|
|
605
|
-
* @param {object} call
|
|
606
|
-
* @param {number} call.id - Call id
|
|
607
|
-
* @param {string} call.peerJid - Who called
|
|
608
|
-
* @param {boolean} call.isVideo - if is video
|
|
609
|
-
* @param {boolean} call.isGroup - if is group
|
|
610
|
-
* @param {boolean} call.canHandleLocally - if we can handle in waweb
|
|
611
|
-
* @param {boolean} call.outgoing - if is outgoing
|
|
612
|
-
* @param {boolean} call.webClientShouldHandle - If Waweb should handle
|
|
613
|
-
* @param {object} call.participants - Participants
|
|
614
|
-
*/
|
|
615
|
-
const cll = new Call(this, call);
|
|
616
|
-
this.emit(Events.INCOMING_CALL, cll);
|
|
617
|
-
});
|
|
639
|
+
await this.pupPage.exposeFunction('onRemoveChatEvent', async (chat) => {
|
|
640
|
+
const _chat = await this.getChatById(chat.id);
|
|
618
641
|
|
|
619
|
-
await page.exposeFunction('onReaction', (reactions) => {
|
|
620
|
-
for (const reaction of reactions) {
|
|
621
642
|
/**
|
|
622
|
-
* Emitted when a
|
|
623
|
-
* @event Client#
|
|
624
|
-
* @param {
|
|
625
|
-
* @param {object} reaction.id - Reaction id
|
|
626
|
-
* @param {number} reaction.orphan - Orphan
|
|
627
|
-
* @param {?string} reaction.orphanReason - Orphan reason
|
|
628
|
-
* @param {number} reaction.timestamp - Timestamp
|
|
629
|
-
* @param {string} reaction.reaction - Reaction
|
|
630
|
-
* @param {boolean} reaction.read - Read
|
|
631
|
-
* @param {object} reaction.msgId - Parent message id
|
|
632
|
-
* @param {string} reaction.senderId - Sender id
|
|
633
|
-
* @param {?number} reaction.ack - Ack
|
|
643
|
+
* Emitted when a chat is removed
|
|
644
|
+
* @event Client#chat_removed
|
|
645
|
+
* @param {Chat} chat
|
|
634
646
|
*/
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
}
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
await page.exposeFunction('onRemoveChatEvent', async (chat) => {
|
|
641
|
-
const _chat = await this.getChatById(chat.id);
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Emitted when a chat is removed
|
|
645
|
-
* @event Client#chat_removed
|
|
646
|
-
* @param {Chat} chat
|
|
647
|
-
*/
|
|
648
|
-
this.emit(Events.CHAT_REMOVED, _chat);
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
await page.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => {
|
|
652
|
-
const _chat = await this.getChatById(chat.id);
|
|
647
|
+
this.emit(Events.CHAT_REMOVED, _chat);
|
|
648
|
+
});
|
|
653
649
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
650
|
+
await this.pupPage.exposeFunction('onArchiveChatEvent', async (chat, currState, prevState) => {
|
|
651
|
+
const _chat = await this.getChatById(chat.id);
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Emitted when a chat is archived/unarchived
|
|
655
|
+
* @event Client#chat_archived
|
|
656
|
+
* @param {Chat} chat
|
|
657
|
+
* @param {boolean} currState
|
|
658
|
+
* @param {boolean} prevState
|
|
659
|
+
*/
|
|
660
|
+
this.emit(Events.CHAT_ARCHIVED, _chat, currState, prevState);
|
|
661
|
+
});
|
|
663
662
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
await page.exposeFunction('onAddMessageCiphertextEvent', msg => {
|
|
663
|
+
await this.pupPage.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => {
|
|
664
|
+
|
|
665
|
+
if(msg.type === 'revoked'){
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Emitted when messages are edited
|
|
670
|
+
* @event Client#message_edit
|
|
671
|
+
* @param {Message} message
|
|
672
|
+
* @param {string} newBody
|
|
673
|
+
* @param {string} prevBody
|
|
674
|
+
*/
|
|
675
|
+
this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody);
|
|
676
|
+
});
|
|
680
677
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
678
|
+
await this.pupPage.exposeFunction('onAddMessageCiphertextEvent', msg => {
|
|
679
|
+
|
|
680
|
+
/**
|
|
681
|
+
* Emitted when messages are edited
|
|
682
|
+
* @event Client#message_ciphertext
|
|
683
|
+
* @param {Message} message
|
|
684
|
+
*/
|
|
685
|
+
this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
688
|
|
|
689
|
-
await
|
|
689
|
+
await this.pupPage.exposeFunction('onPollVoteEvent', (vote) => {
|
|
690
690
|
const _vote = new PollVote(this, vote);
|
|
691
691
|
/**
|
|
692
692
|
* Emitted when some poll option is selected or deselected,
|
|
@@ -696,7 +696,7 @@ class Client extends EventEmitter {
|
|
|
696
696
|
this.emit(Events.VOTE_UPDATE, _vote);
|
|
697
697
|
});
|
|
698
698
|
|
|
699
|
-
await
|
|
699
|
+
await this.pupPage.evaluate(() => {
|
|
700
700
|
window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
|
|
701
701
|
window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
|
|
702
702
|
window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
|
|
@@ -725,7 +725,23 @@ class Client extends EventEmitter {
|
|
|
725
725
|
pollVoteModel && window.onPollVoteEvent(pollVoteModel);
|
|
726
726
|
});
|
|
727
727
|
|
|
728
|
-
{
|
|
728
|
+
if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) {
|
|
729
|
+
const module = window.Store.AddonReactionTable;
|
|
730
|
+
const ogMethod = module.bulkUpsert;
|
|
731
|
+
module.bulkUpsert = ((...args) => {
|
|
732
|
+
window.onReaction(args[0].map(reaction => {
|
|
733
|
+
const msgKey = reaction.id;
|
|
734
|
+
const parentMsgKey = reaction.reactionParentKey;
|
|
735
|
+
const timestamp = reaction.reactionTimestamp / 1000;
|
|
736
|
+
const sender = reaction.author ?? reaction.from;
|
|
737
|
+
const senderUserJid = sender._serialized;
|
|
738
|
+
|
|
739
|
+
return {...reaction, msgKey, parentMsgKey, senderUserJid, timestamp };
|
|
740
|
+
}));
|
|
741
|
+
|
|
742
|
+
return ogMethod(...args);
|
|
743
|
+
}).bind(module);
|
|
744
|
+
} else {
|
|
729
745
|
const module = window.Store.createOrUpdateReactionsModule;
|
|
730
746
|
const ogMethod = module.createOrUpdateReactions;
|
|
731
747
|
module.createOrUpdateReactions = ((...args) => {
|
|
@@ -741,24 +757,7 @@ class Client extends EventEmitter {
|
|
|
741
757
|
}).bind(module);
|
|
742
758
|
}
|
|
743
759
|
});
|
|
744
|
-
|
|
745
|
-
/**
|
|
746
|
-
* Emitted when the client has initialized and is ready to receive messages.
|
|
747
|
-
* @event Client#ready
|
|
748
|
-
*/
|
|
749
|
-
this.emit(Events.READY);
|
|
750
|
-
this.authStrategy.afterAuthReady();
|
|
751
|
-
|
|
752
|
-
// Disconnect when navigating away when in PAIRING state (detect logout)
|
|
753
|
-
this.pupPage.on('framenavigated', async () => {
|
|
754
|
-
const appState = await this.getState();
|
|
755
|
-
if(!appState || appState === WAState.PAIRING) {
|
|
756
|
-
await this.authStrategy.disconnect();
|
|
757
|
-
this.emit(Events.DISCONNECTED, 'NAVIGATION');
|
|
758
|
-
await this.destroy();
|
|
759
|
-
}
|
|
760
|
-
});
|
|
761
|
-
}
|
|
760
|
+
}
|
|
762
761
|
|
|
763
762
|
async initWebVersionCache() {
|
|
764
763
|
const { type: webCacheType, ...webCacheOptions } = this.options.webVersionCache;
|
|
@@ -783,7 +782,8 @@ class Client extends EventEmitter {
|
|
|
783
782
|
} else {
|
|
784
783
|
this.pupPage.on('response', async (res) => {
|
|
785
784
|
if(res.ok() && res.url() === WhatsWebURL) {
|
|
786
|
-
|
|
785
|
+
const indexHtml = await res.text();
|
|
786
|
+
this.currentIndexHtml = indexHtml;
|
|
787
787
|
}
|
|
788
788
|
});
|
|
789
789
|
}
|
|
@@ -861,6 +861,7 @@ class Client extends EventEmitter {
|
|
|
861
861
|
* @property {GroupMention[]} [groupMentions] - An array of object that handle group mentions
|
|
862
862
|
* @property {string[]} [mentions] - User IDs to mention in the message
|
|
863
863
|
* @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message
|
|
864
|
+
* @property {string} [invokedBotWid=undefined] - Bot Wid when doing a bot mention like @Meta AI
|
|
864
865
|
* @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true).
|
|
865
866
|
* @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
|
|
866
867
|
* @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
|
|
@@ -894,9 +895,10 @@ class Client extends EventEmitter {
|
|
|
894
895
|
sendMediaAsDocument: options.sendMediaAsDocument,
|
|
895
896
|
caption: options.caption,
|
|
896
897
|
quotedMessageId: options.quotedMessageId,
|
|
897
|
-
parseVCards: options.parseVCards
|
|
898
|
+
parseVCards: options.parseVCards !== false,
|
|
898
899
|
mentionedJidList: options.mentions || [],
|
|
899
900
|
groupMentions: options.groupMentions,
|
|
901
|
+
invokedBotWid: options.invokedBotWid,
|
|
900
902
|
extraOptions: options.extra
|
|
901
903
|
};
|
|
902
904
|
|
|
@@ -1102,14 +1104,8 @@ class Client extends EventEmitter {
|
|
|
1102
1104
|
async setDisplayName(displayName) {
|
|
1103
1105
|
const couldSet = await this.pupPage.evaluate(async displayName => {
|
|
1104
1106
|
if(!window.Store.Conn.canSetMyPushname()) return false;
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
await window.Store.Settings.setPushname(displayName);
|
|
1108
|
-
return true;
|
|
1109
|
-
} else {
|
|
1110
|
-
const res = await window.Store.Wap.setPushname(displayName);
|
|
1111
|
-
return !res.status || res.status === 200;
|
|
1112
|
-
}
|
|
1107
|
+
await window.Store.Settings.setPushname(displayName);
|
|
1108
|
+
return true;
|
|
1113
1109
|
}, displayName);
|
|
1114
1110
|
|
|
1115
1111
|
return couldSet;
|
|
@@ -1250,7 +1246,9 @@ class Client extends EventEmitter {
|
|
|
1250
1246
|
const profilePic = await this.pupPage.evaluate(async contactId => {
|
|
1251
1247
|
try {
|
|
1252
1248
|
const chatWid = window.Store.WidFactory.createWid(contactId);
|
|
1253
|
-
return
|
|
1249
|
+
return window.compareWwebVersions(window.Debug.VERSION, '<', '2.3000.0')
|
|
1250
|
+
? await window.Store.ProfilePic.profilePicFind(chatWid)
|
|
1251
|
+
: await window.Store.ProfilePic.requestProfilePicFromServer(chatWid);
|
|
1254
1252
|
} catch (err) {
|
|
1255
1253
|
if(err.name === 'ServerStatusCodeError') return undefined;
|
|
1256
1254
|
throw err;
|
|
@@ -1416,10 +1414,18 @@ class Client extends EventEmitter {
|
|
|
1416
1414
|
|
|
1417
1415
|
try {
|
|
1418
1416
|
createGroupResult = await window.Store.GroupUtils.createGroup(
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1417
|
+
{
|
|
1418
|
+
'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode,
|
|
1419
|
+
'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode,
|
|
1420
|
+
'announce': options.announce === undefined ? true : options.announce,
|
|
1421
|
+
'ephemeralDuration': messageTimer,
|
|
1422
|
+
'full': undefined,
|
|
1423
|
+
'parentGroupId': parentGroupWid,
|
|
1424
|
+
'restrict': options.restrict === undefined ? true : options.restrict,
|
|
1425
|
+
'thumb': undefined,
|
|
1426
|
+
'title': title,
|
|
1427
|
+
},
|
|
1428
|
+
participantWids
|
|
1423
1429
|
);
|
|
1424
1430
|
} catch (err) {
|
|
1425
1431
|
return 'CreateGroupError: An unknown error occupied while creating a group';
|
|
@@ -1431,7 +1437,7 @@ class Client extends EventEmitter {
|
|
|
1431
1437
|
const statusCode = participant.error ?? 200;
|
|
1432
1438
|
|
|
1433
1439
|
if (autoSendInviteV4 && statusCode === 403) {
|
|
1434
|
-
window.Store.
|
|
1440
|
+
window.Store.Contact.gadd(participant.wid, { silent: true });
|
|
1435
1441
|
const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage(
|
|
1436
1442
|
await window.Store.Chat.find(participant.wid),
|
|
1437
1443
|
createGroupResult.wid._serialized,
|