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
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Base class which all authentication strategies extend
5
+ */
6
+ class BaseAuthStrategy {
7
+ constructor() {}
8
+ setup(client) {
9
+ this.client = client;
10
+ }
11
+ async beforeBrowserInitialized() {}
12
+ async afterBrowserInitialized() {}
13
+ async onAuthenticationNeeded() {
14
+ return {
15
+ failed: false,
16
+ restart: false,
17
+ failureEventPayload: undefined
18
+ };
19
+ }
20
+ async getAuthEventPayload() {}
21
+ async afterAuthReady() {}
22
+ async disconnect() {}
23
+ async destroy() {}
24
+ async logout() {}
25
+ }
26
+
27
+ module.exports = BaseAuthStrategy;
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const BaseAuthStrategy = require('./BaseAuthStrategy');
6
+
7
+ /**
8
+ * Local directory-based authentication
9
+ * @param {object} options - options
10
+ * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance
11
+ * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/"
12
+ */
13
+ class LocalAuth extends BaseAuthStrategy {
14
+ constructor({ clientId, dataPath }={}) {
15
+ super();
16
+
17
+ const idRegex = /^[-_\w]+$/i;
18
+ if(clientId && !idRegex.test(clientId)) {
19
+ throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.');
20
+ }
21
+
22
+ this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
23
+ this.clientId = clientId;
24
+ }
25
+
26
+ async beforeBrowserInitialized() {
27
+ const puppeteerOpts = this.client.options.puppeteer;
28
+ const sessionDirName = this.clientId ? `session-${this.clientId}` : 'session';
29
+ const dirPath = path.join(this.dataPath, sessionDirName);
30
+
31
+ if(puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) {
32
+ throw new Error('LocalAuth is not compatible with a user-supplied userDataDir.');
33
+ }
34
+
35
+ fs.mkdirSync(dirPath, { recursive: true });
36
+
37
+ this.client.options.puppeteer = {
38
+ ...puppeteerOpts,
39
+ userDataDir: dirPath
40
+ };
41
+
42
+ this.userDataDir = dirPath;
43
+ }
44
+
45
+ async logout() {
46
+ if (this.userDataDir) {
47
+ await fs.promises.rm(this.userDataDir, { recursive: true, force: true })
48
+ .catch((e) => {
49
+ throw new Error(e);
50
+ });
51
+ }
52
+ }
53
+
54
+ }
55
+
56
+ module.exports = LocalAuth;
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ const BaseAuthStrategy = require('./BaseAuthStrategy');
4
+
5
+ /**
6
+ * No session restoring functionality
7
+ * Will need to authenticate via QR code every time
8
+ */
9
+ class NoAuth extends BaseAuthStrategy { }
10
+
11
+
12
+ module.exports = NoAuth;
@@ -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_${this.clientId}`;
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
+ }).catch(() => {});
182
+ } else {
183
+ await fs.promises.unlink(dirElement).catch(() => {});
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;
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const PrivateChat = require('../structures/PrivateChat');
4
+ const GroupChat = require('../structures/GroupChat');
5
+
6
+ class ChatFactory {
7
+ static create(client, data) {
8
+ if(data.isGroup) {
9
+ return new GroupChat(client, data);
10
+ }
11
+
12
+ return new PrivateChat(client, data);
13
+ }
14
+ }
15
+
16
+ module.exports = ChatFactory;
@@ -0,0 +1,16 @@
1
+ 'use strict';
2
+
3
+ const PrivateContact = require('../structures/PrivateContact');
4
+ const BusinessContact = require('../structures/BusinessContact');
5
+
6
+ class ContactFactory {
7
+ static create(client, data) {
8
+ if(data.isBusiness) {
9
+ return new BusinessContact(client, data);
10
+ }
11
+
12
+ return new PrivateContact(client, data);
13
+ }
14
+ }
15
+
16
+ module.exports = ContactFactory;
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Represents a WhatsApp data structure
5
+ */
6
+ class Base {
7
+ constructor(client) {
8
+ /**
9
+ * The client that instantiated this
10
+ * @readonly
11
+ */
12
+ Object.defineProperty(this, 'client', { value: client });
13
+ }
14
+
15
+ _clone() {
16
+ return Object.assign(Object.create(this), this);
17
+ }
18
+
19
+ _patch(data) { return data; }
20
+ }
21
+
22
+ module.exports = Base;
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const Contact = require('./Contact');
4
+
5
+ /**
6
+ * Represents a Business Contact on WhatsApp
7
+ * @extends {Contact}
8
+ */
9
+ class BusinessContact extends Contact {
10
+ _patch(data) {
11
+ /**
12
+ * The contact's business profile
13
+ */
14
+ this.businessProfile = data.businessProfile;
15
+
16
+ return super._patch(data);
17
+ }
18
+
19
+ }
20
+
21
+ module.exports = BusinessContact;
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const MessageMedia = require('./MessageMedia');
4
+ const Util = require('../util/Util');
5
+
6
+ /**
7
+ * Button spec used in Buttons constructor
8
+ * @typedef {Object} ButtonSpec
9
+ * @property {string=} id - Custom ID to set on the button. A random one will be generated if one is not passed.
10
+ * @property {string} body - The text to show on the button.
11
+ */
12
+
13
+ /**
14
+ * @typedef {Object} FormattedButtonSpec
15
+ * @property {string} buttonId
16
+ * @property {number} type
17
+ * @property {Object} buttonText
18
+ */
19
+
20
+ /**
21
+ * Message type buttons
22
+ */
23
+ class Buttons {
24
+ /**
25
+ * @param {string|MessageMedia} body
26
+ * @param {ButtonSpec[]} buttons - See {@link ButtonSpec}
27
+ * @param {string?} title
28
+ * @param {string?} footer
29
+ */
30
+ constructor(body, buttons, title, footer) {
31
+ /**
32
+ * Message body
33
+ * @type {string|MessageMedia}
34
+ */
35
+ this.body = body;
36
+
37
+ /**
38
+ * title of message
39
+ * @type {string}
40
+ */
41
+ this.title = title;
42
+
43
+ /**
44
+ * footer of message
45
+ * @type {string}
46
+ */
47
+ this.footer = footer;
48
+
49
+ if (body instanceof MessageMedia) {
50
+ this.type = 'media';
51
+ this.title = '';
52
+ }else{
53
+ this.type = 'chat';
54
+ }
55
+
56
+ /**
57
+ * buttons of message
58
+ * @type {FormattedButtonSpec[]}
59
+ */
60
+ this.buttons = this._format(buttons);
61
+ if(!this.buttons.length){ throw '[BT01] No buttons';}
62
+
63
+ }
64
+
65
+ /**
66
+ * Creates button array from simple array
67
+ * @param {ButtonSpec[]} buttons
68
+ * @returns {FormattedButtonSpec[]}
69
+ * @example
70
+ * Input: [{id:'customId',body:'button1'},{body:'button2'},{body:'button3'},{body:'button4'}]
71
+ * Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}]
72
+ */
73
+ _format(buttons){
74
+ buttons = buttons.slice(0,3); // phone users can only see 3 buttons, so lets limit this
75
+ return buttons.map((btn) => {
76
+ return {'buttonId':btn.id ? String(btn.id) : Util.generateHash(6),'buttonText':{'displayText':btn.body},'type':1};
77
+ });
78
+ }
79
+
80
+ }
81
+
82
+ module.exports = Buttons;
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const Base = require('./Base');
4
+
5
+ /**
6
+ * Represents a Call on WhatsApp
7
+ * @extends {Base}
8
+ */
9
+ class Call extends Base {
10
+ constructor(client, data) {
11
+ super(client);
12
+
13
+ if (data) this._patch(data);
14
+ }
15
+
16
+ _patch(data) {
17
+ /**
18
+ * Call ID
19
+ * @type {string}
20
+ */
21
+ this.id = data.id;
22
+ /**
23
+ * From
24
+ * @type {string}
25
+ */
26
+ this.from = data.peerJid;
27
+ /**
28
+ * Unix timestamp for when the call was created
29
+ * @type {number}
30
+ */
31
+ this.timestamp = data.offerTime;
32
+ /**
33
+ * Is video
34
+ * @type {boolean}
35
+ */
36
+ this.isVideo = data.isVideo;
37
+ /**
38
+ * Is Group
39
+ * @type {boolean}
40
+ */
41
+ this.isGroup = data.isGroup;
42
+ /**
43
+ * Indicates if the call was sent by the current user
44
+ * @type {boolean}
45
+ */
46
+ this.fromMe = data.outgoing;
47
+ /**
48
+ * Indicates if the call can be handled in waweb
49
+ * @type {boolean}
50
+ */
51
+ this.canHandleLocally = data.canHandleLocally;
52
+ /**
53
+ * Indicates if the call Should be handled in waweb
54
+ * @type {boolean}
55
+ */
56
+ this.webClientShouldHandle = data.webClientShouldHandle;
57
+ /**
58
+ * Object with participants
59
+ * @type {object}
60
+ */
61
+ this.participants = data.participants;
62
+
63
+ return super._patch(data);
64
+ }
65
+
66
+ /**
67
+ * Reject the call
68
+ */
69
+ async reject() {
70
+ return this.client.pupPage.evaluate((peerJid, id) => {
71
+ return window.WWebJS.rejectCall(peerJid, id);
72
+ }, this.from, this.id);
73
+ }
74
+ }
75
+
76
+ module.exports = Call;