whatsapp-web.js 1.31.1-alpha.0 → 1.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Client.js CHANGED
@@ -15,7 +15,7 @@ const { LoadUtils } = require('./util/Injected/Utils');
15
15
  const ChatFactory = require('./factories/ChatFactory');
16
16
  const ContactFactory = require('./factories/ContactFactory');
17
17
  const WebCacheFactory = require('./webCache/WebCacheFactory');
18
- const { Broadcast, Buttons, Call, ClientInfo, Contact, GroupNotification, Label, List, Location, Message, MessageMedia, Poll, PollVote, Reaction } = require('./structures');
18
+ const { ClientInfo, Message, MessageMedia, Contact, Location, Poll, PollVote, GroupNotification, Label, Call, Buttons, List, Reaction, Broadcast, ScheduledEvent } = require('./structures');
19
19
  const NoAuth = require('./authStrategies/NoAuth');
20
20
  const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
21
21
 
@@ -36,6 +36,8 @@ const {exposeFunctionIfAbsent} = require('./util/Puppeteer');
36
36
  * @param {string} options.userAgent - User agent to use in puppeteer
37
37
  * @param {string} options.ffmpegPath - Ffmpeg path to use when formatting videos to webp while sending stickers
38
38
  * @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy.
39
+ * @param {string} options.deviceName - Sets the device name of a current linked device., i.e.: 'TEST'.
40
+ * @param {string} options.browserName - Sets the browser name of a current linked device, i.e.: 'Firefox'.
39
41
  * @param {object} options.proxyAuthentication - Proxy Authentication object.
40
42
  *
41
43
  * @fires Client#qr
@@ -94,7 +96,8 @@ class Client extends EventEmitter {
94
96
  */
95
97
  async inject() {
96
98
  await this.pupPage.waitForFunction('window.Debug?.VERSION != undefined', {timeout: this.options.authTimeoutMs});
97
-
99
+ await this.setDeviceName(this.options.deviceName, this.options.browserName);
100
+ const pairWithPhoneNumber = this.options.pairWithPhoneNumber;
98
101
  const version = await this.getWWebVersion();
99
102
  const isCometOrAbove = parseInt(version.split('.')?.[1]) >= 3000;
100
103
 
@@ -140,41 +143,55 @@ class Client extends EventEmitter {
140
143
  return;
141
144
  }
142
145
 
143
- // Register qr events
144
- let qrRetries = 0;
145
- await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => {
146
- /**
147
- * Emitted when a QR code is received
148
- * @event Client#qr
149
- * @param {string} qr QR Code
150
- */
151
- this.emit(Events.QR_RECEIVED, qr);
152
- if (this.options.qrMaxRetries > 0) {
153
- qrRetries++;
154
- if (qrRetries > this.options.qrMaxRetries) {
155
- this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
156
- await this.destroy();
146
+ // Register qr/code events
147
+ if (pairWithPhoneNumber.phoneNumber) {
148
+ await exposeFunctionIfAbsent(this.pupPage, 'onCodeReceivedEvent', async (code) => {
149
+ /**
150
+ * Emitted when a pairing code is received
151
+ * @event Client#code
152
+ * @param {string} code Code
153
+ * @returns {string} Code that was just received
154
+ */
155
+ this.emit(Events.CODE_RECEIVED, code);
156
+ return code;
157
+ });
158
+ this.requestPairingCode(pairWithPhoneNumber.phoneNumber, pairWithPhoneNumber.showNotification, pairWithPhoneNumber.intervalMs);
159
+ } else {
160
+ let qrRetries = 0;
161
+ await exposeFunctionIfAbsent(this.pupPage, 'onQRChangedEvent', async (qr) => {
162
+ /**
163
+ * Emitted when a QR code is received
164
+ * @event Client#qr
165
+ * @param {string} qr QR Code
166
+ */
167
+ this.emit(Events.QR_RECEIVED, qr);
168
+ if (this.options.qrMaxRetries > 0) {
169
+ qrRetries++;
170
+ if (qrRetries > this.options.qrMaxRetries) {
171
+ this.emit(Events.DISCONNECTED, 'Max qrcode retries reached');
172
+ await this.destroy();
173
+ }
157
174
  }
158
- }
159
- });
175
+ });
160
176
 
161
177
 
162
- await this.pupPage.evaluate(async () => {
163
- const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
164
- const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
165
- const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey);
166
- const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey);
167
- const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey();
168
- const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
169
- const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform;
170
-
171
- window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
172
- window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes
173
- });
178
+ await this.pupPage.evaluate(async () => {
179
+ const registrationInfo = await window.AuthStore.RegistrationUtils.waSignalStore.getRegistrationInfo();
180
+ const noiseKeyPair = await window.AuthStore.RegistrationUtils.waNoiseInfo.get();
181
+ const staticKeyB64 = window.AuthStore.Base64Tools.encodeB64(noiseKeyPair.staticKeyPair.pubKey);
182
+ const identityKeyB64 = window.AuthStore.Base64Tools.encodeB64(registrationInfo.identityKeyPair.pubKey);
183
+ const advSecretKey = await window.AuthStore.RegistrationUtils.getADVSecretKey();
184
+ const platform = window.AuthStore.RegistrationUtils.DEVICE_PLATFORM;
185
+ const getQR = (ref) => ref + ',' + staticKeyB64 + ',' + identityKeyB64 + ',' + advSecretKey + ',' + platform;
186
+
187
+ window.onQRChangedEvent(getQR(window.AuthStore.Conn.ref)); // initial qr
188
+ window.AuthStore.Conn.on('change:ref', (_, ref) => { window.onQRChangedEvent(getQR(ref)); }); // future QR changes
189
+ });
190
+ }
174
191
  }
