whatsapp-web.js 1.34.1 → 1.34.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  <div align="center">
2
2
  <br />
3
3
  <p>
4
- <a href="https://wwebjs.dev"><img src="https://github.com/wwebjs/logos/blob/main/4_Full%20Logo%20Lockup_Small/small_banner_blue.png?raw=true" title="whatsapp-web.js" alt="WWebJS Website" width="500" /></a>
4
+ <a href="https://wwebjs.dev"><img src="https://github.com/wwebjs/assets/blob/main/Collection/GitHub/wwebjs.png?raw=true" title="whatsapp-web.js" alt="WWebJS Website" width="500" /></a>
5
5
  </p>
6
6
  <br />
7
7
  <p>
@@ -14,57 +14,28 @@
14
14
  </div>
15
15
 
16
16
  ## About
17
- **A WhatsApp API client that connects through the WhatsApp Web browser app**
17
+ **A WhatsApp API client that operates via the WhatsApp Web browser.**
18
18
 
19
- The library works by launching the WhatsApp Web browser application and managing it using Puppeteer to create an instance of WhatsApp Web, thereby mitigating the risk of being blocked. The WhatsApp API client connects through the WhatsApp Web browser app, accessing its internal functions. This grants you access to nearly all the features available on WhatsApp Web, enabling dynamic handling similar to any other Node.js application.
19
+ The library launches the WhatsApp Web browser app via Puppeteer, accessing its internal functions and creating a managed instance to reduce the risk of being blocked. This gives the API client nearly all WhatsApp Web features for dynamic use in a Node.js application.
20
20
 
21
21
  > [!IMPORTANT]
22
22
  > **It is not guaranteed you will not be blocked by using this method. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe.**
23
23
 
24
24
  ## Links
25
25
 
26
- * [Website][website]
27
- * [Guide][guide] ([source][guide-source]) _(work in progress)_
28
- * [Documentation][documentation] ([source][documentation-source])
29
- * [WWebJS Discord][discord]
30
26
  * [GitHub][gitHub]
27
+ * [Guide][guide] ([source][guide-source])
28
+ * [Documentation][documentation] ([source][documentation-source])
29
+ * [Discord Server][discord]
31
30
  * [npm][npm]
32
31
 
33
32
  ## Installation
34
33
 
35
- The module is now available on npm! `npm i whatsapp-web.js`
34
+ The module is available on [npm][npm] via `npm i whatsapp-web.js`!
36
35
 
37
36
  > [!NOTE]
38
- > **Node ``v18+`` is required.**
39
-
40
- ## QUICK STEPS TO UPGRADE NODE
41
-
42
- ### Windows
43
-
44
- #### Manual
45
- Just get the latest LTS from the [official node website][nodejs].
46
-
47
- #### npm
48
- ```powershell
49
- sudo npm install -g n
50
- sudo n stable
51
- ```
52
-
53
- #### Choco
54
- ```powershell
55
- choco install nodejs-lts
56
- ```
57
-
58
- #### Winget
59
- ```powershell
60
- winget install OpenJS.NodeJS.LTS
61
- ```
62
-
63
- ### Ubuntu / Debian
64
- ```bash
65
- curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - &&\
66
- sudo apt-get install -y nodejs
67
- ```
37
+ > **Node ``v18`` or higher, is required.**
38
+ > See the [Guide][guide] for quick upgrade instructions.
68
39
 
69
40
  ## Example usage
70
41
 
@@ -165,7 +136,6 @@ See the License for the specific language governing permissions and
165
136
  limitations under the License.
166
137
 
167
138
 
168
- [website]: https://wwebjs.dev
169
139
  [guide]: https://guide.wwebjs.dev/guide
170
140
  [guide-source]: https://github.com/wwebjs/wwebjs.dev/tree/main
171
141
  [documentation]: https://docs.wwebjs.dev/
package/example.js CHANGED
@@ -365,7 +365,7 @@ client.on('message', async msg => {
365
365
  let list = new List('List body', 'btnText', sections, 'Title', 'footer');
366
366
  client.sendMessage(msg.from, list);
367
367
  } else if (msg.body === '!reaction') {
368
- msg.react('👍');
368
+ await msg.react('👍');
369
369
  } else if (msg.body === '!sendpoll') {
370
370
  /** By default the poll is created as a single choice poll: */
371
371
  await msg.reply(new Poll('Winter or Summer?', ['Winter', 'Summer']));
@@ -382,11 +382,20 @@ client.on('message', async msg => {
382
382
  ]
383
383
  })
384
384
  );
