whatsapp-web.js 1.31.0 → 1.32.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/example.js CHANGED
@@ -3,10 +3,26 @@ const { Client, Location, Poll, List, Buttons, LocalAuth } = require('./index');
3
3
  const client = new Client({
4
4
  authStrategy: new LocalAuth(),
5
5
  // proxyAuthentication: { username: 'username', password: 'password' },
6
+ /**
7
+ * This option changes the browser name from defined in user agent to custom.
8
+ */
9
+ // deviceName: 'Your custom name',
10
+ /**
11
+ * This option changes browser type from defined in user agent to yours. It affects the browser icon
12
+ * that is displayed in 'linked devices' section.
13
+ * Valid value are: 'Chrome' | 'Firefox' | 'IE' | 'Opera' | 'Safari' | 'Edge'.
14
+ * If another value is provided, the browser icon in 'linked devices' section will be gray.
15
+ */
16
+ // browserName: 'Firefox',
6
17
  puppeteer: {
7
18
  // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'],
8
19
  headless: false,
9
- }
20
+ },
21
+ // pairWithPhoneNumber: {
22
+ // phoneNumber: '96170100100' // Pair with phone number (format: <COUNTRY_CODE><PHONE_NUMBER>)
23
+ // showNotification: true,
24
+ // intervalMs: 180000 // Time to renew pairing code in milliseconds, defaults to 3 minutes
25
+ // }
10
26
  });
11
27
 
12
28
  // client initialize does not finish at ready now.
@@ -16,19 +32,13 @@ client.on('loading_screen', (percent, message) => {
16
32
  console.log('LOADING SCREEN', percent, message);
17
33
  });
18
34
 
19
- // Pairing code only needs to be requested once
20
- let pairingCodeRequested = false;
21
35
  client.on('qr', async (qr) => {
22
36
  // NOTE: This event will not be fired if a session is specified.
23
37
  console.log('QR RECEIVED', qr);
38
+ });
24
39
 
25
- // paiuting code example
26
- const pairingCodeEnabled = false;
27
- if (pairingCodeEnabled && !pairingCodeRequested) {
28
- const pairingCode = await client.requestPairingCode('96170100100'); // enter the target phone number
29
- console.log('Pairing code enabled, code: '+ pairingCode);
30
- pairingCodeRequested = true;
31
- }
40
+ client.on('code', (code) => {
41
+ console.log('Pairing code:',code);
32
42
  });
33
43
 
