whatsapp-web.js 1.17.0 → 1.18.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/example.js +4 -0
- package/index.d.ts +66 -3
- package/index.js +1 -0
- package/package.json +6 -1
- package/src/Client.js +92 -4
- package/src/authStrategies/BaseAuthStrategy.js +3 -0
- package/src/authStrategies/RemoteAuth.js +204 -0
- package/src/structures/Chat.js +11 -2
- package/src/structures/Message.js +9 -6
- package/src/structures/MessageMedia.js +13 -5
- package/src/structures/Reaction.js +69 -0
- package/src/structures/index.js +1 -0
- package/src/util/Constants.js +5 -2
- package/src/util/Injected.js +15 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://www.npmjs.com/package/whatsapp-web.js) [](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765) ](https://www.npmjs.com/package/whatsapp-web.js) [](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765)  [](https://discord.gg/H7DqQs4)
|
|
2
2
|
|
|
3
3
|
# whatsapp-web.js
|
|
4
4
|
A WhatsApp API client that connects through the WhatsApp Web browser app
|
|
@@ -80,6 +80,7 @@ For more information on saving and restoring sessions, check out the available [
|
|
|
80
80
|
| Get contact info | ✅ |
|
|
81
81
|
| Get profile pictures | ✅ |
|
|
82
82
|
| Set user status message | ✅ |
|
|
83
|
+
| React to messages | ✅ |
|
|
83
84
|
|
|
84
85
|
Something missing? Make an issue and let us know!
|
|
85
86
|
|
package/example.js
CHANGED
|
@@ -7,6 +7,10 @@ const client = new Client({
|
|
|
7
7
|
|
|
8
8
|
client.initialize();
|
|
9
9
|
|
|
10
|
+
client.on('loading_screen', (percent, message) => {
|
|
11
|
+
console.log('LOADING SCREEN', percent, message);
|
|
12
|
+
});
|
|
13
|
+
|
|
10
14
|
client.on('qr', (qr) => {
|
|
11
15
|
// NOTE: This event will not be fired if a session is specified.
|
|
12
16
|
console.log('QR RECEIVED', qr);
|
package/index.d.ts
CHANGED
|
@@ -241,6 +241,15 @@ declare namespace WAWebJS {
|
|
|
241
241
|
message: Message
|
|
242
242
|
) => void): this
|
|
243
243
|
|
|
244
|
+
/** Emitted when a reaction is sent, received, updated or removed */
|
|
245
|
+
on(event: 'message_reaction', listener: (
|
|
246
|
+
/** The reaction object */
|
|
247
|
+
reaction: Reaction
|
|
248
|
+
) => void): this
|
|
249
|
+
|
|
250
|
+
/** Emitted when loading screen is appearing */
|
|
251
|
+
on(event: 'loading_screen', listener: (percent: string, message: string) => void): this
|
|
252
|
+
|
|
244
253
|
/** Emitted when the QR code is received */
|
|
245
254
|
on(event: 'qr', listener: (
|
|
246
255
|
/** qr code string
|
|
@@ -256,6 +265,9 @@ declare namespace WAWebJS {
|
|
|
256
265
|
|
|
257
266
|
/** Emitted when the client has initialized and is ready to receive messages */
|
|
258
267
|
on(event: 'ready', listener: () => void): this
|
|
268
|
+
|
|
269
|
+
/** Emitted when the RemoteAuth session is saved successfully on the external Database */
|
|
270
|
+
on(event: 'remote_session_saved', listener: () => void): this
|
|
259
271
|
}
|
|
260
272
|
|
|
261
273
|
/** Current connection information */
|
|
@@ -345,6 +357,9 @@ declare namespace WAWebJS {
|
|
|
345
357
|
failureEventPayload?: any
|
|
346
358
|
}>;
|
|
347
359
|
getAuthEventPayload: () => Promise<any>;
|
|
360
|
+
afterAuthReady: () => Promise<void>;
|
|
361
|
+
disconnect: () => Promise<void>;
|
|
362
|
+
destroy: () => Promise<void>;
|
|
348
363
|
logout: () => Promise<void>;
|
|
349
364
|
}
|
|
350
365
|
|
|
@@ -365,6 +380,30 @@ declare namespace WAWebJS {
|
|
|
365
380
|
dataPath?: string
|
|
366
381
|
})
|
|
367
382
|
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Remote-based authentication
|
|
386
|
+
*/
|
|
387
|
+
export class RemoteAuth extends AuthStrategy {
|
|
388
|
+
public clientId?: string;
|
|
389
|
+
public dataPath?: string;
|
|
390
|
+
constructor(options?: {
|
|
391
|
+
store: Store,
|
|
392
|
+
clientId?: string,
|
|
393
|
+
dataPath?: string,
|
|
394
|
+
backupSyncIntervalMs: number
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Remote store interface
|
|
400
|
+
*/
|
|
401
|
+
export interface Store {
|
|
402
|
+
sessionExists: ({session: string}) => Promise<boolean> | boolean,
|
|
403
|
+
delete: ({session: string}) => Promise<any> | any,
|
|
404
|
+
save: ({session: string}) => Promise<any> | any,
|
|
405
|
+
extract: ({session: string, path: string}) => Promise<any> | any,
|
|
406
|
+
}
|
|
368
407
|
|
|
369
408
|
/**
|
|
370
409
|
* Legacy session auth strategy
|
|
@@ -463,9 +502,11 @@ declare namespace WAWebJS {
|
|
|
463
502
|
GROUP_LEAVE = 'group_leave',
|
|
464
503
|
GROUP_UPDATE = 'group_update',
|
|
465
504
|
QR_RECEIVED = 'qr',
|
|
505
|
+
LOADING_SCREEN = 'loading_screen',
|
|
466
506
|
DISCONNECTED = 'disconnected',
|
|
467
507
|
STATE_CHANGED = 'change_state',
|
|
468
508
|
BATTERY_CHANGED = 'change_battery',
|
|
509
|
+
REMOTE_SESSION_SAVED = 'remote_session_saved'
|
|
469
510
|
}
|
|
470
511
|
|
|
471
512
|
/** Group notification types */
|
|
@@ -604,6 +645,8 @@ declare namespace WAWebJS {
|
|
|
604
645
|
ack: MessageAck,
|
|
605
646
|
/** If the message was sent to a group, this field will contain the user that sent the message. */
|
|
606
647
|
author?: string,
|
|
648
|
+
/** String that represents from which device type the message was sent */
|
|
649
|
+
deviceType: string,
|
|
607
650
|
/** Message content */
|
|
608
651
|
body: string,
|
|
609
652
|
/** Indicates if the message was a broadcast */
|
|
@@ -704,9 +747,9 @@ declare namespace WAWebJS {
|
|
|
704
747
|
*/
|
|
705
748
|
reply: (content: MessageContent, chatId?: string, options?: MessageSendOptions) => Promise<Message>,
|
|
706
749
|
/** React to this message with an emoji*/
|
|
707
|
-
react: (reaction: string) => Promise
|
|
750
|
+
react: (reaction: string) => Promise<void>,
|
|
708
751
|
/**
|
|
709
|
-
* Forwards this message to another chat
|
|
752
|
+
* Forwards this message to another chat (that you chatted before, otherwise it will fail)
|
|
710
753
|
*/
|
|
711
754
|
forward: (chat: Chat | string) => Promise<void>,
|
|
712
755
|
/** Star this message */
|
|
@@ -803,13 +846,16 @@ declare namespace WAWebJS {
|
|
|
803
846
|
data: string
|
|
804
847
|
/** Document file name. Value can be null */
|
|
805
848
|
filename?: string | null
|
|
849
|
+
/** Document file size in bytes. Value can be null. */
|
|
850
|
+
filesize?: number | null
|
|
806
851
|
|
|
807
852
|
/**
|
|
808
853
|
* @param {string} mimetype MIME type of the attachment
|
|
809
854
|
* @param {string} data Base64-encoded data of the file
|
|
810
855
|
* @param {?string} filename Document file name. Value can be null
|
|
856
|
+
* @param {?number} filesize Document file size in bytes. Value can be null.
|
|
811
857
|
*/
|
|
812
|
-
constructor(mimetype: string, data: string, filename?: string | null)
|
|
858
|
+
constructor(mimetype: string, data: string, filename?: string | null, filesize?: number | null)
|
|
813
859
|
|
|
814
860
|
/** Creates a MessageMedia instance from a local file path */
|
|
815
861
|
static fromFilePath: (filePath: string) => MessageMedia
|
|
@@ -1016,6 +1062,10 @@ declare namespace WAWebJS {
|
|
|
1016
1062
|
* Set this to Infinity to load all messages.
|
|
1017
1063
|
*/
|
|
1018
1064
|
limit?: number
|
|
1065
|
+
/**
|
|
1066
|
+
* Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.
|
|
1067
|
+
*/
|
|
1068
|
+
fromMe?: boolean
|
|
1019
1069
|
}
|
|
1020
1070
|
|
|
1021
1071
|
/**
|
|
@@ -1298,6 +1348,19 @@ declare namespace WAWebJS {
|
|
|
1298
1348
|
|
|
1299
1349
|
constructor(body: string, buttons: Array<{ id?: string; body: string }>, title?: string | null, footer?: string | null)
|
|
1300
1350
|
}
|
|
1351
|
+
|
|
1352
|
+
/** Message type Reaction */
|
|
1353
|
+
export class Reaction {
|
|
1354
|
+
id: MessageId
|
|
1355
|
+
orphan: number
|
|
1356
|
+
orphanReason?: string
|
|
1357
|
+
timestamp: number
|
|
1358
|
+
reaction: string
|
|
1359
|
+
read: boolean
|
|
1360
|
+
msgId: MessageId
|
|
1361
|
+
senderId: string
|
|
1362
|
+
ack?: number
|
|
1363
|
+
}
|
|
1301
1364
|
}
|
|
1302
1365
|
|
|
1303
1366
|
export = WAWebJS
|
package/index.js
CHANGED
|
@@ -25,6 +25,7 @@ module.exports = {
|
|
|
25
25
|
// Auth Strategies
|
|
26
26
|
NoAuth: require('./src/authStrategies/NoAuth'),
|
|
27
27
|
LocalAuth: require('./src/authStrategies/LocalAuth'),
|
|
28
|
+
RemoteAuth: require('./src/authStrategies/RemoteAuth'),
|
|
28
29
|
LegacySessionAuth: require('./src/authStrategies/LegacySessionAuth'),
|
|
29
30
|
|
|
30
31
|
...Constants
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whatsapp-web.js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0-alpha.1",
|
|
4
4
|
"description": "Library for interacting with the WhatsApp Web API ",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"typings": "./index.d.ts",
|
|
@@ -51,5 +51,10 @@
|
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=12.0.0"
|
|
54
|
+
},
|
|
55
|
+
"optionalDependencies": {
|
|
56
|
+
"archiver": "^5.3.1",
|
|
57
|
+
"fs-extra": "^10.1.0",
|
|
58
|
+
"unzipper": "^0.10.11"
|
|
54
59
|
}
|
|
55
60
|
}
|
package/src/Client.js
CHANGED
|
@@ -10,7 +10,7 @@ const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constan
|
|
|
10
10
|
const { ExposeStore, LoadUtils } = require('./util/Injected');
|
|
11
11
|
const ChatFactory = require('./factories/ChatFactory');
|
|
12
12
|
const ContactFactory = require('./factories/ContactFactory');
|
|
13
|
-
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List} = require('./structures');
|
|
13
|
+
const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
|
|
14
14
|
const LegacySessionAuth = require('./authStrategies/LegacySessionAuth');
|
|
15
15
|
const NoAuth = require('./authStrategies/NoAuth');
|
|
16
16
|
|
|
@@ -115,6 +115,52 @@ class Client extends EventEmitter {
|
|
|
115
115
|
referer: 'https://whatsapp.com/'
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
+
await page.evaluate(`function getElementByXpath(path) {
|
|
119
|
+
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
|
|
120
|
+
}`);
|
|
121
|
+
|
|
122
|
+
let lastPercent = null,
|
|
123
|
+
lastPercentMessage = null;
|
|
124
|
+
|
|
125
|
+
await page.exposeFunction('loadingScreen', async (percent, message) => {
|
|
126
|
+
if (lastPercent !== percent || lastPercentMessage !== message) {
|
|
127
|
+
this.emit(Events.LOADING_SCREEN, percent, message);
|
|
128
|
+
lastPercent = percent;
|
|
129
|
+
lastPercentMessage = message;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
await page.evaluate(
|
|
134
|
+
async function (selectors) {
|
|
135
|
+
var observer = new MutationObserver(function () {
|
|
136
|
+
let progressBar = window.getElementByXpath(
|
|
137
|
+
selectors.PROGRESS
|
|
138
|
+
);
|
|
139
|
+
let progressMessage = window.getElementByXpath(
|
|
140
|
+
selectors.PROGRESS_MESSAGE
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (progressBar) {
|
|
144
|
+
window.loadingScreen(
|
|
145
|
+
progressBar.value,
|
|
146
|
+
progressMessage.innerText
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
observer.observe(document, {
|
|
152
|
+
attributes: true,
|
|
153
|
+
childList: true,
|
|
154
|
+
characterData: true,
|
|
155
|
+
subtree: true,
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
PROGRESS: '//*[@id=\'app\']/div/div/div[2]/progress',
|
|
160
|
+
PROGRESS_MESSAGE: '//*[@id=\'app\']/div/div/div[3]',
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
118
164
|
const INTRO_IMG_SELECTOR = '[data-testid="intro-md-beta-logo-dark"], [data-testid="intro-md-beta-logo-light"], [data-asset-intro-image-light="true"], [data-asset-intro-image-dark="true"]';
|
|
119
165
|
const INTRO_QRCODE_SELECTOR = 'div[data-ref] canvas';
|
|
120
166
|
|
|
@@ -132,7 +178,7 @@ class Client extends EventEmitter {
|
|
|
132
178
|
})
|
|
133
179
|
]);
|
|
134
180
|
|
|
135
|
-
// Checks if an error
|
|
181
|
+
// Checks if an error occurred on the first found selector. The second will be discarded and ignored by .race;
|
|
136
182
|
if (needAuthentication instanceof Error) throw needAuthentication;
|
|
137
183
|
|
|
138
184
|
// Scan-qrcode selector was found. Needs authentication
|
|
@@ -373,7 +419,7 @@ class Client extends EventEmitter {
|
|
|
373
419
|
this.emit(Events.MEDIA_UPLOADED, message);
|
|
374
420
|
});
|
|
375
421
|
|
|
376
|
-
await page.exposeFunction('onAppStateChangedEvent', (state) => {
|
|
422
|
+
await page.exposeFunction('onAppStateChangedEvent', async (state) => {
|
|
377
423
|
|
|
378
424
|
/**
|
|
379
425
|
* Emitted when the connection state changes
|
|
@@ -400,6 +446,7 @@ class Client extends EventEmitter {
|
|
|
400
446
|
* @event Client#disconnected
|
|
401
447
|
* @param {WAState|"NAVIGATION"} reason reason that caused the disconnect
|
|
402
448
|
*/
|
|
449
|
+
await this.authStrategy.disconnect();
|
|
403
450
|
this.emit(Events.DISCONNECTED, state);
|
|
404
451
|
this.destroy();
|
|
405
452
|
}
|
|
@@ -439,6 +486,27 @@ class Client extends EventEmitter {
|
|
|
439
486
|
this.emit(Events.INCOMING_CALL, cll);
|
|
440
487
|
});
|
|
441
488
|
|
|
489
|
+
await page.exposeFunction('onReaction', (reactions) => {
|
|
490
|
+
for (const reaction of reactions) {
|
|
491
|
+
/**
|
|
492
|
+
* Emitted when a reaction is sent, received, updated or removed
|
|
493
|
+
* @event Client#message_reaction
|
|
494
|
+
* @param {object} reaction
|
|
495
|
+
* @param {object} reaction.id - Reaction id
|
|
496
|
+
* @param {number} reaction.orphan - Orphan
|
|
497
|
+
* @param {?string} reaction.orphanReason - Orphan reason
|
|
498
|
+
* @param {number} reaction.timestamp - Timestamp
|
|
499
|
+
* @param {string} reaction.reaction - Reaction
|
|
500
|
+
* @param {boolean} reaction.read - Read
|
|
501
|
+
* @param {object} reaction.msgId - Parent message id
|
|
502
|
+
* @param {string} reaction.senderId - Sender id
|
|
503
|
+
* @param {?number} reaction.ack - Ack
|
|
504
|
+
*/
|
|
505
|
+
|
|
506
|
+
this.emit(Events.MESSAGE_REACTION, new Reaction(this, reaction));
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
|
|
442
510
|
await page.evaluate(() => {
|
|
443
511
|
window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
|
|
444
512
|
window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
|
|
@@ -458,6 +526,22 @@ class Client extends EventEmitter {
|
|
|
458
526
|
}
|
|
459
527
|
}
|
|
460
528
|
});
|
|
529
|
+
|
|
530
|
+
{
|
|
531
|
+
const module = window.Store.createOrUpdateReactionsModule;
|
|
532
|
+
const ogMethod = module.createOrUpdateReactions;
|
|
533
|
+
module.createOrUpdateReactions = ((...args) => {
|
|
534
|
+
window.onReaction(args[0].map(reaction => {
|
|
535
|
+
const msgKey = window.Store.MsgKey.fromString(reaction.msgKey);
|
|
536
|
+
const parentMsgKey = window.Store.MsgKey.fromString(reaction.parentMsgKey);
|
|
537
|
+
const timestamp = reaction.timestamp / 1000;
|
|
538
|
+
|
|
539
|
+
return {...reaction, msgKey, parentMsgKey, timestamp };
|
|
540
|
+
}));
|
|
541
|
+
|
|
542
|
+
return ogMethod(...args);
|
|
543
|
+
}).bind(module);
|
|
544
|
+
}
|
|
461
545
|
});
|
|
462
546
|
|
|
463
547
|
/**
|
|
@@ -465,11 +549,13 @@ class Client extends EventEmitter {
|
|
|
465
549
|
* @event Client#ready
|
|
466
550
|
*/
|
|
467
551
|
this.emit(Events.READY);
|
|
552
|
+
this.authStrategy.afterAuthReady();
|
|
468
553
|
|
|
469
554
|
// Disconnect when navigating away when in PAIRING state (detect logout)
|
|
470
555
|
this.pupPage.on('framenavigated', async () => {
|
|
471
556
|
const appState = await this.getState();
|
|
472
557
|
if(!appState || appState === WAState.PAIRING) {
|
|
558
|
+
await this.authStrategy.disconnect();
|
|
473
559
|
this.emit(Events.DISCONNECTED, 'NAVIGATION');
|
|
474
560
|
await this.destroy();
|
|
475
561
|
}
|
|
@@ -481,6 +567,7 @@ class Client extends EventEmitter {
|
|
|
481
567
|
*/
|
|
482
568
|
async destroy() {
|
|
483
569
|
await this.pupBrowser.close();
|
|
570
|
+
await this.authStrategy.destroy();
|
|
484
571
|
}
|
|
485
572
|
|
|
486
573
|
/**
|
|
@@ -949,7 +1036,8 @@ class Client extends EventEmitter {
|
|
|
949
1036
|
}
|
|
950
1037
|
|
|
951
1038
|
return await this.pupPage.evaluate(async number => {
|
|
952
|
-
const
|
|
1039
|
+
const wid = window.Store.WidFactory.createWid(number);
|
|
1040
|
+
const result = await window.Store.QueryExist(wid);
|
|
953
1041
|
if (!result || result.wid === undefined) return null;
|
|
954
1042
|
return result.wid;
|
|
955
1043
|
}, number);
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Require Optional Dependencies */
|
|
4
|
+
try {
|
|
5
|
+
var fs = require('fs-extra');
|
|
6
|
+
var unzipper = require('unzipper');
|
|
7
|
+
var archiver = require('archiver');
|
|
8
|
+
} catch {
|
|
9
|
+
fs = undefined;
|
|
10
|
+
unzipper = undefined;
|
|
11
|
+
archiver = undefined;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { Events } = require('./../util/Constants');
|
|
16
|
+
const BaseAuthStrategy = require('./BaseAuthStrategy');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Remote-based authentication
|
|
20
|
+
* @param {object} options - options
|
|
21
|
+
* @param {object} options.store - Remote database store instance
|
|
22
|
+
* @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance
|
|
23
|
+
* @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/"
|
|
24
|
+
* @param {number} options.backupSyncIntervalMs - Sets the time interval for periodic session backups. Accepts values starting from 60000ms {1 minute}
|
|
25
|
+
*/
|
|
26
|
+
class RemoteAuth extends BaseAuthStrategy {
|
|
27
|
+
constructor({ clientId, dataPath, store, backupSyncIntervalMs } = {}) {
|
|
28
|
+
if (!fs && !unzipper && !archiver) throw new Error('Optional Dependencies [fs-extra, unzipper, archiver] are required to use RemoteAuth. Make sure to run npm install correctly and remove the --no-optional flag');
|
|
29
|
+
super();
|
|
30
|
+
|
|
31
|
+
const idRegex = /^[-_\w]+$/i;
|
|
32
|
+
if (clientId && !idRegex.test(clientId)) {
|
|
33
|
+
throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.');
|
|
34
|
+
}
|
|
35
|
+
if (!backupSyncIntervalMs || backupSyncIntervalMs < 60000) {
|
|
36
|
+
throw new Error('Invalid backupSyncIntervalMs. Accepts values starting from 60000ms {1 minute}.');
|
|
37
|
+
}
|
|
38
|
+
if(!store) throw new Error('Remote database store is required.');
|
|
39
|
+
|
|
40
|
+
this.store = store;
|
|
41
|
+
this.clientId = clientId;
|
|
42
|
+
this.backupSyncIntervalMs = backupSyncIntervalMs;
|
|
43
|
+
this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
|
|
44
|
+
this.tempDir = `${this.dataPath}/wwebjs_temp_session`;
|
|
45
|
+
this.requiredDirs = ['Default', 'IndexedDB', 'Local Storage']; /* => Required Files & Dirs in WWebJS to restore session */
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async beforeBrowserInitialized() {
|
|
49
|
+
const puppeteerOpts = this.client.options.puppeteer;
|
|
50
|
+
const sessionDirName = this.clientId ? `RemoteAuth-${this.clientId}` : 'RemoteAuth';
|
|
51
|
+
const dirPath = path.join(this.dataPath, sessionDirName);
|
|
52
|
+
|
|
53
|
+
if (puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) {
|
|
54
|
+
throw new Error('RemoteAuth is not compatible with a user-supplied userDataDir.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.userDataDir = dirPath;
|
|
58
|
+
this.sessionName = sessionDirName;
|
|
59
|
+
|
|
60
|
+
await this.extractRemoteSession();
|
|
61
|
+
|
|
62
|
+
this.client.options.puppeteer = {
|
|
63
|
+
...puppeteerOpts,
|
|
64
|
+
userDataDir: dirPath
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async logout() {
|
|
69
|
+
await this.disconnect();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async destroy() {
|
|
73
|
+
clearInterval(this.backupSync);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async disconnect() {
|
|
77
|
+
await this.deleteRemoteSession();
|
|
78
|
+
|
|
79
|
+
let pathExists = await this.isValidPath(this.userDataDir);
|
|
80
|
+
if (pathExists) {
|
|
81
|
+
await fs.promises.rm(this.userDataDir, {
|
|
82
|
+
recursive: true,
|
|
83
|
+
force: true
|
|
84
|
+
}).catch(() => {});
|
|
85
|
+
}
|
|
86
|
+
clearInterval(this.backupSync);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async afterAuthReady() {
|
|
90
|
+
const sessionExists = await this.store.sessionExists({session: this.sessionName});
|
|
91
|
+
if(!sessionExists) {
|
|
92
|
+
await this.delay(60000); /* Initial delay sync required for session to be stable enough to recover */
|
|
93
|
+
await this.storeRemoteSession({emit: true});
|
|
94
|
+
}
|
|
95
|
+
var self = this;
|
|
96
|
+
this.backupSync = setInterval(async function () {
|
|
97
|
+
await self.storeRemoteSession();
|
|
98
|
+
}, this.backupSyncIntervalMs);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async storeRemoteSession(options) {
|
|
102
|
+
/* Compress & Store Session */
|
|
103
|
+
const pathExists = await this.isValidPath(this.userDataDir);
|
|
104
|
+
if (pathExists) {
|
|
105
|
+
await this.compressSession();
|
|
106
|
+
await this.store.save({session: this.sessionName});
|
|
107
|
+
await fs.promises.unlink(`${this.sessionName}.zip`);
|
|
108
|
+
await fs.promises.rm(`${this.tempDir}`, {
|
|
109
|
+
recursive: true,
|
|
110
|
+
force: true
|
|
111
|
+
}).catch(() => {});
|
|
112
|
+
if(options && options.emit) this.client.emit(Events.REMOTE_SESSION_SAVED);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async extractRemoteSession() {
|
|
117
|
+
const pathExists = await this.isValidPath(this.userDataDir);
|
|
118
|
+
const compressedSessionPath = `${this.sessionName}.zip`;
|
|
119
|
+
const sessionExists = await this.store.sessionExists({session: this.sessionName});
|
|
120
|
+
if (pathExists) {
|
|
121
|
+
await fs.promises.rm(this.userDataDir, {
|
|
122
|
+
recursive: true,
|
|
123
|
+
force: true
|
|
124
|
+
}).catch(() => {});
|
|
125
|
+
}
|
|
126
|
+
if (sessionExists) {
|
|
127
|
+
await this.store.extract({session: this.sessionName, path: compressedSessionPath});
|
|
128
|
+
await this.unCompressSession(compressedSessionPath);
|
|
129
|
+
} else {
|
|
130
|
+
fs.mkdirSync(this.userDataDir, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async deleteRemoteSession() {
|
|
135
|
+
const sessionExists = await this.store.sessionExists({session: this.sessionName});
|
|
136
|
+
if (sessionExists) await this.store.delete({session: this.sessionName});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async compressSession() {
|
|
140
|
+
const archive = archiver('zip');
|
|
141
|
+
const stream = fs.createWriteStream(`${this.sessionName}.zip`);
|
|
142
|
+
|
|
143
|
+
await fs.copy(this.userDataDir, this.tempDir).catch(() => {});
|
|
144
|
+
await this.deleteMetadata();
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
archive
|
|
147
|
+
.directory(this.tempDir, false)
|
|
148
|
+
.on('error', err => reject(err))
|
|
149
|
+
.pipe(stream);
|
|
150
|
+
|
|
151
|
+
stream.on('close', () => resolve());
|
|
152
|
+
archive.finalize();
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async unCompressSession(compressedSessionPath) {
|
|
157
|
+
var stream = fs.createReadStream(compressedSessionPath);
|
|
158
|
+
await new Promise((resolve, reject) => {
|
|
159
|
+
stream.pipe(unzipper.Extract({
|
|
160
|
+
path: this.userDataDir
|
|
161
|
+
}))
|
|
162
|
+
.on('error', err => reject(err))
|
|
163
|
+
.on('finish', () => resolve());
|
|
164
|
+
});
|
|
165
|
+
await fs.promises.unlink(compressedSessionPath);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async deleteMetadata() {
|
|
169
|
+
const sessionDirs = [this.tempDir, path.join(this.tempDir, 'Default')];
|
|
170
|
+
for (const dir of sessionDirs) {
|
|
171
|
+
const sessionFiles = await fs.promises.readdir(dir);
|
|
172
|
+
for (const element of sessionFiles) {
|
|
173
|
+
if (!this.requiredDirs.includes(element)) {
|
|
174
|
+
const dirElement = path.join(dir, element);
|
|
175
|
+
const stats = await fs.promises.lstat(dirElement);
|
|
176
|
+
|
|
177
|
+
if (stats.isDirectory()) {
|
|
178
|
+
await fs.promises.rm(dirElement, {
|
|
179
|
+
recursive: true,
|
|
180
|
+
force: true
|
|
181
|
+
});
|
|
182
|
+
} else {
|
|
183
|
+
await fs.promises.unlink(dirElement);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async isValidPath(path) {
|
|
191
|
+
try {
|
|
192
|
+
await fs.promises.access(path);
|
|
193
|
+
return true;
|
|
194
|
+
} catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async delay(ms) {
|
|
200
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = RemoteAuth;
|
package/src/structures/Chat.js
CHANGED
|
@@ -170,13 +170,22 @@ class Chat extends Base {
|
|
|
170
170
|
|
|
171
171
|
/**
|
|
172
172
|
* Loads chat messages, sorted from earliest to latest.
|
|
173
|
-
* @param {Object} searchOptions Options for searching messages. Right now only limit is supported.
|
|
173
|
+
* @param {Object} searchOptions Options for searching messages. Right now only limit and fromMe is supported.
|
|
174
174
|
* @param {Number} [searchOptions.limit] The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages.
|
|
175
|
+
* @param {Boolean} [searchOptions.fromMe] Return only messages from the bot number or vise versa. To get all messages, leave the option undefined.
|
|
175
176
|
* @returns {Promise<Array<Message>>}
|
|
176
177
|
*/
|
|
177
178
|
async fetchMessages(searchOptions) {
|
|
178
179
|
let messages = await this.client.pupPage.evaluate(async (chatId, searchOptions) => {
|
|
179
|
-
const msgFilter = m =>
|
|
180
|
+
const msgFilter = (m) => {
|
|
181
|
+
if (m.isNotification) {
|
|
182
|
+
return false; // dont include notification messages
|
|
183
|
+
}
|
|
184
|
+
if (searchOptions && searchOptions.fromMe && m.id.fromMe !== searchOptions.fromMe) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
};
|
|
180
189
|
|
|
181
190
|
const chat = window.Store.Chat.get(chatId);
|
|
182
191
|
let msgs = chat.msgs.getModelsArray().filter(msgFilter);
|
|
@@ -342,6 +342,8 @@ class Message extends Base {
|
|
|
342
342
|
*/
|
|
343
343
|
async react(reaction){
|
|
344
344
|
await this.client.pupPage.evaluate(async (messageId, reaction) => {
|
|
345
|
+
if (!messageId) { return undefined; }
|
|
346
|
+
|
|
345
347
|
const msg = await window.Store.Msg.get(messageId);
|
|
346
348
|
await window.Store.sendReactionToMsg(msg, reaction);
|
|
347
349
|
}, this.id._serialized, reaction);
|
|
@@ -356,7 +358,7 @@ class Message extends Base {
|
|
|
356
358
|
}
|
|
357
359
|
|
|
358
360
|
/**
|
|
359
|
-
* Forwards this message to another chat
|
|
361
|
+
* Forwards this message to another chat (that you chatted before, otherwise it will fail)
|
|
360
362
|
*
|
|
361
363
|
* @param {string|Chat} chat Chat model or chat ID to which the message will be forwarded
|
|
362
364
|
* @returns {Promise}
|
|
@@ -408,12 +410,13 @@ class Message extends Base {
|
|
|
408
410
|
signal: (new AbortController).signal
|
|
409
411
|
});
|
|
410
412
|
|
|
411
|
-
const data = window.WWebJS.
|
|
413
|
+
const data = await window.WWebJS.arrayBufferToBase64Async(decryptedMedia);
|
|
412
414
|
|
|
413
415
|
return {
|
|
414
416
|
data,
|
|
415
417
|
mimetype: msg.mimetype,
|
|
416
|
-
filename: msg.filename
|
|
418
|
+
filename: msg.filename,
|
|
419
|
+
filesize: msg.size
|
|
417
420
|
};
|
|
418
421
|
} catch (e) {
|
|
419
422
|
if(e.status && e.status === 404) return undefined;
|
|
@@ -422,7 +425,7 @@ class Message extends Base {
|
|
|
422
425
|
}, this.id._serialized);
|
|
423
426
|
|
|
424
427
|
if (!result) return undefined;
|
|
425
|
-
return new MessageMedia(result.mimetype, result.data, result.filename);
|
|
428
|
+
return new MessageMedia(result.mimetype, result.data, result.filename, result.filesize);
|
|
426
429
|
}
|
|
427
430
|
|
|
428
431
|
/**
|
|
@@ -449,7 +452,7 @@ class Message extends Base {
|
|
|
449
452
|
let msg = window.Store.Msg.get(msgId);
|
|
450
453
|
|
|
451
454
|
if (msg.canStar()) {
|
|
452
|
-
return
|
|
455
|
+
return window.Store.Cmd.sendStarMsgs(msg.chat, [msg], false);
|
|
453
456
|
}
|
|
454
457
|
}, this.id._serialized);
|
|
455
458
|
}
|
|
@@ -462,7 +465,7 @@ class Message extends Base {
|
|
|
462
465
|
let msg = window.Store.Msg.get(msgId);
|
|
463
466
|
|
|
464
467
|
if (msg.canStar()) {
|
|
465
|
-
return msg.chat
|
|
468
|
+
return window.Store.Cmd.sendUnstarMsgs(msg.chat, [msg], false);
|
|
466
469
|
}
|
|
467
470
|
}, this.id._serialized);
|
|
468
471
|
}
|
|
@@ -10,10 +10,11 @@ const { URL } = require('url');
|
|
|
10
10
|
* Media attached to a message
|
|
11
11
|
* @param {string} mimetype MIME type of the attachment
|
|
12
12
|
* @param {string} data Base64-encoded data of the file
|
|
13
|
-
* @param {?string} filename Document file name
|
|
13
|
+
* @param {?string} filename Document file name. Value can be null
|
|
14
|
+
* @param {?number} filesize Document file size in bytes. Value can be null
|
|
14
15
|
*/
|
|
15
16
|
class MessageMedia {
|
|
16
|
-
constructor(mimetype, data, filename) {
|
|
17
|
+
constructor(mimetype, data, filename, filesize) {
|
|
17
18
|
/**
|
|
18
19
|
* MIME type of the attachment
|
|
19
20
|
* @type {string}
|
|
@@ -27,10 +28,16 @@ class MessageMedia {
|
|
|
27
28
|
this.data = data;
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
+
* Document file name. Value can be null
|
|
31
32
|
* @type {?string}
|
|
32
33
|
*/
|
|
33
34
|
this.filename = filename;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Document file size in bytes. Value can be null
|
|
38
|
+
* @type {?number}
|
|
39
|
+
*/
|
|
40
|
+
this.filesize = filesize;
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/**
|
|
@@ -68,6 +75,7 @@ class MessageMedia {
|
|
|
68
75
|
const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options);
|
|
69
76
|
const response = await fetch(url, reqOptions);
|
|
70
77
|
const mime = response.headers.get('Content-Type');
|
|
78
|
+
const size = response.headers.get('Content-Length');
|
|
71
79
|
|
|
72
80
|
const contentDisposition = response.headers.get('Content-Disposition');
|
|
73
81
|
const name = contentDisposition ? contentDisposition.match(/((?<=filename=")(.*)(?="))/) : null;
|
|
@@ -83,7 +91,7 @@ class MessageMedia {
|
|
|
83
91
|
data = btoa(data);
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
return { data, mime, name };
|
|
94
|
+
return { data, mime, name, size };
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
const res = options.client
|
|
@@ -96,7 +104,7 @@ class MessageMedia {
|
|
|
96
104
|
if (!mimetype)
|
|
97
105
|
mimetype = res.mime;
|
|
98
106
|
|
|
99
|
-
return new MessageMedia(mimetype, res.data, filename);
|
|
107
|
+
return new MessageMedia(mimetype, res.data, filename, res.size || null);
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Base = require('./Base');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a Reaction on WhatsApp
|
|
7
|
+
* @extends {Base}
|
|
8
|
+
*/
|
|
9
|
+
class Reaction extends Base {
|
|
10
|
+
constructor(client, data) {
|
|
11
|
+
super(client);
|
|
12
|
+
|
|
13
|
+
if (data) this._patch(data);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_patch(data) {
|
|
17
|
+
/**
|
|
18
|
+
* Reaction ID
|
|
19
|
+
* @type {object}
|
|
20
|
+
*/
|
|
21
|
+
this.id = data.msgKey;
|
|
22
|
+
/**
|
|
23
|
+
* Orphan
|
|
24
|
+
* @type {number}
|
|
25
|
+
*/
|
|
26
|
+
this.orphan = data.orphan;
|
|
27
|
+
/**
|
|
28
|
+
* Orphan reason
|
|
29
|
+
* @type {?string}
|
|
30
|
+
*/
|
|
31
|
+
this.orphanReason = data.orphanReason;
|
|
32
|
+
/**
|
|
33
|
+
* Unix timestamp for when the reaction was created
|
|
34
|
+
* @type {number}
|
|
35
|
+
*/
|
|
36
|
+
this.timestamp = data.timestamp;
|
|
37
|
+
/**
|
|
38
|
+
* Reaction
|
|
39
|
+
* @type {string}
|
|
40
|
+
*/
|
|
41
|
+
this.reaction = data.reactionText;
|
|
42
|
+
/**
|
|
43
|
+
* Read
|
|
44
|
+
* @type {boolean}
|
|
45
|
+
*/
|
|
46
|
+
this.read = data.read;
|
|
47
|
+
/**
|
|
48
|
+
* Message ID
|
|
49
|
+
* @type {object}
|
|
50
|
+
*/
|
|
51
|
+
this.msgId = data.parentMsgKey;
|
|
52
|
+
/**
|
|
53
|
+
* Sender ID
|
|
54
|
+
* @type {string}
|
|
55
|
+
*/
|
|
56
|
+
this.senderId = data.senderUserJid;
|
|
57
|
+
/**
|
|
58
|
+
* ACK
|
|
59
|
+
* @type {?number}
|
|
60
|
+
*/
|
|
61
|
+
this.ack = data.ack;
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
return super._patch(data);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = Reaction;
|
package/src/structures/index.js
CHANGED
package/src/util/Constants.js
CHANGED
|
@@ -11,7 +11,7 @@ exports.DefaultOptions = {
|
|
|
11
11
|
qrMaxRetries: 0,
|
|
12
12
|
takeoverOnConflict: false,
|
|
13
13
|
takeoverTimeoutMs: 0,
|
|
14
|
-
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
14
|
+
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36',
|
|
15
15
|
ffmpegPath: 'ffmpeg',
|
|
16
16
|
bypassCSP: false
|
|
17
17
|
};
|
|
@@ -41,15 +41,18 @@ exports.Events = {
|
|
|
41
41
|
MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone',
|
|
42
42
|
MESSAGE_REVOKED_ME: 'message_revoke_me',
|
|
43
43
|
MESSAGE_ACK: 'message_ack',
|
|
44
|
+
MESSAGE_REACTION: 'message_reaction',
|
|
44
45
|
MEDIA_UPLOADED: 'media_uploaded',
|
|
45
46
|
GROUP_JOIN: 'group_join',
|
|
46
47
|
GROUP_LEAVE: 'group_leave',
|
|
47
48
|
GROUP_UPDATE: 'group_update',
|
|
48
49
|
QR_RECEIVED: 'qr',
|
|
50
|
+
LOADING_SCREEN: 'loading_screen',
|
|
49
51
|
DISCONNECTED: 'disconnected',
|
|
50
52
|
STATE_CHANGED: 'change_state',
|
|
51
53
|
BATTERY_CHANGED: 'change_battery',
|
|
52
|
-
INCOMING_CALL: 'incoming_call'
|
|
54
|
+
INCOMING_CALL: 'incoming_call',
|
|
55
|
+
REMOTE_SESSION_SAVED: 'remote_session_saved'
|
|
53
56
|
};
|
|
54
57
|
|
|
55
58
|
/**
|
package/src/util/Injected.js
CHANGED
|
@@ -50,6 +50,7 @@ exports.ExposeStore = (moduleRaidStr) => {
|
|
|
50
50
|
window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0];
|
|
51
51
|
window.Store.ConversationMsgs = window.mR.findModule('loadEarlierMsgs')[0];
|
|
52
52
|
window.Store.sendReactionToMsg = window.mR.findModule('sendReactionToMsg')[0].sendReactionToMsg;
|
|
53
|
+
window.Store.createOrUpdateReactionsModule = window.mR.findModule('createOrUpdateReactions')[0];
|
|
53
54
|
window.Store.StickerTools = {
|
|
54
55
|
...window.mR.findModule('toWebpSticker')[0],
|
|
55
56
|
...window.mR.findModule('addWebpMetadata')[0]
|
|
@@ -479,6 +480,20 @@ exports.LoadUtils = () => {
|
|
|
479
480
|
return window.btoa(binary);
|
|
480
481
|
};
|
|
481
482
|
|
|
483
|
+
window.WWebJS.arrayBufferToBase64Async = (arrayBuffer) =>
|
|
484
|
+
new Promise((resolve, reject) => {
|
|
485
|
+
const blob = new Blob([arrayBuffer], {
|
|
486
|
+
type: 'application/octet-stream',
|
|
487
|
+
});
|
|
488
|
+
const fileReader = new FileReader();
|
|
489
|
+
fileReader.onload = () => {
|
|
490
|
+
const [, data] = fileReader.result.split(',');
|
|
491
|
+
resolve(data);
|
|
492
|
+
};
|
|
493
|
+
fileReader.onerror = (e) => reject(e);
|
|
494
|
+
fileReader.readAsDataURL(blob);
|
|
495
|
+
});
|
|
496
|
+
|
|
482
497
|
window.WWebJS.getFileHash = async (data) => {
|
|
483
498
|
let buffer = await data.arrayBuffer();
|
|
484
499
|
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
|