175
192
 
176
193
  await exposeFunctionIfAbsent(this.pupPage, 'onAuthAppStateChangedEvent', async (state) => {
177
- if (state == 'UNPAIRED_IDLE') {
194
+ if (state == 'UNPAIRED_IDLE' && !pairWithPhoneNumber.phoneNumber) {
178
195
  // refresh qr code
179
196
  window.Store.Cmd.refreshQR();
180
197
  }
@@ -343,15 +360,32 @@ class Client extends EventEmitter {
343
360
  /**
344
361
  * Request authentication via pairing code instead of QR code
345
362
  * @param {string} phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil)
346
- * @param {boolean} showNotification - Show notification to pair on phone number
363
+ * @param {boolean} [showNotification = true] - Show notification to pair on phone number
364
+ * @param {number} [intervalMs = 180000] - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes)
347
365
  * @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH"
348
366
  */
349
- async requestPairingCode(phoneNumber, showNotification = true) {
350
- return await this.pupPage.evaluate(async (phoneNumber, showNotification) => {
351
- window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
352
- await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
353
- return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
354
- }, phoneNumber, showNotification);
367
+ async requestPairingCode(phoneNumber, showNotification = true, intervalMs = 180000) {
368
+ return await this.pupPage.evaluate(async (phoneNumber, showNotification, intervalMs) => {
369
+ const getCode = async () => {
370
+ while (!window.AuthStore.PairingCodeLinkUtils) {
371
+ await new Promise(resolve => setTimeout(resolve, 250));
372
+ }
373
+ window.AuthStore.PairingCodeLinkUtils.setPairingType('ALT_DEVICE_LINKING');
374
+ await window.AuthStore.PairingCodeLinkUtils.initializeAltDeviceLinking();
375
+ return window.AuthStore.PairingCodeLinkUtils.startAltLinkingFlow(phoneNumber, showNotification);
376
+ };
377
+ if (window.codeInterval) {
378
+ clearInterval(window.codeInterval); // remove existing interval
379
+ }
380
+ window.codeInterval = setInterval(async () => {
381
+ if (window.AuthStore.AppState.state != 'UNPAIRED' && window.AuthStore.AppState.state != 'UNPAIRED_IDLE') {
382
+ clearInterval(window.codeInterval);
383
+ return;
384
+ }
385
+ window.onCodeReceivedEvent(await getCode());
386
+ }, intervalMs);
387
+ return window.onCodeReceivedEvent(await getCode());
388
+ }, phoneNumber, showNotification, intervalMs);
355
389
  }
356
390
 
357
391
  /**
@@ -434,6 +468,9 @@ class Client extends EventEmitter {
434
468
  let revoked_msg;
435
469
  if (last_message && msg.id.id === last_message.id.id) {
436
470
  revoked_msg = new Message(this, last_message);
471
+
472
+ if (message.protocolMessageKey)
473
+ revoked_msg.id = { ...message.protocolMessageKey };
437
474
  }
438
475
 
439
476
  /**
@@ -672,14 +709,15 @@ class Client extends EventEmitter {
672
709
  this.emit(Events.MESSAGE_CIPHERTEXT, new Message(this, msg));
673
710
  });
674
711
 
675
- await exposeFunctionIfAbsent(this.pupPage, 'onPollVoteEvent', (vote) => {
676
- const _vote = new PollVote(this, vote);
677
- /**
678
- * Emitted when some poll option is selected or deselected,
679
- * shows a user's current selected option(s) on the poll
680
- * @event Client#vote_update
681
- */
682
- this.emit(Events.VOTE_UPDATE, _vote);
712
+ await exposeFunctionIfAbsent(this.pupPage, 'onPollVoteEvent', (votes) => {
713
+ for (const vote of votes) {
714
+ /**
715
+ * Emitted when some poll option is selected or deselected,
716
+ * shows a user's current selected option(s) on the poll
717
+ * @event Client#vote_update
718
+ */
719
+ this.emit(Events.VOTE_UPDATE, new PollVote(this, vote));
720
+ }
683
721
  });
684
722
 
685
723
  await this.pupPage.evaluate(() => {
@@ -706,10 +744,6 @@ class Client extends EventEmitter {
706
744
  }
707
745
  });
708
746
  window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);});
709
- window.Store.PollVote.on('add', async (vote) => {
710
- const pollVoteModel = await window.WWebJS.getPollVoteModel(vote);
711
- pollVoteModel && window.onPollVoteEvent(pollVoteModel);
712
- });
713
747
 
714
748
  if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620')) {
715
749
  const module = window.Store.AddonReactionTable;
@@ -727,6 +761,39 @@ class Client extends EventEmitter {
727
761
 
728
762
  return ogMethod(...args);
729
763
  }).bind(module);
764
+
765
+ const pollVoteModule = window.Store.AddonPollVoteTable;
766
+ const ogPollVoteMethod = pollVoteModule.bulkUpsert;
767
+
768
+ pollVoteModule.bulkUpsert = (async (...args) => {
769
+ const votes = await Promise.all(args[0].map(async vote => {
770
+ const msgKey = vote.id;
771
+ const parentMsgKey = vote.pollUpdateParentKey;
772
+ const timestamp = vote.t / 1000;
773
+ const sender = vote.author ?? vote.from;
774
+ const senderUserJid = sender._serialized;
775
+
776
+ let parentMessage = window.Store.Msg.get(parentMsgKey._serialized);
777
+ if (!parentMessage) {
778
+ const fetched = await window.Store.Msg.getMessagesById([parentMsgKey._serialized]);
779
+ parentMessage = fetched?.messages?.[0] || null;
780
+ }
781
+
782
+ return {
783
+ ...vote,
784
+ msgKey,
785
+ sender,
786
+ parentMsgKey,
787
+ senderUserJid,
788
+ timestamp,
789
+ parentMessage
790
+ };
791
+ }));
792
+
793
+ window.onPollVoteEvent(votes);
794
+
795
+ return ogPollVoteMethod.apply(pollVoteModule, args);
796
+ }).bind(pollVoteModule);
730
797
  } else {
731
798
  const module = window.Store.createOrUpdateReactionsModule;
732
799
  const ogMethod = module.createOrUpdateReactions;
@@ -813,6 +880,19 @@ class Client extends EventEmitter {
813
880
  });
