whatsapp-web.js 1.34.2 → 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/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>
@@ -556,6 +562,9 @@ declare namespace WAWebJS {
556
562
  /** Timeout for authentication selector in puppeteer
557
563
  * @default 0 */
558
564
  authTimeoutMs?: number,
565
+ /** function to be evaluated On New Document
566
+ * @default undefined */
567
+ evalOnNewDoc?: Function,
559
568
  /** Puppeteer launch options. View docs here: https://github.com/puppeteer/puppeteer/ */
560
569
  puppeteer?: puppeteer.PuppeteerNodeLaunchOptions & puppeteer.ConnectOptions
561
570
  /** Determines how to save and restore sessions. Will use LegacySessionAuth if options.session is set. Otherwise, NoAuth will be used. */
@@ -882,12 +891,14 @@ declare namespace WAWebJS {
882
891
  GROUP_MEMBERSHIP_REQUEST = 'group_membership_request',
883
892
  GROUP_UPDATE = 'group_update',
884
893
  QR_RECEIVED = 'qr',
894
+ CODE_RECEIVED = 'code',
885
895
  LOADING_SCREEN = 'loading_screen',
886
896
  DISCONNECTED = 'disconnected',
887
897
  STATE_CHANGED = 'change_state',
888
898
  BATTERY_CHANGED = 'change_battery',
889
899
  REMOTE_SESSION_SAVED = 'remote_session_saved',
890
- CALL = 'call'
900
+ INCOMING_CALL = 'call',
901
+ VOTE_UPDATE = 'vote_update',
891
902
  }
892
903
 
893
904
  /** Group notification types */
@@ -896,6 +907,8 @@ declare namespace WAWebJS {
896
907
  INVITE = 'invite',
897
908
  REMOVE = 'remove',
898
909
  LEAVE = 'leave',
910
+ PROMOTE = 'promote',
911
+ DEMOTE = 'demote',
899
912
  SUBJECT = 'subject',
900
913
  DESCRIPTION = 'description',
901
914
  PICTURE = 'picture',
@@ -919,6 +932,7 @@ declare namespace WAWebJS {
919
932
  AUDIO = 'audio',
920
933
  VOICE = 'ptt',
921
934
  IMAGE = 'image',
935
+ ALBUM = 'album',
922
936
  VIDEO = 'video',
923
937
  DOCUMENT = 'document',
924
938
  STICKER = 'sticker',
@@ -1210,6 +1224,10 @@ declare namespace WAWebJS {
1210
1224
  * Once the event is canceled, it can not be edited.
1211
1225
  */
1212
1226
  editScheduledEvent: (editedEventObject: Event) => Promise<Message | null>,
1227
+ /**
1228
+ * Send votes to the poll message
1229
+ */
1230
+ vote: (selectedOptions: Array<string>) => Promise<void>,
1213
1231
  }
1214
1232
 
1215
1233
  /** ID that represents a message */
@@ -1273,7 +1291,7 @@ declare namespace WAWebJS {
1273
1291
  endTime?: Date,
1274
1292
  /** The location of the event */
1275
1293
  location?: string,
1276
- /** 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` */
1277
1295
  callType?: string,
1278
1296
  /**
1279
1297
  * Indicates if a scheduled event should be sent as an already canceled
@@ -1300,7 +1318,7 @@ declare namespace WAWebJS {
1300
1318
  messageSecret?: string;
1301
1319
  };
1302
1320
 
1303
- constructor(name: string, startTime: Date, options?: EventSendOptions)
1321
+ constructor(name: string, startTime: Date, options?: ScheduledEventSendOptions)
1304
1322
  }
1305
1323
 
1306
1324
  /** Represents a Poll Vote on WhatsApp */
@@ -1560,6 +1578,8 @@ declare namespace WAWebJS {
1560
1578
  /** Gets the Contact's common groups with you. Returns empty array if you don't have any common group. */
1561
1579
  getCommonGroups: () => Promise<ChatId[]>
1562
1580
 
1581
+ /** Gets the Contact's current status broadcast. */
1582
+ getBroadcast: () => Promise<Broadcast>
1563
1583
  }
1564
1584
 
1565
1585
  export interface ContactId {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-web.js",
3
- "version": "1.34.2",
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);
@@ -1970,7 +2013,7 @@ class Client extends EventEmitter {
1970
2013
 
1971
2014
  return labels.map(data => new Label(this, data));
1972
2015
  }
1973
-
2016
+
1974
2017
  /**
1975
2018
  * Get all current Broadcast
1976
2019
  * @returns {Promise<Array<Broadcast>>}
@@ -1982,6 +2025,49 @@ class Client extends EventEmitter {
1982
2025
  return broadcasts.map(data => new Broadcast(this, data));
1983
2026
  }
1984
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
+
1985
2071
  /**
1986
2072
  * Get Label instance by ID
1987
2073
  * @param {string} labelId
@@ -2425,7 +2511,7 @@ class Client extends EventEmitter {
2425
2511
  async getPollVotes(messageId) {
2426
2512
  const msg = await this.getMessageById(messageId);
2427
2513
  if (!msg) return [];
2428
- if (msg.type != 'poll_creation') throw 'Invalid usage! Can only be used with a pollCreation message';
2514
+ if (msg.type != MessageTypes.POLL_CREATION) throw 'Invalid usage! Can only be used with a pollCreation message';
2429
2515
 
2430
2516
  const pollVotes = await this.pupPage.evaluate( async (msg) => {
2431
2517
  const msgKey = window.Store.MsgKey.fromString(msg.id._serialized);
@@ -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
 
@@ -749,6 +756,32 @@ class Message extends Base {
749
756
  async getPollVotes() {
750
757
  return await this.client.getPollVotes(this.id._serialized);
751
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
+ }
752
785
  }
753
786
 
754
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');
@@ -103,8 +108,9 @@ exports.ExposeStore = () => {
103
108
  window.Store.FindOrCreateChat = window.require('WAWebFindChatAction');
104
109
  window.Store.CustomerNoteUtils = window.require('WAWebNoteAction');
105
110
  window.Store.BusinessGatingUtils = window.require('WAWebBizGatingUtils');
106
- window.Store.PollsVotesSchema = require('WAWebPollsVotesSchema');
107
-
111
+ window.Store.PollsVotesSchema = window.require('WAWebPollsVotesSchema');
112
+ window.Store.PollsSendVote = window.require('WAWebPollsSendVoteMsgAction');
113
+
108
114
  window.Store.Settings = {
109
115
  ...window.require('WAWebUserPrefsGeneral'),
110
116
  ...window.require('WAWebUserPrefsNotifications'),
@@ -139,7 +145,8 @@ exports.ExposeStore = () => {
139
145
  ...window.require('WAWebGroupCreateJob'),
140
146
  ...window.require('WAWebGroupModifyInfoJob'),
141
147
  ...window.require('WAWebExitGroupAction'),
142
- ...window.require('WAWebContactProfilePicThumbBridge')
148
+ ...window.require('WAWebContactProfilePicThumbBridge'),
149
+ ...window.require('WAWebSetPropertyGroupAction')
143
150
  };
144
151
  window.Store.GroupParticipants = {
145
152
  ...window.require('WAWebModifyParticipantsGroupAction'),
@@ -193,6 +200,12 @@ exports.ExposeStore = () => {
193
200
  ...window.require('WAWebSaveContactAction'),
194
201
  ...window.require('WAWebDeleteContactAction')
195
202
  };
203
+ window.Store.StatusUtils = {
204
+ ...window.require('WAWebContactStatusBridge'),
205
+ ...window.require('WAWebSendStatusMsgAction'),
206
+ ...window.require('WAWebRevokeStatusAction'),
207
+ ...window.require('WAWebStatusGatingUtils')
208
+ };
196
209
 
197
210
  if (!window.Store.Chat._find || !window.Store.Chat.findImpl) {
198
211
  window.Store.Chat._find = e => {
@@ -22,10 +22,11 @@ exports.LoadUtils = () => {
22
22
 
23
23
  window.WWebJS.sendMessage = async (chat, content, options = {}) => {
24
24
  const isChannel = window.Store.ChatGetters.getIsNewsletter(chat);
25
+ const isStatus = window.Store.ChatGetters.getIsBroadcast(chat);
25
26
 
26
27
  let mediaOptions = {};
27
28
  if (options.media) {
28
- mediaOptions = options.sendMediaAsSticker && !isChannel
29
+ mediaOptions = options.sendMediaAsSticker && !isChannel && !isStatus
29
30
  ? await window.WWebJS.processStickerData(options.media)
30
31
  : await window.WWebJS.processMediaData(options.media, {
31
32
  forceSticker: options.sendMediaAsSticker,
@@ -33,7 +34,8 @@ exports.LoadUtils = () => {
33
34
  forceVoice: options.sendAudioAsVoice,
34
35
  forceDocument: options.sendMediaAsDocument,
35
36
  forceMediaHd: options.sendMediaAsHd,
36
- sendToChannel: isChannel
37
+ sendToChannel: isChannel,
38
+ sendToStatus: isStatus
37
39
  });
38
40
  mediaOptions.caption = options.caption;
39
41
  content = options.sendMediaAsSticker ? undefined : mediaOptions.preview;
@@ -92,15 +94,15 @@ exports.LoadUtils = () => {
92
94
  delete options.location;
93
95
  }
94
96
 
95
- let _pollOptions = {};
97
+ let pollOptions = {};
96
98
  if (options.poll) {
97
- const { pollName, pollOptions } = options.poll;
99
+ const { pollName, pollOptions: _pollOptions } = options.poll;
98
100
  const { allowMultipleAnswers, messageSecret } = options.poll.options;
99
- _pollOptions = {
101
+ pollOptions = {
100
102
  kind: 'pollCreation',
101
103
  type: 'poll_creation',
102
104
  pollName: pollName,
103
- pollOptions: pollOptions,
105
+ pollOptions: _pollOptions,
104
106
  pollSelectableOptionsCount: allowMultipleAnswers ? 0 : 1,
105
107
  messageSecret:
106
108
  Array.isArray(messageSecret) && messageSecret.length === 32
@@ -125,7 +127,7 @@ exports.LoadUtils = () => {
125
127
  degreesLongitude: 0,
126
128
  name: eventSendOptions.location
127
129
  },
128
- eventJoinLink: await window.Store.ScheduledEventMsgUtils.createEventCallLink(
130
+ eventJoinLink: eventSendOptions.callType === 'none' ? null : await window.Store.ScheduledEventMsgUtils.createEventCallLink(
129
131
  startTimeTs,
130
132
  eventSendOptions.callType
131
133
  ),
@@ -246,6 +248,10 @@ exports.LoadUtils = () => {
246
248
  participant = window.Store.WidFactory.asUserWidOrThrow(from);
247
249
  }
248
250
 
251
+ if (typeof chat.id?.isStatus === 'function' && chat.id.isStatus()) {
252
+ participant = window.Store.WidFactory.asUserWidOrThrow(from);
253
+ }
254
+
249
255
  const newMsgKey = new window.Store.MsgKey({
250
256
  from: from,
251
257
  to: chat.id,
@@ -264,7 +270,7 @@ exports.LoadUtils = () => {
264
270
  id: newMsgKey,
265
271
  ack: 0,
266
272
  body: content,
267
- from: meUser,
273
+ from: from,
268
274
  to: chat.id,
269
275
  local: true,
270
276
  self: 'out',
@@ -276,7 +282,7 @@ exports.LoadUtils = () => {
276
282
  ...(mediaOptions.toJSON ? mediaOptions.toJSON() : {}),
277
283
  ...quotedMsgOptions,
278
284
  ...locationOptions,
279
- ..._pollOptions,
285
+ ...pollOptions,
280
286
  ...eventOptions,
281
287
  ...vcardOptions,
282
288
  ...buttonOptions,
@@ -320,6 +326,37 @@ exports.LoadUtils = () => {
320
326
  return msg;
321
327
  }
322
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
+
323
360
  const [msgPromise, sendMsgResultPromise] = window.Store.SendMessage.addAndSendMsgToChat(chat, message);
324
361
  await msgPromise;
325
362
 
@@ -407,7 +444,7 @@ exports.LoadUtils = () => {
407
444
  return stickerInfo;
408
445
  };
409
446
 
410
- window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, forceMediaHd, sendToChannel }) => {
447
+ window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, forceMediaHd, sendToChannel, sendToStatus }) => {
411
448
  const file = window.WWebJS.mediaInfoToFile(mediaInfo);
412
449
  const opaqueData = await window.Store.OpaqueData.createFromData(file, file.type);
413
450
  const mediaParams = {
@@ -430,7 +467,11 @@ exports.LoadUtils = () => {
430
467
  isNewsletter: sendToChannel,
431
468
  });
432
469
 
433
- 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')) {
434
475
  const waveform = mediaObject.contentInfo.waveform;
435
476
  mediaData.waveform =
436
477
  waveform || await window.WWebJS.generateWaveform(file);
@@ -445,13 +486,21 @@ exports.LoadUtils = () => {
445
486
 
446
487
  mediaData.renderableUrl = mediaData.mediaBlob.url();
447
488
  mediaObject.consolidate(mediaData.toJSON());
489
+
448
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
+ }
449
498
 
450
499
  const dataToUpload = {
451
500
  mimetype: mediaData.mimetype,
452
501
  mediaObject,
453
502
  mediaType,
454
- ...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash } : {})
503
+ ...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash() } : {})
455
504
  };
456
505
 
457
506
  const uploadedMedia = !sendToChannel
@@ -1153,4 +1202,20 @@ exports.LoadUtils = () => {
1153
1202
 
1154
1203
  return { lid, phone };
1155
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
+ };
1156
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