whatsapp-web.js 1.28.0 → 1.29.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.
@@ -2,15 +2,20 @@
2
2
 
3
3
  const PrivateChat = require('../structures/PrivateChat');
4
4
  const GroupChat = require('../structures/GroupChat');
5
+ const Channel = require('../structures/Channel');
5
6
 
6
7
  class ChatFactory {
7
8
  static create(client, data) {
8
- if(data.isGroup) {
9
+ if (data.isGroup) {
9
10
  return new GroupChat(client, data);
10
11
  }
12
+
13
+ if (data.isChannel) {
14
+ return new Channel(client, data);
15
+ }
11
16
 
12
17
  return new PrivateChat(client, data);
13
18
  }
14
19
  }
15
20
 
16
- module.exports = ChatFactory;
21
+ module.exports = ChatFactory;
@@ -43,7 +43,7 @@ class Broadcast extends Base {
43
43
  * Messages statuses
44
44
  * @type {Message[]}
45
45
  */
46
- this.msgs = data.msgs.map(msg => new Message(this.client, msg));
46
+ this.msgs = data.msgs?.map(msg => new Message(this.client, msg));
47
47
 
48
48
  return super._patch(data);
49
49
  }
@@ -0,0 +1,382 @@
1
+ 'use strict';
2
+
3
+ const Base = require('./Base');
4
+ const Message = require('./Message');
5
+
6
+ /**
7
+ * Channel ID structure
8
+ * @typedef {Object} ChannelId
9
+ * @property {string} server
10
+ * @property {string} user
11
+ * @property {string} _serialized
12
+ */
13
+
14
+ /**
15
+ * Represents a Channel on WhatsApp
16
+ * @extends {Base}
17
+ */
18
+ class Channel extends Base {
19
+ constructor(client, data) {
20
+ super(client);
21
+
22
+ if (data) this._patch(data);
23
+ }
24
+
25
+ _patch(data) {
26
+ this.channelMetadata = data.channelMetadata;
27
+
28
+ /**
29
+ * ID that represents the channel
30
+ * @type {ChannelId}
31
+ */
32
+ this.id = data.id;
33
+
34
+ /**
35
+ * Title of the channel
36
+ * @type {string}
37
+ */
38
+ this.name = data.name;
39
+
40
+ /**
41
+ * The channel description
42
+ * @type {string}
43
+ */
44
+ this.description = data.channelMetadata.description;
45
+
46
+ /**
47
+ * Indicates if it is a Channel
48
+ * @type {boolean}
49
+ */
50
+ this.isChannel = data.isChannel;
51
+
52
+ /**
53
+ * Indicates if it is a Group
54
+ * @type {boolean}
55
+ */
56
+ this.isGroup = data.isGroup;
57
+
58
+ /**
59
+ * Indicates if the channel is readonly
60
+ * @type {boolean}
61
+ */
62
+ this.isReadOnly = data.isReadOnly;
63
+
64
+ /**
65
+ * Amount of messages unread
66
+ * @type {number}
67
+ */
68
+ this.unreadCount = data.unreadCount;
69
+
70
+ /**
71
+ * Unix timestamp for when the last activity occurred
72
+ * @type {number}
73
+ */
74
+ this.timestamp = data.t;
75
+
76
+ /**
77
+ * Indicates if the channel is muted or not
78
+ * @type {boolean}
79
+ */
80
+ this.isMuted = data.isMuted;
81
+
82
+ /**
83
+ * Unix timestamp for when the mute expires
84
+ * @type {number}
85
+ */
86
+ this.muteExpiration = data.muteExpiration;
87
+
88
+ /**
89
+ * Last message in the channel
90
+ * @type {Message}
91
+ */
92
+ this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined;
93
+
94
+ return super._patch(data);
95
+ }
96
+
97
+ /**
98
+ * Gets the subscribers of the channel (only those who are in your contact list)
99
+ * @param {?number} limit Optional parameter to specify the limit of subscribers to retrieve
100
+ * @returns {Promise<{contact: Contact, role: string}[]>} Returns an array of objects that handle the subscribed contacts and their roles in the channel
101
+ */
102
+ async getSubscribers(limit) {
103
+ return await this.client.pupPage.evaluate(async (channelId, limit) => {
104
+ const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
105
+ if (!channel) return [];
106
+ !limit && (limit = window.Store.ChannelUtils.getMaxSubscriberNumber());
107
+ const response = await window.Store.ChannelSubscribers.mexFetchNewsletterSubscribers(channelId, limit);
108
+ const contacts = window.Store.ChannelSubscribers.getSubscribersInContacts(response.subscribers);
109
+ return Promise.all(contacts.map((obj) => ({
110
+ ...obj,
111
+ contact: window.WWebJS.getContactModel(obj.contact)
112
+ })));
113
+ }, this.id._serialized, limit);
114
+ }
115
+
116
+ /**
117
+ * Updates the channel subject
118
+ * @param {string} newSubject
119
+ * @returns {Promise<boolean>} Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions.
120
+ */
121
+ async setSubject(newSubject) {
122
+ const success = await this._setChannelMetadata({ name: newSubject }, { editName: true });
123
+ success && (this.name = newSubject);
124
+ return success;
125
+ }
126
+
127
+ /**
128
+ * Updates the channel description
129
+ * @param {string} newDescription
130
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
131
+ */
132
+ async setDescription(newDescription) {
133
+ const success = await this._setChannelMetadata({ description: newDescription }, { editDescription: true });
134
+ success && (this.description = newDescription);
135
+ return success;
136
+ }
137
+
138
+ /**
139
+ * Updates the channel profile picture
140
+ * @param {MessageMedia} newProfilePicture
141
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
142
+ */
143
+ async setProfilePicture(newProfilePicture) {
144
+ return await this._setChannelMetadata({ picture: newProfilePicture }, { editPicture: true });
145
+ }
146
+
147
+ /**
148
+ * Updates available reactions to use in the channel
149
+ *
150
+ * Valid values for passing to the method are:
151
+ * 0 for NONE reactions to be avaliable
152
+ * 1 for BASIC reactions to be available: 👍, ❤️, 😂, 😮, 😢, 🙏
153
+ * 2 for ALL reactions to be available
154
+ * @param {number} reactionCode
155
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
156
+ */
157
+ async setReactionSetting(reactionCode) {
158
+ if (![0, 1, 2].includes(reactionCode)) return false;
159
+ const reactionMapper = {
160
+ 0: 3,
161
+ 1: 1,
162
+ 2: 0
163
+ };
164
+ const success = await this._setChannelMetadata(
165
+ { reactionCodesSetting: reactionMapper[reactionCode] },
166
+ { editReactionCodesSetting: true }
167
+ );
168
+ success && (this.channelMetadata.reactionCodesSetting = reactionCode);
169
+ return success;
170
+ }
171
+
172
+ /**
173
+ * Mutes the channel
174
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
175
+ */
176
+ async mute() {
177
+ const success = await this._muteUnmuteChannel('MUTE');
178
+ if (success) {
179
+ this.isMuted = true;
180
+ this.muteExpiration = -1;
181
+ }
182
+ return success;
183
+ }
184
+
185
+ /**
186
+ * Unmutes the channel
187
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
188
+ */
189
+ async unmute() {
190
+ const success = await this._muteUnmuteChannel('UNMUTE');
191
+ if (success) {
192
+ this.isMuted = false;
193
+ this.muteExpiration = 0;
194
+ }
195
+ return success;
196
+ }
197
+
198
+ /**
199
+ * Message options
200
+ * @typedef {Object} MessageSendOptions
201
+ * @property {?string} caption Image or video caption
202
+ * @property {?string[]} mentions User IDs of user that will be mentioned in the message
203
+ * @property {?MessageMedia} media Image or video to be sent
204
+ */
205
+
206
+ /**
207
+ * Sends a message to this channel
208
+ * @param {string|MessageMedia} content
209
+ * @param {?MessageSendOptions} options
210
+ * @returns {Promise<Message>} Message that was just sent
211
+ */
212
+ async sendMessage(content, options) {
213
+ return this.client.sendMessage(this.id._serialized, content, options);
214
+ }
215
+
216
+ /**
217
+ * Sets the channel as seen
218
+ * @returns {Promise<boolean>}
219
+ */
220
+ async sendSeen() {
221
+ return this.client.sendSeen(this.id._serialized);
222
+ }
223
+
224
+ /**
225
+ * @typedef {Object} SendChannelAdminInviteOptions
226
+ * @property {?string} comment The comment to be added to an invitation
227
+ */
228
+
229
+ /**
230
+ * Sends a channel admin invitation to a user, allowing them to become an admin of the channel
231
+ * @param {string} chatId The ID of a user to send the channel admin invitation to
232
+ * @param {SendChannelAdminInviteOptions} options
233
+ * @returns {Promise<boolean>} Returns true if an invitation was sent successfully, false otherwise
234
+ */
235
+ async sendChannelAdminInvite(chatId, options = {}) {
236
+ return this.client.sendChannelAdminInvite(chatId, this.id._serialized, options);
237
+ }
238
+
239
+ /**
240
+ * Accepts a channel admin invitation and promotes the current user to a channel admin
241
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
242
+ */
243
+ async acceptChannelAdminInvite() {
244
+ return this.client.acceptChannelAdminInvite(this.id._serialized);
245
+ }
246
+
247
+ /**
248
+ * Revokes a channel admin invitation sent to a user by a channel owner
249
+ * @param {string} userId The user ID the invitation was sent to
250
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
251
+ */
252
+ async revokeChannelAdminInvite(userId) {
253
+ return this.client.revokeChannelAdminInvite(this.id._serialized, userId);
254
+ }
255
+
256
+ /**
257
+ * Demotes a channel admin to a regular subscriber (can be used also for self-demotion)
258
+ * @param {string} userId The user ID to demote
259
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
260
+ */
261
+ async demoteChannelAdmin(userId) {
262
+ return this.client.demoteChannelAdmin(this.id._serialized, userId);
263
+ }
264
+
265
+ /**
266
+ * Options for transferring a channel ownership to another user
267
+ * @typedef {Object} TransferChannelOwnershipOptions
268
+ * @property {boolean} [shouldDismissSelfAsAdmin = false] If true, after the channel ownership is being transferred to another user, the current user will be dismissed as a channel admin and will become to a channel subscriber.
269
+ */
270
+
271
+ /**
272
+ * Transfers a channel ownership to another user.
273
+ * Note: the user you are transferring the channel ownership to must be a channel admin.
274
+ * @param {string} newOwnerId
275
+ * @param {TransferChannelOwnershipOptions} options
276
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
277
+ */
278
+ async transferChannelOwnership(newOwnerId, options = {}) {
279
+ return this.client.transferChannelOwnership(this.id._serialized, newOwnerId, options);
280
+ }
281
+
282
+ /**
283
+ * Loads channel messages, sorted from earliest to latest
284
+ * @param {Object} searchOptions Options for searching messages. Right now only limit and fromMe is supported
285
+ * @param {Number} [searchOptions.limit] The amount of messages to return. If no limit is specified, the available messages will be returned. Note that the actual number of returned messages may be smaller if there aren't enough messages in the conversation. Set this to Infinity to load all messages
286
+ * @param {Boolean} [searchOptions.fromMe] Return only messages from the bot number or vise versa. To get all messages, leave the option undefined
287
+ * @returns {Promise<Array<Message>>}
288
+ */
289
+ async fetchMessages(searchOptions) {
290
+ let messages = await this.client.pupPage.evaluate(async (channelId, searchOptions) => {
291
+ const msgFilter = (m) => {
292
+ if (m.isNotification || m.type === 'newsletter_notification') {
293
+ return false; // dont include notification messages
294
+ }
295
+ if (searchOptions && searchOptions.fromMe !== undefined && m.id.fromMe !== searchOptions.fromMe) {
296
+ return false;
297
+ }
298
+ return true;
299
+ };
300
+
301
+ const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
302
+ let msgs = channel.msgs.getModelsArray().filter(msgFilter);
303
+
304
+ if (searchOptions && searchOptions.limit > 0) {
305
+ while (msgs.length < searchOptions.limit) {
306
+ const loadedMessages = await window.Store.ConversationMsgs.loadEarlierMsgs(channel);
307
+ if (!loadedMessages || !loadedMessages.length) break;
308
+ msgs = [...loadedMessages.filter(msgFilter), ...msgs];
309
+ }
310
+
311
+ if (msgs.length > searchOptions.limit) {
312
+ msgs.sort((a, b) => (a.t > b.t) ? 1 : -1);
313
+ msgs = msgs.splice(msgs.length - searchOptions.limit);
314
+ }
315
+ }
316
+
317
+ return msgs.map(m => window.WWebJS.getMessageModel(m));
318
+
319
+ }, this.id._serialized, searchOptions);
320
+
321
+ return messages.map((msg) => new Message(this.client, msg));
322
+ }
323
+
324
+ /**
325
+ * Deletes the channel you created
326
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
327
+ */
328
+ async deleteChannel() {
329
+ return this.client.deleteChannel(this.id._serialized);
330
+ }
331
+
332
+ /**
333
+ * Internal method to change the channel metadata
334
+ * @param {string|number|MessageMedia} value The new value to set
335
+ * @param {string} property The property of a channel metadata to change
336
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
337
+ */
338
+ async _setChannelMetadata(value, property) {
339
+ return await this.client.pupPage.evaluate(async (channelId, value, property) => {
340
+ const channel = await window.WWebJS.getChat(channelId, { getAsModel: false });
341
+ if (!channel) return false;
342
+ if (property.editPicture) {
343
+ value.picture = value.picture
344
+ ? await window.WWebJS.cropAndResizeImage(value.picture, {
345
+ asDataUrl: true,
346
+ mimetype: 'image/jpeg',
347
+ size: 640,
348
+ quality: 1
349
+ })
350
+ : null;
351
+ }
352
+ try {
353
+ await window.Store.ChannelUtils.editNewsletterMetadataAction(channel, property, value);
354
+ return true;
355
+ } catch (err) {
356
+ if (err.name === 'ServerStatusCodeError') return false;
357
+ throw err;
358
+ }
359
+ }, this.id._serialized, value, property);
360
+ }
361
+
362
+ /**
363
+ * Internal method to mute or unmute the channel
364
+ * @param {string} action The action: 'MUTE' or 'UNMUTE'
365
+ * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise
366
+ */
367
+ async _muteUnmuteChannel(action) {
368
+ return await this.client.pupPage.evaluate(async (channelId, action) => {
369
+ try {
370
+ action === 'MUTE'
371
+ ? await window.Store.ChannelUtils.muteNewsletter([channelId])
372
+ : await window.Store.ChannelUtils.unmuteNewsletter([channelId]);
373
+ return true;
374
+ } catch (err) {
375
+ if (err.name === 'ServerStatusCodeError') return false;
376
+ throw err;
377
+ }
378
+ }, this.id._serialized, action);
379
+ }
380
+ }
381
+
382
+ module.exports = Channel;
@@ -95,7 +95,7 @@ class Chat extends Base {
95
95
  }
96
96
 
97
97
  /**
98
- * Set the message as seen
98
+ * Sets the chat as seen
99
99
  * @returns {Promise<Boolean>} result
100
100
  */
101
101
  async sendSeen() {
@@ -104,7 +104,7 @@ class Chat extends Base {
104
104
 
105
105
  /**
106
106
  * Clears all messages from the chat
107
- * @returns {Promise<Boolean>} result
107
+ * @returns {Promise<boolean>} result
108
108
  */
109
109
  async clearMessages() {
110
110
  return this.client.pupPage.evaluate(chatId => {
@@ -154,17 +154,25 @@ class Chat extends Base {
154
154
 
155
155
  /**
156
156
  * Mutes this chat forever, unless a date is specified
157
- * @param {?Date} unmuteDate Date at which the Chat will be unmuted, leave as is to mute forever
157
+ * @param {?Date} unmuteDate Date when the chat will be unmuted, don't provide a value to mute forever
158
+ * @returns {Promise<{isMuted: boolean, muteExpiration: number}>}
158
159
  */
159
160
  async mute(unmuteDate) {
160
- return this.client.muteChat(this.id._serialized, unmuteDate);
161
+ const result = await this.client.muteChat(this.id._serialized, unmuteDate);
162
+ this.isMuted = result.isMuted;
163
+ this.muteExpiration = result.muteExpiration;
164
+ return result;
161
165
  }
162
166
 
163
167
  /**
164
168
  * Unmutes this chat
169
+ * @returns {Promise<{isMuted: boolean, muteExpiration: number}>}
165
170
  */
166
171
  async unmute() {
167
- return this.client.unmuteChat(this.id._serialized);
172
+ const result = await this.client.unmuteChat(this.id._serialized);
173
+ this.isMuted = result.isMuted;
174
+ this.muteExpiration = result.muteExpiration;
175
+ return result;
168
176
  }
169
177
 
170
178
  /**
@@ -193,7 +201,7 @@ class Chat extends Base {
193
201
  return true;
194
202
  };
195
203
 
196
- const chat = window.Store.Chat.get(chatId);
204
+ const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
197
205
  let msgs = chat.msgs.getModelsArray().filter(msgFilter);
198
206
 
199
207
  if (searchOptions && searchOptions.limit > 0) {
@@ -191,8 +191,7 @@ class GroupChat extends Chat {
191
191
  */
192
192
  async removeParticipants(participantIds) {
193
193
  return await this.client.pupPage.evaluate(async (chatId, participantIds) => {
194
- const chatWid = window.Store.WidFactory.createWid(chatId);
195
- const chat = await window.Store.Chat.find(chatWid);
194
+ const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
196
195
  const participants = participantIds.map(p => {
197
196
  return chat.groupMetadata.participants.get(p);
198
197
  }).filter(p => Boolean(p));
@@ -208,8 +207,7 @@ class GroupChat extends Chat {
208
207
  */
209
208
  async promoteParticipants(participantIds) {
210
209
  return await this.client.pupPage.evaluate(async (chatId, participantIds) => {
211
- const chatWid = window.Store.WidFactory.createWid(chatId);
212
- const chat = await window.Store.Chat.find(chatWid);
210
+ const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
213
211
  const participants = participantIds.map(p => {
214
212
  return chat.groupMetadata.participants.get(p);
215
213
  }).filter(p => Boolean(p));
@@ -225,8 +223,7 @@ class GroupChat extends Chat {
225
223
  */
226
224
  async demoteParticipants(participantIds) {
227
225
  return await this.client.pupPage.evaluate(async (chatId, participantIds) => {
228
- const chatWid = window.Store.WidFactory.createWid(chatId);
229
- const chat = await window.Store.Chat.find(chatWid);
226
+ const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
230
227
  const participants = participantIds.map(p => {
231
228
  return chat.groupMetadata.participants.get(p);
232
229
  }).filter(p => Boolean(p));
@@ -466,8 +463,7 @@ class GroupChat extends Chat {
466
463
  */
467
464
  async leave() {
468
465
  await this.client.pupPage.evaluate(async chatId => {
469
- const chatWid = window.Store.WidFactory.createWid(chatId);
470
- const chat = await window.Store.Chat.find(chatWid);
466
+ const chat = await window.WWebJS.getChat(chatId, { getAsModel: false });
471
467
  return window.Store.GroupUtils.sendExitGroup(chat);
472
468
  }, this.id._serialized);
473
469
  }
@@ -6,6 +6,7 @@
6
6
  * @property {string} [name] Location name
7
7
  * @property {string} [address] Location address
8
8
  * @property {string} [url] URL address to be shown within a location message
9
+ * @property {string} [description] Location full description
9
10
  */
10
11
 
11
12
  /**
@@ -45,7 +45,7 @@ class Message extends Base {
45
45
  * Indicates if the message has media available for download
46
46
  * @type {boolean}
47
47
  */
48
- this.hasMedia = Boolean(data.mediaKey && data.directPath);
48
+ this.hasMedia = Boolean(data.directPath);
49
49
 
50
50
  /**
51
51
  * Message content
@@ -498,23 +498,26 @@ class Message extends Base {
498
498
  /**
499
499
  * Deletes a message from the chat
500
500
  * @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.
501
+ * @param {?boolean} [clearMedia = true] If true, any associated media will also be deleted from a device.
501
502
  */
502
- async delete(everyone) {
503
- await this.client.pupPage.evaluate(async (msgId, everyone) => {
503
+ async delete(everyone, clearMedia = true) {
504
+ await this.client.pupPage.evaluate(async (msgId, everyone, clearMedia) => {
504
505
  const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0];
505
- let chat = await window.Store.Chat.find(msg.id.remote);
506
+ const chat = window.Store.Chat.get(msg.id.remote) || (await window.Store.Chat.find(msg.id.remote));
506
507
 
507
- const canRevoke = window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg);
508
+ const canRevoke =
509
+ window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg);
510
+
508
511
  if (everyone && canRevoke) {
509
- if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0')) {
510
- return window.Store.Cmd.sendRevokeMsgs(chat, { list: [msg], type: 'message' }, { clearMedia: true });
511
- } else {
512
- return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' });
513
- }
512
+ return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0')
513
+ ? window.Store.Cmd.sendRevokeMsgs(chat, { list: [msg], type: 'message' }, { clearMedia: clearMedia })
514
+ : window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' });
514
515
  }
515
516
 
516
- return window.Store.Cmd.sendDeleteMsgs(chat, [msg], true);
517
- }, this.id._serialized, everyone);
517
+ return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0')
518
+ ? window.Store.Cmd.sendDeleteMsgs(chat, { list: [msg], type: 'message' }, clearMedia)
519
+ : window.Store.Cmd.sendDeleteMsgs(chat, [msg], clearMedia);
520
+ }, this.id._serialized, everyone, clearMedia);
518
521
  }
519
522
 
520
523
  /**
@@ -5,6 +5,7 @@ module.exports = {
5
5
  ClientInfo: require('./ClientInfo'),
6
6
  Contact: require('./Contact'),
7
7
  GroupChat: require('./GroupChat'),
8
+ Channel: require('./Channel'),
8
9
  Location: require('./Location'),
9
10
  Message: require('./Message'),
10
11
  MessageMedia: require('./MessageMedia'),
@@ -65,7 +65,6 @@ exports.ExposeStore = () => {
65
65
  window.Store.SendSeen = window.require('WAWebUpdateUnreadChatAction');
66
66
  window.Store.User = window.require('WAWebUserPrefsMeUser');
67
67
  window.Store.ContactMethods = window.require('WAWebContactGetters');
68
- window.Store.UploadUtils = window.require('WAWebUploadManager');
69
68
  window.Store.UserConstructor = window.require('WAWebWid');
70
69
  window.Store.Validators = window.require('WALinkify');
71
70
  window.Store.VCard = window.require('WAWebFrontendVcardUtils');
@@ -84,7 +83,7 @@ exports.ExposeStore = () => {
84
83
  window.Store.LinkPreview = window.require('WAWebLinkPreviewChatAction');
85
84
  window.Store.Socket = window.require('WADeprecatedSendIq');
86
85
  window.Store.SocketWap = window.require('WAWap');
87
- window.Store.SearchContext = window.require('WAWebChatMessageSearch').getSearchContext;
86
+ window.Store.SearchContext = window.require('WAWebChatMessageSearch');
88
87
  window.Store.DrawerManager = window.require('WAWebDrawerManager').DrawerManager;
89
88
  window.Store.LidUtils = window.require('WAWebApiContact');
90
89
  window.Store.WidToJid = window.require('WAWebWidToJid');
@@ -93,23 +92,26 @@ exports.ExposeStore = () => {
93
92
  window.Store.pinUnpinMsg = window.require('WAWebSendPinMessageAction').sendPinInChatMsg;
94
93
  window.Store.QueryExist = window.require('WAWebQueryExistsJob').queryWidExists;
95
94
  window.Store.ReplyUtils = window.require('WAWebMsgReply');
96
- window.Store.Settings = window.require('WAWebUserPrefsGeneral');
97
95
  window.Store.BotSecret = window.require('WAWebBotMessageSecret');
98
96
  window.Store.BotProfiles = window.require('WAWebBotProfileCollection');
97
+ window.Store.ContactCollection = window.require('WAWebContactCollection').ContactCollection;
99
98
  window.Store.DeviceList = window.require('WAWebApiDeviceList');
100
99
  window.Store.HistorySync = window.require('WAWebSendNonMessageDataRequest');
101
- if (window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1014111620'))
102
- window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode;
100
+ window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode;
101
+ window.Store.ChatGetters = window.require('WAWebChatGetters');
103
102
 
103
+ window.Store.Settings = {
104
+ ...window.require('WAWebUserPrefsGeneral'),
105
+ ...window.require('WAWebUserPrefsNotifications'),
106
+ setPushname: window.require('WAWebSetPushnameConnAction').setPushname
107
+ };
104
108
  window.Store.NumberInfo = {
105
109
  ...window.require('WAPhoneUtils'),
106
110
  ...window.require('WAPhoneFindCC')
107
111
  };
108
-
109
112
  window.Store.ForwardUtils = {
110
113
  ...window.require('WAWebForwardMessagesToChat')
111
114
  };
112
-
113
115
  window.Store.StickerTools = {
114
116
  ...window.require('WAWebImageUtils'),
115
117
  ...window.require('WAWebAddWebpMetadata')
@@ -137,6 +139,41 @@ exports.ExposeStore = () => {
137
139
  ...window.require('WAWebApiMembershipApprovalRequestStore'),
138
140
  ...window.require('WASmaxGroupsMembershipRequestsActionRPC')
139
141
  };
142
+ window.Store.ChannelUtils = {
143
+ ...window.require('WAWebLoadNewsletterPreviewChatAction'),
144
+ ...window.require('WAWebNewsletterMetadataQueryJob'),
145
+ ...window.require('WAWebNewsletterCreateQueryJob'),
146
+ ...window.require('WAWebEditNewsletterMetadataAction'),
147
+ ...window.require('WAWebNewsletterDeleteAction'),
148
+ ...window.require('WAWebNewsletterSubscribeAction'),
149
+ ...window.require('WAWebNewsletterUnsubscribeAction'),
150
+ ...window.require('WAWebNewsletterDirectorySearchAction'),
151
+ ...window.require('WAWebNewsletterToggleMuteStateJob'),
152
+ ...window.require('WAWebNewsletterGatingUtils'),
153
+ ...window.require('WAWebNewsletterModelUtils'),
154
+ ...window.require('WAWebMexAcceptNewsletterAdminInviteJob'),
155
+ ...window.require('WAWebMexRevokeNewsletterAdminInviteJob'),
156
+ ...window.require('WAWebChangeNewsletterOwnerAction'),
157
+ ...window.require('WAWebDemoteNewsletterAdminAction'),
158
+ ...window.require('WAWebNewsletterDemoteAdminJob'),
159
+ countryCodesIso: window.require('WAWebCountriesNativeCountryNames'),
160
+ currentRegion: window.require('WAWebL10N').getRegion(),
161
+ };
162
+ window.Store.SendChannelMessage = {
163
+ ...window.require('WAWebNewsletterUpdateMsgsRecordsJob'),
164
+ ...window.require('WAWebMsgDataFromModel'),
165
+ ...window.require('WAWebNewsletterSendMessageJob'),
166
+ ...window.require('WAWebNewsletterSendMsgAction'),
167
+ ...window.require('WAMediaCalculateFilehash')
168
+ };
169
+ window.Store.ChannelSubscribers = {
170
+ ...window.require('WAWebMexFetchNewsletterSubscribersJob'),
171
+ ...window.require('WAWebNewsletterSubscriberListAction')
172
+ };
173
+ window.Store.AddressbookContactUtils = {
174
+ ...window.require('WAWebSaveContactAction'),
175
+ ...window.require('WAWebDeleteContactAction')
176
+ };
140
177
 
141
178
  if (!window.Store.Chat._find || !window.Store.Chat.findImpl) {
142
179
  window.Store.Chat._find = e => {