814
881
  }
815
882
 
883
+ async setDeviceName(deviceName, browserName) {
884
+ (deviceName || browserName) && await this.pupPage.evaluate((deviceName, browserName) => {
885
+ const func = window.require('WAWebMiscBrowserUtils').info;
886
+ window.require('WAWebMiscBrowserUtils').info = () => {
887
+ return {
888
+ ...func(),
889
+ ...(deviceName ? { os: deviceName } : {}),
890
+ ...(browserName ? { name: browserName } : {})
891
+ };
892
+ };
893
+ }, deviceName, browserName);
894
+ }
895
+
816
896
  /**
817
897
  * Mark as seen for the Chat
818
898
  * @param {string} chatId
@@ -853,6 +933,7 @@ class Client extends EventEmitter {
853
933
  * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true).
854
934
  * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null.
855
935
  * @property {boolean} [ignoreQuoteErrors = true] - Should the bot send a quoted message without the quoted message if it fails to get the quote?
936
+ * @property {boolean} [waitUntilMsgSent = false] - Should the bot wait for the message send result?
856
937
  * @property {MessageMedia} [media] - Media to be sent
857
938
  * @property {any} [extra] - Extra options
858
939
  */
@@ -903,6 +984,7 @@ class Client extends EventEmitter {
903
984
  groupMentions: options.groupMentions,
904
985
  invokedBotWid: options.invokedBotWid,
905
986
  ignoreQuoteErrors: options.ignoreQuoteErrors !== false,
987
+ waitUntilMsgSent: options.waitUntilMsgSent || false,
906
988
  extraOptions: options.extra
907
989
  };
