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 +20 -10
- package/index.d.ts +129 -6
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/Client.js +193 -50
- package/src/structures/Chat.js +8 -0
- package/src/structures/GroupChat.js +26 -14
- package/src/structures/Message.js +37 -2
- package/src/structures/ScheduledEvent.js +71 -0
- package/src/structures/index.js +2 -1
- package/src/util/Constants.js +8 -1
- package/src/util/Injected/Store.js +9 -2
- package/src/util/Injected/Utils.js +51 -35
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
|
-
|
|
26
|
-
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
1271
|
-
mentions?:
|
|
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
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 {
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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 {
|
|
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)
|
|
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
|
-
'
|
|
1568
|
-
'
|
|
1569
|
-
'
|
|
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 =
|
|
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;
|
package/src/structures/Chat.js
CHANGED
|
@@ -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
|
-
|
|
103
|
-
|
|
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 (
|
|
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.
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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;
|
package/src/structures/index.js
CHANGED
package/src/util/Constants.js
CHANGED
|
@@ -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('
|
|
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.
|
|
874
|
-
data: dataUrl.replace(`data:${options.
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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
|
-
|
|
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
|
|