385
+ } else if (msg.body === '!vote') {
386
+ if (msg.hasQuotedMsg) {
387
+ const quotedMsg = await msg.getQuotedMessage();
388
+ if (quotedMsg.type === 'poll_creation') {
389
+ await quotedMsg.vote(msg.body.replace('!vote', ''));
390
+ } else {
391
+ msg.reply('Can only be used on poll messages');
392
+ }
393
+ }
385
394
  } else if (msg.body === '!edit') {
386
395
  if (msg.hasQuotedMsg) {
387
396
  const quotedMsg = await msg.getQuotedMessage();
388
397
  if (quotedMsg.fromMe) {
389
- quotedMsg.edit(msg.body.replace('!edit', ''));
398
+ await quotedMsg.edit(msg.body.replace('!edit', ''));
390
399
  } else {
391
400
  msg.reply('I can only edit my own messages');
392
401
  }
@@ -514,6 +523,13 @@ client.on('message', async msg => {
514
523
  // NOTE: this action will take effect after you restart the client.
515
524
  const backgroundSync = await client.setBackgroundSync(true);
516
525
  console.log(backgroundSync);
526
+ } else if (msg.body === '!postStatus') {
527
+ await client.sendMessage('status@broadcast', 'Hello there!');
528
+ // send with a different style
529
+ await client.sendMessage('status@broadcast', 'Hello again! Looks different?', {
530
+ fontStyle: 1,
531
+ backgroundColor: '#0b3296'
532
+ });
517
533
  }
518
534
  });
519
535
 
package/index.d.ts CHANGED
@@ -113,9 +113,15 @@ declare namespace WAWebJS {
113
113
 
114
114
  /** Get all current Labels */
115
115
  getLabels(): Promise<Label[]>
116
-
116
+
117
117
  /** Get all current Broadcasts */
118
118
  getBroadcasts(): Promise<Broadcast[]>
119
+
120
+ /** Get broadcast instance by current user ID */
121
+ getBroadcastById(contactId: string): Promise<Broadcast>
122
+
123
+ /** Revoke current own status messages */
124
+ revokeStatusMessage(messageId: string): Promise<void>
119
125
 
120
126
  /** Change labels in chats */
121
127
  addOrRemoveLabels(labelIds: Array<number|string>, chatIds: Array<string>): Promise<void>
@@ -242,7 +248,26 @@ declare namespace WAWebJS {
242
248
  syncHistory(chatId: string): Promise<boolean>
243
249
 
244
250
  /** Save new contact to user's addressbook or edit the existing one */
245
- saveOrEditAddressbookContact(phoneNumber: string, firstName: string, lastName: string, syncToAddressbook?: boolean): Promise<ChatId>
251
+ saveOrEditAddressbookContact(phoneNumber: string, firstName: string, lastName: string, syncToAddressbook?: boolean): Promise<void>
252
+
253
+ /**
254
+ * Add or edit a customer note
255
+ * @see https://faq.whatsapp.com/1433099287594476
256
+ */
257
+ addOrEditCustomerNote(userId: string, note: string): Promise<void>
258
+
259
+ /**
260
+ * Get a customer note
261
+ * @see https://faq.whatsapp.com/1433099287594476
262
+ */
263
+ getCustomerNote(userId: string): Promise<{
264
+ chatId: string;
265
+ content: string;
266
+ createdAt: number;
267
+ id: string;
268
+ modifiedAt: number;
269
+ type: string;
270
+ }>
246
271
 
247
272
  /** Deletes the contact from user's addressbook */
248
273
  deleteAddressbookContact(honeNumber: string): Promise<void>
@@ -287,6 +312,9 @@ declare namespace WAWebJS {
287
312
  */
288
313
  transferChannelOwnership(channelId: string, newOwnerId: string, options?: TransferChannelOwnershipOptions): Promise<boolean>;
289
314
 
315
+ /** Get Poll Votes */
316
+ getPollVotes(messageId: string): Promise<PollVote[]>
317
+
290
318
  /** Generic event */
291
319
  on(event: string, listener: (...args: any) => void): this
292
320
 
@@ -534,6 +562,9 @@ declare namespace WAWebJS {
534
562
  /** Timeout for authentication selector in puppeteer
535
563
  * @default 0 */
536
564
  authTimeoutMs?: number,
565
+ /** function to be evaluated On New Document
566
+ * @default undefined */
567
+ evalOnNewDoc?: Function,
537
568
  /** Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/ */
538
569
  puppeteer?: puppeteer.PuppeteerNodeLaunchOptions & puppeteer.ConnectOptions
539
570
  /** Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used. */
@@ -860,12 +891,14 @@ declare namespace WAWebJS {
860
891
  GROUP_MEMBERSHIP_REQUEST = 'group_membership_request',
861
892
  GROUP_UPDATE = 'group_update',
862
893
  QR_RECEIVED = 'qr',
894
+ CODE_RECEIVED = 'code',
863
895
  LOADING_SCREEN = 'loading_screen',
864
896
  DISCONNECTED = 'disconnected',
865
897
  STATE_CHANGED = 'change_state',
866
898
  BATTERY_CHANGED = 'change_battery',
867
899
  REMOTE_SESSION_SAVED = 'remote_session_saved',
868
- CALL = 'call'
900
+ INCOMING_CALL = 'call',
901
+ VOTE_UPDATE = 'vote_update',
869
902
  }
870
903
 
871
904
  /** Group notification types */
@@ -874,6 +907,8 @@ declare namespace WAWebJS {
874
907
  INVITE = 'invite',
875
908
  REMOVE = 'remove',
876
909
  LEAVE = 'leave',
910
+ PROMOTE = 'promote',
911
+ DEMOTE = 'demote',
877
912
  SUBJECT = 'subject',
878
913
  DESCRIPTION = 'description',
879
914
  PICTURE = 'picture',
@@ -897,6 +932,7 @@ declare namespace WAWebJS {
897
932
  AUDIO = 'audio',
898
933
  VOICE = 'ptt',
899
934
  IMAGE = 'image',
935
+ ALBUM = 'album',
900
936
  VIDEO = 'video',
901
937
  DOCUMENT = 'document',
902
938
  STICKER = 'sticker',
@@ -1173,6 +1209,10 @@ declare namespace WAWebJS {
1173
1209
  * Gets the payment details associated with a given message
1174
1210
  */
1175
1211
  getPayment: () => Promise<Payment>,
1212
+ /**
1213
+ * Get Poll Votes associated with the given message
1214
+ */
1215
+ getPollVotes: () => Promise<PollVote[]>,
1176
1216
  /**
1177
1217
  * Gets the reactions associated with the given message
1178
1218
  */
@@ -1184,6 +1224,10 @@ declare namespace WAWebJS {
1184
1224
  * Once the event is canceled, it can not be edited.
1185
1225
  */
1186
1226
  editScheduledEvent: (editedEventObject: Event) => Promise<Message | null>,
1227
+ /**
1228
+ * Send votes to the poll message
1229
+ */
1230
+ vote: (selectedOptions: Array<string>) => Promise<void>,
1187
1231
  }
1188
1232
 
1189
1233
  /** ID that represents a message */
@@ -1247,7 +1291,7 @@ declare namespace WAWebJS {
1247
1291
  endTime?: Date,
1248
1292
  /** The location of the event */
1249
1293
  location?: string,
1250
- /** The type of a WhatsApp call link to generate, valid values are: `video` | `voice` */
1294
+ /** The type of a WhatsApp call link to generate, valid values are: `video` | `voice` | `none` */
1251
1295
  callType?: string,
1252
1296
  /**
1253
1297
  * Indicates if a scheduled event should be sent as an already canceled
@@ -1274,7 +1318,7 @@ declare namespace WAWebJS {
1274
1318
  messageSecret?: string;
1275
1319
  };
1276
1320
 
1277
- constructor(name: string, startTime: Date, options?: EventSendOptions)
1321
+ constructor(name: string, startTime: Date, options?: ScheduledEventSendOptions)
1278
1322
  }
1279
1323
 
1280
1324
  /** Represents a Poll Vote on WhatsApp */
@@ -1534,6 +1578,8 @@ declare namespace WAWebJS {
1534
1578
  /** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */
1535
1579
  getCommonGroups: () => Promise<ChatId[]>
1536
1580
 
1581
+ /** Gets the Contact's current status broadcast. */
1582
+ getBroadcast: () => Promise<Broadcast>
1537
1583
  }
1538
1584
 
1539
1585
  export interface ContactId {
@@ -1707,6 +1753,17 @@ declare namespace WAWebJS {
1707
1753
  getPinnedMessages: () => Promise<[Message]|[]>
1708
1754
  /** Sync history conversation of the Chat */
1709
1755
  syncHistory: () => Promise<boolean>
1756
+ /** Add or edit a customer note */
1757
+ addOrEditCustomerNote: (note: string) => Promise<void>
1758
+ /** Get a customer note */
1759
+ getCustomerNote: () => Promise<{
1760
+ chatId: string;
1761
+ content: string;
1762
+ createdAt: number;
1763
+ id: string;
1764
+ modifiedAt: number;
1765
+ type: string;
1766
+ }>
1710
1767
  }
1711
1768
 
1712
1769
  export interface Channel {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-web.js",
3
- "version": "1.34.1",
3
+ "version": "1.34.4",
4
4
  "description": "Library for interacting with the WhatsApp Web API ",
5
5
  "main": "./index.js",
6
6
  "typings": "./index.d.ts",
@@ -34,7 +34,7 @@
34
34
  "mime": "^3.0.0",
35
35
  "node-fetch": "^2.6.9",
36
36
  "node-webpmux": "3.1.7",
37
- "puppeteer": "^18.2.1"
37
+ "puppeteer": "^24.31.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node-fetch": "^2.5.12",
package/src/Client.js CHANGED
@@ -6,7 +6,7 @@ const moduleRaid = require('@pedroslopez/moduleraid/moduleraid');
6
6
 
7
7
  const Util = require('./util/Util');
8
8
  const InterfaceController = require('./util/InterfaceController');
9
- const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constants');
9
+ const { WhatsWebURL, DefaultOptions, Events, WAState, MessageTypes } = require('./util/Constants');
10
10
  const { ExposeAuthStore } = require('./util/Injected/AuthStore/AuthStore');
11
11
  const { ExposeStore } = require('./util/Injected/Store');
12
12
  const { ExposeLegacyAuthStore } = require('./util/Injected/AuthStore/LegacyAuthStore');
@@ -27,6 +27,7 @@ const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
27
27
  * @param {string} options.webVersion - The version of WhatsApp Web to use. Use options.webVersionCache to configure how the version is retrieved.
28
28
  * @param {object} options.webVersionCache - Determines how to retrieve the WhatsApp Web version. Defaults to a local cache (LocalWebCache) that falls back to latest if the requested version is not found.
29
29
  * @param {number} options.authTimeoutMs - Timeout for authentication selector in puppeteer
30
+ * @param {function} options.evalOnNewDoc - function to eval on new doc
30
31
  * @param {object} options.puppeteer - Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/
31
32
  * @param {number} options.qrMaxRetries - How many times should the qrcode be refreshed before giving up
32
33
  * @param {string} options.restartOnAuthFail - @deprecated This option should be set directly on the LegacySessionAuth.
@@ -95,7 +96,20 @@ class Client extends EventEmitter {
95
96
  * Private function
96
97
  */
97
98
  async inject() {
98
- await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs});
99
+ if(this.options.authTimeoutMs === undefined || this.options.authTimeoutMs==0){
100
+ this.options.authTimeoutMs = 30000;
101
+ }
102
+ let start = Date.now();
103
+ let timeout = this.options.authTimeoutMs;
104
+ let res = false;
105
+ while(start > (Date.now() - timeout)){
106
+ res = await this.pupPage.evaluate('window.Debug?.VERSION != undefined');
107
+ if(res){break;}
108
+ await new Promise(r => setTimeout(r, 200));
109
+ }
110
+ if(!res){
111
+ throw 'auth timeout';
112
+ }
99
113
  await this.setDeviceName(this.options.deviceName, this.options.browserName);
100
114
  const pairWithPhoneNumber = this.options.pairWithPhoneNumber;
101
115
  const version = await this.getWWebVersion();
@@ -225,9 +239,17 @@ class Client extends EventEmitter {
225
239
  await new Promise(r => setTimeout(r, 2000));
226
240
  await this.pupPage.evaluate(ExposeLegacyStore);
227
241
  }
228
-
229
- // Check window.Store Injection
230
- await this.pupPage.waitForFunction('window.Store != undefined');
242
+ let start = Date.now();
243
+ let res = false;
244
+ while(start > (Date.now() - 30000)){
245
+ // Check window.Store Injection
246
+ res = await this.pupPage.evaluate('window.Store != undefined');
247
+ if(res){break;}
248
+ await new Promise(r => setTimeout(r, 200));
249
+ }
250
+ if(!res){
251
+ throw 'ready timeout';
252
+ }
231
253
 
232
254
  /**
233
255
  * Current connection information
@@ -300,7 +322,7 @@ class Client extends EventEmitter {
300
322
  page = await browser.newPage();
301
323
  } else {
302
324
  const browserArgs = [...(puppeteerOpts.args || [])];
303
- if(!browserArgs.find(arg => arg.includes('--user-agent'))) {
325
+ if(this.options.userAgent !== false && !browserArgs.find(arg => arg.includes('--user-agent'))) {
304
326
  browserArgs.push(`--user-agent=${this.options.userAgent}`);
305
327
  }
306
328
  // navigator.webdriver fix
@@ -313,8 +335,9 @@ class Client extends EventEmitter {
313
335
  if (this.options.proxyAuthentication !== undefined) {
314
336
  await page.authenticate(this.options.proxyAuthentication);
315
337
  }
316
-
317
- await page.setUserAgent(this.options.userAgent);
338
+ if(this.options.userAgent !== false) {
339
+ await page.setUserAgent(this.options.userAgent);
340
+ }
318
341
  if (this.options.bypassCSP) await page.setBypassCSP(true);
319
342
 
320
343
  this.pupBrowser = browser;
@@ -322,7 +345,11 @@ class Client extends EventEmitter {
322
345
 
323
346
  await this.authStrategy.afterBrowserInitialized();
324
347
  await this.initWebVersionCache();
325
-
348
+
349
+ if (this.options.evalOnNewDoc !== undefined) {
350
+ await page.evaluateOnNewDocument(this.options.evalOnNewDoc);
351
+ }
352
+
326
353
  // ocVersion (isOfficialClient patch)
327
354
  // remove after 2.3000.x hard release
328
355
  await page.evaluateOnNewDocument(() => {
@@ -948,9 +975,10 @@ class Client extends EventEmitter {
948
975
  */
949
976
  async sendMessage(chatId, content, options = {}) {
950
977
  const isChannel = /@\w*newsletter\b/.test(chatId);
978
+ const isStatus = /@\w*broadcast\b/.test(chatId);
951
979
 
952
980
  if (isChannel && [
953
- options.sendMediaAsDocument, options.quotedMessageId,
981
+ options.sendMediaAsDocument, options.quotedMessageId,
954
982
  options.parseVCards, options.isViewOnce,
955
983
  content instanceof Location, content instanceof Contact,
956
984
  content instanceof Buttons, content instanceof List,
@@ -958,6 +986,16 @@ class Client extends EventEmitter {
958
986
  ].includes(true)) {
959
987
  console.warn('The message type is currently not supported for sending in channels,\nthe supported message types are: text, image, sticker, gif, video, voice and poll.');
960
988
  return null;
989
+
990
+ } else if (isStatus && [
991
+ options.sendMediaAsDocument, options.quotedMessageId,
992
+ options.parseVCards, options.isViewOnce, options.sendMediaAsSticker,
993
+ content instanceof Location, content instanceof Contact,
994
+ content instanceof Poll, content instanceof Buttons, content instanceof List,
995
+ Array.isArray(content) && content.length > 0 && content[0] instanceof Contact
996
+ ].includes(true)) {
997
+ console.warn('The message type is currently not supported for sending in status broadcast,\nthe supported message types are: text, image, gif, audio and video.');
998
+ return null;
961
999
  }
962
1000
 
963
1001
  if (options.mentions) {
@@ -1193,7 +1231,12 @@ class Client extends EventEmitter {
1193
1231
 
1194
1232
  return ContactFactory.create(this, contact);
1195
1233
  }
1196
-
1234
+
1235
+ /**
1236
+ * Get message by ID
1237
+ * @param {string} messageId
1238
+ * @returns {Promise<Message>}
1239
+ */
1197
1240
  async getMessageById(messageId) {
1198
1241
  const msg = await this.pupPage.evaluate(async messageId => {
1199
1242
  let msg = window.Store.Msg.get(messageId);
@@ -1225,11 +1268,18 @@ class Client extends EventEmitter {
1225
1268
 
1226
1269
  const msgs = await window.Store.PinnedMsgUtils.getTable().equals(['chatId'], chatWid.toString());
1227
1270
 
1228
- const pinnedMsgs = msgs.map((msg) => window.Store.Msg.get(msg.parentMsgKey));
1271
+ const pinnedMsgs = (
1272
+ await Promise.all(
1273
+ msgs.filter(msg => msg.pinType == 1).map(async (msg) => {
1274
+ const res = await window.Store.Msg.getMessagesById([msg.parentMsgKey]);
1275
+ return res?.messages?.[0];
1276
+ })
1277
+ )
1278
+ ).filter(Boolean);
1229
1279
 
1230
1280
  return !pinnedMsgs.length
1231
1281
  ? []
1232
- : pinnedMsgs.map((msg) => window.WWebJS.getMessageModel(msg));
1282
+ : await Promise.all(pinnedMsgs.map((msg) => window.WWebJS.getMessageModel(msg)));
1233
1283
  }, chatId);
1234
1284
 
1235
1285
  return pinnedMsgs.map((msg) => new Message(this, msg));
@@ -1524,7 +1574,7 @@ class Client extends EventEmitter {
1524
1574
  const commonGroups = await this.pupPage.evaluate(async (contactId) => {
1525
1575
  let contact = window.Store.Contact.get(contactId);
1526
1576
  if (!contact) {
1527
- const wid = window.Store.WidFactory.createUserWid(contactId);
1577
+ const wid = window.Store.WidFactory.createWid(contactId);
1528
1578
  const chatConstructor = window.Store.Contact.getModelsArray().find(c=>!c.isGroup).constructor;
1529
1579
  contact = new chatConstructor({id: wid});
1530
1580
  }
@@ -1963,7 +2013,7 @@ class Client extends EventEmitter {
1963
2013
 
1964
2014
  return labels.map(data => new Label(this, data));
1965
2015
  }
1966
-
2016
+
1967
2017
  /**
1968
2018
  * Get all current Broadcast
1969
2019
  * @returns {Promise<Array<Broadcast>>}
@@ -1975,6 +2025,49 @@ class Client extends EventEmitter {
1975
2025
  return broadcasts.map(data => new Broadcast(this, data));
1976
2026
  }
1977
2027
 
2028
+ /**
2029
+ * Get broadcast instance by current user ID
2030
+ * @param {string} contactId
2031
+ * @returns {Promise<Broadcast>}
2032
+ */
2033
+ async getBroadcastById(contactId) {
2034
+ const broadcast = await this.pupPage.evaluate(async (userId) => {
2035
+ let status;
2036
+ try {
2037
+ status = window.Store.Status.get(userId);
2038
+ if (!status) {
2039
+ status = await window.Store.Status.find(userId);
2040
+ }
2041
+ } catch {
2042
+ status = null;
2043
+ }
2044
+
2045
+ if (status) return window.WWebJS.getStatusModel(status);
2046
+ }, contactId);
2047
+ return new Broadcast(this, broadcast);
2048
+ }
2049
+
2050
+ /**
2051
+ * Revoke current own status messages
2052
+ * @param {string} messageId
2053
+ * @returns {Promise<void>}
2054
+ */
2055
+ async revokeStatusMessage(messageId) {
2056
+ return await this.pupPage.evaluate(async (msgId) => {
2057
+ const status = window.Store.Status.getMyStatus();
2058
+ if (!status) return;
2059
+
2060
+ const msg =
2061
+ window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
2062
+ if (!msg) return;
2063
+
2064
+ if (!msg.id.fromMe || !msg.id.remote.isStatus())
2065
+ throw 'Invalid usage! Can only revoke the message its from own status broadcast';
2066
+
2067
+ return await window.Store.StatusUtils.sendStatusRevokeMsgAction(status, msg);
2068
+ }, messageId);
2069
+ }
2070
+
1978
2071
  /**
1979
2072
  * Get Label instance by ID
1980
2073
  * @param {string} labelId
@@ -2310,13 +2403,15 @@ class Client extends EventEmitter {
2310
2403
  * @param {string} firstName
2311
2404
  * @param {string} lastName
2312
2405
  * @param {boolean} [syncToAddressbook = false] If set to true, the contact will also be saved to the user's address book on their phone. False by default
2313
- * @returns {Promise<import('..').ChatId>} Object in a wid format
2406
+ * @returns {Promise<void>}
2314
2407
  */
2315
2408
  async saveOrEditAddressbookContact(phoneNumber, firstName, lastName, syncToAddressbook = false)
2316
2409
  {
2317
2410
  return await this.pupPage.evaluate(async (phoneNumber, firstName, lastName, syncToAddressbook) => {
2318
2411
  return await window.Store.AddressbookContactUtils.saveContactAction(
2319
2412
  phoneNumber,
2413
+ phoneNumber,
2414
+ null,
2320
2415
  null,
2321
2416
  firstName,
2322
2417
  lastName,
@@ -2356,6 +2451,83 @@ class Client extends EventEmitter {
2356
2451
  }));
2357
2452
  }, userIds);
2358
2453
  }
2454
+
2455
+ /**
2456
+ * Add or edit a customer note
2457
+ * @see https://faq.whatsapp.com/1433099287594476
2458
+ * @param {string} userId The ID of a customer to add a note to
2459
+ * @param {string} note The note to add
2460
+ * @returns {Promise<void>}
2461
+ */
2462
+ async addOrEditCustomerNote(userId, note) {
2463
+ return await this.pupPage.evaluate(async (userId, note) => {
2464
+ if (!window.Store.BusinessGatingUtils.smbNotesV1Enabled()) return;
2465
+
2466
+ return window.Store.CustomerNoteUtils.noteAddAction(
2467
+ 'unstructured',
2468
+ window.Store.WidToJid.widToUserJid(window.Store.WidFactory.createWid(userId)),
2469
+ note
2470
+ );
2471
+ }, userId, note);
2472
+ }
2473
+
2474
+ /**
2475
+ * Get a customer note
2476
+ * @see https://faq.whatsapp.com/1433099287594476
2477
+ * @param {string} userId The ID of a customer to get a note from
2478
+ * @returns {Promise<{
2479
+ * chatId: string,
2480
+ * content: string,
2481
+ * createdAt: number,
2482
+ * id: string,
2483
+ * modifiedAt: number,
2484
+ * type: string
2485
+ * }>}
2486
+ */
2487
+ async getCustomerNote(userId) {
2488
+ return await this.pupPage.evaluate(async (userId) => {
2489
+ if (!window.Store.BusinessGatingUtils.smbNotesV1Enabled()) return null;
2490
+
2491
+ const note = await window.Store.CustomerNoteUtils.retrieveOnlyNoteForChatJid(
2492
+ window.Store.WidToJid.widToUserJid(window.Store.WidFactory.createWid(userId))
2493
+ );
2494
+
2495
+ let serialized = note?.serialize();
2496
+
2497
+ if (!serialized) return null;
2498
+
2499
+ serialized.chatId = window.Store.JidToWid.userJidToUserWid(serialized.chatJid)._serialized;
2500
+ delete serialized.chatJid;
2501
+
2502
+ return serialized;
2503
+ }, userId);
2504
+ }
2505
+
2506
+ /**
2507
+ * Get Poll Votes
2508
+ * @param {string} messageId
2509
+ * @return {Promise<Array<PollVote>>}
2510
+ */
2511
+ async getPollVotes(messageId) {
2512
+ const msg = await this.getMessageById(messageId);
2513
+ if (!msg) return [];
2514
+ if (msg.type != MessageTypes.POLL_CREATION) throw 'Invalid usage! Can only be used with a pollCreation message';
2515
+
2516
+ const pollVotes = await this.pupPage.evaluate( async (msg) => {
2517
+ const msgKey = window.Store.MsgKey.fromString(msg.id._serialized);
2518
+ let pollVotes = await window.Store.PollsVotesSchema.getTable().equals(['parentMsgKey'], msgKey.toString());
2519
+
2520
+ return pollVotes.map(item => {
2521
+ const typedArray = new Uint8Array(item.selectedOptionLocalIds);
2522
+ return {
2523
+ ...item,
2524
+ selectedOptionLocalIds: Array.from(typedArray)
2525
+ };
2526
+ });
2527
+ }, msg);
2528
+
2529
+ return pollVotes.map((pollVote) => new PollVote(this.client, {...pollVote, parentMessage: msg}));
2530
+ }
2359
2531
  }
2360
2532
 
2361
2533
  module.exports = Client;
@@ -294,6 +294,36 @@ class Chat extends Base {
294
294
  async syncHistory() {
295
295
  return this.client.syncHistory(this.id._serialized);
296
296
  }
297
+
298
+ /**
299
+ * Add or edit a customer note
300
+ * @see https://faq.whatsapp.com/1433099287594476
301
+ * @param {string} note The note to add
302
+ * @returns {Promise<void>}
303
+ */
304
+ async addOrEditCustomerNote(note) {
305
+ if (this.isGroup || this.isChannel) return;
306
+
307
+ return this.client.addOrEditCustomerNote(this.id._serialized, note);
308
+ }
309
+
310
+ /**
311
+ * Get a customer note
312
+ * @see https://faq.whatsapp.com/1433099287594476
313
+ * @returns {Promise<{
314
+ * chatId: string,
315
+ * content: string,
316
+ * createdAt: number,
317
+ * id: string,
318
+ * modifiedAt: number,
319
+ * type: string
320
+ * }>}
321
+ */
322
+ async getCustomerNote() {
323
+ if (this.isGroup || this.isChannel) return null;
324
+
325
+ return this.client.getCustomerNote(this.id._serialized);
326
+ }
297
327
  }
298
328
 
299
329
  module.exports = Chat;
@@ -186,7 +186,7 @@ class Contact extends Base {
186
186
  async getAbout() {
187
187
  const about = await this.client.pupPage.evaluate(async (contactId) => {
188
188
  const wid = window.Store.WidFactory.createWid(contactId);
189
- return window.Store.StatusUtils.getStatus(wid);
189
+ return window.Store.StatusUtils.getStatus({'token':'', 'wid': wid});
190
190
  }, this.id._serialized);
191
191
 
192
192
  if (typeof about.status !== 'string')
@@ -202,7 +202,14 @@ class Contact extends Base {
202
202
  async getCommonGroups() {
203
203
  return await this.client.getCommonGroups(this.id._serialized);
204
204
  }
205
-
205
+
206
+ /**
207
+ * Gets the Contact's current status broadcast.
208
+ * @returns {Promise<Broadcast>}
209
+ */
210
+ async getBroadcast() {
211
+ return await this.client.getBroadcastById(this.id._serialized);
212
+ }
206
213
  }
207
214
 
208
215
  module.exports = Contact;
@@ -297,12 +297,12 @@ class GroupChat extends Chat {
297
297
  */
298
298
  async setAddMembersAdminsOnly(adminsOnly=true) {
299
299
  const success = await this.client.pupPage.evaluate(async (groupId, adminsOnly) => {
300
- const chatWid = window.Store.WidFactory.createWid(groupId);
300
+ const chat = await window.WWebJS.getChat(groupId, { getAsModel: false });
301
301
  try {
302
- const response = await window.Store.GroupUtils.setGroupMemberAddMode(chatWid, 'member_add_mode', adminsOnly ? 0 : 1);
303
- return response.name === 'SetMemberAddModeResponseSuccess';
302
+ await window.Store.GroupUtils.setGroupProperty(chat, 'member_add_mode', adminsOnly ? 0 : 1);
303
+ return true;
304
304
  } catch (err) {
305
- if(err.name === 'SmaxParsingFailure') return false;
305
+ if(err.name === 'ServerStatusCodeError') return false;
306
306
  throw err;
307
307
  }
308
308
  }, this.id._serialized, adminsOnly);
@@ -318,9 +318,9 @@ class GroupChat extends Chat {
318
318
  */
319
319
  async setMessagesAdminsOnly(adminsOnly=true) {
320
320
  const success = await this.client.pupPage.evaluate(async (chatId, adminsOnly) => {
321
- const chatWid = window.Store.WidFactory.createWid(chatId);
321
+ const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
322
322
  try {
323
- await window.Store.GroupUtils.setGroupProperty(chatWid, 'announcement', adminsOnly ? 1 : 0);
323
+ await window.Store.GroupUtils.setGroupProperty(chat, 'announcement', adminsOnly ? 1 : 0);
324
324
  return true;
325
325
  } catch (err) {
326
326
  if(err.name === 'ServerStatusCodeError') return false;
@@ -341,9 +341,9 @@ class GroupChat extends Chat {
341
341
  */
342
342
  async setInfoAdminsOnly(adminsOnly=true) {
343
343
  const success = await this.client.pupPage.evaluate(async (chatId, adminsOnly) => {
344
- const chatWid = window.Store.WidFactory.createWid(chatId);
344
+ const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
345
345
  try {
346
- await window.Store.GroupUtils.setGroupProperty(chatWid, 'restrict', adminsOnly ? 1 : 0);
346
+ await window.Store.GroupUtils.setGroupProperty(chat, 'restrict', adminsOnly ? 1 : 0);
347
347
  return true;
348
348
  } catch (err) {
349
349
  if(err.name === 'ServerStatusCodeError') return false;
@@ -356,7 +356,7 @@ class GroupChat extends Chat {
356
356
  this.groupMetadata.restrict = adminsOnly;
357
357
  return true;
358
358
  }
359
-
359
+
360
360
  /**
361
361
  * Deletes the group's picture.
362
362
  * @returns {Promise<boolean>} Returns true if the picture was properly deleted. This can return false if the user does not have the necessary permissions.
@@ -449,7 +449,9 @@ class Message extends Base {
449
449
 
450
450
  const result = await this.client.pupPage.evaluate(async (msgId) => {
451
451
  const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
452
- if (!msg || !msg.mediaData) {
452
+
453
+ // REUPLOADING mediaStage means the media is expired and the download button is spinning, cannot be downloaded now
454
+ if (!msg || !msg.mediaData || msg.mediaData.mediaStage === 'REUPLOADING') {
453
455
  return null;
454
456
  }
455
457
  if (msg.mediaData.mediaStage != 'RESOLVED') {
@@ -466,6 +468,10 @@ class Message extends Base {
466
468
  }
467
469
 
468
470
  try {
471
+ const mockQpl = {
472
+ addAnnotations: function() { return this; },
473
+ addPoint: function() { return this; }
474
+ };
469
475
  const decryptedMedia = await window.Store.DownloadManager.downloadAndMaybeDecrypt({
470
476
  directPath: msg.directPath,
471
477
  encFilehash: msg.encFilehash,
@@ -473,7 +479,8 @@ class Message extends Base {
473
479
  mediaKey: msg.mediaKey,
474
480
  mediaKeyTimestamp: msg.mediaKeyTimestamp,
475
481
  type: msg.type,
476
- signal: (new AbortController).signal
482
+ signal: (new AbortController).signal,
483
+ downloadQpl: mockQpl
477
484
  });
478
485
 
479
486
  const data = await window.WWebJS.arrayBufferToBase64Async(decryptedMedia);
@@ -562,7 +569,7 @@ class Message extends Base {
562
569
  */
563
570
  async unpin() {
564
571
  return await this.client.pupPage.evaluate(async (msgId) => {
565
- return await window.WWebJS.pinUnpinMsgAction(msgId, 2);
572
+ return await window.WWebJS.pinUnpinMsgAction(msgId, 2, 0);
566
573
  }, this.id._serialized);
567
574
  }
568
575
 
@@ -742,6 +749,39 @@ class Message extends Base {
742
749
 
743
750
  return edittedEventMsg && new Message(this.client, edittedEventMsg);
744
751
  }
752
+ /**
753
+ * Returns the PollVote this poll message
754
+ * @returns {Promise<PollVote[]>}
755
+ */
756
+ async getPollVotes() {
757
+ return await this.client.getPollVotes(this.id._serialized);
758
+ }
759
+
760
+ /**
761
+ * Send votes to the poll message
762
+ * @param {Array<string>} selectedOptions Array of options selected.
763
+ * @returns {Promise}
764
+ */
765
+ async vote(selectedOptions) {
766
+ if (this.type != MessageTypes.POLL_CREATION) throw 'Invalid usage! Can only be used with a pollCreation message';
767
+
768
+ await this.client.pupPage.evaluate(async (messageId, votes) => {
769
+ if (!messageId) return null;
770
+ if (!Array.isArray(votes)) votes = [votes];
771
+ let localIdSet = new Set();
772
+ const msg =
773
+ window.Store.Msg.get(messageId) || (await window.Store.Msg.getMessagesById([messageId]))?.messages?.[0];
774
+ if (!msg) return null;
775
+
776
+ msg.pollOptions.forEach(a => {
777
+ for (const option of votes) {
778
+ if (a.name === option) localIdSet.add(a.localId);
779
+ }
780
+ });
781
+
782
+ await window.Store.PollsSendVote.sendVote(msg, localIdSet);
783
+ }, this.id._serialized, selectedOptions);
784
+ }
745
785
  }
746
786
 
747
787
  module.exports = Message;
@@ -6,7 +6,7 @@
6
6
  * @property {?string} description The scheduled event description
7
7
  * @property {?Date} endTime The end time of the event
8
8
  * @property {?string} location The location of the event
9
- * @property {?string} callType The type of a WhatsApp call link to generate, valid values are: `video` | `voice`
9
+ * @property {?string} callType The type of a WhatsApp call link to generate, valid values are: `video` | `voice` | `none`
10
10
  * @property {boolean} [isEventCanceled = false] Indicates if a scheduled event should be sent as an already canceled
11
11
  * @property {?Array<number>} messageSecret The custom message secret, can be used as an event ID. NOTE: it has to be a unique vector with a length of 32
12
12
  */
@@ -58,10 +58,10 @@ class ScheduledEvent {
58
58
  }(`Empty '${propName}' parameter value is provided.`);
59
59
  }
60
60
 
61
- if (propName === 'callType' && propValue && !['video', 'voice'].includes(propValue)) {
61
+ if (propName === 'callType' && propValue && !['video', 'voice', 'none'].includes(propValue)) {
62
62
  throw new class CreateScheduledEventError extends Error {
63
63
  constructor(m) { super(m); }
64
- }(`Invalid '${propName}' parameter value is provided. Valid values are: 'voice' | 'video'.`);
64
+ }(`Invalid '${propName}' parameter value is provided. Valid values are: 'voice' | 'video' | 'none'.`);
65
65
  }
66
66
 
67
67
  return propValue;
@@ -85,6 +85,7 @@ exports.MessageTypes = {
85
85
  AUDIO: 'audio',
86
86
  VOICE: 'ptt',
87
87
  IMAGE: 'image',
88
+ ALBUM: 'album',
88
89
  VIDEO: 'video',
89
90
  DOCUMENT: 'document',
90
91
  STICKER: 'sticker',
@@ -130,6 +131,8 @@ exports.GroupNotificationTypes = {
130
131
  INVITE: 'invite',
131
132
  REMOVE: 'remove',
132
133
  LEAVE: 'leave',
134
+ PROMOTE: 'promote',
135
+ DEMOTE: 'demote',
133
136
  SUBJECT: 'subject',
134
137
  DESCRIPTION: 'description',
135
138
  PICTURE: 'picture',
@@ -54,6 +54,7 @@ exports.ExposeStore = () => {
54
54
  window.Store.MediaObject = window.require('WAWebMediaStorage');
55
55
  window.Store.MediaTypes = window.require('WAWebMmsMediaTypes');
56
56
  window.Store.MediaUpload = window.require('WAWebMediaMmsV4Upload');
57
+ window.Store.MediaUpdate = window.require('WAWebMediaUpdateMsg');
57
58
  window.Store.MsgKey = window.require('WAWebMsgKey');
58
59
  window.Store.OpaqueData = window.require('WAWebMediaOpaqueData');
59
60
  window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge');
@@ -62,9 +63,14 @@ exports.ExposeStore = () => {
62
63
  window.Store.SendDelete = window.require('WAWebDeleteChatAction');
63
64
  window.Store.SendMessage = window.require('WAWebSendMsgChatAction');
64
65
  window.Store.EditMessage = window.require('WAWebSendMessageEditAction');
66
+ window.Store.MediaDataUtils = window.require('WAWebMediaDataUtils');
67
+ window.Store.BlobCache = window.require('WAWebMediaInMemoryBlobCache');
65
68
  window.Store.SendSeen = window.require('WAWebUpdateUnreadChatAction');
66
69
  window.Store.User = window.require('WAWebUserPrefsMeUser');
67
- window.Store.ContactMethods = window.require('WAWebContactGetters');
70
+ window.Store.ContactMethods = {
71
+ ...window.require('WAWebContactGetters'),
72
+ ...window.require('WAWebFrontendContactGetters')
73
+ };
68
74
  window.Store.UserConstructor = window.require('WAWebWid');
69
75
  window.Store.Validators = window.require('WALinkify');
70
76
  window.Store.WidFactory = window.require('WAWebWidFactory');
@@ -72,7 +78,6 @@ exports.ExposeStore = () => {
72
78
  window.Store.PresenceUtils = window.require('WAWebPresenceChatAction');
73
79
  window.Store.ChatState = window.require('WAWebChatStateBridge');
74
80
  window.Store.findCommonGroups = window.require('WAWebFindCommonGroupsContactAction').findCommonGroups;
75
- window.Store.StatusUtils = window.require('WAWebContactStatusBridge');
76
81
  window.Store.ConversationMsgs = window.require('WAWebChatLoadMessages');
77
82
  window.Store.sendReactionToMsg = window.require('WAWebSendReactionMsgAction').sendReactionToMsg;
78
83
  window.Store.createOrUpdateReactionsModule = window.require('WAWebDBCreateOrUpdateReactions');
@@ -97,13 +102,15 @@ exports.ExposeStore = () => {
97
102
  window.Store.HistorySync = window.require('WAWebSendNonMessageDataRequest');
98
103
  window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode;
99
104
  window.Store.AddonPollVoteTable = window.require('WAWebAddonPollVoteTableMode').pollVoteTableMode;
100
- window.Store.PinnedMsgUtils = window.require('WAWebPinInChatSchema');
101
105
  window.Store.ChatGetters = window.require('WAWebChatGetters');
102
- window.Store.PinnedMsgUtils = window.require('WAWebSendPinMessageAction');
103
106
  window.Store.UploadUtils = window.require('WAWebUploadManager');
104
107
  window.Store.WAWebStreamModel = window.require('WAWebStreamModel');
105
108
  window.Store.FindOrCreateChat = window.require('WAWebFindChatAction');
106
-
109
+ window.Store.CustomerNoteUtils = window.require('WAWebNoteAction');
110
+ window.Store.BusinessGatingUtils = window.require('WAWebBizGatingUtils');
111
+ window.Store.PollsVotesSchema = window.require('WAWebPollsVotesSchema');
112
+ window.Store.PollsSendVote = window.require('WAWebPollsSendVoteMsgAction');
113
+
107
114
  window.Store.Settings = {
108
115
  ...window.require('WAWebUserPrefsGeneral'),
109
116
  ...window.require('WAWebUserPrefsNotifications'),
@@ -116,6 +123,10 @@ exports.ExposeStore = () => {
116
123
  window.Store.ForwardUtils = {
117
124
  ...window.require('WAWebChatForwardMessage')
118
125
  };
126
+ window.Store.PinnedMsgUtils = {
127
+ ...window.require('WAWebPinInChatSchema'),
128
+ ...window.require('WAWebSendPinMessageAction')
129
+ };
119
130
  window.Store.ScheduledEventMsgUtils = {
120
131
  ...window.require('WAWebGenerateEventCallLink'),
121
132
  ...window.require('WAWebSendEventEditMsgAction'),
@@ -134,7 +145,8 @@ exports.ExposeStore = () => {
134
145
  ...window.require('WAWebGroupCreateJob'),
135
146
  ...window.require('WAWebGroupModifyInfoJob'),
136
147
  ...window.require('WAWebExitGroupAction'),
137
- ...window.require('WAWebContactProfilePicThumbBridge')
148
+ ...window.require('WAWebContactProfilePicThumbBridge'),
149
+ ...window.require('WAWebSetPropertyGroupAction')
138
150
  };
139
151
  window.Store.GroupParticipants = {
140
152
  ...window.require('WAWebModifyParticipantsGroupAction'),
@@ -188,6 +200,12 @@ exports.ExposeStore = () => {
188
200
  ...window.require('WAWebSaveContactAction'),
189
201
  ...window.require('WAWebDeleteContactAction')
190
202
  };
203
+ window.Store.StatusUtils = {
204
+ ...window.require('WAWebContactStatusBridge'),
205
+ ...window.require('WAWebSendStatusMsgAction'),
206
+ ...window.require('WAWebRevokeStatusAction'),
207
+ ...window.require('WAWebStatusGatingUtils')
208
+ };
191
209
 
192
210
  if (!window.Store.Chat._find || !window.Store.Chat.findImpl) {
193
211
  window.Store.Chat._find = e => {
@@ -211,23 +229,35 @@ exports.ExposeStore = () => {
211
229
  * @param {Function} callback Modified function
212
230
  */
213
231
  window.injectToFunction = (target, callback) => {
214
- let module = window.require(target.module);
215
-
216
- if (!module) return;
217
-
218
- const path = target.function.split('.');
219
- const funcName = path.pop();
220
- for (const key of path) {
221
- module = module[key];
222
- }
232
+ try {
233
+ let module = window.require(target.module);
234
+ if (!module) return;
235
+
236
+ const path = target.function.split('.');
237
+ const funcName = path.pop();
223
238
 
224
- const originalFunction = module[funcName];
225
- module[funcName] = (...args) => callback(originalFunction, ...args);
239
+ for (const key of path) {
240
+ if (!module[key]) return;
241
+ module = module[key];
242
+ }
243
+
244
+ const originalFunction = module[funcName];
245
+ if (typeof originalFunction !== 'function') return;
246
+
247
+ module[funcName] = (...args) => {
248
+ try {
249
+ return callback(originalFunction, ...args);
250
+ } catch {
251
+ return originalFunction(...args);
252
+ }
253
+ };
254
+
255
+ } catch {
256
+ return;
257
+ }
226
258
  };
227
259
 
228
260
  window.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); });
229
261
 
230
262
  window.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); });
231
-
232
- window.injectToFunction({ module: 'WAWebLid1X1MigrationGating', function: 'Lid1X1MigrationUtils.isLidMigrated' }, () => false);
233
263
  };
@@ -6,7 +6,7 @@ exports.LoadUtils = () => {
6
6
  window.WWebJS.forwardMessage = async (chatId, msgId) => {
7
7
  const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
8
8
  const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
9
- return window.Store.ForwardUtils.forwardMessages(chat, [msg], true, true);
9
+ return await window.Store.ForwardUtils.forwardMessages({'chat': chat, 'msgs' : [msg], 'multicast': true, 'includeCaption': true, 'appendedText' : undefined});
10
10
  };
11
11
 
12
12
  window.WWebJS.sendSeen = async (chatId) => {
@@ -14,6 +14,7 @@ exports.LoadUtils = () => {
14
14
  if (chat) {
15
15
  window.Store.WAWebStreamModel.Stream.markAvailable();
16
16
  await window.Store.SendSeen.sendSeen(chat);
17
+ window.Store.WAWebStreamModel.Stream.markUnavailable();
17
18
  return true;
18
19
  }
19
20
  return false;
@@ -21,10 +22,11 @@ exports.LoadUtils = () => {
21
22
 
22
23
  window.WWebJS.sendMessage = async (chat, content, options = {}) => {
23
24
  const isChannel = window.Store.ChatGetters.getIsNewsletter(chat);
25
+ const isStatus = window.Store.ChatGetters.getIsBroadcast(chat);
24
26
 
25
27
  let mediaOptions = {};
26
28
  if (options.media) {
27
- mediaOptions = options.sendMediaAsSticker && !isChannel
29
+ mediaOptions = options.sendMediaAsSticker && !isChannel && !isStatus
28
30
  ? await window.WWebJS.processStickerData(options.media)
29
31
  : await window.WWebJS.processMediaData(options.media, {
30
32
  forceSticker: options.sendMediaAsSticker,
@@ -32,7 +34,8 @@ exports.LoadUtils = () => {
32
34
  forceVoice: options.sendAudioAsVoice,
33
35
  forceDocument: options.sendMediaAsDocument,
34
36
  forceMediaHd: options.sendMediaAsHd,
35
- sendToChannel: isChannel
37
+ sendToChannel: isChannel,
38
+ sendToStatus: isStatus
36
39
  });
37
40
  mediaOptions.caption = options.caption;
38
41
  content = options.sendMediaAsSticker ? undefined : mediaOptions.preview;
@@ -65,14 +68,7 @@ exports.LoadUtils = () => {
65
68
  }
66
69
 
67
70
  if (options.mentionedJidList) {
68
- options.mentionedJidList = await Promise.all(
69
- options.mentionedJidList.map(async (id) => {
70
- const wid = window.Store.WidFactory.createWid(id);
71
- if (await window.Store.QueryExist(wid)) {
72
- return wid;
73
- }
74
- })
75
- );
71
+ options.mentionedJidList = options.mentionedJidList.map((id) => window.Store.WidFactory.createWid(id));
76
72
  options.mentionedJidList = options.mentionedJidList.filter(Boolean);
77
73
  }
78
74
 
@@ -98,15 +94,15 @@ exports.LoadUtils = () => {
98
94
  delete options.location;
99
95
  }
100
96
 
101
- let _pollOptions = {};
97
+ let pollOptions = {};
102
98
  if (options.poll) {
103
- const { pollName, pollOptions } = options.poll;
99
+ const { pollName, pollOptions: _pollOptions } = options.poll;
104
100
  const { allowMultipleAnswers, messageSecret } = options.poll.options;
105
- _pollOptions = {
101
+ pollOptions = {
106
102
  kind: 'pollCreation',
107
103
  type: 'poll_creation',
108
104
  pollName: pollName,
109
- pollOptions: pollOptions,
105
+ pollOptions: _pollOptions,
110
106
  pollSelectableOptionsCount: allowMultipleAnswers ? 0 : 1,
111
107
  messageSecret:
112
108
  Array.isArray(messageSecret) && messageSecret.length === 32
@@ -131,7 +127,7 @@ exports.LoadUtils = () => {
131
127
  degreesLongitude: 0,
132
128
  name: eventSendOptions.location
133
129
  },
134
- eventJoinLink: await window.Store.ScheduledEventMsgUtils.createEventCallLink(
130
+ eventJoinLink: eventSendOptions.callType === 'none' ? null : await window.Store.ScheduledEventMsgUtils.createEventCallLink(
135
131
  startTimeTs,
136
132
  eventSendOptions.callType
137
133
  ),
@@ -252,6 +248,10 @@ exports.LoadUtils = () => {
252
248
  participant = window.Store.WidFactory.asUserWidOrThrow(from);
253
249
  }
254
250
 
251
+ if (typeof chat.id?.isStatus === 'function' && chat.id.isStatus()) {
252
+ participant = window.Store.WidFactory.asUserWidOrThrow(from);
253
+ }
254
+
255
255
  const newMsgKey = new window.Store.MsgKey({
256
256
  from: from,
257
257
  to: chat.id,
@@ -270,7 +270,7 @@ exports.LoadUtils = () => {
270
270
  id: newMsgKey,
271
271
  ack: 0,
272
272
  body: content,
273
- from: meUser,
273
+ from: from,
274
274
  to: chat.id,
275
275
  local: true,
276
276
  self: 'out',
@@ -282,7 +282,7 @@ exports.LoadUtils = () => {
282
282
  ...(mediaOptions.toJSON ? mediaOptions.toJSON() : {}),
283
283
  ...quotedMsgOptions,
284
284
  ...locationOptions,
285
- ..._pollOptions,
285
+ ...pollOptions,
286
286
  ...eventOptions,
287
287
  ...vcardOptions,
288
288
  ...buttonOptions,
@@ -326,6 +326,37 @@ exports.LoadUtils = () => {
326
326
  return msg;
327
327
  }
328
328
 
329
+ if (isStatus) {
330
+ const { backgroundColor, fontStyle } = extraOptions;
331
+ const isMedia = Object.keys(mediaOptions).length > 0;
332
+ const mediaUpdate = data => window.Store.MediaUpdate(data, mediaOptions);
333
+ const msg = new window.Store.Msg.modelClass({
334
+ ...message,
335
+ author: participant ? participant : null,
336
+ messageSecret: window.crypto.getRandomValues(new Uint8Array(32)),
337
+ cannotBeRanked: window.Store.StatusUtils.canCheckStatusRankingPosterGating()
338
+ });
339
+
340
+ // for text only
341
+ const statusOptions = {
342
+ color: backgroundColor && window.WWebJS.assertColor(backgroundColor) || 0xff7acca5,
343
+ font: fontStyle >= 0 && fontStyle <= 7 && fontStyle || 0,
344
+ text: msg.body
345
+ };
346
+
347
+ await window.Store.StatusUtils[
348
+ isMedia ?
349
+ 'sendStatusMediaMsgAction' : 'sendStatusTextMsgAction'
350
+ ](
351
+ ...(
352
+ isMedia ?
353
+ [msg, mediaUpdate] : [statusOptions]
354
+ )
355
+ );
356
+
357
+ return msg;
358
+ }
359
+
329
360
  const [msgPromise, sendMsgResultPromise] = window.Store.SendMessage.addAndSendMsgToChat(chat, message);
330
361
  await msgPromise;
331
362
 
@@ -339,14 +370,7 @@ exports.LoadUtils = () => {
339
370
  delete options.extraOptions;
340
371
 
341
372
  if (options.mentionedJidList) {
342
- options.mentionedJidList = await Promise.all(
343
- options.mentionedJidList.map(async (id) => {
344
- const wid = window.Store.WidFactory.createWid(id);
345
- if (await window.Store.QueryExist(wid)) {
346
- return wid;
347
- }
348
- })
349
- );
373
+ options.mentionedJidList = options.mentionedJidList.map((id) => window.Store.WidFactory.createWid(id));
350
374
  options.mentionedJidList = options.mentionedJidList.filter(Boolean);
351
375
  }
352
376
 
@@ -420,7 +444,7 @@ exports.LoadUtils = () => {
420
444
  return stickerInfo;
421
445
  };
422
446
 
423
- window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, forceMediaHd, sendToChannel }) => {
447
+ window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, forceMediaHd, sendToChannel, sendToStatus }) => {
424
448
  const file = window.WWebJS.mediaInfoToFile(mediaInfo);
425
449
  const opaqueData = await window.Store.OpaqueData.createFromData(file, file.type);
426
450
  const mediaParams = {
@@ -443,7 +467,11 @@ exports.LoadUtils = () => {
443
467
  isNewsletter: sendToChannel,
444
468
  });
445
469
 
446
- if (forceVoice && mediaData.type === 'ptt') {
470
+ if (!mediaData.filehash) {
471
+ throw new Error('media-fault: sendToChat filehash undefined');
472
+ }
473
+
474
+ if ((forceVoice && mediaData.type === 'ptt') || (sendToStatus && mediaData.type === 'audio')) {
447
475
  const waveform = mediaObject.contentInfo.waveform;
448
476
  mediaData.waveform =
449
477
  waveform || await window.WWebJS.generateWaveform(file);
@@ -458,13 +486,21 @@ exports.LoadUtils = () => {
458
486
 
459
487
  mediaData.renderableUrl = mediaData.mediaBlob.url();
460
488
  mediaObject.consolidate(mediaData.toJSON());
489
+
461
490
  mediaData.mediaBlob.autorelease();
491
+ const shouldUseMediaCache = window.Store.MediaDataUtils.shouldUseMediaCache(
492
+ window.Store.MediaTypes.castToV4(mediaObject.type)
493
+ );
494
+ if (shouldUseMediaCache && mediaData.mediaBlob instanceof window.Store.OpaqueData) {
495
+ const formData = mediaData.mediaBlob.formData();
496
+ window.Store.BlobCache.InMemoryMediaBlobCache.put(mediaObject.filehash, formData);
497
+ }
462
498
 
463
499
  const dataToUpload = {
464
500
  mimetype: mediaData.mimetype,
465
501
  mediaObject,
466
502
  mediaType,
467
- ...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash } : {})
503
+ ...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash() } : {})
468
504
  };
469
505
 
470
506
  const uploadedMedia = !sendToChannel
@@ -838,22 +874,22 @@ exports.LoadUtils = () => {
838
874
  };
839
875
 
840
876
  window.WWebJS.rejectCall = async (peerJid, id) => {
841
- peerJid = peerJid.split('@')[0] + '@s.whatsapp.net';
842
- let userId = window.Store.User.getMaybeMePnUser().user + '@s.whatsapp.net';
877
+ let userId = window.Store.User.getMaybeMePnUser()._serialized;
878
+
843
879
  const stanza = window.Store.SocketWap.wap('call', {
844
880
  id: window.Store.SocketWap.generateId(),
845
- from: window.Store.SocketWap.USER_JID(userId),
846
- to: window.Store.SocketWap.USER_JID(peerJid),
881
+ from: userId,
882
+ to: peerJid,
847
883
  }, [
848
884
  window.Store.SocketWap.wap('reject', {
849
885
  'call-id': id,
850
- 'call-creator': window.Store.SocketWap.USER_JID(peerJid),
886
+ 'call-creator': peerJid,
851
887
  count: '0',
852
888
  })
853
889
  ]);
854
890
  await window.Store.Socket.deprecatedCastStanza(stanza);
855
891
  };
856
-
892
+
857
893
  window.WWebJS.cropAndResizeImage = async (media, options = {}) => {
858
894
  if (!media.mimetype.includes('image'))
859
895
  throw new Error('Media is not an image');
@@ -1166,4 +1202,20 @@ exports.LoadUtils = () => {
1166
1202
 
1167
1203
  return { lid, phone };
1168
1204
  };
1205
+
1206
+ window.WWebJS.assertColor = (hex) => {
1207
+ let color;
1208
+ if (typeof hex === 'number') {
1209
+ color = hex > 0 ? hex : 0xffffffff + parseInt(hex) + 1;
1210
+ } else if (typeof hex === 'string') {
1211
+ let number = hex.trim().replace('#', '');
1212
+ if (number.length <= 6) {
1213
+ number = 'FF' + number.padStart(6, '0');
1214
+ }
1215
+ color = parseInt(number, 16);
1216
+ } else {
1217
+ throw 'Invalid hex color';
1218
+ }
1219
+ return color;
1220
+ };
1169
1221
  };
@@ -14,9 +14,9 @@ class InterfaceController {
14
14
  * @param {string} chatId ID of the chat window that will be opened
15
15
  */
16
16
  async openChatWindow(chatId) {
17
- await this.pupPage.evaluate(async (chatId) => {
17
+ return await this.pupPage.evaluate(async (chatId) => {
18
18
  const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
19
- await window.Store.Cmd.openChatBottom(chat);
19
+ return await window.Store.Cmd.openChatBottom({'chat':chat});
20
20
  }, chatId);
21
21
  }
22
22