908
990
 
@@ -923,6 +1005,9 @@ class Client extends EventEmitter {
923
1005
  } else if (content instanceof Poll) {
924
1006
  internalOptions.poll = content;
925
1007
  content = '';
1008
+ } else if (content instanceof ScheduledEvent) {
1009
+ internalOptions.event = content;
1010
+ content = '';
926
1011
  } else if (content instanceof Contact) {
927
1012
  internalOptions.contactCard = content.id._serialized;
928
1013
  content = '';
@@ -1127,6 +1212,29 @@ class Client extends EventEmitter {
1127
1212
  return null;
1128
1213
  }
1129
1214
 
1215
+ /**
1216
+ * Gets instances of all pinned messages in a chat
1217
+ * @param {string} chatId The chat ID
1218
+ * @returns {Promise<[Message]|[]>}
1219
+ */
1220
+ async getPinnedMessages(chatId) {
1221
+ const pinnedMsgs = await this.pupPage.evaluate(async (chatId) => {
1222
+ const chatWid = window.Store.WidFactory.createWid(chatId);
1223
+ const chat = window.Store.Chat.get(chatWid) ?? await window.Store.Chat.find(chatWid);
1224
+ if (!chat) return [];
1225
+
1226
+ const msgs = await window.Store.PinnedMsgUtils.getTable().equals(['chatId'], chatWid.toString());
1227
+
1228
+ const pinnedMsgs = msgs.map((msg) => window.Store.Msg.get(msg.parentMsgKey));
1229
+
1230
+ return !pinnedMsgs.length
1231
+ ? []
1232
+ : pinnedMsgs.map((msg) => window.WWebJS.getMessageModel(msg));
1233
+ }, chatId);
1234
+
1235
+ return pinnedMsgs.map((msg) => new Message(this, msg));
1236
+ }
1237
+
1130
1238
  /**
1131
1239
  * Returns an object with information about the invite code's group
1132
1240
  * @param {string} inviteCode
@@ -1528,6 +1636,10 @@ class Client extends EventEmitter {
1528
1636
  * @property {string|undefined} parentGroupId The ID of a parent community group to link the newly created group with (won't take an effect if the group is been creating with myself only)
1529
1637
  * @property {boolean} [autoSendInviteV4 = true] If true, the inviteV4 will be sent to those participants who have restricted others from being automatically added to groups, otherwise the inviteV4 won't be sent (true by default)
1530
1638
  * @property {string} [comment = ''] The comment to be added to an inviteV4 (empty string by default)
1639
+ * @property {boolean} [memberAddMode = false] If true, only admins can add members to the group (false by default)
1640
+ * @property {boolean} [membershipApprovalMode = false] If true, group admins will be required to approve anyone who wishes to join the group (false by default)
1641
+ * @property {boolean} [isRestrict = true] If true, only admins can change group group info (true by default)
1642
+ * @property {boolean} [isAnnounce = false] If true, only admins can send messages (false by default)
1531
1643
  */
1532
1644
 
1533
1645
  /**
@@ -1542,7 +1654,12 @@ class Client extends EventEmitter {
1542
1654
  participants.map(p => (p instanceof Contact) ? p.id._serialized : p);
1543
1655
 
1544
1656
  return await this.pupPage.evaluate(async (title, participants, options) => {
1545
- const { messageTimer = 0, parentGroupId, autoSendInviteV4 = true, comment = '' } = options;
1657
+ const {
1658
+ messageTimer = 0,
1659
+ parentGroupId,
1660
+ autoSendInviteV4 = true,
1661
+ comment = '',
1662
+ } = options;
1546
1663
  const participantData = {}, participantWids = [], failedParticipants = [];
1547
1664
  let createGroupResult, parentGroupWid;
1548
1665
 
@@ -1555,7 +1672,9 @@ class Client extends EventEmitter {
1555
1672
 
1556
1673
  for (const participant of participants) {
1557
1674
  const pWid = window.Store.WidFactory.createWid(participant);
1558
- if ((await window.Store.QueryExist(pWid))?.wid) participantWids.push(pWid);
1675
+ if ((await window.Store.QueryExist(pWid))?.wid) {
1676
+ participantWids.push({ phoneNumber: pWid });
1677
+ }
1559
1678
  else failedParticipants.push(participant);
1560
1679
  }
1561
1680
 
@@ -1564,14 +1683,13 @@ class Client extends EventEmitter {
1564
1683
  try {
1565
1684
  createGroupResult = await window.Store.GroupUtils.createGroup(
1566
1685
  {
1567
- 'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode,
1568
- 'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode,
1569
- 'announce': options.announce === undefined ? true : options.announce,
1686
+ 'addressingModeOverride': 'lid',
1687
+ 'memberAddMode': options.memberAddMode ?? false,
1688
+ 'membershipApprovalMode': options.membershipApprovalMode ?? false,
1689
+ 'announce': options.announce ?? false,
1690
+ 'restrict': options.isRestrict !== undefined ? !options.isRestrict : false,
1570
1691
  'ephemeralDuration': messageTimer,
1571
- 'full': undefined,
1572
1692
  'parentGroupId': parentGroupWid,
1573
- 'restrict': options.restrict === undefined ? true : options.restrict,
1574
- 'thumb': undefined,
1575
1693
  'title': title,
1576
1694
  },
1577
1695
  participantWids
@@ -1582,13 +1700,14 @@ class Client extends EventEmitter {
1582
1700
 
1583
1701
  for (const participant of createGroupResult.participants) {
1584
1702
  let isInviteV4Sent = false;
1703
+ participant.wid.server == 'lid' && (participant.wid = window.Store.LidUtils.getPhoneNumber(participant.wid));
1585
1704
  const participantId = participant.wid._serialized;
1586
1705
  const statusCode = participant.error || 200;
1587
1706
 
1588
1707
  if (autoSendInviteV4 && statusCode === 403) {
1589
1708
  window.Store.Contact.gadd(participant.wid, { silent: true });
1590
1709
  const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage(
1591
- await window.Store.Chat.find(participant.wid),
1710
+ window.Store.Chat.get(participant.wid) || await window.Store.Chat.find(participant.wid),
1592
1711
  createGroupResult.wid._serialized,
1593
1712
  createGroupResult.subject,
1594
1713
  participant.invite_code,
@@ -1596,9 +1715,7 @@ class Client extends EventEmitter {
1596
1715
  comment,
1597
1716
  await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid)
1598
1717
  );
1599
- isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6')
1600
- ? addParticipantResult === 'OK'
1601
- : addParticipantResult.messageSendResult === 'OK';
1718
+ isInviteV4Sent = addParticipantResult.messageSendResult === 'OK';
1602
1719
  }
1603
1720
 
1604
1721
  participantData[participantId] = {
@@ -2148,6 +2265,45 @@ class Client extends EventEmitter {
2148
2265
  }, chatId);
2149
2266
  }
2150
2267
 
2268
+ /**
2269
+ * Generates a WhatsApp call link (video call or voice call)
2270
+ * @param {Date} startTime The start time of the call
2271
+ * @param {string} callType The type of a WhatsApp call link to generate, valid values are: `video` | `voice`
2272
+ * @returns {Promise<string>} The WhatsApp call link (https://call.whatsapp.com/video/XxXxXxXxXxXxXx) or an empty string if a generation failed.
2273
+ */
2274
+ async createCallLink(startTime, callType) {
2275
+ if (!['video', 'voice'].includes(callType)) {
2276
+ throw new class CreateCallLinkError extends Error {
2277
+ constructor(m) { super(m); }
2278
+ }('Invalid \'callType\' parameter value is provided. Valid values are: \'voice\' | \'video\'.');
2279
+ }
2280
+
2281
+ startTime = Math.floor(startTime.getTime() / 1000);
2282
+
2283
+ return await this.pupPage.evaluate(async (startTimeTs, callType) => {
2284
+ const response = await window.Store.ScheduledEventMsgUtils.createEventCallLink(startTimeTs, callType);
2285
+ return response ?? '';
2286
+ }, startTime, callType);
2287
+ }
2288
+
2289
+ /**
2290
+ * Sends a response to the scheduled event message, indicating whether a user is going to attend the event or not
2291
+ * @param {number} response The response code to the scheduled event message. Valid values are: `0` for NONE response (removes a previous response) | `1` for GOING | `2` for NOT GOING | `3` for MAYBE going
2292
+ * @param {string} eventMessageId The scheduled event message ID
2293
+ * @returns {Promise<boolean>}
2294
+ */
2295
+ async sendResponseToScheduledEvent(response, eventMessageId) {
2296
+ if (![0, 1, 2, 3].includes(response)) return false;
2297
+
2298
+ return await this.pupPage.evaluate(async (response, msgId) => {
2299
+ const eventMsg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
2300
+ if (!eventMsg) return false;
2301
+
2302
+ await window.Store.ScheduledEventMsgUtils.sendEventResponseMsg(response, eventMsg);
2303
+ return true;
2304
+ }, response, eventMessageId);
2305
+ }
2306
+
2151
2307
  /**
2152
2308
  * Save new contact to user's addressbook or edit the existing one
2153
2309
  * @param {string} phoneNumber The contact's phone number in a format "17182222222", where "1" is a country code
@@ -2180,6 +2336,28 @@ class Client extends EventEmitter {
2180
2336
  return await window.Store.AddressbookContactUtils.deleteContactAction(phoneNumber);
2181
2337
  }, phoneNumber);
2182
2338
  }
2339
+
2340
+ /**
2341
+ * Get lid and phone number for multiple users
2342
+ * @param {string[]} userIds - Array of user IDs
2343
+ * @returns {Promise<Array<{ lid: string, pn: string }>>}
2344
+ */
2345
+ async getContactLidAndPhone(userIds) {
2346
+ return await this.pupPage.evaluate((userIds) => {
2347
+ !Array.isArray(userIds) && (userIds = [userIds]);
2348
+ return userIds.map(userId => {
2349
+ const wid = window.Store.WidFactory.createWid(userId);
2350
+ const isLid = wid.server === 'lid';
2351
+ const lid = isLid ? wid : window.Store.LidUtils.getCurrentLid(wid);
2352
+ const phone = isLid ? window.Store.LidUtils.getPhoneNumber(wid) : wid;
2353
+
2354
+ return {
2355
+ lid: lid._serialized,
2356
+ pn: phone._serialized
2357
+ };
2358
+ });
2359
+ }, userIds);
2360
+ }
2183
2361
  }