34
44
  client.on('authenticated', () => {
package/index.d.ts CHANGED
@@ -99,6 +99,9 @@ declare namespace WAWebJS {
99
99
  /** Get message by ID */
100
100
  getMessageById(messageId: string): Promise<Message>
101
101
 
102
+ /** Gets instances of all pinned messages in a chat */
103
+ getPinnedMessages(chatId: string): Promise<[Message]|[]>
104
+
102
105
  /** Get all current contact instances */
103
106
  getContacts(): Promise<Contact[]>
104
107
 
@@ -157,10 +160,11 @@ declare namespace WAWebJS {
157
160
  /**
158
161
  * Request authentication via pairing code instead of QR code
159
162
  * @param phoneNumber - Phone number in international, symbol-free format (e.g. 12025550108 for US, 551155501234 for Brazil)
160
- * @param showNotification - Show notification to pair on phone number
163
+ * @param showNotification - Show notification to pair on phone number. Defaults to `true`
164
+ * @param intervalMs - The interval in milliseconds on how frequent to generate pairing code (WhatsApp default to 3 minutes). Defaults to `180000`
161
165
  * @returns {Promise<string>} - Returns a pairing code in format "ABCDEFGH"
162
166
  */
163
- requestPairingCode(phoneNumber: string, showNotification = true): Promise<string>
167
+ requestPairingCode(phoneNumber: string, showNotification?: boolean, intervalMs?: number): Promise<string>
164
168
 
165
169
  /** Force reset of connection state for the client */
166
170
  resetState(): Promise<void>
@@ -242,6 +246,9 @@ declare namespace WAWebJS {
242
246
 
243
247
  /** Deletes the contact from user's addressbook */
244
248
  deleteAddressbookContact(honeNumber: string): Promise<void>
249
+
250
+ /** Get Contact lid and phone */
251
+ getContactLidAndPhone(userIds: string[]): Promise<{ lid: string; pn: string }[]>
245
252
 
246
253
  /** Changes and returns the archive state of the Chat */
247
254
  unarchiveChat(chatId: string): Promise<boolean>
@@ -255,6 +262,16 @@ declare namespace WAWebJS {
255
262
  /** Deletes the current user's profile picture */
256
263
  deleteProfilePicture(): Promise<boolean>
257
264
 
265
+ /** Generates a WhatsApp call link (video call or voice call) */
266
+ createCallLink(startTime: Date, callType: string): Promise<string>
267
+
268
+ /**
269
+ * Sends a response to the scheduled event message, indicating whether a user is going to attend the event or not
270
+ * @param response The response code to the event message. Valid values are: `0` for NONE response (removes a previous response) | `1` for GOING | `2` for NOT GOING | `3` for MAYBE going
271
+ * @param eventMessageId The event message ID
272
+ */
273
+ sendResponseToScheduledEvent(response: number, eventMessageId: string): Promise<boolean>
274
+
258
275
  /** Gets an array of membership requests */
259
276
  getGroupMembershipRequests(groupId: string): Promise<Array<GroupMembershipRequest>>
260
277
 
@@ -367,7 +384,7 @@ declare namespace WAWebJS {
367
384
  ack: MessageAck
368
385
  ) => void): this
369
386
 
370
- /** Emitted when an ack event occurrs on message type */
387
+ /** Emitted when an edit event occurrs on message type */
371
388
  on(event: 'message_edit', listener: (
372
389
  /** The message that was affected */
373
390
  message: Message,
@@ -444,6 +461,13 @@ declare namespace WAWebJS {
444
461
  qr: string
445
462
  ) => void): this
446
463
 
464
+ /** Emitted when the phone number pairing code is received */
465
+ on(event: 'code', listener: (
466
+ /** pairing code string
467
+ * @example `8W2WZ3TS` */
468
+ code: string
469
+ ) => void): this
470
+
447
471
  /** Emitted when a call is received */
448
472
  on(event: 'call', listener: (
449
473
  /** The call that started */
@@ -541,8 +565,27 @@ declare namespace WAWebJS {
541
565
  /** Ffmpeg path to use when formatting videos to webp while sending stickers
542
566
  * @default 'ffmpeg' */
543
567
  ffmpegPath?: string,
568
+ /** Sets bypassing of page's Content-Security-Policy
569
+ * @default false */
570
+ bypassCSP?: boolean,
571
+ /** Sets the device name of a current linked device., i.e.: 'TEST' */
572
+ deviceName?: string,
573
+ /**
574
+ * Sets the browser name of a current linked device, i.e.: 'Firefox'.
575
+ * Valid value are: 'Chrome' | 'Firefox' | 'IE' | 'Opera' | 'Safari' | 'Edge'
576
+ */
577
+ browserName?: string,
544
578
  /** Object with proxy autentication requirements @default: undefined */
545
579
  proxyAuthentication?: {username: string, password: string} | undefined
580
+ /** Phone number pairing configuration. Refer the requestPairingCode function of Client.
581
+ * @default
582
+ * {
583
+ * phoneNumber: "",
584
+ * showNotification: true,
585
+ * intervalMs: 180000,
586
+ * }
587
+ */
588
+ pairWithPhoneNumber?: {phoneNumber: string, showNotification?: boolean, intervalMs?: number}
546
589
  }
547
590
 
548
591
  export interface LocalWebCacheOptions {
@@ -681,6 +724,22 @@ declare namespace WAWebJS {
681
724
  * @default ''
682
725
  */
683
726
  comment?: string
727
+ /** If true, only admins can add members to the group (false by default)
728
+ * @default false
729
+ */
730
+ memberAddMode?: boolean,
731
+ /** If true, group admins will be required to approve anyone who wishes to join the group (false by default)
732
+ * @default false
733
+ */
734
+ membershipApprovalMode?: boolean,
735
+ /** If true, only admins can change group group info (true by default)
736
+ * @default true
737
+ */
738
+ isRestrict?: boolean,
739
+ /** If true, only admins can send messages (false by default)
740
+ * @default false
741
+ */
742
+ isAnnounce?: boolean,
684
743
  }
685
744
 
686
745
  /** An object that handles the result for createGroup method */
@@ -870,6 +929,7 @@ declare namespace WAWebJS {
870
929
  REACTION = 'reaction',
871
930
  TEMPLATE_BUTTON_REPLY = 'template_button_reply',
872
931
  POLL_CREATION = 'poll_creation',
932
+ SCHEDULED_EVENT_CREATION = 'scheduled_event_creation',
873
933
  }
874
934
 
875
935
  /** Client status */
@@ -1039,6 +1099,24 @@ declare namespace WAWebJS {
1039
1099
  pollOptions: string[],
1040
1100
  /** False for a single choice poll, true for a multiple choice poll */
1041
1101
  allowMultipleAnswers: boolean,
1102
+ /** The start time of the event in timestamp (10 digits) */
1103
+ eventStartTime: number,
1104
+ /** The end time of the event in timestamp (10 digits) */
1105
+ eventEndTime?: number,
1106
+ /** The event description */
1107
+ eventDescription?: string,
1108
+ /** The location of the event */
1109
+ eventLocation?: {
1110
+ degreesLatitude: number;
1111
+ degreesLongitude: number;
1112
+ name: string;
1113
+ },
1114
+ /** WhatsApp call link (video call or voice call) */
1115
+ eventJoinLink?: string,
1116
+ /** Indicates if an event should be sent as an already canceled */
1117
+ isEventCaneled: boolean,
1118
+ /** The custom message secret, can be used as an event ID */
1119
+ messageSecret?: Array<number>,
1042
1120
  /*
1043
1121
  * Reloads this Message object's data in-place with the latest values from WhatsApp Web.
1044
1122
  * Note that the Message must still be in the web app cache for this to work, otherwise will return null.
@@ -1096,6 +1174,11 @@ declare namespace WAWebJS {
1096
1174
  getReactions: () => Promise<ReactionList[]>,
1097
1175
  /** Edits the current message */
1098
1176
  edit: (content: MessageContent, options?: MessageEditOptions) => Promise<Message | null>,
1177
+ /**
1178
+ * Edits the current ScheduledEvent message.
1179
+ * Once the event is canceled, it can not be edited.
1180
+ */
1181
+ editScheduledEvent: (editedEventObject: Event) => Promise<Message | null>,
1099
1182
  }
1100
1183
 
1101
1184
  /** ID that represents a message */
@@ -1151,6 +1234,44 @@ declare namespace WAWebJS {
1151
1234
  constructor(pollName: string, pollOptions: Array<string>, options?: PollSendOptions)
1152
1235
  }
1153
1236
 
1237
+ /** ScheduledEvent send options */
1238
+ export interface ScheduledEventSendOptions {
1239
+ /** The scheduled event description */
1240
+ description?: string,
1241
+ /** The end time of the event */
1242
+ endTime?: Date,
1243
+ /** The location of the event */
1244
+ location?: string,
1245
+ /** The type of a WhatsApp call link to generate, valid values are: `video` | `voice` */
1246
+ callType?: string,
1247
+ /**
1248
+ * Indicates if a scheduled event should be sent as an already canceled
1249
+ * @default false
1250
+ */
1251
+ isEventCanceled?: boolean
1252
+ /**
1253
+ * The custom message secret, can be used as an event ID
1254
+ * @note It has to be a unique vector with a length of 32
1255
+ */
1256
+ messageSecret: Array<number>|undefined
1257
+ }
1258
+
1259
+ /** Represents a ScheduledEvent on WhatsApp */
1260
+ export class ScheduledEvent {
1261
+ name: string
1262
+ startTimeTs: number
1263
+ eventSendOptions: {
1264
+ description?: string;
1265
+ endTimeTs?: number;
1266
+ location?: string;
1267
+ callType?: string;
1268
+ isEventCanceled?: boolean;
1269
+ messageSecret?: string;
1270
+ };
1271
+
1272
+ constructor(name: string, startTime: Date, options?: EventSendOptions)
1273
+ }
1274
+
1154
1275
  /** Represents a Poll Vote on WhatsApp */
1155
1276
  export interface PollVote {
1156
1277
  /** The person who voted */
@@ -1267,8 +1388,8 @@ declare namespace WAWebJS {
1267
1388
  export interface MessageEditOptions {
1268
1389
  /** Show links preview. Has no effect on multi-device accounts. */
1269
1390
  linkPreview?: boolean
1270
- /** Contacts that are being mentioned in the message */
1271
- mentions?: Contact[]
1391
+ /** User IDs of users that being mentioned in the message */
1392
+ mentions?: string[]
1272
1393
  /** Extra options */
1273
1394
  extra?: any
1274
1395
  }
@@ -1306,7 +1427,7 @@ declare namespace WAWebJS {
1306
1427
  static fromUrl: (url: string, options?: MediaFromURLOptions) => Promise<MessageMedia>
1307
1428
  }
1308
1429
 
1309
- export type MessageContent = string | MessageMedia | Location | Poll | Contact | Contact[] | List | Buttons
1430
+ export type MessageContent = string | MessageMedia | Location | Poll | Contact | Contact[] | List | Buttons | ScheduledEvent
1310
1431
 
1311
1432
  /**
1312
1433
  * Represents a Contact on WhatsApp
@@ -1572,6 +1693,8 @@ declare namespace WAWebJS {
1572
1693
  getLabels: () => Promise<Label[]>,
1573
1694
  /** Add or remove labels to this Chat */
1574
1695
  changeLabels: (labelIds: Array<string | number>) => Promise<void>
1696
+ /** Gets instances of all pinned messages in a chat */
1697
+ getPinnedMessages: () => Promise<[Message]|[]>
1575
1698
  /** Sync history conversation of the Chat */
1576
1699
  syncHistory: () => Promise<boolean>
1577
1700
  }
package/index.js CHANGED
@@ -20,6 +20,7 @@ module.exports = {
20
20
  ClientInfo: require('./src/structures/ClientInfo'),
21
21
  Location: require('./src/structures/Location'),
22
22
  Poll: require('./src/structures/Poll'),
23
+ ScheduledEvent: require('./src/structures/ScheduledEvent'),
23
24
  ProductMetadata: require('./src/structures/ProductMetadata'),
24
25
  List: require('./src/structures/List'),
25
26
  Buttons: require('./src/structures/Buttons'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-web.js",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Library for interacting with the WhatsApp Web API ",
5
5
  "main": "./index.js",
6
6
  "typings": "./index.d.ts",
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
  /**
@@ -813,6 +847,19 @@ class Client extends EventEmitter {
813
847
  });
814
848
  }
815
849
 
850
+ async setDeviceName(deviceName, browserName) {
851
+ (deviceName || browserName) && await this.pupPage.evaluate((deviceName, browserName) => {
852
+ const func = window.require('WAWebMiscBrowserUtils').info;
853
+ window.require('WAWebMiscBrowserUtils').info = () => {
854
+ return {
855
+ ...func(),
856
+ ...(deviceName ? { os: deviceName } : {}),
857
+ ...(browserName ? { name: browserName } : {})
858
+ };
859
+ };
860
+ }, deviceName, browserName);
861
+ }
862
+
816
863
  /**
817
864
  * Mark as seen for the Chat
818
865
  * @param {string} chatId
@@ -923,6 +970,9 @@ class Client extends EventEmitter {
923
970
  } else if (content instanceof Poll) {
924
971
  internalOptions.poll = content;
925
972
  content = '';
973
+ } else if (content instanceof ScheduledEvent) {
974
+ internalOptions.event = content;
975
+ content = '';
926
976
  } else if (content instanceof Contact) {
927
977
  internalOptions.contactCard = content.id._serialized;
928
978
  content = '';
@@ -1127,6 +1177,29 @@ class Client extends EventEmitter {
1127
1177
  return null;
1128
1178
  }
1129
1179
 
1180
+ /**
1181
+ * Gets instances of all pinned messages in a chat
1182
+ * @param {string} chatId The chat ID
1183
+ * @returns {Promise<[Message]|[]>}
1184
+ */
1185
+ async getPinnedMessages(chatId) {
1186
+ const pinnedMsgs = await this.pupPage.evaluate(async (chatId) => {
1187
+ const chatWid = window.Store.WidFactory.createWid(chatId);
1188
+ const chat = window.Store.Chat.get(chatWid) ?? await window.Store.Chat.find(chatWid);
1189
+ if (!chat) return [];
1190
+
1191
+ const msgs = await window.Store.PinnedMsgUtils.getTable().equals(['chatId'], chatWid.toString());
1192
+
1193
+ const pinnedMsgs = msgs.map((msg) => window.Store.Msg.get(msg.parentMsgKey));
1194
+
1195
+ return !pinnedMsgs.length
1196
+ ? []
1197
+ : pinnedMsgs.map((msg) => window.WWebJS.getMessageModel(msg));
1198
+ }, chatId);
1199
+
1200
+ return pinnedMsgs.map((msg) => new Message(this, msg));
1201
+ }
1202
+
1130
1203
  /**
1131
1204
  * Returns an object with information about the invite code's group
1132
1205
  * @param {string} inviteCode
@@ -1528,6 +1601,10 @@ class Client extends EventEmitter {
1528
1601
  * @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
1602
  * @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
1603
  * @property {string} [comment = ''] The comment to be added to an inviteV4 (empty string by default)
1604
+ * @property {boolean} [memberAddMode = false] If true, only admins can add members to the group (false by default)
1605
+ * @property {boolean} [membershipApprovalMode = false] If true, group admins will be required to approve anyone who wishes to join the group (false by default)
1606
+ * @property {boolean} [isRestrict = true] If true, only admins can change group group info (true by default)
1607
+ * @property {boolean} [isAnnounce = false] If true, only admins can send messages (false by default)
1531
1608
  */
1532
1609
 
1533
1610
  /**
@@ -1542,7 +1619,12 @@ class Client extends EventEmitter {
1542
1619
  participants.map(p => (p instanceof Contact) ? p.id._serialized : p);
1543
1620
 
1544
1621
  return await this.pupPage.evaluate(async (title, participants, options) => {
1545
- const { messageTimer = 0, parentGroupId, autoSendInviteV4 = true, comment = '' } = options;
1622
+ const {
1623
+ messageTimer = 0,
1624
+ parentGroupId,
1625
+ autoSendInviteV4 = true,
1626
+ comment = '',
1627
+ } = options;
1546
1628
  const participantData = {}, participantWids = [], failedParticipants = [];
1547
1629
  let createGroupResult, parentGroupWid;
1548
1630
 
@@ -1555,7 +1637,9 @@ class Client extends EventEmitter {
1555
1637
 
1556
1638
  for (const participant of participants) {
1557
1639
  const pWid = window.Store.WidFactory.createWid(participant);
1558
- if ((await window.Store.QueryExist(pWid))?.wid) participantWids.push(pWid);
1640
+ if ((await window.Store.QueryExist(pWid))?.wid) {
1641
+ participantWids.push({ phoneNumber: pWid });
1642
+ }
1559
1643
  else failedParticipants.push(participant);
1560
1644
  }
1561
1645
 
@@ -1564,14 +1648,13 @@ class Client extends EventEmitter {
1564
1648
  try {
1565
1649
  createGroupResult = await window.Store.GroupUtils.createGroup(
1566
1650
  {
1567
- 'memberAddMode': options.memberAddMode === undefined ? true : options.memberAddMode,
1568
- 'membershipApprovalMode': options.membershipApprovalMode === undefined ? false : options.membershipApprovalMode,
1569
- 'announce': options.announce === undefined ? true : options.announce,
1651
+ 'addressingModeOverride': 'lid',
1652
+ 'memberAddMode': options.memberAddMode ?? false,
1653
+ 'membershipApprovalMode': options.membershipApprovalMode ?? false,
1654
+ 'announce': options.announce ?? false,
1655
+ 'restrict': options.isRestrict !== undefined ? !options.isRestrict : false,
1570
1656
  'ephemeralDuration': messageTimer,
1571
- 'full': undefined,
1572
1657
  'parentGroupId': parentGroupWid,
1573
- 'restrict': options.restrict === undefined ? true : options.restrict,
1574
- 'thumb': undefined,
1575
1658
  'title': title,
1576
1659
  },
1577
1660
  participantWids
@@ -1582,13 +1665,14 @@ class Client extends EventEmitter {
1582
1665
 
1583
1666
  for (const participant of createGroupResult.participants) {
1584
1667
  let isInviteV4Sent = false;
1668
+ participant.wid.server == 'lid' && (participant.wid = window.Store.LidUtils.getPhoneNumber(participant.wid));
1585
1669
  const participantId = participant.wid._serialized;
1586
1670
  const statusCode = participant.error || 200;
1587
1671
 
1588
1672
  if (autoSendInviteV4 && statusCode === 403) {
1589
1673
  window.Store.Contact.gadd(participant.wid, { silent: true });
1590
1674
  const addParticipantResult = await window.Store.GroupInviteV4.sendGroupInviteMessage(
1591
- await window.Store.Chat.find(participant.wid),
1675
+ window.Store.Chat.get(participant.wid) || await window.Store.Chat.find(participant.wid),
1592
1676
  createGroupResult.wid._serialized,
1593
1677
  createGroupResult.subject,
1594
1678
  participant.invite_code,
@@ -1596,9 +1680,7 @@ class Client extends EventEmitter {
1596
1680
  comment,
1597
1681
  await window.WWebJS.getProfilePicThumbToBase64(createGroupResult.wid)
1598
1682
  );
1599
- isInviteV4Sent = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2335.6')
1600
- ? addParticipantResult === 'OK'
1601
- : addParticipantResult.messageSendResult === 'OK';
1683
+ isInviteV4Sent = addParticipantResult.messageSendResult === 'OK';
1602
1684
  }
1603
1685
 
1604
1686
  participantData[participantId] = {
@@ -2148,6 +2230,45 @@ class Client extends EventEmitter {
2148
2230
  }, chatId);
2149
2231
  }
2150
2232
 
2233
+ /**
2234
+ * Generates a WhatsApp call link (video call or voice call)
2235
+ * @param {Date} startTime The start time of the call
2236
+ * @param {string} callType The type of a WhatsApp call link to generate, valid values are: `video` | `voice`
2237
+ * @returns {Promise<string>} The WhatsApp call link (https://call.whatsapp.com/video/XxXxXxXxXxXxXx) or an empty string if a generation failed.
2238
+ */
2239
+ async createCallLink(startTime, callType) {
2240
+ if (!['video', 'voice'].includes(callType)) {
2241
+ throw new class CreateCallLinkError extends Error {
2242
+ constructor(m) { super(m); }
2243
+ }('Invalid \'callType\' parameter value is provided. Valid values are: \'voice\' | \'video\'.');
2244
+ }
2245
+
2246
+ startTime = Math.floor(startTime.getTime() / 1000);
2247
+
2248
+ return await this.pupPage.evaluate(async (startTimeTs, callType) => {
2249
+ const response = await window.Store.ScheduledEventMsgUtils.createEventCallLink(startTimeTs, callType);
2250
+ return response ?? '';
2251
+ }, startTime, callType);
2252
+ }
2253
+
2254
+ /**
2255
+ * Sends a response to the scheduled event message, indicating whether a user is going to attend the event or not
2256
+ * @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
2257
+ * @param {string} eventMessageId The scheduled event message ID
2258
+ * @returns {Promise<boolean>}
2259
+ */
2260
+ async sendResponseToScheduledEvent(response, eventMessageId) {
2261
+ if (![0, 1, 2, 3].includes(response)) return false;
2262
+
2263
+ return await this.pupPage.evaluate(async (response, msgId) => {
2264
+ const eventMsg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
2265
+ if (!eventMsg) return false;
2266
+
2267
+ await window.Store.ScheduledEventMsgUtils.sendEventResponseMsg(response, eventMsg);
2268
+ return true;
2269
+ }, response, eventMessageId);
2270
+ }
2271
+
2151
2272
  /**
2152
2273
  * Save new contact to user's addressbook or edit the existing one
2153
2274
  * @param {string} phoneNumber The contact's phone number in a format "17182222222", where "1" is a country code
@@ -2180,6 +2301,28 @@ class Client extends EventEmitter {
2180
2301
  return await window.Store.AddressbookContactUtils.deleteContactAction(phoneNumber);
2181
2302
  }, phoneNumber);
2182
2303
  }
2304
+
2305
+ /**
2306
+ * Get lid and phone number for multiple users
2307
+ * @param {string[]} userIds - Array of user IDs
2308
+ * @returns {Promise<Array<{ lid: string, pn: string }>>}
2309
+ */
2310
+ async getContactLidAndPhone(userIds) {
2311
+ return await this.pupPage.evaluate((userIds) => {
2312
+ !Array.isArray(userIds) && (userIds = [userIds]);
2313
+ return userIds.map(userId => {
2314
+ const wid = window.Store.WidFactory.createWid(userId);
2315
+ const isLid = wid.server === 'lid';
2316
+ const lid = isLid ? wid : window.Store.LidUtils.getCurrentLid(wid);
2317
+ const phone = isLid ? window.Store.LidUtils.getPhoneNumber(wid) : wid;
2318
+
2319
+ return {
2320
+ lid: lid._serialized,
2321
+ pn: phone._serialized
2322
+ };
2323
+ });
2324
+ }, userIds);
2325
+ }
2183
2326
  }
2184
2327
 
2185
2328
  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 };
@@ -7,6 +7,7 @@ const Order = require('./Order');
7
7
  const Payment = require('./Payment');
8
8
  const Reaction = require('./Reaction');
9
9
  const Contact = require('./Contact');
10
+ const ScheduledEvent = require('./ScheduledEvent'); // eslint-disable-line no-unused-vars
10
11
  const { MessageTypes } = require('../util/Constants');
11
12
 
12
13
  /**
@@ -51,7 +52,7 @@ class Message extends Base {
51
52
  * Message content
52
53
  * @type {string}
53
54
  */
54
- this.body = this.hasMedia ? data.caption || '' : data.body || data.pollName || '';
55
+ this.body = this.hasMedia ? data.caption || '' : data.body || data.pollName || data.eventName || '';
55
56
 
56
57
  /**
57
58
  * Message type
@@ -285,7 +286,7 @@ class Message extends Base {
285
286
  this.allowMultipleAnswers = Boolean(!data.pollSelectableOptionsCount);
286
287
  this.pollInvalidated = data.pollInvalidated;
287
288
  this.isSentCagPollCreation = data.isSentCagPollCreation;
288
- this.messageSecret = Object.keys(data.messageSecret).map((key) => data.messageSecret[key]);
289
+ this.messageSecret = data.messageSecret ? Object.keys(data.messageSecret).map((key) => data.messageSecret[key]) : [];
289
290
  }
290
291
 
291
292
  return super._patch(data);
@@ -699,6 +700,40 @@ class Message extends Base {
699
700
  }
700
701
  return null;
701
702
  }
703
+
704
+ /**
705
+ * Edits the current ScheduledEvent message.
706
+ * Once the scheduled event is canceled, it can not be edited.
707
+ * @param {ScheduledEvent} editedEventObject
708
+ * @returns {Promise<?Message>}
709
+ */
710
+ async editScheduledEvent(editedEventObject) {
711
+ if (!this.fromMe) {
712
+ return null;
713
+ }
714
+
715
+ const edittedEventMsg = await this.client.pupPage.evaluate(async (msgId, editedEventObject) => {
716
+ const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
717
+ if (!msg) return null;
718
+
719
+ const { name, startTimeTs, eventSendOptions } = editedEventObject;
720
+ const eventOptions = {
721
+ name: name,
722
+ description: eventSendOptions.description,
723
+ startTime: startTimeTs,
724
+ endTime: eventSendOptions.endTimeTs,
725
+ location: eventSendOptions.location,
726
+ callType: eventSendOptions.callType,
727
+ isEventCanceled: eventSendOptions.isEventCanceled,
728
+ };
729
+
730
+ await window.Store.ScheduledEventMsgUtils.sendEventEditMessage(eventOptions, msg);
731
+ const editedMsg = window.Store.Msg.get(msg.id._serialized);
732
+ return editedMsg?.serialize();
733
+ }, this.id._serialized, editedEventObject);
734
+
735
+ return edittedEventMsg && new Message(this.client, edittedEventMsg);
736
+ }
702
737
  }
703
738
 
704
739
  module.exports = Message;
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * ScheduledEvent send options
5
+ * @typedef {Object} ScheduledEventSendOptions
6
+ * @property {?string} description The scheduled event description
7
+ * @property {?Date} endTime The end time of the event
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`
10
+ * @property {boolean} [isEventCanceled = false] Indicates if a scheduled event should be sent as an already canceled
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
+ */
13
+
14
+ /** Represents a ScheduledEvent on WhatsApp */
15
+ class ScheduledEvent {
16
+ /**
17
+ * @param {string} name
18
+ * @param {Date} startTime
19
+ * @param {ScheduledEventSendOptions} options
20
+ */
21
+ constructor(name, startTime, options = {}) {
22
+ /**
23
+ * The name of the event
24
+ * @type {string}
25
+ */
26
+ this.name = this._validateInputs('name', name).trim();
27
+
28
+ /**
29
+ * The start time of the event
30
+ * @type {number}
31
+ */
32
+ this.startTimeTs = Math.floor(startTime.getTime() / 1000);
33
+
34
+ /**
35
+ * The send options for the event
36
+ * @type {Object}
37
+ */
38
+ this.eventSendOptions = {
39
+ description: options.description?.trim(),
40
+ endTimeTs: options.endTime ? Math.floor(options.endTime.getTime() / 1000) : null,
41
+ location: options.location?.trim(),
42
+ callType: this._validateInputs('callType', options.callType),
43
+ isEventCanceled: options.isEventCanceled ?? false,
44
+ messageSecret: options.messageSecret
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Inner function to validate input values
50
+ * @param {string} propName The property name to validate the value of
51
+ * @param {string | number} propValue The property value to validate
52
+ * @returns {string | number} The property value if a validation succeeded
53
+ */
54
+ _validateInputs(propName, propValue) {
55
+ if (propName === 'name' && !propValue) {
56
+ throw new class CreateScheduledEventError extends Error {
57
+ constructor(m) { super(m); }
58
+ }(`Empty '${propName}' parameter value is provided.`);
59
+ }
60
+
61
+ if (propName === 'callType' && propValue && !['video', 'voice'].includes(propValue)) {
62
+ throw new class CreateScheduledEventError extends Error {
63
+ constructor(m) { super(m); }
64
+ }(`Invalid '${propName}' parameter value is provided. Valid values are: 'voice' | 'video'.`);
65
+ }
66
+
67
+ return propValue;
68
+ }
69
+ }
70
+
71
+ module.exports = ScheduledEvent;
@@ -22,5 +22,6 @@ module.exports = {
22
22
  Reaction: require('./Reaction'),
23
23
  Poll: require('./Poll'),
24
24
  PollVote: require('./PollVote'),
25
- Broadcast: require('./Broadcast')
25
+ Broadcast: require('./Broadcast'),
26
+ ScheduledEvent: require('./ScheduledEvent'),
26
27
  };
@@ -18,7 +18,12 @@ exports.DefaultOptions = {
18
18
  userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36',
19
19
  ffmpegPath: 'ffmpeg',
20
20
  bypassCSP: false,
21
- proxyAuthentication: undefined
21
+ proxyAuthentication: undefined,
22
+ pairWithPhoneNumber: {
23
+ phoneNumber: '',
24
+ showNotification: true,
25
+ intervalMs: 180000,
26
+ },
22
27
  };
23
28
 
24
29
  /**
@@ -60,6 +65,7 @@ exports.Events = {
60
65
  GROUP_MEMBERSHIP_REQUEST: 'group_membership_request',
61
66
  GROUP_UPDATE: 'group_update',
62
67
  QR_RECEIVED: 'qr',
68
+ CODE_RECEIVED: 'code',
63
69
  LOADING_SCREEN: 'loading_screen',
64
70
  DISCONNECTED: 'disconnected',
65
71
  STATE_CHANGED: 'change_state',
@@ -111,6 +117,7 @@ exports.MessageTypes = {
111
117
  REACTION: 'reaction',
112
118
  TEMPLATE_BUTTON_REPLY: 'template_button_reply',
113
119
  POLL_CREATION: 'poll_creation',
120
+ SCHEDULED_EVENT_CREATION: 'scheduled_event_creation',
114
121
  };
115
122
 
116
123
  /**
@@ -88,7 +88,6 @@ exports.ExposeStore = () => {
88
88
  window.Store.WidToJid = window.require('WAWebWidToJid');
89
89
  window.Store.JidToWid = window.require('WAWebJidToWid');
90
90
  window.Store.getMsgInfo = window.require('WAWebApiMessageInfoStore').queryMsgInfo;
91
- window.Store.pinUnpinMsg = window.require('WAWebSendPinMessageAction').sendPinInChatMsg;
92
91
  window.Store.QueryExist = window.require('WAWebQueryExistsJob').queryWidExists;
93
92
  window.Store.ReplyUtils = window.require('WAWebMsgReply');
94
93
  window.Store.BotSecret = window.require('WAWebBotMessageSecret');
@@ -97,8 +96,11 @@ exports.ExposeStore = () => {
97
96
  window.Store.DeviceList = window.require('WAWebApiDeviceList');
98
97
  window.Store.HistorySync = window.require('WAWebSendNonMessageDataRequest');
99
98
  window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode;
99
+ window.Store.PinnedMsgUtils = window.require('WAWebPinInChatSchema');
100
100
  window.Store.ChatGetters = window.require('WAWebChatGetters');
101
+ window.Store.PinnedMsgUtils = window.require('WAWebSendPinMessageAction');
101
102
  window.Store.UploadUtils = window.require('WAWebUploadManager');
103
+ window.Store.WAWebStreamModel = window.require('WAWebStreamModel');
102
104
 
103
105
  window.Store.Settings = {
104
106
  ...window.require('WAWebUserPrefsGeneral'),
@@ -110,7 +112,12 @@ exports.ExposeStore = () => {
110
112
  ...window.require('WAPhoneFindCC')
111
113
  };
112
114
  window.Store.ForwardUtils = {
113
- ...window.require('WAWebForwardMessagesToChat')
115
+ ...window.require('WAWebChatForwardMessage')
116
+ };
117
+ window.Store.ScheduledEventMsgUtils = {
118
+ ...window.require('WAWebGenerateEventCallLink'),
119
+ ...window.require('WAWebSendEventEditMsgAction'),
120
+ ...window.require('WAWebSendEventResponseMsgAction')
114
121
  };
115
122
  window.Store.VCard = {
116
123
  ...window.require('WAWebFrontendVcardUtils'),
@@ -6,17 +6,13 @@ 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
-
10
- if (window.compareWwebVersions(window.Debug.VERSION, '>', '2.3000.0')) {
11
- return window.Store.ForwardUtils.forwardMessagesToChats([msg], [chat], true);
12
- } else {
13
- return chat.forwardMessages([msg]);
14
- }
9
+ return window.Store.ForwardUtils.forwardMessages(chat, [msg], true, true);
15
10
  };
16
11
 
17
12
  window.WWebJS.sendSeen = async (chatId) => {
18
13
  const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
19
14
  if (chat) {
15
+ window.Store.WAWebStreamModel.Stream.markAvailable();
20
16
  await window.Store.SendSeen.sendSeen(chat);
21
17
  return true;
22
18
  }
@@ -120,6 +116,34 @@ exports.LoadUtils = () => {
120
116
  delete options.poll;
121
117
  }
122
118
 
119
+ let eventOptions = {};
120
+ if (options.event) {
121
+ const { name, startTimeTs, eventSendOptions } = options.event;
122
+ const { messageSecret } = eventSendOptions;
123
+ eventOptions = {
124
+ type: 'event_creation',
125
+ eventName: name,
126
+ eventDescription: eventSendOptions.description,
127
+ eventStartTime: startTimeTs,
128
+ eventEndTime: eventSendOptions.endTimeTs,
129
+ eventLocation: eventSendOptions.location && {
130
+ degreesLatitude: 0,
131
+ degreesLongitude: 0,
132
+ name: eventSendOptions.location
133
+ },
134
+ eventJoinLink: await window.Store.ScheduledEventMsgUtils.createEventCallLink(
135
+ startTimeTs,
136
+ eventSendOptions.callType
137
+ ),
138
+ isEventCanceled: eventSendOptions.isEventCanceled,
139
+ messageSecret:
140
+ Array.isArray(messageSecret) && messageSecret.length === 32
141
+ ? new Uint8Array(messageSecret)
142
+ : window.crypto.getRandomValues(new Uint8Array(32)),
143
+ };
144
+ delete options.event;
145
+ }
146
+
123
147
  let vcardOptions = {};
124
148
  if (options.contactCard) {
125
149
  let contact = window.Store.Contact.get(options.contactCard);
@@ -259,6 +283,7 @@ exports.LoadUtils = () => {
259
283
  ...quotedMsgOptions,
260
284
  ...locationOptions,
261
285
  ..._pollOptions,
286
+ ...eventOptions,
262
287
  ...vcardOptions,
263
288
  ...buttonOptions,
264
289
  ...listOptions,
@@ -870,8 +895,8 @@ exports.LoadUtils = () => {
870
895
  return dataUrl;
871
896
 
872
897
  return Object.assign(media, {
873
- mimetype: options.mimeType,
874
- data: dataUrl.replace(`data:${options.mimeType};base64,`, '')
898
+ mimetype: options.mimetype,
899
+ data: dataUrl.replace(`data:${options.mimetype};base64,`, '')
875
900
  });
876
901
  };
877
902
 
@@ -940,30 +965,14 @@ exports.LoadUtils = () => {
940
965
  return undefined;
941
966
  };
942
967
 
943
- window.WWebJS.getAddParticipantsRpcResult = async (groupMetadata, groupWid, participantWid) => {
944
- const participantLidArgs = groupMetadata?.isLidAddressingMode
945
- ? {
946
- phoneNumber: participantWid,
947
- lid: window.Store.LidUtils.getCurrentLid(participantWid)
948
- }
949
- : { phoneNumber: participantWid };
950
-
968
+ window.WWebJS.getAddParticipantsRpcResult = async (groupWid, participantWid) => {
951
969
  const iqTo = window.Store.WidToJid.widToGroupJid(groupWid);
952
970
 
953
- const participantArgs =
954
- participantLidArgs.lid
955
- ? [{
956
- participantJid: window.Store.WidToJid.widToUserJid(participantLidArgs.lid),
957
- phoneNumberMixinArgs: {
958
- anyPhoneNumber: window.Store.WidToJid.widToUserJid(participantLidArgs.phoneNumber)
959
- }
960
- }]
961
- : [{
962
- participantJid: window.Store.WidToJid.widToUserJid(participantLidArgs.phoneNumber)
963
- }];
971
+ const participantArgs = [{
972
+ participantJid: window.Store.WidToJid.widToUserJid(participantWid)
973
+ }];
964
974
 
965
975
  let rpcResult, resultArgs;
966
- const isOldImpl = window.compareWwebVersions(window.Debug.VERSION, '<=', '2.2335.9');
967
976
  const data = {
968
977
  name: undefined,
969
978
  code: undefined,
@@ -973,12 +982,10 @@ exports.LoadUtils = () => {
973
982
 
974
983
  try {
975
984
  rpcResult = await window.Store.GroupParticipants.sendAddParticipantsRPC({ participantArgs, iqTo });
976
- resultArgs = isOldImpl
977
- ? rpcResult.value.addParticipant[0].addParticipantsParticipantMixins
978
- : rpcResult.value.addParticipant[0]
979
- .addParticipantsParticipantAddedOrNonRegisteredWaUserParticipantErrorLidResponseMixinGroup
980
- .value
981
- .addParticipantsParticipantMixins;
985
+ resultArgs = rpcResult.value.addParticipant[0]
986
+ .addParticipantsParticipantAddedOrNonRegisteredWaUserParticipantErrorLidResponseMixinGroup
987
+ .value
988
+ .addParticipantsParticipantMixins;
982
989
  } catch (err) {
983
990
  data.code = 400;
984
991
  return data;
@@ -1122,7 +1129,16 @@ exports.LoadUtils = () => {
1122
1129
  window.WWebJS.pinUnpinMsgAction = async (msgId, action, duration) => {
1123
1130
  const message = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
1124
1131
  if (!message) return false;
1125
- const response = await window.Store.pinUnpinMsg(message, action, duration);
1132
+
1133
+ if (typeof duration !== 'number') return false;
1134
+
1135
+ const originalFunction = window.require('WAWebPinMsgConstants').getPinExpiryDuration;
1136
+ window.require('WAWebPinMsgConstants').getPinExpiryDuration = () => duration;
1137
+
1138
+ const response = await window.Store.PinnedMsgUtils.sendPinInChatMsg(message, action, duration);
1139
+
1140
+ window.require('WAWebPinMsgConstants').getPinExpiryDuration = originalFunction;
1141
+
1126
1142
  return response.messageSendResult === 'OK';
1127
1143
  };
1128
1144