whatsapp-web.js 1.19.5 → 1.20.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
@@ -1,8 +1,12 @@
1
- const { Client, Location, List, Buttons, LocalAuth} = require('./index');
1
+ const { Client, Location, List, Buttons, LocalAuth } = require('./index');
2
2
 
3
3
  const client = new Client({
4
4
  authStrategy: new LocalAuth(),
5
- puppeteer: { headless: false }
5
+ // proxyAuthentication: { username: 'username', password: 'password' },
6
+ puppeteer: {
7
+ // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'],
8
+ headless: false
9
+ }
6
10
  });
7
11
 
8
12
  client.initialize();
@@ -189,11 +193,11 @@ client.on('message', async msg => {
189
193
  client.interface.openChatWindowAt(quotedMsg.id._serialized);
190
194
  }
191
195
  } else if (msg.body === '!buttons') {
192
- let button = new Buttons('Button body',[{body:'bt1'},{body:'bt2'},{body:'bt3'}],'title','footer');
196
+ let button = new Buttons('Button body', [{ body: 'bt1' }, { body: 'bt2' }, { body: 'bt3' }], 'title', 'footer');
193
197
  client.sendMessage(msg.from, button);
194
198
  } else if (msg.body === '!list') {
195
- let sections = [{title:'sectionTitle',rows:[{title:'ListItem1', description: 'desc'},{title:'ListItem2'}]}];
196
- let list = new List('List body','btnText',sections,'Title','footer');
199
+ let sections = [{ title: 'sectionTitle', rows: [{ title: 'ListItem1', description: 'desc' }, { title: 'ListItem2' }] }];
200
+ let list = new List('List body', 'btnText', sections, 'Title', 'footer');
197
201
  client.sendMessage(msg.from, list);
198
202
  } else if (msg.body === '!reaction') {
199
203
  msg.react('👍');
@@ -231,7 +235,7 @@ client.on('message_ack', (msg, ack) => {
231
235
  ACK_PLAYED: 4
232
236
  */
233
237
 
234
- if(ack == 3) {
238
+ if (ack == 3) {
235
239
  // The message was read
236
240
  }
237
241
  });
@@ -254,7 +258,7 @@ client.on('group_update', (notification) => {
254
258
  });
255
259
 
256
260
  client.on('change_state', state => {
257
- console.log('CHANGE STATE', state );
261
+ console.log('CHANGE STATE', state);
258
262
  });
259
263
 
260
264
  // Change to false if you don't want to reject incoming calls
@@ -270,3 +274,51 @@ client.on('disconnected', (reason) => {
270
274
  console.log('Client was logged out', reason);
271
275
  });
272
276
 
277
+ client.on('contact_changed', async (message, oldId, newId, isContact) => {
278
+ /** The time the event occurred. */
279
+ const eventTime = (new Date(message.timestamp * 1000)).toLocaleString();
280
+
281
+ console.log(
282
+ `The contact ${oldId.slice(0, -5)}` +
283
+ `${!isContact ? ' that participates in group ' +
284
+ `${(await client.getChatById(message.to ?? message.from)).name} ` : ' '}` +
285
+ `changed their phone number\nat ${eventTime}.\n` +
286
+ `Their new phone number is ${newId.slice(0, -5)}.\n`);
287
+
288
+ /**
289
+ * Information about the {@name message}:
290
+ *
291
+ * 1. If a notification was emitted due to a group participant changing their phone number:
292
+ * {@name message.author} is a participant's id before the change.
293
+ * {@name message.recipients[0]} is a participant's id after the change (a new one).
294
+ *
295
+ * 1.1 If the contact who changed their number WAS in the current user's contact list at the time of the change:
296
+ * {@name message.to} is a group chat id the event was emitted in.
297
+ * {@name message.from} is a current user's id that got an notification message in the group.
298
+ * Also the {@name message.fromMe} is TRUE.
299
+ *
300
+ * 1.2 Otherwise:
301
+ * {@name message.from} is a group chat id the event was emitted in.
302
+ * {@name message.to} is @type {undefined}.
303
+ * Also {@name message.fromMe} is FALSE.
304
+ *
305
+ * 2. If a notification was emitted due to a contact changing their phone number:
306
+ * {@name message.templateParams} is an array of two user's ids:
307
+ * the old (before the change) and a new one, stored in alphabetical order.
308
+ * {@name message.from} is a current user's id that has a chat with a user,
309
+ * whos phone number was changed.
310
+ * {@name message.to} is a user's id (after the change), the current user has a chat with.
311
+ */
312
+ });
313
+
314
+ client.on('group_admin_changed', (notification) => {
315
+ if (notification.type === 'promote') {
316
+ /**
317
+ * Emitted when a current user is promoted to an admin.
318
+ * {@link notification.author} is a user who performs the action of promoting/demoting the current user.
319
+ */
320
+ console.log(`You were promoted by ${notification.author}`);
321
+ } else if (notification.type === 'demote')
322
+ /** Emitted when a current user is demoted to a regular user. */
323
+ console.log(`You were demoted by ${notification.author}`);
324
+ });
package/index.d.ts CHANGED
@@ -148,6 +148,12 @@ declare namespace WAWebJS {
148
148
  /** Unmutes the Chat */
149
149
  unmuteChat(chatId: string): Promise<void>
150
150
 
151
+ /** Sets the current user's profile picture */
152
+ setProfilePicture(media: MessageMedia): Promise<boolean>
153
+
154
+ /** Deletes the current user's profile picture */
155
+ deleteProfilePicture(): Promise<boolean>
156
+
151
157
  /** Generic event */
152
158
  on(event: string, listener: (...args: any) => void): this
153
159
 
@@ -192,12 +198,30 @@ declare namespace WAWebJS {
192
198
  notification: GroupNotification
193
199
  ) => void): this
194
200
 
201
+ /** Emitted when a current user is promoted to an admin or demoted to a regular user */
202
+ on(event: 'group_admin_changed', listener: (
203
+ /** GroupNotification with more information about the action */
204
+ notification: GroupNotification
205
+ ) => void): this
206
+
195
207
  /** Emitted when group settings are updated, such as subject, description or picture */
196
208
  on(event: 'group_update', listener: (
197
209
  /** GroupNotification with more information about the action */
198
210
  notification: GroupNotification
199
211
  ) => void): this
200
212
 
213
+ /** Emitted when a contact or a group participant changed their phone number. */
214
+ on(event: 'contact_changed', listener: (
215
+ /** Message with more information about the event. */
216
+ message: Message,
217
+ /** Old user's id. */
218
+ oldId : String,
219
+ /** New user's id. */
220
+ newId : String,
221
+ /** Indicates if a contact or a group participant changed their phone number. */
222
+ isContact : Boolean
223
+ ) => void): this
224
+
201
225
  /** Emitted when media has been uploaded for a message sent by the client */
202
226
  on(event: 'media_uploaded', listener: (
203
227
  /** The message with media that was uploaded */
@@ -217,6 +241,12 @@ declare namespace WAWebJS {
217
241
  /** The new ACK value */
218
242
  ack: MessageAck
219
243
  ) => void): this
244
+
245
+ /** Emitted when a chat unread count changes */
246
+ on(event: 'unread_count', listener: (
247
+ /** The chat that was affected */
248
+ chat: Chat
249
+ ) => void): this
220
250
 
221
251
  /** Emitted when a new message is created, which may include the current user's own messages */
222
252
  on(event: 'message_create', listener: (
@@ -247,6 +277,22 @@ declare namespace WAWebJS {
247
277
  reaction: Reaction
248
278
  ) => void): this
249
279
 
280
+ /** Emitted when a chat is removed */
281
+ on(event: 'chat_removed', listener: (
282
+ /** The chat that was removed */
283
+ chat: Chat
284
+ ) => void): this
285
+
286
+ /** Emitted when a chat is archived/unarchived */
287
+ on(event: 'chat_archived', listener: (
288
+ /** The chat that was archived/unarchived */
289
+ chat: Chat,
290
+ /** State the chat is currently in */
291
+ currState: boolean,
292
+ /** State the chat was previously in */
293
+ prevState: boolean
294
+ ) => void): this
295
+
250
296
  /** Emitted when loading screen is appearing */
251
297
  on(event: 'loading_screen', listener: (percent: string, message: string) => void): this
252
298
 
@@ -341,7 +387,9 @@ declare namespace WAWebJS {
341
387
  userAgent?: string
342
388
  /** Ffmpeg path to use when formating videos to webp while sending stickers
343
389
  * @default 'ffmpeg' */
344
- ffmpegPath?: string
390
+ ffmpegPath?: string,
391
+ /** Object with proxy autentication requirements @default: undefined */
392
+ proxyAuthentication?: {username: string, password: string} | undefined
345
393
  }
346
394
 
347
395
  /**
@@ -498,8 +546,10 @@ declare namespace WAWebJS {
498
546
  MESSAGE_REVOKED_ME = 'message_revoke_me',
499
547
  MESSAGE_ACK = 'message_ack',
500
548
  MEDIA_UPLOADED = 'media_uploaded',
549
+ CONTACT_CHANGED = 'contact_changed',
501
550
  GROUP_JOIN = 'group_join',
502
551
  GROUP_LEAVE = 'group_leave',
552
+ GROUP_ADMIN_CHANGED = 'group_admin_changed',
503
553
  GROUP_UPDATE = 'group_update',
504
554
  QR_RECEIVED = 'qr',
505
555
  LOADING_SCREEN = 'loading_screen',
@@ -637,6 +687,7 @@ declare namespace WAWebJS {
637
687
  * broadcast: false,
638
688
  * fromMe: false,
639
689
  * hasQuotedMsg: false,
690
+ * hasReaction: false,
640
691
  * location: undefined,
641
692
  * mentionedIds: []
642
693
  * }
@@ -666,6 +717,8 @@ declare namespace WAWebJS {
666
717
  hasMedia: boolean,
667
718
  /** Indicates if the message was sent as a reply to another message */
668
719
  hasQuotedMsg: boolean,
720
+ /** Indicates whether there are reactions to the message */
721
+ hasReaction: boolean,
669
722
  /** Indicates the duration of the message in seconds */
670
723
  duration: string,
671
724
  /** ID that represents the message */
@@ -767,6 +820,10 @@ declare namespace WAWebJS {
767
820
  * Gets the payment details associated with a given message
768
821
  */
769
822
  getPayment: () => Promise<Payment>,
823
+ /**
824
+ * Gets the reactions associated with the given message
825
+ */
826
+ getReactions: () => Promise<ReactionList[]>,
770
827
  }
771
828
 
772
829
  /** ID that represents a message */
@@ -1019,6 +1076,8 @@ declare namespace WAWebJS {
1019
1076
  timestamp: number,
1020
1077
  /** Amount of messages unread */
1021
1078
  unreadCount: number,
1079
+ /** Last message fo chat */
1080
+ lastMessage: Message,
1022
1081
 
1023
1082
  /** Archives this chat */
1024
1083
  archive: () => Promise<void>,
@@ -1162,6 +1221,10 @@ declare namespace WAWebJS {
1162
1221
  revokeInvite: () => Promise<void>;
1163
1222
  /** Makes the bot leave the group */
1164
1223
  leave: () => Promise<void>;
1224
+ /** Sets the group's picture.*/
1225
+ setPicture: (media: MessageMedia) => Promise<boolean>;
1226
+ /** Deletes the group's picture */
1227
+ deletePicture: () => Promise<boolean>;
1165
1228
  }
1166
1229
 
1167
1230
  /**
@@ -1365,6 +1428,13 @@ declare namespace WAWebJS {
1365
1428
  senderId: string
1366
1429
  ack?: number
1367
1430
  }
1431
+
1432
+ export type ReactionList = {
1433
+ id: string,
1434
+ aggregateEmoji: string,
1435
+ hasReactionByMe: boolean,
1436
+ senders: Array<Reaction>
1437
+ }
1368
1438
  }
1369
1439
 
1370
1440
  export = WAWebJS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-web.js",
3
- "version": "1.19.5",
3
+ "version": "1.20.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
@@ -10,7 +10,7 @@ const { WhatsWebURL, DefaultOptions, Events, WAState } = require('./util/Constan
10
10
  const { ExposeStore, LoadUtils } = require('./util/Injected');
11
11
  const ChatFactory = require('./factories/ChatFactory');
12
12
  const ContactFactory = require('./factories/ContactFactory');
13
- const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List, Reaction } = require('./structures');
13
+ const { ClientInfo, Message, MessageMedia, Contact, Location, GroupNotification, Label, Call, Buttons, List, Reaction, Chat } = require('./structures');
14
14
  const LegacySessionAuth = require('./authStrategies/LegacySessionAuth');
15
15
  const NoAuth = require('./authStrategies/NoAuth');
16
16
 
@@ -29,6 +29,7 @@ const NoAuth = require('./authStrategies/NoAuth');
29
29
  * @param {string} options.userAgent - User agent to use in puppeteer
30
30
  * @param {string} options.ffmpegPath - Ffmpeg path to use when formating videos to webp while sending stickers
31
31
  * @param {boolean} options.bypassCSP - Sets bypassing of page's Content-Security-Policy.
32
+ * @param {object} options.proxyAuthentication - Proxy Authentication object.
32
33
  *
33
34
  * @fires Client#qr
34
35
  * @fires Client#authenticated
@@ -45,6 +46,8 @@ const NoAuth = require('./authStrategies/NoAuth');
45
46
  * @fires Client#group_update
46
47
  * @fires Client#disconnected
47
48
  * @fires Client#change_state
49
+ * @fires Client#contact_changed
50
+ * @fires Client#group_admin_changed
48
51
  */
49
52
  class Client extends EventEmitter {
50
53
  constructor(options = {}) {
@@ -100,6 +103,10 @@ class Client extends EventEmitter {
100
103
  browser = await puppeteer.launch({...puppeteerOpts, args: browserArgs});
101
104
  page = (await browser.pages())[0];
102
105
  }
106
+
107
+ if (this.options.proxyAuthentication !== undefined) {
108
+ await page.authenticate(this.options.proxyAuthentication);
109
+ }
103
110
 
104
111
  await page.setUserAgent(this.options.userAgent);
105
112
  if (this.options.bypassCSP) await page.setBypassCSP(true);
@@ -317,6 +324,13 @@ class Client extends EventEmitter {
317
324
  * @param {GroupNotification} notification GroupNotification with more information about the action
318
325
  */
319
326
  this.emit(Events.GROUP_LEAVE, notification);
327
+ } else if (msg.subtype === 'promote' || msg.subtype === 'demote') {
328
+ /**
329
+ * Emitted when a current user is promoted to an admin or demoted to a regular user.
330
+ * @event Client#group_admin_changed
331
+ * @param {GroupNotification} notification GroupNotification with more information about the action
332
+ */
333
+ this.emit(Events.GROUP_ADMIN_CHANGED, notification);
320
334
  } else {
321
335
  /**
322
336
  * Emitted when group settings are updated, such as subject, description or picture.
@@ -376,6 +390,36 @@ class Client extends EventEmitter {
376
390
  last_message = msg;
377
391
  }
378
392
 
393
+ /**
394
+ * The event notification that is received when one of
395
+ * the group participants changes thier phone number.
396
+ */
397
+ const isParticipant = msg.type === 'gp2' && msg.subtype === 'modify';
398
+
399
+ /**
400
+ * The event notification that is received when one of
401
+ * the contacts changes thier phone number.
402
+ */
403
+ const isContact = msg.type === 'notification_template' && msg.subtype === 'change_number';
404
+
405
+ if (isParticipant || isContact) {
406
+ /** {@link GroupNotification} object does not provide enough information about this event, so a {@link Message} object is used. */
407
+ const message = new Message(this, msg);
408
+
409
+ const newId = isParticipant ? msg.recipients[0] : msg.to;
410
+ const oldId = isParticipant ? msg.author : msg.templateParams.find(id => id !== newId);
411
+
412
+ /**
413
+ * Emitted when a contact or a group participant changes their phone number.
414
+ * @event Client#contact_changed
415
+ * @param {Message} message Message with more information about the event.
416
+ * @param {String} oldId The user's id (an old one) who changed their phone number
417
+ * and who triggered the notification.
418
+ * @param {String} newId The user's new id after the change.
419
+ * @param {Boolean} isContact Indicates if a contact or a group participant changed their phone number.
420
+ */
421
+ this.emit(Events.CONTACT_CHANGED, message, oldId, newId, isContact);
422
+ }
379
423
  });
380
424
 
381
425
  await page.exposeFunction('onRemoveMessageEvent', (msg) => {
@@ -407,6 +451,15 @@ class Client extends EventEmitter {
407
451
 
408
452
  });
409
453
 
454
+ await page.exposeFunction('onChatUnreadCountEvent', async (data) =>{
455
+ const chat = await this.getChatById(data.id);
456
+
457
+ /**
458
+ * Emitted when the chat unread count changes
459
+ */
460
+ this.emit(Events.UNREAD_COUNT, chat);
461
+ });
462
+
410
463
  await page.exposeFunction('onMessageMediaUploadedEvent', (msg) => {
411
464
 
412
465
  const message = new Message(this, msg);
@@ -507,6 +560,26 @@ class Client extends EventEmitter {
507
560
  }
508
561
  });
509
562
 
563
+ await page.exposeFunction('onRemoveChatEvent', (chat) => {
564
+ /**
565
+ * Emitted when a chat is removed
566
+ * @event Client#chat_removed
567
+ * @param {Chat} chat
568
+ */
569
+ this.emit(Events.CHAT_REMOVED, new Chat(this, chat));
570
+ });
571
+
572
+ await page.exposeFunction('onArchiveChatEvent', (chat, currState, prevState) => {
573
+ /**
574
+ * Emitted when a chat is archived/unarchived
575
+ * @event Client#chat_archived
576
+ * @param {Chat} chat
577
+ * @param {boolean} currState
578
+ * @param {boolean} prevState
579
+ */
580
+ this.emit(Events.CHAT_ARCHIVED, new Chat(this, chat), currState, prevState);
581
+ });
582
+
510
583
  await page.evaluate(() => {
511
584
  window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); });
512
585
  window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); });
@@ -516,6 +589,8 @@ class Client extends EventEmitter {
516
589
  window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); });
517
590
  window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); });
