whatsapp-web.js 1.15.2 → 1.15.6

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.
@@ -0,0 +1,133 @@
1
+
2
+ # Contributor Covenant Code of Conduct
3
+
4
+ ## Our Pledge
5
+
6
+ We as members, contributors, and leaders pledge to make participation in our
7
+ community a harassment-free experience for everyone, regardless of age, body
8
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
9
+ identity and expression, level of experience, education, socio-economic status,
10
+ nationality, personal appearance, race, religion, or sexual identity
11
+ and orientation.
12
+
13
+ We pledge to act and interact in ways that contribute to an open, welcoming,
14
+ diverse, inclusive, and healthy community.
15
+
16
+ ## Our Standards
17
+
18
+ Examples of behavior that contributes to a positive environment for our
19
+ community include:
20
+
21
+ * Demonstrating empathy and kindness toward other people
22
+ * Being respectful of differing opinions, viewpoints, and experiences
23
+ * Giving and gracefully accepting constructive feedback
24
+ * Accepting responsibility and apologizing to those affected by our mistakes,
25
+ and learning from the experience
26
+ * Focusing on what is best not just for us as individuals, but for the
27
+ overall community
28
+
29
+ Examples of unacceptable behavior include:
30
+
31
+ * The use of sexualized language or imagery, and sexual attention or
32
+ advances of any kind
33
+ * Trolling, insulting or derogatory comments, and personal or political attacks
34
+ * Public or private harassment
35
+ * Publishing others' private information, such as a physical or email
36
+ address, without their explicit permission
37
+ * Other conduct which could reasonably be considered inappropriate in a
38
+ professional setting
39
+
40
+ ## Enforcement Responsibilities
41
+
42
+ Community leaders are responsible for clarifying and enforcing our standards of
43
+ acceptable behavior and will take appropriate and fair corrective action in
44
+ response to any behavior that they deem inappropriate, threatening, offensive,
45
+ or harmful.
46
+
47
+ Community leaders have the right and responsibility to remove, edit, or reject
48
+ comments, commits, code, wiki edits, issues, and other contributions that are
49
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
50
+ decisions when appropriate.
51
+
52
+ ## Scope
53
+
54
+ This Code of Conduct applies within all community spaces, and also applies when
55
+ an individual is officially representing the community in public spaces.
56
+ Examples of representing our community include using an official e-mail address,
57
+ posting via an official social media account, or acting as an appointed
58
+ representative at an online or offline event.
59
+
60
+ ## Enforcement
61
+
62
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
63
+ reported to the community leaders responsible for enforcement at
64
+ [pedroslopez@me.com](mailto:pedroslopez@me.com).
65
+ All complaints will be reviewed and investigated promptly and fairly.
66
+
67
+ All community leaders are obligated to respect the privacy and security of the
68
+ reporter of any incident.
69
+
70
+ ## Enforcement Guidelines
71
+
72
+ Community leaders will follow these Community Impact Guidelines in determining
73
+ the consequences for any action they deem in violation of this Code of Conduct:
74
+
75
+ ### 1. Correction
76
+
77
+ **Community Impact**: Use of inappropriate language or other behavior deemed
78
+ unprofessional or unwelcome in the community.
79
+
80
+ **Consequence**: A private, written warning from community leaders, providing
81
+ clarity around the nature of the violation and an explanation of why the
82
+ behavior was inappropriate. A public apology may be requested.
83
+
84
+ ### 2. Warning
85
+
86
+ **Community Impact**: A violation through a single incident or series
87
+ of actions.
88
+
89
+ **Consequence**: A warning with consequences for continued behavior. No
90
+ interaction with the people involved, including unsolicited interaction with
91
+ those enforcing the Code of Conduct, for a specified period of time. This
92
+ includes avoiding interactions in community spaces as well as external channels
93
+ like social media. Violating these terms may lead to a temporary or
94
+ permanent ban.
95
+
96
+ ### 3. Temporary Ban
97
+
98
+ **Community Impact**: A serious violation of community standards, including
99
+ sustained inappropriate behavior.
100
+
101
+ **Consequence**: A temporary ban from any sort of interaction or public
102
+ communication with the community for a specified period of time. No public or
103
+ private interaction with the people involved, including unsolicited interaction
104
+ with those enforcing the Code of Conduct, is allowed during this period.
105
+ Violating these terms may lead to a permanent ban.
106
+
107
+ ### 4. Permanent Ban
108
+
109
+ **Community Impact**: Demonstrating a pattern of violation of community
110
+ standards, including sustained inappropriate behavior, harassment of an
111
+ individual, or aggression toward or disparagement of classes of individuals.
112
+
113
+ **Consequence**: A permanent ban from any sort of public interaction within
114
+ the community.
115
+
116
+ ## Attribution
117
+
118
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119
+ version 2.0, available at
120
+ [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
121
+
122
+ Community Impact Guidelines were inspired by
123
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124
+
125
+ For answers to common questions about this code of conduct, see the FAQ at
126
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available
127
+ at [https://www.contributor-covenant.org/translations][translations].
128
+
129
+ [homepage]: https://www.contributor-covenant.org
130
+ [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
131
+ [Mozilla CoC]: https://github.com/mozilla/diversity
132
+ [FAQ]: https://www.contributor-covenant.org/faq
133
+ [translations]: https://www.contributor-covenant.org/translations
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![npm](https://img.shields.io/npm/v/whatsapp-web.js.svg)](https://www.npmjs.com/package/whatsapp-web.js) [![Depfu](https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg)](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765) ![WhatsApp_Web 2.2146.9](https://img.shields.io/badge/WhatsApp_Web-2.2146.9-brightgreen.svg) [![Discord Chat](https://img.shields.io/discord/698610475432411196.svg?logo=discord)](https://discord.gg/H7DqQs4)
1
+ [![npm](https://img.shields.io/npm/v/whatsapp-web.js.svg)](https://www.npmjs.com/package/whatsapp-web.js) [![Depfu](https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg)](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765) ![WhatsApp_Web 2.2202.12](https://img.shields.io/badge/WhatsApp_Web-2.2202.12-brightgreen.svg) [![Discord Chat](https://img.shields.io/discord/698610475432411196.svg?logo=discord)](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
package/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { EventEmitter } from 'events'
3
3
  import { RequestInit } from 'node-fetch'
4
- import puppeteer = require('puppeteer')
4
+ import puppeteer from 'puppeteer'
5
5
 
6
6
  declare namespace WAWebJS {
7
7
 
@@ -166,7 +166,7 @@ declare namespace WAWebJS {
166
166
  /** Emitted when the client has been disconnected */
167
167
  on(event: 'disconnected', listener: (
168
168
  /** reason that caused the disconnect */
169
- reason: WAState | "NAVIGATED"
169
+ reason: WAState | "NAVIGATION"
170
170
  ) => void): this
171
171
 
172
172
  /** Emitted when a user joins the chat via invite link or is added by an admin */
@@ -443,6 +443,26 @@ declare namespace WAWebJS {
443
443
  PAYMENT = 'payment',
444
444
  UNKNOWN = 'unknown',
445
445
  GROUP_INVITE = 'groups_v4_invite',
446
+ LIST = 'list',
447
+ LIST_RESPONSE = 'list_response',
448
+ BUTTONS_RESPONSE = 'buttons_response',
449
+ PAYMENT = 'payment',
450
+ BROADCAST_NOTIFICATION = 'broadcast_notification',
451
+ CALL_LOG = 'call_log',
452
+ CIPHERTEXT = 'ciphertext',
453
+ DEBUG = 'debug',
454
+ E2E_NOTIFICATION = 'e2e_notification',
455
+ GP2 = 'gp2',
456
+ GROUP_NOTIFICATION = 'group_notification',
457
+ HSM = 'hsm',
458
+ INTERACTIVE = 'interactive',
459
+ NATIVE_FLOW = 'native_flow',
460
+ NOTIFICATION = 'notification',
461
+ NOTIFICATION_TEMPLATE = 'notification_template',
462
+ OVERSIZED = 'oversized',
463
+ PROTOCOL = 'protocol',
464
+ REACTION = 'reaction',
465
+ TEMPLATE_BUTTON_REPLY = 'template_button_reply',
446
466
  }
447
467
 
448
468
  /** Client status */
@@ -525,6 +545,10 @@ declare namespace WAWebJS {
525
545
  broadcast: boolean,
526
546
  /** Indicates if the message was a status update */
527
547
  isStatus: boolean,
548
+ /** Indicates if the message is a Gif */
549
+ isGif: boolean,
550
+ /** Indicates if the message will disappear after it expires */
551
+ isEphemeral: boolean,
528
552
  /** ID for the Chat that this message was sent to, except if the message was sent by the current user */
529
553
  from: string,
530
554
  /** Indicates if the message was sent by the current user */
@@ -690,6 +714,7 @@ declare namespace WAWebJS {
690
714
 
691
715
  export interface MediaFromURLOptions {
692
716
  client?: Client
717
+ filename?: string
693
718
  unsafeMime?: boolean
694
719
  reqOptions?: RequestInit
695
720
  }
@@ -907,10 +932,9 @@ declare namespace WAWebJS {
907
932
 
908
933
  export interface MessageSearchOptions {
909
934
  /**
910
- * The amount of messages to return.
935
+ * The amount of messages to return. If no limit is specified, the available messages will be returned.
911
936
  * Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation.
912
937
  * Set this to Infinity to load all messages.
913
- * @default 50
914
938
  */
915
939
  limit?: number
916
940
  }
@@ -1189,11 +1213,11 @@ declare namespace WAWebJS {
1189
1213
  /** Message type buttons */
1190
1214
  export class Buttons {
1191
1215
  body: string | MessageMedia
1192
- buttons: Array<Array<string>>
1216
+ buttons: Array<{ buttonId: string; buttonText: {displayText: string}; type: number }>
1193
1217
  title?: string | null
1194
1218
  footer?: string | null
1195
1219
 
1196
- constructor(body: string, buttons: Array<Array<string>>, title?: string | null, footer?: string | null)
1220
+ constructor(body: string, buttons: Array<{ id?: string; body: string }>, title?: string | null, footer?: string | null)
1197
1221
  }
1198
1222
  }
1199
1223
 
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "whatsapp-web.js",
3
- "version": "1.15.2",
3
+ "version": "1.15.6",
4
4
  "description": "Library for interacting with the WhatsApp Web API ",
5
5
  "main": "./index.js",
6
6
  "typings": "./index.d.ts",
7
7
  "scripts": {
8
- "test": "mocha tests",
8
+ "test": "mocha tests --recursive",
9
+ "test-single": "mocha",
9
10
  "shell": "node --experimental-repl-await ./shell.js",
10
11
  "generate-docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --verbose"
11
12
  },
@@ -31,21 +32,22 @@
31
32
  "@pedroslopez/moduleraid": "^5.0.2",
32
33
  "fluent-ffmpeg": "^2.1.2",
33
34
  "jsqr": "^1.3.1",
34
- "mime": "^2.4.5",
35
+ "mime": "^3.0.0",
36
+ "node-fetch": "^2.6.5",
35
37
  "node-webpmux": "^3.1.0",
36
- "puppeteer": "^10.1.0",
38
+ "puppeteer": "^13.0.0",
37
39
  "sharp": "^0.28.3"
38
40
  },
39
41
  "devDependencies": {
40
- "@types/node-fetch": "^2.5.11",
42
+ "@types/node-fetch": "^2.5.12",
41
43
  "chai": "^4.3.4",
42
- "dotenv": "^10.0.0",
43
- "eslint": "^7.27.0",
44
- "eslint-plugin-mocha": "^9.0.0",
44
+ "dotenv": "^16.0.0",
45
+ "eslint": "^8.4.1",
46
+ "eslint-plugin-mocha": "^10.0.3",
45
47
  "jsdoc": "^3.6.4",
46
48
  "jsdoc-baseline": "^0.1.5",
47
49
  "mocha": "^9.0.2",
48
- "sinon": "^11.1.1"
50
+ "sinon": "^13.0.1"
49
51
  },
50
52
  "engines": {
51
53
  "node": ">=12.0.0"
package/src/Client.js CHANGED
@@ -74,21 +74,28 @@ class Client extends EventEmitter {
74
74
  } else {
75
75
  browser = await puppeteer.launch(this.options.puppeteer);
76
76
  page = (await browser.pages())[0];
77
- }
77
+ }
78
78
 
79
- page.setUserAgent(this.options.userAgent);
79
+ await page.setUserAgent(this.options.userAgent);
80
80
 
81
81
  this.pupBrowser = browser;
82
82
  this.pupPage = page;
83
83
 
84
+ // remember me
85
+ await page.evaluateOnNewDocument(() => {
86
+ localStorage.setItem('remember-me', 'true');
87
+ });
88
+
84
89
  if (this.options.session) {
85
90
  await page.evaluateOnNewDocument(
86
91
  session => {
87
- localStorage.clear();
88
- localStorage.setItem('WABrowserId', session.WABrowserId);
89
- localStorage.setItem('WASecretBundle', session.WASecretBundle);
90
- localStorage.setItem('WAToken1', session.WAToken1);
91
- localStorage.setItem('WAToken2', session.WAToken2);
92
+ if(document.referrer === 'https://whatsapp.com/') {
93
+ localStorage.clear();
94
+ localStorage.setItem('WABrowserId', session.WABrowserId);
95
+ localStorage.setItem('WASecretBundle', session.WASecretBundle);
96
+ localStorage.setItem('WAToken1', session.WAToken1);
97
+ localStorage.setItem('WAToken2', session.WAToken2);
98
+ }
92
99
  }, this.options.session);
93
100
  }
94
101
 
@@ -99,12 +106,13 @@ class Client extends EventEmitter {
99
106
  await page.goto(WhatsWebURL, {
100
107
  waitUntil: 'load',
101
108
  timeout: 0,
109
+ referer: 'https://whatsapp.com/'
102
110
  });
103
111
 
104
112
  const KEEP_PHONE_CONNECTED_IMG_SELECTOR = '[data-icon="intro-md-beta-logo-dark"], [data-icon="intro-md-beta-logo-light"], [data-asset-intro-image-light="true"], [data-asset-intro-image-dark="true"]';
105
113
 
106
114
  if (this.options.session) {
107
- // Check if session restore was successfull
115
+ // Check if session restore was successful
108
116
  try {
109
117
  await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: this.options.authTimeoutMs });
110
118
  } catch (err) {
@@ -163,14 +171,26 @@ class Client extends EventEmitter {
163
171
  this._qrRefreshInterval = setInterval(getQrCode, this.options.qrRefreshIntervalMs);
164
172
 
165
173
  // Wait for code scan
166
- await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: 0 });
167
- clearInterval(this._qrRefreshInterval);
168
- this._qrRefreshInterval = undefined;
174
+ try {
175
+ await page.waitForSelector(KEEP_PHONE_CONNECTED_IMG_SELECTOR, { timeout: 0 });
176
+ clearInterval(this._qrRefreshInterval);
177
+ this._qrRefreshInterval = undefined;
178
+ } catch(error) {
179
+ if (
180
+ error.name === 'ProtocolError' &&
181
+ error.message &&
182
+ error.message.match(/Target closed/)
183
+ ) {
184
+ // something has called .destroy() while waiting
185
+ return;
186
+ }
169
187
 
188
+ throw error;
189
+ }
170
190
  }
171
191
 
172
192
  await page.evaluate(ExposeStore, moduleRaid.toString());
173
-
193
+
174
194
  // Get session tokens
175
195
  const localStorage = JSON.parse(await page.evaluate(() => {
176
196
  return JSON.stringify(window.localStorage);
@@ -222,8 +242,6 @@ class Client extends EventEmitter {
222
242
 
223
243
  // Register events
224
244
  await page.exposeFunction('onAddMessageEvent', msg => {
225
- if (!msg.isNewMsg) return;
226
-
227
245
  if (msg.type === 'gp2') {
228
246
  const notification = new GroupNotification(this, msg);
229
247
  if (msg.subtype === 'add' || msg.subtype === 'invite') {
@@ -408,7 +426,6 @@ class Client extends EventEmitter {
408
426
  });
409
427
 
410
428
  await page.evaluate(() => {
411
- window.Store.Msg.on('add', (msg) => { if (msg.isNewMsg) window.onAddMessageEvent(window.WWebJS.getMessageModel(msg)); });
412
429
  window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
413
430
  window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
414
431
  window.Store.Msg.on('change:ack', (msg,ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); });
@@ -417,6 +434,16 @@ class Client extends EventEmitter {
417
434
  window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); });
418
435
  window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); });
419
436
  window.Store.Call.on('add', (call) => { window.onIncomingCall(call); });
437
+ window.Store.Msg.on('add', (msg) => {
438
+ if (msg.isNewMsg) {
439
+ if(msg.type === 'ciphertext') {
440
+ // defer message event until ciphertext is resolved (type changed)
441
+ msg.once('change:type', (_msg) => window.onAddMessageEvent(window.WWebJS.getMessageModel(_msg)));
442
+ } else {
443
+ window.onAddMessageEvent(window.WWebJS.getMessageModel(msg));
444
+ }
445
+ }
446
+ });
420
447
  });
421
448
 
422
449
  /**
@@ -425,11 +452,13 @@ class Client extends EventEmitter {
425
452
  */
426
453
  this.emit(Events.READY);
427
454
 
428
- // Disconnect when navigating away
429
- // Because WhatsApp Web now reloads when logging out from the device, this also covers that case
455
+ // Disconnect when navigating away when in PAIRING state (detect logout)
430
456
  this.pupPage.on('framenavigated', async () => {
431
- this.emit(Events.DISCONNECTED, 'NAVIGATION');
432
- await this.destroy();
457
+ const appState = await this.getState();
458
+ if(!appState || appState === WAState.PAIRING) {
459
+ this.emit(Events.DISCONNECTED, 'NAVIGATION');
460
+ await this.destroy();
461
+ }
433
462
  });
434
463
  }
435
464
 
@@ -514,7 +543,7 @@ class Client extends EventEmitter {
514
543
  quotedMessageId: options.quotedMessageId,
515
544
  parseVCards: options.parseVCards === false ? false : true,
516
545
  mentionedJidList: Array.isArray(options.mentions) ? options.mentions.map(contact => contact.id._serialized) : [],
517
- ...options.extra
546
+ extraOptions: options.extra
518
547
  };
519
548
 
520
549
  const sendSeen = typeof options.sendSeen === 'undefined' ? true : options.sendSeen;
@@ -701,6 +730,7 @@ class Client extends EventEmitter {
701
730
  */
702
731
  async getState() {
703
732
  return await this.pupPage.evaluate(() => {
733
+ if(!window.Store) return null;
704
734
  return window.Store.AppState.state;
705
735
  });
706
736
  }
@@ -722,7 +752,7 @@ class Client extends EventEmitter {
722
752
  return await this.pupPage.evaluate(async chatId => {
723
753
  let chat = await window.Store.Chat.get(chatId);
724
754
  await window.Store.Cmd.archiveChat(chat, true);
725
- return chat.archive;
755
+ return true;
726
756
  }, chatId);
727
757
  }
728
758
 
@@ -734,7 +764,7 @@ class Client extends EventEmitter {
734
764
  return await this.pupPage.evaluate(async chatId => {
735
765
  let chat = await window.Store.Chat.get(chatId);
736
766
  await window.Store.Cmd.archiveChat(chat, false);
737
- return chat.archive;
767
+ return false;
738
768
  }, chatId);
739
769
  }
740
770
 
@@ -3,13 +3,27 @@
3
3
  const MessageMedia = require('./MessageMedia');
4
4
  const Util = require('../util/Util');
5
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
+
6
20
  /**
7
21
  * Message type buttons
8
22
  */
9
23
  class Buttons {
10
24
  /**
11
25
  * @param {string|MessageMedia} body
12
- * @param {Array<Array<string>>} buttons
26
+ * @param {ButtonSpec[]} buttons - See {@link ButtonSpec}
13
27
  * @param {string?} title
14
28
  * @param {string?} footer
15
29
  */
@@ -41,7 +55,7 @@ class Buttons {
41
55
 
42
56
  /**
43
57
  * buttons of message
44
- * @type {Array<Array<string>>}
58
+ * @type {FormattedButtonSpec[]}
45
59
  */
46
60
  this.buttons = this._format(buttons);
47
61
  if(!this.buttons.length){ throw '[BT01] No buttons';}
@@ -50,8 +64,8 @@ class Buttons {
50
64
 
51
65
  /**
52
66
  * Creates button array from simple array
53
- * @param {Array<Array<string>>} buttons
54
- * @returns {Array<Array<string>>}
67
+ * @param {ButtonSpec[]} buttons
68
+ * @returns {FormattedButtonSpec[]}
55
69
  * @example
56
70
  * Input: [{id:'customId',body:'button1'},{body:'button2'},{body:'button3'},{body:'button4'}]
57
71
  * Returns: [{ buttonId:'customId',buttonText:{'displayText':'button1'},type: 1 },{buttonId:'n3XKsL',buttonText:{'displayText':'button2'},type:1},{buttonId:'NDJk0a',buttonText:{'displayText':'button3'},type:1}]
@@ -65,7 +65,7 @@ class Chat extends Base {
65
65
 
66
66
  /**
67
67
  * Indicates if the chat is muted or not
68
- * @type {number}
68
+ * @type {boolean}
69
69
  */
70
70
  this.isMuted = data.isMuted;
71
71
 
@@ -171,30 +171,32 @@ class Chat extends Base {
171
171
  /**
172
172
  * Loads chat messages, sorted from earliest to latest.
173
173
  * @param {Object} searchOptions Options for searching messages. Right now only limit is supported.
174
- * @param {Number} [searchOptions.limit=50] The amount of messages to return. 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.
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
175
  * @returns {Promise<Array<Message>>}
176
176
  */
177
177
  async fetchMessages(searchOptions) {
178
- if (!searchOptions || !searchOptions.limit) {
179
- searchOptions = { limit: 50 };
180
- }
181
- let messages = await this.client.pupPage.evaluate(async (chatId, limit) => {
178
+ let messages = await this.client.pupPage.evaluate(async (chatId, searchOptions) => {
182
179
  const msgFilter = m => !m.isNotification; // dont include notification messages
183
180
 
184
181
  const chat = window.Store.Chat.get(chatId);
185
182
  let msgs = chat.msgs.models.filter(msgFilter);
186
183
 
187
- while (msgs.length < limit) {
188
- const loadedMessages = await chat.loadEarlierMsgs();
189
- if (!loadedMessages) break;
190
- msgs = [...loadedMessages.filter(msgFilter), ...msgs];
184
+ if (searchOptions && searchOptions.limit > 0) {
185
+ while (msgs.length < searchOptions.limit) {
186
+ const loadedMessages = await chat.loadEarlierMsgs();
187
+ if (!loadedMessages) break;
188
+ msgs = [...loadedMessages.filter(msgFilter), ...msgs];
189
+ }
190
+
191
+ if (msgs.length > searchOptions.limit) {
192
+ msgs.sort((a, b) => (a.t > b.t) ? 1 : -1);
193
+ msgs = msgs.splice(msgs.length - searchOptions.limit);
194
+ }
191
195
  }
192
196
 
193
- msgs.sort((a, b) => (a.t > b.t) ? 1 : -1);
194
- if (msgs.length > limit) msgs = msgs.splice(msgs.length - limit);
195
197
  return msgs.map(m => window.WWebJS.getMessageModel(m));
196
198
 
197
- }, this.id._serialized, searchOptions.limit);
199
+ }, this.id._serialized, searchOptions);
198
200
 
199
201
  return messages.map(m => new Message(this.client, m));
200
202
  }
@@ -57,7 +57,7 @@ class List {
57
57
  */
58
58
  _format(sections){
59
59
  if(!sections.length){throw '[LT02] List without sections';}
60
- if(sections.length > 1){throw '[LT05] Lists with more than one section are having problems';}
60
+ if(sections.length > 1 && sections.filter(s => typeof s.title == 'undefined').length > 1){throw '[LT05] You can\'t have more than one empty title.';}
61
61
  return sections.map( (section) =>{
62
62
  if(!section.rows.length){throw '[LT03] Section without rows';}
63
63
  return {
@@ -76,4 +76,4 @@ class List {
76
76
 
77
77
  }
78
78
 
79
- module.exports = List;
79
+ module.exports = List;
@@ -179,6 +179,18 @@ class Message extends Base {
179
179
  */
180
180
  this.token = data.token ? data.token : undefined;
181
181
 
182
+ /**
183
+ * Indicates whether the message is a Gif
184
+ * @type {boolean}
185
+ */
186
+ this.isGif = Boolean(data.isGif);
187
+
188
+ /**
189
+ * Indicates if the message will disappear after it expires
190
+ * @type {boolean}
191
+ */
192
+ this.isEphemeral = data.isEphemeral;
193
+
182
194
  /** Title */
183
195
  if (data.title) {
184
196
  this.title = data.title;
@@ -376,7 +388,7 @@ class Message extends Base {
376
388
  await this.client.pupPage.evaluate((msgId, everyone) => {
377
389
  let msg = window.Store.Msg.get(msgId);
378
390
 
379
- if (everyone && msg.id.fromMe && msg.canRevoke()) {
391
+ if (everyone && msg.id.fromMe && msg._canRevoke()) {
380
392
  return window.Store.Cmd.sendRevokeMsgs(msg.chat, [msg], true);
381
393
  }
382
394
 
@@ -50,29 +50,29 @@ class MessageMedia {
50
50
  * Creates a MessageMedia instance from a URL
51
51
  * @param {string} url
52
52
  * @param {Object} [options]
53
- * @param {number} [options.unsafeMime=false]
53
+ * @param {boolean} [options.unsafeMime=false]
54
+ * @param {string} [options.filename]
54
55
  * @param {object} [options.client]
55
56
  * @param {object} [options.reqOptions]
56
57
  * @param {number} [options.reqOptions.size=0]
57
58
  * @returns {Promise<MessageMedia>}
58
59
  */
59
60
  static async fromUrl(url, options = {}) {
60
- let mimetype;
61
+ const pUrl = new URL(url);
62
+ let mimetype = mime.getType(pUrl.pathname);
61
63
 
62
- if (!options.unsafeMime) {
63
- const pUrl = new URL(url);
64
- mimetype = mime.getType(pUrl.pathname);
65
-
66
- if (!mimetype)
67
- throw new Error('Unable to determine MIME type');
68
- }
64
+ if (!mimetype && !options.unsafeMime)
65
+ throw new Error('Unable to determine MIME type using URL. Set unsafeMime to true to download it anyway.');
69
66
 
70
67
  async function fetchData (url, options) {
71
68
  const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options);
72
69
  const response = await fetch(url, reqOptions);
73
70
  const mime = response.headers.get('Content-Type');
74
- let data = '';
75
71
 
72
+ const contentDisposition = response.headers.get('Content-Disposition');
73
+ const name = contentDisposition ? contentDisposition.match(/((?<=filename=")(.*)(?="))/) : null;
74
+
75
+ let data = '';
76
76
  if (response.buffer) {
77
77
  data = (await response.buffer()).toString('base64');
78
78
  } else {
@@ -83,18 +83,21 @@ class MessageMedia {
83
83
  data = btoa(data);
84
84
  }
85
85
 
86
- return { data, mime };
86
+ return { data, mime, name };
87
87
  }
88
88
 
89
89
  const res = options.client
90
90
  ? (await options.client.pupPage.evaluate(fetchData, url, options.reqOptions))
91
91
  : (await fetchData(url, options.reqOptions));
92
92
 
93
+ const filename = options.filename ||
94
+ (res.name ? res.name[0] : (pUrl.pathname.split('/').pop() || 'file'));
95
+
93
96
  if (!mimetype)
94
97
  mimetype = res.mime;
95
98
 
96
- return new MessageMedia(mimetype, res.data, null);
99
+ return new MessageMedia(mimetype, res.data, filename);
97
100
  }
98
101
  }
99
102
 
100
- module.exports = MessageMedia;
103
+ module.exports = MessageMedia;
@@ -40,7 +40,6 @@ class Payment extends Base {
40
40
 
41
41
  /**
42
42
  * The paymentStatus
43
- * @type {number}
44
43
  *
45
44
  * Possible Status
46
45
  * 0:UNKNOWN_STATUS
@@ -55,6 +54,8 @@ class Payment extends Base {
55
54
  * 9:CANCELLED
56
55
  * 10:WAITING_FOR_PAYER
57
56
  * 11:WAITING
57
+ *
58
+ * @type {number}
58
59
  */
59
60
  this.paymentStatus = data.paymentStatus;
60
61
 
@@ -77,8 +77,25 @@ exports.MessageTypes = {
77
77
  UNKNOWN: 'unknown',
78
78
  GROUP_INVITE: 'groups_v4_invite',
79
79
  LIST: 'list',
80
+ LIST_RESPONSE: 'list_response',
80
81
  BUTTONS_RESPONSE: 'buttons_response',
81
- PAYMENT: 'payment'
82
+ PAYMENT: 'payment',
83
+ BROADCAST_NOTIFICATION: 'broadcast_notification',
84
+ CALL_LOG: 'call_log',
85
+ CIPHERTEXT: 'ciphertext',
86
+ DEBUG: 'debug',
87
+ E2E_NOTIFICATION: 'e2e_notification',
88
+ GP2: 'gp2',
89
+ GROUP_NOTIFICATION: 'group_notification',
90
+ HSM: 'hsm',
91
+ INTERACTIVE: 'interactive',
92
+ NATIVE_FLOW: 'native_flow',
93
+ NOTIFICATION: 'notification',
94
+ NOTIFICATION_TEMPLATE: 'notification_template',
95
+ OVERSIZED: 'oversized',
96
+ PROTOCOL: 'protocol',
97
+ REACTION: 'reaction',
98
+ TEMPLATE_BUTTON_REPLY: 'template_button_reply',
82
99
  };
83
100
 
84
101
  /**
@@ -5,11 +5,10 @@ exports.ExposeStore = (moduleRaidStr) => {
5
5
  eval('var moduleRaid = ' + moduleRaidStr);
6
6
  // eslint-disable-next-line no-undef
7
7
  window.mR = moduleRaid();
8
- window.Store = Object.assign({}, window.mR.findModule('Chat')[0].default);
8
+ window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default);
9
9
  window.Store.AppState = window.mR.findModule('STREAM')[0].Socket;
10
10
  window.Store.Conn = window.mR.findModule('Conn')[0].Conn;
11
- window.Store.CryptoLib = window.mR.findModule('decryptE2EMedia')[0];
12
- window.Store.Wap = window.mR.findModule('Wap')[0].default;
11
+ window.Store.Wap = window.mR.findModule('queryLinkPreview')[0].default;
13
12
  window.Store.SendSeen = window.mR.findModule('sendSeen')[0];
14
13
  window.Store.SendClear = window.mR.findModule('sendClear')[0];
15
14
  window.Store.SendDelete = window.mR.findModule('sendDelete')[0];
@@ -22,7 +21,7 @@ exports.ExposeStore = (moduleRaidStr) => {
22
21
  window.Store.MediaObject = window.mR.findModule('getOrCreateMediaObject')[0];
23
22
  window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0];
24
23
  window.Store.NumberInfo = window.mR.findModule('formattedPhoneNumber')[0];
25
- window.Store.Cmd = window.mR.findModule('Cmd')[0].default;
24
+ window.Store.Cmd = window.mR.findModule('Cmd')[0].Cmd;
26
25
  window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0];
27
26
  window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0];
28
27
  window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default;
@@ -30,10 +29,9 @@ exports.ExposeStore = (moduleRaidStr) => {
30
29
  window.Store.WidFactory = window.mR.findModule('createWid')[0];
31
30
  window.Store.BlockContact = window.mR.findModule('blockContact')[0];
32
31
  window.Store.GroupMetadata = window.mR.findModule((module) => module.default && module.default.handlePendingInvite)[0].default;
33
- window.Store.Sticker = window.mR.findModule('Sticker')[0].default.Sticker;
34
32
  window.Store.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default;
35
- window.Store.Label = window.mR.findModule('LabelCollection')[0].default;
36
- window.Store.Features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0].default;
33
+ window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection;
34
+ window.Store.Features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0].GK;
37
35
  window.Store.QueryOrder = window.mR.findModule('queryOrder')[0];
38
36
  window.Store.QueryProduct = window.mR.findModule('queryProduct')[0];
39
37
  window.Store.DownloadManager = window.mR.findModule('downloadManager')[0].downloadManager;
@@ -155,7 +153,7 @@ exports.LoadUtils = () => {
155
153
  }
156
154
  }
157
155
 
158
- let extraOptions = {};
156
+ let buttonOptions = {};
159
157
  if(options.buttons){
160
158
  let caption;
161
159
  if(options.buttons.type === 'chat') {
@@ -164,7 +162,7 @@ exports.LoadUtils = () => {
164
162
  }else{
165
163
  caption = options.caption ? options.caption : ' '; //Caption can't be empty
166
164
  }
167
- extraOptions = {
165
+ buttonOptions = {
168
166
  productHeaderImageRejected: false,
169
167
  isFromTemplate: false,
170
168
  isDynamicReplyButtonsMsg: true,
@@ -177,12 +175,12 @@ exports.LoadUtils = () => {
177
175
  delete options.buttons;
178
176
  }
179
177
 
178
+ let listOptions = {};
180
179
  if(options.list){
181
180
  if(window.Store.Conn.platform === 'smba' || window.Store.Conn.platform === 'smbi'){
182
181
  throw '[LT01] Whatsapp business can\'t send this yet';
183
182
  }
184
- extraOptions = {
185
- ...extraOptions,
183
+ listOptions = {
186
184
  type: 'list',
187
185
  footer: options.list.footer,
188
186
  list: {
@@ -192,7 +190,7 @@ exports.LoadUtils = () => {
192
190
  body: options.list.description
193
191
  };
194
192
  delete options.list;
195
- delete extraOptions.list.footer;
193
+ delete listOptions.list.footer;
196
194
  }
197
195
 
198
196
  const newMsgId = new window.Store.MsgKey({
@@ -201,6 +199,15 @@ exports.LoadUtils = () => {
201
199
  id: window.Store.genId(),
202
200
  });
203
201
 
202
+ const extraOptions = options.extraOptions || {};
203
+ delete options.extraOptions;
204
+
205
+ const ephemeralSettings = {
206
+ ephemeralDuration: chat.isEphemeralSettingOn() ? chat.getEphemeralSetting() : undefined,
207
+ ephemeralSettingTimestamp: chat.getEphemeralSettingTimestamp() || undefined,
208
+ disappearingModeInitiator: chat.getDisappearingModeInitiator() || undefined,
209
+ };
210
+
204
211
  const message = {
205
212
  ...options,
206
213
  id: newMsgId,
@@ -213,10 +220,13 @@ exports.LoadUtils = () => {
213
220
  t: parseInt(new Date().getTime() / 1000),
214
221
  isNewMsg: true,
215
222
  type: 'chat',
223
+ ...ephemeralSettings,
216
224
  ...locationOptions,
217
225
  ...attOptions,
218
226
  ...quotedMsgOptions,
219
227
  ...vcardOptions,
228
+ ...buttonOptions,
229
+ ...listOptions,
220
230
  ...extraOptions
221
231
  };
222
232
 
@@ -315,6 +325,7 @@ exports.LoadUtils = () => {
315
325
  window.WWebJS.getMessageModel = message => {
316
326
  const msg = message.serialize();
317
327
 
328
+ msg.isEphemeral = message.isEphemeral;
318
329
  msg.isStatusV3 = message.isStatusV3;
319
330
  msg.links = (message.getLinks()).map(link => ({
320
331
  link: link.href,
@@ -28,7 +28,7 @@ class InterfaceController {
28
28
  async openChatDrawer(chatId) {
29
29
  await this.pupPage.evaluate(async chatId => {
30
30
  let chat = await window.Store.Chat.get(chatId);
31
- await window.Store.Cmd.chatInfoDrawer(chat);
31
+ await window.Store.Cmd.openDrawerMid(chat);
32
32
  }, chatId);
33
33
  }
34
34