whatsapp-web-sj.js 1.26.0 → 1.31.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.
@@ -9,9 +9,10 @@ const BaseAuthStrategy = require('./BaseAuthStrategy');
9
9
  * @param {object} options - options
10
10
  * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance
11
11
  * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/"
12
+ * @param {number} options.rmMaxRetries - Sets the maximum number of retries for removing the session directory
12
13
  */
13
14
  class LocalAuth extends BaseAuthStrategy {
14
- constructor({ clientId, dataPath }={}) {
15
+ constructor({ clientId, dataPath, rmMaxRetries }={}) {
15
16
  super();
16
17
 
17
18
  const idRegex = /^[-_\w]+$/i;
@@ -21,6 +22,7 @@ class LocalAuth extends BaseAuthStrategy {
21
22
 
22
23
  this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
23
24
  this.clientId = clientId;
25
+ this.rmMaxRetries = rmMaxRetries ?? 4;
24
26
  }
25
27
 
26
28
  async beforeBrowserInitialized() {
@@ -44,7 +46,7 @@ class LocalAuth extends BaseAuthStrategy {
44
46
 
45
47
  async logout() {
46
48
  if (this.userDataDir) {
47
- await fs.promises.rm(this.userDataDir, { recursive: true, force: true })
49
+ await fs.promises.rm(this.userDataDir, { recursive: true, force: true, maxRetries: this.rmMaxRetries })
48
50
  .catch((e) => {
49
51
  throw new Error(e);
50
52
  });
@@ -22,9 +22,10 @@ const BaseAuthStrategy = require('./BaseAuthStrategy');
22
22
  * @param {string} options.clientId - Client id to distinguish instances if you are using multiple, otherwise keep null if you are using only one instance
23
23
  * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/"
24
24
  * @param {number} options.backupSyncIntervalMs - Sets the time interval for periodic session backups. Accepts values starting from 60000ms {1 minute}
25
+ * @param {number} options.rmMaxRetries - Sets the maximum number of retries for removing the session directory
25
26
  */
26
27
  class RemoteAuth extends BaseAuthStrategy {
27
- constructor({ clientId, dataPath, store, backupSyncIntervalMs } = {}) {
28
+ constructor({ clientId, dataPath, store, backupSyncIntervalMs, rmMaxRetries } = {}) {
28
29
  if (!fs && !unzipper && !archiver) throw new Error('Optional Dependencies [fs-extra, unzipper, archiver] are required to use RemoteAuth. Make sure to run npm install correctly and remove the --no-optional flag');
29
30
  super();
30
31
 
@@ -43,6 +44,7 @@ class RemoteAuth extends BaseAuthStrategy {
43
44
  this.dataPath = path.resolve(dataPath || './.wwebjs_auth/');
44
45
  this.tempDir = `${this.dataPath}/wwebjs_temp_session_${this.clientId}`;
45
46
  this.requiredDirs = ['Default', 'IndexedDB', 'Local Storage']; /* => Required Files & Dirs in WWebJS to restore session */
47
+ this.rmMaxRetries = rmMaxRetries ?? 4;
46
48
  }
47
49
 
48
50
  async beforeBrowserInitialized() {
@@ -80,7 +82,8 @@ class RemoteAuth extends BaseAuthStrategy {
80
82
  if (pathExists) {
81
83
  await fs.promises.rm(this.userDataDir, {
82
84
  recursive: true,
83
- force: true
85
+ force: true,
86
+ maxRetries: this.rmMaxRetries,
84
87
  }).catch(() => {});
85
88
  }
86
89
  clearInterval(this.backupSync);
@@ -107,7 +110,8 @@ class RemoteAuth extends BaseAuthStrategy {
107
110
  await fs.promises.unlink(`${this.sessionName}.zip`);
108
111
  await fs.promises.rm(`${this.tempDir}`, {
109
112
  recursive: true,
110
- force: true
113
+ force: true,
114
+ maxRetries: this.rmMaxRetries,
111
115
  }).catch(() => {});
112
116
  if(options && options.emit) this.client.emit(Events.REMOTE_SESSION_SAVED);
113
117
  }
@@ -120,7 +124,8 @@ class RemoteAuth extends BaseAuthStrategy {
120
124
  if (pathExists) {
121
125
  await fs.promises.rm(this.userDataDir, {
122
126
  recursive: true,
123
- force: true
127
+ force: true,
128
+ maxRetries: this.rmMaxRetries,
124
129
  }).catch(() => {});
125
130
  }
126
131
  if (sessionExists) {
@@ -177,7 +182,8 @@ class RemoteAuth extends BaseAuthStrategy {
177
182
  if (stats.isDirectory()) {
178
183
  await fs.promises.rm(dirElement, {
179
184
  recursive: true,
180
- force: true
185
+ force: true,
186
+ maxRetries: this.rmMaxRetries,
181
187
  }).catch(() => {});
182
188
  } else {
183
189
  await fs.promises.unlink(dirElement).catch(() => {});
@@ -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;
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+
3
+ const Base = require('./Base');
4
+ const Message = require('./Message');
5
+
6
+ /**
7
+ * Represents a Status/Story on WhatsApp
8
+ * @extends {Base}
9
+ */
10
+ class Broadcast extends Base {
11
+ constructor(client, data) {
12
+ super(client);
13
+
14
+ if (data) this._patch(data);
15
+ }
16
+
17
+ _patch(data) {
18
+ /**
19
+ * ID that represents the chat
20
+ * @type {object}
21
+ */
22
+ this.id = data.id;
23
+
24
+ /**
25
+ * Unix timestamp of last status
26
+ * @type {number}
27
+ */
28
+ this.timestamp = data.t;
29
+
30
+ /**
31
+ * Number of available statuses
32
+ * @type {number}
33
+ */
34
+ this.totalCount = data.totalCount;
35
+
36
+ /**
37
+ * Number of not viewed
38
+ * @type {number}
39
+ */
40
+ this.unreadCount = data.unreadCount;
41
+
42
+ /**
43
+ * Messages statuses
44
+ * @type {Message[]}
45
+ */
46
+ this.msgs = data.msgs?.map(msg => new Message(this.client, msg));
47
+
48
+ return super._patch(data);
49
+ }
50
+
51
+ /**
52
+ * Returns the Chat this message was sent in
53
+ * @returns {Promise<Chat>}
54
+ */
55
+ getChat() {
56
+ return this.client.getChatById(this.id._serialized);
57
+ }
58
+
59
+ /**
60
+ * Returns the Contact this message was sent from
61
+ * @returns {Promise<Contact>}
62
+ */
63
+ getContact() {
64
+ return this.client.getContactById(this.id._serialized);
65
+ }
66
+
67
+ }
68
+
69
+ module.exports = Broadcast;
@@ -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;
@@ -79,7 +79,7 @@ class Chat extends Base {
79
79
  * Last message fo chat
80
80
  * @type {Message}
81
81
  */
82
- this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined;
82
+ this.lastMessage = data.lastMessage ? new Message(this.client, data.lastMessage) : undefined;
83
83
 
84
84
  return super._patch(data);
85
85
  }
@@ -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) {
@@ -270,6 +278,14 @@ class Chat extends Base {
270
278
  async changeLabels(labelIds) {
271
279
  return this.client.addOrRemoveLabels(labelIds, [this.id._serialized]);
272
280
  }
281
+
282
+ /**
283
+ * Sync chat history conversation
284
+ * @return {Promise<boolean>} True if operation completed successfully, false otherwise.
285
+ */
286
+ async syncHistory() {
287
+ return this.client.syncHistory(this.id._serialized);
288
+ }
273
289
  }
274
290
 
275
291
  module.exports = Chat;