518
591
  window.Store.Call.on('add', (call) => { window.onIncomingCall(call); });
592
+ window.Store.Chat.on('remove', async (chat) => { window.onRemoveChatEvent(await window.WWebJS.getChatModel(chat)); });
593
+ window.Store.Chat.on('change:archive', async (chat, currState, prevState) => { window.onArchiveChatEvent(await window.WWebJS.getChatModel(chat), currState, prevState); });
519
594
  window.Store.Msg.on('add', (msg) => {
520
595
  if (msg.isNewMsg) {
521
596
  if(msg.type === 'ciphertext') {
@@ -526,7 +601,8 @@ class Client extends EventEmitter {
526
601
  }
527
602
  }
528
603
  });
529
-
604
+ window.Store.Chat.on('change:unreadCount', (chat) => {window.onChatUnreadCountEvent(chat);});
605
+
530
606
  {
531
607
  const module = window.Store.createOrUpdateReactionsModule;
532
608
  const ogMethod = module.createOrUpdateReactions;
@@ -1178,6 +1254,31 @@ class Client extends EventEmitter {
1178
1254
 
1179
1255
  return blockedContacts.map(contact => ContactFactory.create(this.client, contact));
1180
1256
  }
1257
+
1258
+ /**
1259
+ * Sets the current user's profile picture.
1260
+ * @param {MessageMedia} media
1261
+ * @returns {Promise<boolean>} Returns true if the picture was properly updated.
1262
+ */
1263
+ async setProfilePicture(media) {
1264
+ const success = await this.pupPage.evaluate((chatid, media) => {
1265
+ return window.WWebJS.setPicture(chatid, media);
1266
+ }, this.info.wid._serialized, media);
1267
+
1268
+ return success;
1269
+ }
1270
+
1271
+ /**
1272
+ * Deletes the current user's profile picture.
1273
+ * @returns {Promise<boolean>} Returns true if the picture was properly deleted.
1274
+ */
1275
+ async deleteProfilePicture() {
1276
+ const success = await this.pupPage.evaluate((chatid) => {
1277
+ return window.WWebJS.deletePicture(chatid);
1278
+ }, this.info.wid._serialized);
1279
+
1280
+ return success;
1281
+ }
1181
1282
  }
1182
1283
 
1183
1284
  module.exports = Client;
@@ -75,6 +75,12 @@ class Chat extends Base {
75
75
  */
76
76
  this.muteExpiration = data.muteExpiration;
77
77
 
78
+ /**
79
+ * Last message fo chat
80
+ * @type {Message}
81
+ */
82
+ this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined;
83
+
78
84
  return super._patch(data);
79
85
  }
80
86
 
@@ -213,6 +213,31 @@ class GroupChat extends Chat {
213
213
  return true;
214
214
  }
215
215
 
216
+ /**
217
+ * Deletes the group's picture.
218
+ * @returns {Promise<boolean>} Returns true if the picture was properly deleted. This can return false if the user does not have the necessary permissions.
219
+ */
220
+ async deletePicture() {
221
+ const success = await this.client.pupPage.evaluate((chatid) => {
222
+ return window.WWebJS.deletePicture(chatid);
223
+ }, this.id._serialized);
224
+
225
+ return success;
226
+ }
227
+
228
+ /**
229
+ * Sets the group's picture.
230
+ * @param {MessageMedia} media
231
+ * @returns {Promise<boolean>} Returns true if the picture was properly updated. This can return false if the user does not have the necessary permissions.
232
+ */
233
+ async setPicture(media) {
234
+ const success = await this.client.pupPage.evaluate((chatid, media) => {
235
+ return window.WWebJS.setPicture(chatid, media);
236
+ }, this.id._serialized, media);
237
+
238
+ return success;
239
+ }
240
+
216
241
  /**
217
242
  * Gets the invite code for a specific group
218
243
  * @returns {Promise<string>} Group's invite code
@@ -5,7 +5,8 @@ const MessageMedia = require('./MessageMedia');
5
5
  const Location = require('./Location');
6
6
  const Order = require('./Order');
7
7
  const Payment = require('./Payment');
8
- const { MessageTypes } = require('../util/Constants');
8
+ const Reaction = require('./Reaction');
9
+ const {MessageTypes} = require('../util/Constants');
9
10
 
10
11
  /**
11
12
  * Represents a Message on WhatsApp
@@ -88,8 +89,7 @@ class Message extends Base {
88
89
  * String that represents from which device type the message was sent
89
90
  * @type {string}
90
91
  */
91
- this.deviceType = data.id.id.length > 21 ? 'android' : data.id.id.substring(0, 2) == '3A' ? 'ios' : 'web';
92
-
92
+ this.deviceType = typeof data.id.id === 'string' && data.id.id.length > 21 ? 'android' : typeof data.id.id === 'string' && data.id.id.substring(0, 2) === '3A' ? 'ios' : 'web';
93
93
  /**
94
94
  * Indicates if the message was forwarded
95
95
  * @type {boolean}
@@ -108,7 +108,7 @@ class Message extends Base {
108
108
  * Indicates if the message is a status update
109
109
  * @type {boolean}
110
110
  */
111
- this.isStatus = data.isStatusV3;
111
+ this.isStatus = data.isStatusV3 || data.id.remote === 'status@broadcast';
112
112
 
113
113
  /**
114
114
  * Indicates if the message was starred
@@ -134,6 +134,12 @@ class Message extends Base {
134
134
  */
135
135
  this.hasQuotedMsg = data.quotedMsg ? true : false;
136
136
 
137
+ /**
138
+ * Indicates whether there are reactions to the message
139
+ * @type {boolean}
140
+ */
141
+ this.hasReaction = data.hasReaction ? true : false;
142
+
137
143
  /**
138
144
  * Indicates the duration of the message in seconds
139
145
  * @type {string}
@@ -436,15 +442,16 @@ class Message extends Base {
436
442
  * @param {?boolean} everyone If true and the message is sent by the current user or the user is an admin, will delete it for everyone in the chat.
437
443
  */
438
444
  async delete(everyone) {
439
- await this.client.pupPage.evaluate((msgId, everyone) => {
445
+ await this.client.pupPage.evaluate(async (msgId, everyone) => {
440
446
  let msg = window.Store.Msg.get(msgId);
441
-
447
+ let chat = await window.Store.Chat.find(msg.id.remote);
448
+
442
449
  const canRevoke = window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg);
443
450
  if (everyone && canRevoke) {
444
- return window.Store.Cmd.sendRevokeMsgs(msg.chat, [msg], { type: msg.id.fromMe ? 'Sender' : 'Admin' });
451
+ return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' });
445
452
  }
446
453
 
447
- return window.Store.Cmd.sendDeleteMsgs(msg.chat, [msg], true);
454
+ return window.Store.Cmd.sendDeleteMsgs(chat, [msg], true);
448
455
  }, this.id._serialized, everyone);
449
456
  }
450
457
 
@@ -452,11 +459,12 @@ class Message extends Base {
452
459
  * Stars this message
453
460
  */
454
461
  async star() {
455
- await this.client.pupPage.evaluate((msgId) => {
462
+ await this.client.pupPage.evaluate(async (msgId) => {
456
463
  let msg = window.Store.Msg.get(msgId);
457
-
464
+
458
465
  if (window.Store.MsgActionChecks.canStarMsg(msg)) {
459
- return window.Store.Cmd.sendStarMsgs(msg.chat, [msg], false);
466
+ let chat = await window.Store.Chat.find(msg.id.remote);
467
+ return window.Store.Cmd.sendStarMsgs(chat, [msg], false);
460
468
  }
461
469
  }, this.id._serialized);
462
470
  }
@@ -465,11 +473,12 @@ class Message extends Base {
465
473
  * Unstars this message
466
474
  */
467
475
  async unstar() {
468
- await this.client.pupPage.evaluate((msgId) => {
476
+ await this.client.pupPage.evaluate(async (msgId) => {
469
477
  let msg = window.Store.Msg.get(msgId);
470
478
 
471
479
  if (window.Store.MsgActionChecks.canStarMsg(msg)) {
472
- return window.Store.Cmd.sendUnstarMsgs(msg.chat, [msg], false);
480
+ let chat = await window.Store.Chat.find(msg.id.remote);
481
+ return window.Store.Cmd.sendUnstarMsgs(chat, [msg], false);
473
482
  }
474
483
  }, this.id._serialized);
475
484
  }
@@ -529,6 +538,44 @@ class Message extends Base {
529
538
  }
530
539
  return undefined;
531
540
  }
541
+
542
+
543
+ /**
544
+ * Reaction List
545
+ * @typedef {Object} ReactionList
546
+ * @property {string} id Original emoji
547
+ * @property {string} aggregateEmoji aggregate emoji
548
+ * @property {boolean} hasReactionByMe Flag who sent the reaction
549
+ * @property {Array<Reaction>} senders Reaction senders, to this message
550
+ */
551
+
552
+ /**
553
+ * Gets the reactions associated with the given message
554
+ * @return {Promise<ReactionList[]>}
555
+ */
556
+ async getReactions() {
557
+ if (!this.hasReaction) {
558
+ return undefined;
559
+ }
560
+
561
+ const reactions = await this.client.pupPage.evaluate(async (msgId) => {
562
+ const msgReactions = await window.Store.Reactions.find(msgId);
563
+ if (!msgReactions || !msgReactions.reactions.length) return null;
564
+ return msgReactions.reactions.serialize();
565
+ }, this.id._serialized);
566
+
567
+ if (!reactions) {
568
+ return undefined;
569
+ }
570
+
571
+ return reactions.map(reaction => {
572
+ reaction.senders = reaction.senders.map(sender => {
573
+ sender.timestamp = Math.round(sender.timestamp / 1000);
574
+ return new Reaction(this.client, sender);
575
+ });
576
+ return reaction;
577
+ });
578
+ }
532
579
  }
533
580
 
534
581
  module.exports = Message;
@@ -13,7 +13,8 @@ exports.DefaultOptions = {
13
13
  takeoverTimeoutMs: 0,
14
14
  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',
15
15
  ffmpegPath: 'ffmpeg',
16
- bypassCSP: false
16
+ bypassCSP: false,
17
+ proxyAuthentication: undefined
17
18
  };
18
19
 
19
20
  /**
@@ -36,15 +37,20 @@ exports.Events = {
36
37
  AUTHENTICATED: 'authenticated',
37
38
  AUTHENTICATION_FAILURE: 'auth_failure',
38
39
  READY: 'ready',
40
+ CHAT_REMOVED: 'chat_removed',
41
+ CHAT_ARCHIVED: 'chat_archived',
39
42
  MESSAGE_RECEIVED: 'message',
40
43
  MESSAGE_CREATE: 'message_create',
41
44
  MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone',
42
45
  MESSAGE_REVOKED_ME: 'message_revoke_me',
43
46
  MESSAGE_ACK: 'message_ack',
47
+ UNREAD_COUNT: 'unread_count',
44
48
  MESSAGE_REACTION: 'message_reaction',
45
49
  MEDIA_UPLOADED: 'media_uploaded',
50
+ CONTACT_CHANGED: 'contact_changed',
46
51
  GROUP_JOIN: 'group_join',
47
52
  GROUP_LEAVE: 'group_leave',
53
+ GROUP_ADMIN_CHANGED: 'group_admin_changed',
48
54
  GROUP_UPDATE: 'group_update',
49
55
  QR_RECEIVED: 'qr',
50
56
  LOADING_SCREEN: 'loading_screen',
@@ -41,7 +41,7 @@ exports.ExposeStore = (moduleRaidStr) => {
41
41
  window.Store.ProfilePic = window.mR.findModule('profilePicResync')[0];
42
42
  window.Store.PresenceUtils = window.mR.findModule('sendPresenceAvailable')[0];
43
43
  window.Store.ChatState = window.mR.findModule('sendChatStateComposing')[0];
44
- window.Store.GroupParticipants = window.mR.findModule('promoteParticipants')[1];
44
+ window.Store.GroupParticipants = window.mR.findModule('promoteParticipants')[0];
45
45
  window.Store.JoinInviteV4 = window.mR.findModule('sendJoinGroupViaInviteV4')[0];
46
46
  window.Store.findCommonGroups = window.mR.findModule('findCommonGroups')[0].findCommonGroups;
47
47
  window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0];
@@ -54,6 +54,8 @@ exports.ExposeStore = (moduleRaidStr) => {
54
54
  window.Store.QuotedMsg = window.mR.findModule('getQuotedMsgObj')[0];
55
55
  window.Store.Socket = window.mR.findModule('deprecatedSendIq')[0];
56
56
  window.Store.SocketWap = window.mR.findModule('wap')[0];
57
+ window.Store.SearchContext = window.mR.findModule('getSearchContext')[0].getSearchContext;
58
+ window.Store.DrawerManager = window.mR.findModule('DrawerManager')[0].DrawerManager;
57
59
  window.Store.StickerTools = {
58
60
  ...window.mR.findModule('toWebpSticker')[0],
59
61
  ...window.mR.findModule('addWebpMetadata')[0]
@@ -62,7 +64,8 @@ exports.ExposeStore = (moduleRaidStr) => {
62
64
  window.Store.GroupUtils = {
63
65
  ...window.mR.findModule('createGroup')[0],
64
66
  ...window.mR.findModule('setGroupDescription')[0],
65
- ...window.mR.findModule('sendExitGroup')[0]
67
+ ...window.mR.findModule('sendExitGroup')[0],
68
+ ...window.mR.findModule('sendSetPicture')[0]
66
69
  };
67
70
 
68
71
  if (!window.Store.Chat._find) {
@@ -242,11 +245,12 @@ exports.LoadUtils = () => {
242
245
 
243
246
  const meUser = window.Store.User.getMaybeMeUser();
244
247
  const isMD = window.Store.MDBackend;
245
-
248
+ const newId = await window.Store.MsgKey.newId();
249
+
246
250
  const newMsgId = new window.Store.MsgKey({
247
251
  from: meUser,
248
252
  to: chat.id,
249
- id: window.Store.MsgKey.newId(),
253
+ id: newId,
250
254
  participant: isMD && chat.id.isGroup() ? meUser : undefined,
251
255
  selfDir: 'out',
252
256
  });
@@ -271,6 +275,7 @@ exports.LoadUtils = () => {
271
275
  ...ephemeralFields,
272
276
  ...locationOptions,
273
277
  ...attOptions,
278
+ ...(attOptions.toJSON ? attOptions.toJSON() : {}),
274
279
  ...quotedMsgOptions,
275
280
  ...vcardOptions,
276
281
  ...buttonOptions,
@@ -426,7 +431,15 @@ exports.LoadUtils = () => {
426
431
  await window.Store.GroupMetadata.update(chatWid);
427
432
  res.groupMetadata = chat.groupMetadata.serialize();
428
433
  }
429
-
434
+
435
+ res.lastMessage = null;
436
+ if (res.msgs && res.msgs.length) {
437
+ const lastMessage = window.Store.Msg.get(chat.lastReceivedKey._serialized);
438
+ if (lastMessage) {
439
+ res.lastMessage = window.WWebJS.getMessageModel(lastMessage);
440
+ }
441
+ }
442
+
430
443
  delete res.msgs;
431
444
  delete res.msgUnsyncedButtonReplyMsgs;
432
445
  delete res.unsyncedButtonReplies;
@@ -625,4 +638,73 @@ exports.LoadUtils = () => {
625
638
  ]);
626
639
  await window.Store.Socket.deprecatedCastStanza(stanza);
627
640
  };
641
+
642
+ window.WWebJS.cropAndResizeImage = async (media, options = {}) => {
643
+ if (!media.mimetype.includes('image'))
644
+ throw new Error('Media is not an image');
645
+
646
+ if (options.mimetype && !options.mimetype.includes('image'))
647
+ delete options.mimetype;
648
+
649
+ options = Object.assign({ size: 640, mimetype: media.mimetype, quality: .75, asDataUrl: false }, options);
650
+
651
+ const img = await new Promise ((resolve, reject) => {
652
+ const img = new Image();
653
+ img.onload = () => resolve(img);
654
+ img.onerror = reject;
655
+ img.src = `data:${media.mimetype};base64,${media.data}`;
656
+ });
657
+
658
+ const sl = Math.min(img.width, img.height);
659
+ const sx = Math.floor((img.width - sl) / 2);
660
+ const sy = Math.floor((img.height - sl) / 2);
661
+
662
+ const canvas = document.createElement('canvas');
663
+ canvas.width = options.size;
664
+ canvas.height = options.size;
665
+
666
+ const ctx = canvas.getContext('2d');
667
+ ctx.drawImage(img, sx, sy, sl, sl, 0, 0, options.size, options.size);
668
+
669
+ const dataUrl = canvas.toDataURL(options.mimetype, options.quality);
670
+
671
+ if (options.asDataUrl)
672
+ return dataUrl;
673
+
674
+ return Object.assign(media, {
675
+ mimetype: options.mimeType,
676
+ data: dataUrl.replace(`data:${options.mimeType};base64,`, '')
677
+ });
678
+ };
679
+
680
+ window.WWebJS.setPicture = async (chatid, media) => {
681
+ const thumbnail = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 96 });
682
+ const profilePic = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 640 });
683
+
684
+ const chatWid = window.Store.WidFactory.createWid(chatid);
685
+ try {
686
+ const collection = window.Store.ProfilePicThumb.get(chatid);
687
+ if (!collection.canSet()) return;
688
+
689
+ const res = await window.Store.GroupUtils.sendSetPicture(chatWid, thumbnail, profilePic);
690
+ return res ? res.status === 200 : false;
691
+ } catch (err) {
692
+ if(err.name === 'ServerStatusCodeError') return false;
693
+ throw err;
694
+ }
695
+ };
696
+
697
+ window.WWebJS.deletePicture = async (chatid) => {
698
+ const chatWid = window.Store.WidFactory.createWid(chatid);
699
+ try {
700
+ const collection = window.Store.ProfilePicThumb.get(chatid);
701
+ if (!collection.canDelete()) return;
702
+
703
+ const res = await window.Store.GroupUtils.requestDeletePicture(chatWid);
704
+ return res ? res.status === 200 : false;
705
+ } catch (err) {
706
+ if(err.name === 'ServerStatusCodeError') return false;
707
+ throw err;
708
+ }
709
+ };
628
710
  };
@@ -50,7 +50,9 @@ class InterfaceController {
50
50
  async openChatWindowAt(msgId) {
51
51
  await this.pupPage.evaluate(async msgId => {
52
52
  let msg = await window.Store.Msg.get(msgId);
53
- await window.Store.Cmd.openChatAt(msg.chat, msg.chat.getSearchContext(msg));
53
+ let chat = await window.Store.Chat.find(msg.id.remote);
54
+ let searchContext = await window.Store.SearchContext(chat,msg);
55
+ await window.Store.Cmd.openChatAt(chat, searchContext);
54
56
  }, msgId);
55
57
  }
56
58
 
@@ -70,7 +72,7 @@ class InterfaceController {
70
72
  */
71
73
  async closeRightDrawer() {
72
74
  await this.pupPage.evaluate(async () => {
73
- await window.Store.Cmd.closeDrawerRight();
75
+ await window.Store.DrawerManager.closeDrawerRight();
74
76
  });
75
77
  }
76
78