2184
2362
 
2185
2363
  module.exports = Client;
@@ -279,6 +279,14 @@ class Chat extends Base {
279
279
  return this.client.addOrRemoveLabels(labelIds, [this.id._serialized]);
280
280
  }
281
281
 
282
+ /**
283
+ * Gets instances of all pinned messages in a chat
284
+ * @returns {Promise<[Message]|[]>}
285
+ */
286
+ async getPinnedMessages() {
287
+ return this.client.getPinnedMessages(this.id._serialized);
288
+ }
289
+
282
290
  /**
283
291
  * Sync chat history conversation
284
292
  * @return {Promise<boolean>} True if operation completed successfully, false otherwise.
@@ -82,7 +82,7 @@ class GroupChat extends Chat {
82
82
 
83
83
  !Array.isArray(participantIds) && (participantIds = [participantIds]);
84
84
  const groupWid = window.Store.WidFactory.createWid(groupId);
85
- const group = await window.Store.Chat.find(groupWid);
85
+ const group = window.Store.Chat.get(groupWid) || (await window.Store.Chat.find(groupWid));
86
86
  const participantWids = participantIds.map((p) => window.Store.WidFactory.createWid(p));
87
87
 
88
88
  const errorCodes = {
@@ -99,8 +99,8 @@ class GroupChat extends Chat {
99
99
  };
100
100
 
101
101
  await window.Store.GroupQueryAndUpdate({ id: groupId });
102
- const groupMetadata = group.groupMetadata;
103
- const groupParticipants = groupMetadata?.participants;
102
+
103
+ let groupParticipants = group.groupMetadata?.participants.serialize();
104
104
 
105
105
  if (!groupParticipants) {
106
106
  return errorCodes.isGroupEmpty;
@@ -110,6 +110,10 @@ class GroupChat extends Chat {
110
110
  return errorCodes.iAmNotAdmin;
111
111
  }
112
112
 
113
+ groupParticipants.map(({ id }) => {
114
+ return id.server === 'lid' ? window.Store.LidUtils.getPhoneNumber(id) : id;
115
+ });
116
+
113
117
  const _getSleepTime = (sleep) => {
114
118
  if (!Array.isArray(sleep) || sleep.length === 2 && sleep[0] === sleep[1]) {
115
119
  return sleep;
@@ -121,16 +125,17 @@ class GroupChat extends Chat {
121
125
  return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0];
122
126
  };
123
127
 
124
- for (const pWid of participantWids) {
128
+ for (let pWid of participantWids) {
125
129
  const pId = pWid._serialized;
126
-
130
+ pWid = pWid.server === 'lid' ? window.Store.LidUtils.getPhoneNumber(pWid) : pWid;
131
+
127
132
  participantData[pId] = {
128
133
  code: undefined,
129
134
  message: undefined,
130
135
  isInviteV4Sent: false
131
136
  };
132
137
 
133
- if (groupParticipants.some(p => p.id._serialized === pId)) {
138
+ if (groupParticipants.some(p => p._serialized === pId)) {
134
139
  participantData[pId].code = 409;
135
140
  participantData[pId].message = errorCodes[409];
136
141
  continue;
@@ -143,7 +148,7 @@ class GroupChat extends Chat {
143
148
  }
144
149
 
145
150
  const rpcResult =
146
- await window.WWebJS.getAddParticipantsRpcResult(groupMetadata, groupWid, pWid);
151
+ await window.WWebJS.getAddParticipantsRpcResult(groupWid, pWid);
147
152
  const { code: rpcResultCode } = rpcResult;
148
153
 
149
154
  participantData[pId].code = rpcResultCode;
@@ -155,7 +160,7 @@ class GroupChat extends Chat {
155
160
  window.Store.Contact.gadd(pWid, { silent: true });
156
161
 
157
162
  if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' &&
158
- (userChat = await window.Store.Chat.find(pWid))) {
163
+ (userChat = window.Store.Chat.get(pWid) || (await window.Store.Chat.find(pWid)))) {
159
164
  const groupName = group.formattedTitle || group.name;
160
165
  const res = await window.Store.GroupInviteV4.sendGroupInviteMessage(
161
166
  userChat,
@@ -166,9 +171,7 @@ class GroupChat extends Chat {
166
171
  comment,
167
172
  await window.WWebJS.getProfilePicThumbToBase64(groupWid)
168
173
  );
169
- isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6')
170
- ? res === 'OK'
171
- : res.messageSendResult === 'OK';
174
+ isInviteV4Sent = res.messageSendResult === 'OK';
172
175
  }
173
176
 
174
177
  participantData[pId].isInviteV4Sent = isInviteV4Sent;
@@ -193,7 +196,10 @@ class GroupChat extends Chat {
193
196
  return await this.client.pupPage.evaluate(async (chatId, participantIds) => {
194
197
  const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
195
198
  const participants = participantIds.map(p => {
196
- return chat.groupMetadata.participants.get(p);
199
+ const wid = window.Store.WidFactory.createWid(p);
200
+ const lid = wid.server!=='lid' ? window.Store.LidUtils.getCurrentLid(wid) : wid;
201
+ const phone = wid.server=='lid' ? window.Store.LidUtils.getPhoneNumber(wid) : wid;
202
+ return chat.groupMetadata.participants.get(lid?._serialized) || chat.groupMetadata.participants.get(phone?._serialized);
197
203
  }).filter(p => Boolean(p));
198
204
  await window.Store.GroupParticipants.removeParticipants(chat, participants);
199
205
  return { status: 200 };
@@ -209,7 +215,10 @@ class GroupChat extends Chat {
209
215
  return await this.client.pupPage.evaluate(async (chatId, participantIds) => {
210
216
  const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
211
217
  const participants = participantIds.map(p => {
212
- return chat.groupMetadata.participants.get(p);
218
+ const wid = window.Store.WidFactory.createWid(p);
219
+ const lid = wid.server!=='lid' ? window.Store.LidUtils.getCurrentLid(wid) : wid;
220
+ const phone = wid.server=='lid' ? window.Store.LidUtils.getPhoneNumber(wid) : wid;
221
+ return chat.groupMetadata.participants.get(lid?._serialized) || chat.groupMetadata.participants.get(phone?._serialized);
213
222
  }).filter(p => Boolean(p));
214
223
  await window.Store.GroupParticipants.promoteParticipants(chat, participants);
215
224
  return { status: 200 };
@@ -225,7 +234,10 @@ class GroupChat extends Chat {
225
234
  return await this.client.pupPage.evaluate(async (chatId, participantIds) => {
226
235
  const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
227
236
  const participants = participantIds.map(p => {
228
- return chat.groupMetadata.participants.get(p);
237
+ const wid = window.Store.WidFactory.createWid(p);
238
+ const lid = wid.server!=='lid' ? window.Store.LidUtils.getCurrentLid(wid) : wid;
239
+ const phone = wid.server=='lid' ? window.Store.LidUtils.getPhoneNumber(wid) : wid;
240
+ return chat.groupMetadata.participants.get(lid?._serialized) || chat.groupMetadata.participants.get(phone?._serialized);
229
241
  }).filter(p => Boolean(p));
230
242
  await window.Store.GroupParticipants.demoteParticipants(chat, participants);
231
243
  return { status: 200 };