wwebjs-electron
Advanced tools
| 'use strict'; | ||
| const Base = require('./Base'); | ||
| const Message = require('./Message'); | ||
| /** | ||
| * Represents a Status/Story on WhatsApp | ||
| * @extends {Base} | ||
| */ | ||
| class Broadcast extends Base { | ||
| constructor(client, data) { | ||
| super(client); | ||
| if (data) this._patch(data); | ||
| } | ||
| _patch(data) { | ||
| /** | ||
| * ID that represents the chat | ||
| * @type {object} | ||
| */ | ||
| this.id = data.id; | ||
| /** | ||
| * Unix timestamp of last status | ||
| * @type {number} | ||
| */ | ||
| this.timestamp = data.t; | ||
| /** | ||
| * Number of available statuses | ||
| * @type {number} | ||
| */ | ||
| this.totalCount = data.totalCount; | ||
| /** | ||
| * Number of not viewed | ||
| * @type {number} | ||
| */ | ||
| this.unreadCount = data.unreadCount; | ||
| /** | ||
| * Messages statuses | ||
| * @type {Message[]} | ||
| */ | ||
| this.msgs = data.msgs?.map(msg => new Message(this.client, msg)); | ||
| return super._patch(data); | ||
| } | ||
| /** | ||
| * Returns the Chat this message was sent in | ||
| * @returns {Promise<Chat>} | ||
| */ | ||
| getChat() { | ||
| return this.client.getChatById(this.id._serialized); | ||
| } | ||
| /** | ||
| * Returns the Contact this message was sent from | ||
| * @returns {Promise<Contact>} | ||
| */ | ||
| getContact() { | ||
| return this.client.getContactById(this.id._serialized); | ||
| } | ||
| } | ||
| module.exports = Broadcast; |
| 'use strict'; | ||
| const Base = require('./Base'); | ||
| const Message = require('./Message'); | ||
| /** | ||
| * Channel ID structure | ||
| * @typedef {Object} ChannelId | ||
| * @property {string} server | ||
| * @property {string} user | ||
| * @property {string} _serialized | ||
| */ | ||
| /** | ||
| * Represents a Channel on WhatsApp | ||
| * @extends {Base} | ||
| */ | ||
| class Channel extends Base { | ||
| constructor(client, data) { | ||
| super(client); | ||
| if (data) this._patch(data); | ||
| } | ||
| _patch(data) { | ||
| this.channelMetadata = data.channelMetadata; | ||
| /** | ||
| * ID that represents the channel | ||
| * @type {ChannelId} | ||
| */ | ||
| this.id = data.id; | ||
| /** | ||
| * Title of the channel | ||
| * @type {string} | ||
| */ | ||
| this.name = data.name; | ||
| /** | ||
| * The channel description | ||
| * @type {string} | ||
| */ | ||
| this.description = data.channelMetadata.description; | ||
| /** | ||
| * Indicates if it is a Channel | ||
| * @type {boolean} | ||
| */ | ||
| this.isChannel = data.isChannel; | ||
| /** | ||
| * Indicates if it is a Group | ||
| * @type {boolean} | ||
| */ | ||
| this.isGroup = data.isGroup; | ||
| /** | ||
| * Indicates if the channel is readonly | ||
| * @type {boolean} | ||
| */ | ||
| this.isReadOnly = data.isReadOnly; | ||
| /** | ||
| * Amount of messages unread | ||
| * @type {number} | ||
| */ | ||
| this.unreadCount = data.unreadCount; | ||
| /** | ||
| * Unix timestamp for when the last activity occurred | ||
| * @type {number} | ||
| */ | ||
| this.timestamp = data.t; | ||
| /** | ||
| * Indicates if the channel is muted or not | ||
| * @type {boolean} | ||
| */ | ||
| this.isMuted = data.isMuted; | ||
| /** | ||
| * Unix timestamp for when the mute expires | ||
| * @type {number} | ||
| */ | ||
| this.muteExpiration = data.muteExpiration; | ||
| /** | ||
| * Last message in the channel | ||
| * @type {Message} | ||
| */ | ||
| this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined; | ||
| return super._patch(data); | ||
| } | ||
| /** | ||
| * Gets the subscribers of the channel (only those who are in your contact list) | ||
| * @param {?number} limit Optional parameter to specify the limit of subscribers to retrieve | ||
| * @returns {Promise<{contact: Contact, role: string}[]>} Returns an array of objects that handle the subscribed contacts and their roles in the channel | ||
| */ | ||
| async getSubscribers(limit) { | ||
| return await this.client.pupPage.evaluate(async (channelId, limit) => { | ||
| const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); | ||
| if (!channel) return []; | ||
| !limit && (limit = window.Store.ChannelUtils.getMaxSubscriberNumber()); | ||
| const response = await window.Store.ChannelSubscribers.mexFetchNewsletterSubscribers(channelId, limit); | ||
| const contacts = window.Store.ChannelSubscribers.getSubscribersInContacts(response.subscribers); | ||
| return Promise.all(contacts.map((obj) => ({ | ||
| ...obj, | ||
| contact: window.WWebJS.getContactModel(obj.contact) | ||
| }))); | ||
| }, this.id._serialized, limit); | ||
| } | ||
| /** | ||
| * Updates the channel subject | ||
| * @param {string} newSubject | ||
| * @returns {Promise<boolean>} Returns true if the subject was properly updated. This can return false if the user does not have the necessary permissions. | ||
| */ | ||
| async setSubject(newSubject) { | ||
| const success = await this._setChannelMetadata({ name: newSubject }, { editName: true }); | ||
| success && (this.name = newSubject); | ||
| return success; | ||
| } | ||
| /** | ||
| * Updates the channel description | ||
| * @param {string} newDescription | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async setDescription(newDescription) { | ||
| const success = await this._setChannelMetadata({ description: newDescription }, { editDescription: true }); | ||
| success && (this.description = newDescription); | ||
| return success; | ||
| } | ||
| /** | ||
| * Updates the channel profile picture | ||
| * @param {MessageMedia} newProfilePicture | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async setProfilePicture(newProfilePicture) { | ||
| return await this._setChannelMetadata({ picture: newProfilePicture }, { editPicture: true }); | ||
| } | ||
| /** | ||
| * Updates available reactions to use in the channel | ||
| * | ||
| * Valid values for passing to the method are: | ||
| * 0 for NONE reactions to be avaliable | ||
| * 1 for BASIC reactions to be available: 👍, ❤️, 😂, 😮, 😢, 🙏 | ||
| * 2 for ALL reactions to be available | ||
| * @param {number} reactionCode | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async setReactionSetting(reactionCode) { | ||
| if (![0, 1, 2].includes(reactionCode)) return false; | ||
| const reactionMapper = { | ||
| 0: 3, | ||
| 1: 1, | ||
| 2: 0 | ||
| }; | ||
| const success = await this._setChannelMetadata( | ||
| { reactionCodesSetting: reactionMapper[reactionCode] }, | ||
| { editReactionCodesSetting: true } | ||
| ); | ||
| success && (this.channelMetadata.reactionCodesSetting = reactionCode); | ||
| return success; | ||
| } | ||
| /** | ||
| * Mutes the channel | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async mute() { | ||
| const success = await this._muteUnmuteChannel('MUTE'); | ||
| if (success) { | ||
| this.isMuted = true; | ||
| this.muteExpiration = -1; | ||
| } | ||
| return success; | ||
| } | ||
| /** | ||
| * Unmutes the channel | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async unmute() { | ||
| const success = await this._muteUnmuteChannel('UNMUTE'); | ||
| if (success) { | ||
| this.isMuted = false; | ||
| this.muteExpiration = 0; | ||
| } | ||
| return success; | ||
| } | ||
| /** | ||
| * Message options | ||
| * @typedef {Object} MessageSendOptions | ||
| * @property {?string} caption Image or video caption | ||
| * @property {?string[]} mentions User IDs of user that will be mentioned in the message | ||
| * @property {?MessageMedia} media Image or video to be sent | ||
| */ | ||
| /** | ||
| * Sends a message to this channel | ||
| * @param {string|MessageMedia} content | ||
| * @param {?MessageSendOptions} options | ||
| * @returns {Promise<Message>} Message that was just sent | ||
| */ | ||
| async sendMessage(content, options) { | ||
| return this.client.sendMessage(this.id._serialized, content, options); | ||
| } | ||
| /** | ||
| * Sets the channel as seen | ||
| * @returns {Promise<boolean>} | ||
| */ | ||
| async sendSeen() { | ||
| return this.client.sendSeen(this.id._serialized); | ||
| } | ||
| /** | ||
| * @typedef {Object} SendChannelAdminInviteOptions | ||
| * @property {?string} comment The comment to be added to an invitation | ||
| */ | ||
| /** | ||
| * Sends a channel admin invitation to a user, allowing them to become an admin of the channel | ||
| * @param {string} chatId The ID of a user to send the channel admin invitation to | ||
| * @param {SendChannelAdminInviteOptions} options | ||
| * @returns {Promise<boolean>} Returns true if an invitation was sent successfully, false otherwise | ||
| */ | ||
| async sendChannelAdminInvite(chatId, options = {}) { | ||
| return this.client.sendChannelAdminInvite(chatId, this.id._serialized, options); | ||
| } | ||
| /** | ||
| * Accepts a channel admin invitation and promotes the current user to a channel admin | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async acceptChannelAdminInvite() { | ||
| return this.client.acceptChannelAdminInvite(this.id._serialized); | ||
| } | ||
| /** | ||
| * Revokes a channel admin invitation sent to a user by a channel owner | ||
| * @param {string} userId The user ID the invitation was sent to | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async revokeChannelAdminInvite(userId) { | ||
| return this.client.revokeChannelAdminInvite(this.id._serialized, userId); | ||
| } | ||
| /** | ||
| * Demotes a channel admin to a regular subscriber (can be used also for self-demotion) | ||
| * @param {string} userId The user ID to demote | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async demoteChannelAdmin(userId) { | ||
| return this.client.demoteChannelAdmin(this.id._serialized, userId); | ||
| } | ||
| /** | ||
| * Options for transferring a channel ownership to another user | ||
| * @typedef {Object} TransferChannelOwnershipOptions | ||
| * @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. | ||
| */ | ||
| /** | ||
| * Transfers a channel ownership to another user. | ||
| * Note: the user you are transferring the channel ownership to must be a channel admin. | ||
| * @param {string} newOwnerId | ||
| * @param {TransferChannelOwnershipOptions} options | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async transferChannelOwnership(newOwnerId, options = {}) { | ||
| return this.client.transferChannelOwnership(this.id._serialized, newOwnerId, options); | ||
| } | ||
| /** | ||
| * Loads channel messages, sorted from earliest to latest | ||
| * @param {Object} searchOptions Options for searching messages. Right now only limit and fromMe is supported | ||
| * @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 | ||
| * @param {Boolean} [searchOptions.fromMe] Return only messages from the bot number or vise versa. To get all messages, leave the option undefined | ||
| * @returns {Promise<Array<Message>>} | ||
| */ | ||
| async fetchMessages(searchOptions) { | ||
| let messages = await this.client.pupPage.evaluate(async (channelId, searchOptions) => { | ||
| const msgFilter = (m) => { | ||
| if (m.isNotification || m.type === 'newsletter_notification') { | ||
| return false; // dont include notification messages | ||
| } | ||
| if (searchOptions && searchOptions.fromMe !== undefined && m.id.fromMe !== searchOptions.fromMe) { | ||
| return false; | ||
| } | ||
| return true; | ||
| }; | ||
| const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); | ||
| let msgs = channel.msgs.getModelsArray().filter(msgFilter); | ||
| if (searchOptions && searchOptions.limit > 0) { | ||
| while (msgs.length < searchOptions.limit) { | ||
| const loadedMessages = await window.Store.ConversationMsgs.loadEarlierMsgs(channel); | ||
| if (!loadedMessages || !loadedMessages.length) break; | ||
| msgs = [...loadedMessages.filter(msgFilter), ...msgs]; | ||
| } | ||
| if (msgs.length > searchOptions.limit) { | ||
| msgs.sort((a, b) => (a.t > b.t) ? 1 : -1); | ||
| msgs = msgs.splice(msgs.length - searchOptions.limit); | ||
| } | ||
| } | ||
| return msgs.map(m => window.WWebJS.getMessageModel(m)); | ||
| }, this.id._serialized, searchOptions); | ||
| return messages.map((msg) => new Message(this.client, msg)); | ||
| } | ||
| /** | ||
| * Deletes the channel you created | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async deleteChannel() { | ||
| return this.client.deleteChannel(this.id._serialized); | ||
| } | ||
| /** | ||
| * Internal method to change the channel metadata | ||
| * @param {string|number|MessageMedia} value The new value to set | ||
| * @param {string} property The property of a channel metadata to change | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async _setChannelMetadata(value, property) { | ||
| return await this.client.pupPage.evaluate(async (channelId, value, property) => { | ||
| const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); | ||
| if (!channel) return false; | ||
| if (property.editPicture) { | ||
| value.picture = value.picture | ||
| ? await window.WWebJS.cropAndResizeImage(value.picture, { | ||
| asDataUrl: true, | ||
| mimetype: 'image/jpeg', | ||
| size: 640, | ||
| quality: 1 | ||
| }) | ||
| : null; | ||
| } | ||
| try { | ||
| await window.Store.ChannelUtils.editNewsletterMetadataAction(channel, property, value); | ||
| return true; | ||
| } catch (err) { | ||
| if (err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
| } | ||
| }, this.id._serialized, value, property); | ||
| } | ||
| /** | ||
| * Internal method to mute or unmute the channel | ||
| * @param {string} action The action: 'MUTE' or 'UNMUTE' | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async _muteUnmuteChannel(action) { | ||
| return await this.client.pupPage.evaluate(async (channelId, action) => { | ||
| try { | ||
| action === 'MUTE' | ||
| ? await window.Store.ChannelUtils.muteNewsletter([channelId]) | ||
| : await window.Store.ChannelUtils.unmuteNewsletter([channelId]); | ||
| return true; | ||
| } catch (err) { | ||
| if (err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
| } | ||
| }, this.id._serialized, action); | ||
| } | ||
| } | ||
| module.exports = Channel; |
| 'use strict'; | ||
| /** | ||
| * Poll send options | ||
| * @typedef {Object} PollSendOptions | ||
| * @property {boolean} [allowMultipleAnswers=false] If false it is a single choice poll, otherwise it is a multiple choice poll (false by default) | ||
| * @property {?Array<number>} messageSecret The custom message secret, can be used as a poll ID. NOTE: it has to be a unique vector with a length of 32 | ||
| */ | ||
| /** Represents a Poll on WhatsApp */ | ||
| class Poll { | ||
| /** | ||
| * @param {string} pollName | ||
| * @param {Array<string>} pollOptions | ||
| * @param {PollSendOptions} options | ||
| */ | ||
| constructor(pollName, pollOptions, options = {}) { | ||
| /** | ||
| * The name of the poll | ||
| * @type {string} | ||
| */ | ||
| this.pollName = pollName.trim(); | ||
| /** | ||
| * The array of poll options | ||
| * @type {Array.<{name: string, localId: number}>} | ||
| */ | ||
| this.pollOptions = pollOptions.map((option, index) => ({ | ||
| name: option.trim(), | ||
| localId: index | ||
| })); | ||
| /** | ||
| * The send options for the poll | ||
| * @type {PollSendOptions} | ||
| */ | ||
| this.options = { | ||
| allowMultipleAnswers: options.allowMultipleAnswers === true, | ||
| messageSecret: options.messageSecret | ||
| }; | ||
| } | ||
| } | ||
| module.exports = Poll; |
| 'use strict'; | ||
| const Message = require('./Message'); | ||
| const Base = require('./Base'); | ||
| /** | ||
| * Selected poll option structure | ||
| * @typedef {Object} SelectedPollOption | ||
| * @property {number} id The local selected or deselected option ID | ||
| * @property {string} name The option name | ||
| */ | ||
| /** | ||
| * Represents a Poll Vote on WhatsApp | ||
| * @extends {Base} | ||
| */ | ||
| class PollVote extends Base { | ||
| constructor(client, data) { | ||
| super(client); | ||
| if (data) this._patch(data); | ||
| } | ||
| _patch(data) { | ||
| /** | ||
| * The person who voted | ||
| * @type {string} | ||
| */ | ||
| this.voter = data.sender; | ||
| /** | ||
| * The selected poll option(s) | ||
| * If it's an empty array, the user hasn't selected any options on the poll, | ||
| * may occur when they deselected all poll options | ||
| * @type {SelectedPollOption[]} | ||
| */ | ||
| if (data.selectedOptionLocalIds.length > 0) { | ||
| if(data.parentMessage) { // temporary failsafe | ||
| this.selectedOptions = data.selectedOptionLocalIds.map((e) => ({ | ||
| name: data.parentMessage.pollOptions.find((x) => x.localId === e).name, | ||
| localId: e | ||
| })); | ||
| } else { | ||
| this.selectedOptions = data.selectedOptionLocalIds.map((e) => ({ | ||
| name: undefined, | ||
| localId: e | ||
| })); | ||
| } | ||
| } else { | ||
| this.selectedOptions = []; | ||
| } | ||
| /** | ||
| * Timestamp the option was selected or deselected at | ||
| * @type {number} | ||
| */ | ||
| this.interractedAtTs = data.senderTimestampMs; | ||
| /** | ||
| * The poll creation message associated with the poll vote | ||
| * @type {Message} | ||
| */ | ||
| this.parentMessage = new Message(this.client, data.parentMessage); | ||
| /** | ||
| * The poll creation message id | ||
| * @type {Object} | ||
| */ | ||
| this.parentMsgKey = data.parentMsgKey; | ||
| return super._patch(data); | ||
| } | ||
| } | ||
| module.exports = PollVote; |
| 'use strict'; | ||
| /** | ||
| * ScheduledEvent send options | ||
| * @typedef {Object} ScheduledEventSendOptions | ||
| * @property {?string} description The scheduled event description | ||
| * @property {?Date} endTime The end time of the event | ||
| * @property {?string} location The location of the event | ||
| * @property {?string} callType The type of a WhatsApp call link to generate, valid values are: `video` | `voice` | ||
| * @property {boolean} [isEventCanceled = false] Indicates if a scheduled event should be sent as an already canceled | ||
| * @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 | ||
| */ | ||
| /** Represents a ScheduledEvent on WhatsApp */ | ||
| class ScheduledEvent { | ||
| /** | ||
| * @param {string} name | ||
| * @param {Date} startTime | ||
| * @param {ScheduledEventSendOptions} options | ||
| */ | ||
| constructor(name, startTime, options = {}) { | ||
| /** | ||
| * The name of the event | ||
| * @type {string} | ||
| */ | ||
| this.name = this._validateInputs('name', name).trim(); | ||
| /** | ||
| * The start time of the event | ||
| * @type {number} | ||
| */ | ||
| this.startTimeTs = Math.floor(startTime.getTime() / 1000); | ||
| /** | ||
| * The send options for the event | ||
| * @type {Object} | ||
| */ | ||
| this.eventSendOptions = { | ||
| description: options.description?.trim(), | ||
| endTimeTs: options.endTime ? Math.floor(options.endTime.getTime() / 1000) : null, | ||
| location: options.location?.trim(), | ||
| callType: this._validateInputs('callType', options.callType), | ||
| isEventCanceled: options.isEventCanceled ?? false, | ||
| messageSecret: options.messageSecret | ||
| }; | ||
| } | ||
| /** | ||
| * Inner function to validate input values | ||
| * @param {string} propName The property name to validate the value of | ||
| * @param {string | number} propValue The property value to validate | ||
| * @returns {string | number} The property value if a validation succeeded | ||
| */ | ||
| _validateInputs(propName, propValue) { | ||
| if (propName === 'name' && !propValue) { | ||
| throw new class CreateScheduledEventError extends Error { | ||
| constructor(m) { super(m); } | ||
| }(`Empty '${propName}' parameter value is provided.`); | ||
| } | ||
| if (propName === 'callType' && propValue && !['video', 'voice'].includes(propValue)) { | ||
| throw new class CreateScheduledEventError extends Error { | ||
| constructor(m) { super(m); } | ||
| }(`Invalid '${propName}' parameter value is provided. Valid values are: 'voice' | 'video'.`); | ||
| } | ||
| return propValue; | ||
| } | ||
| } | ||
| module.exports = ScheduledEvent; |
| 'use strict'; | ||
| exports.ExposeAuthStore = () => { | ||
| window.AuthStore = {}; | ||
| window.AuthStore.AppState = window.require('WAWebSocketModel').Socket; | ||
| window.AuthStore.Cmd = window.require('WAWebCmd').Cmd; | ||
| window.AuthStore.Conn = window.require('WAWebConnModel').Conn; | ||
| window.AuthStore.OfflineMessageHandler = window.require('WAWebOfflineHandler').OfflineMessageHandler; | ||
| window.AuthStore.PairingCodeLinkUtils = window.require('WAWebAltDeviceLinkingApi'); | ||
| window.AuthStore.Base64Tools = window.require('WABase64'); | ||
| window.AuthStore.RegistrationUtils = { | ||
| ...window.require('WAWebCompanionRegClientUtils'), | ||
| ...window.require('WAWebAdvSignatureApi'), | ||
| ...window.require('WAWebUserPrefsInfoStore'), | ||
| ...window.require('WAWebSignalStoreApi'), | ||
| }; | ||
| }; |
| 'use strict'; | ||
| //TODO: To be removed by version 2.3000.x hard release | ||
| exports.ExposeLegacyAuthStore = (moduleRaidStr) => { | ||
| eval('var moduleRaid = ' + moduleRaidStr); | ||
| // eslint-disable-next-line no-undef | ||
| window.mR = moduleRaid(); | ||
| window.AuthStore = {}; | ||
| window.AuthStore.AppState = window.mR.findModule('Socket')[0].Socket; | ||
| window.AuthStore.Cmd = window.mR.findModule('Cmd')[0].Cmd; | ||
| window.AuthStore.Conn = window.mR.findModule('Conn')[0].Conn; | ||
| window.AuthStore.OfflineMessageHandler = window.mR.findModule('OfflineMessageHandler')[0].OfflineMessageHandler; | ||
| window.AuthStore.PairingCodeLinkUtils = window.mR.findModule('initializeAltDeviceLinking')[0]; | ||
| window.AuthStore.Base64Tools = window.mR.findModule('encodeB64')[0]; | ||
| window.AuthStore.RegistrationUtils = { | ||
| ...window.mR.findModule('getCompanionWebClientFromBrowser')[0], | ||
| ...window.mR.findModule('verifyKeyIndexListAccountSignature')[0], | ||
| ...window.mR.findModule('waNoiseInfo')[0], | ||
| ...window.mR.findModule('waSignalStore')[0], | ||
| }; | ||
| }; |
| 'use strict'; | ||
| //TODO: To be removed by version 2.3000.x hard release | ||
| // Exposes the internal Store to the WhatsApp Web client | ||
| exports.ExposeLegacyStore = () => { | ||
| window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default); | ||
| window.Store.AppState = window.mR.findModule('Socket')[0].Socket; | ||
| window.Store.Conn = window.mR.findModule('Conn')[0].Conn; | ||
| window.Store.BlockContact = window.mR.findModule('blockContact')[0]; | ||
| window.Store.Call = window.mR.findModule((module) => module.default && module.default.Call)[0].default.Call; | ||
| window.Store.Cmd = window.mR.findModule('Cmd')[0].Cmd; | ||
| window.Store.CryptoLib = window.mR.findModule('decryptE2EMedia')[0]; | ||
| window.Store.DownloadManager = window.mR.findModule('downloadManager')[0].downloadManager; | ||
| window.Store.GroupMetadata = window.mR.findModule('GroupMetadata')[0].default.GroupMetadata; | ||
| window.Store.GroupQueryAndUpdate = window.mR.findModule('queryAndUpdateGroupMetadataById')[0].queryAndUpdateGroupMetadataById; | ||
| window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection; | ||
| window.Store.MediaPrep = window.mR.findModule('prepRawMedia')[0]; | ||
| window.Store.MediaObject = window.mR.findModule('getOrCreateMediaObject')[0]; | ||
| window.Store.NumberInfo = window.mR.findModule('formattedPhoneNumber')[0]; | ||
| window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0]; | ||
| window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0]; | ||
| window.Store.MsgKey = window.mR.findModule((module) => module.default && module.default.fromString)[0].default; | ||
| window.Store.OpaqueData = window.mR.findModule(module => module.default && module.default.createFromData)[0].default; | ||
| window.Store.QueryProduct = window.mR.findModule('queryProduct')[0]; | ||
| window.Store.QueryOrder = window.mR.findModule('queryOrder')[0]; | ||
| window.Store.SendClear = window.mR.findModule('sendClear')[0]; | ||
| window.Store.SendDelete = window.mR.findModule('sendDelete')[0]; | ||
| window.Store.SendMessage = window.mR.findModule('addAndSendMsgToChat')[0]; | ||
| window.Store.EditMessage = window.mR.findModule('addAndSendMessageEdit')[0]; | ||
| window.Store.SendSeen = window.mR.findModule('sendSeen')[0]; | ||
| window.Store.User = window.mR.findModule('getMaybeMeUser')[0]; | ||
| window.Store.ContactMethods = window.mR.findModule('getUserid')[0]; | ||
| window.Store.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default; | ||
| window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default; | ||
| window.Store.Validators = window.mR.findModule('findLinks')[0]; | ||
| window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0]; | ||
| window.Store.WidFactory = window.mR.findModule('createWid')[0]; | ||
| window.Store.ProfilePic = window.mR.findModule('profilePicResync')[0]; | ||
| window.Store.PresenceUtils = window.mR.findModule('sendPresenceAvailable')[0]; | ||
| window.Store.ChatState = window.mR.findModule('sendChatStateComposing')[0]; | ||
| window.Store.findCommonGroups = window.mR.findModule('findCommonGroups')[0].findCommonGroups; | ||
| window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0]; | ||
| window.Store.ConversationMsgs = window.mR.findModule('loadEarlierMsgs')[0]; | ||
| window.Store.sendReactionToMsg = window.mR.findModule('sendReactionToMsg')[0].sendReactionToMsg; | ||
| window.Store.createOrUpdateReactionsModule = window.mR.findModule('createOrUpdateReactions')[0]; | ||
| window.Store.EphemeralFields = window.mR.findModule('getEphemeralFields')[0]; | ||
| window.Store.MsgActionChecks = window.mR.findModule('canSenderRevokeMsg')[0]; | ||
| window.Store.QuotedMsg = window.mR.findModule('getQuotedMsgObj')[0]; | ||
| window.Store.LinkPreview = window.mR.findModule('getLinkPreview')[0]; | ||
| window.Store.Socket = window.mR.findModule('deprecatedSendIq')[0]; | ||
| window.Store.SocketWap = window.mR.findModule('wap')[0]; | ||
| window.Store.SearchContext = window.mR.findModule('getSearchContext')[0].getSearchContext; | ||
| window.Store.DrawerManager = window.mR.findModule('DrawerManager')[0].DrawerManager; | ||
| window.Store.LidUtils = window.mR.findModule('getCurrentLid')[0]; | ||
| window.Store.WidToJid = window.mR.findModule('widToUserJid')[0]; | ||
| window.Store.JidToWid = window.mR.findModule('userJidToUserWid')[0]; | ||
| window.Store.getMsgInfo = (window.mR.findModule('sendQueryMsgInfo')[0] || {}).sendQueryMsgInfo || window.mR.findModule('queryMsgInfo')[0].queryMsgInfo; | ||
| window.Store.pinUnpinMsg = window.mR.findModule('sendPinInChatMsg')[0].sendPinInChatMsg; | ||
| /* eslint-disable no-undef, no-cond-assign */ | ||
| window.Store.QueryExist = ((m = window.mR.findModule('queryExists')[0]) ? m.queryExists : window.mR.findModule('queryExist')[0].queryWidExists); | ||
| window.Store.ReplyUtils = (m = window.mR.findModule('canReplyMsg')).length > 0 && m[0]; | ||
| /* eslint-enable no-undef, no-cond-assign */ | ||
| window.Store.Settings = { | ||
| ...window.mR.findModule('ChatlistPanelState')[0], | ||
| setPushname: window.mR.findModule((m) => m.setPushname && !m.ChatlistPanelState)[0].setPushname | ||
| }; | ||
| window.Store.StickerTools = { | ||
| ...window.mR.findModule('toWebpSticker')[0], | ||
| ...window.mR.findModule('addWebpMetadata')[0] | ||
| }; | ||
| window.Store.GroupUtils = { | ||
| ...window.mR.findModule('createGroup')[0], | ||
| ...window.mR.findModule('setGroupDescription')[0], | ||
| ...window.mR.findModule('sendExitGroup')[0], | ||
| ...window.mR.findModule('sendSetPicture')[0] | ||
| }; | ||
| window.Store.GroupParticipants = { | ||
| ...window.mR.findModule('promoteParticipants')[0], | ||
| ...window.mR.findModule('sendAddParticipantsRPC')[0] | ||
| }; | ||
| window.Store.GroupInvite = { | ||
| ...window.mR.findModule('resetGroupInviteCode')[0], | ||
| ...window.mR.findModule('queryGroupInvite')[0] | ||
| }; | ||
| window.Store.GroupInviteV4 = { | ||
| ...window.mR.findModule('queryGroupInviteV4')[0], | ||
| ...window.mR.findModule('sendGroupInviteMessage')[0] | ||
| }; | ||
| window.Store.MembershipRequestUtils = { | ||
| ...window.mR.findModule('getMembershipApprovalRequests')[0], | ||
| ...window.mR.findModule('sendMembershipRequestsActionRPC')[0] | ||
| }; | ||
| if (!window.Store.Chat._find) { | ||
| window.Store.Chat._find = e => { | ||
| const target = window.Store.Chat.get(e); | ||
| return target ? Promise.resolve(target) : Promise.resolve({ | ||
| id: e | ||
| }); | ||
| }; | ||
| } | ||
| // eslint-disable-next-line no-undef | ||
| if ((m = window.mR.findModule('ChatCollection')[0]) && m.ChatCollection && typeof m.ChatCollection.findImpl === 'undefined' && typeof m.ChatCollection._find !== 'undefined') m.ChatCollection.findImpl = m.ChatCollection._find; | ||
| const _isMDBackend = window.mR.findModule('isMDBackend'); | ||
| if(_isMDBackend && _isMDBackend[0] && _isMDBackend[0].isMDBackend) { | ||
| window.Store.MDBackend = _isMDBackend[0].isMDBackend(); | ||
| } else { | ||
| window.Store.MDBackend = true; | ||
| } | ||
| const _features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0]; | ||
| if(_features) { | ||
| window.Store.Features = _features.LegacyPhoneFeatures; | ||
| } | ||
| /** | ||
| * Target options object description | ||
| * @typedef {Object} TargetOptions | ||
| * @property {string|number} module The name or a key of the target module to search | ||
| * @property {number} index The index value of the target module | ||
| * @property {string} function The function name to get from a module | ||
| */ | ||
| /** | ||
| * Function to modify functions | ||
| * @param {TargetOptions} target Options specifying the target function to search for modifying | ||
| * @param {Function} callback Modified function | ||
| */ | ||
| window.injectToFunction = (target, callback) => { | ||
| const module = typeof target.module === 'string' | ||
| ? window.mR.findModule(target.module) | ||
| : window.mR.modules[target.module]; | ||
| const originalFunction = module[target.index][target.function]; | ||
| const modifiedFunction = (...args) => callback(originalFunction, ...args); | ||
| module[target.index][target.function] = modifiedFunction; | ||
| }; | ||
| window.injectToFunction({ module: 'mediaTypeFromProtobuf', index: 0, function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); | ||
| window.injectToFunction({ module: 'typeAttributeFromProtobuf', index: 0, function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); | ||
| }; |
| 'use strict'; | ||
| exports.ExposeStore = () => { | ||
| /** | ||
| * Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version. | ||
| * @param {string} lOperand The left operand for the WWeb version string to compare with | ||
| * @param {string} operator The comparison operator | ||
| * @param {string} rOperand The right operand for the WWeb version string to compare with | ||
| * @returns {boolean} Boolean value that indicates the result of the comparison | ||
| */ | ||
| window.compareWwebVersions = (lOperand, operator, rOperand) => { | ||
| if (!['>', '>=', '<', '<=', '='].includes(operator)) { | ||
| throw new class _ extends Error { | ||
| constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } | ||
| }('Invalid comparison operator is provided'); | ||
| } | ||
| if (typeof lOperand !== 'string' || typeof rOperand !== 'string') { | ||
| throw new class _ extends Error { | ||
| constructor(m) { super(m); this.name = 'CompareWwebVersionsError'; } | ||
| }('A non-string WWeb version type is provided'); | ||
| } | ||
| lOperand = lOperand.replace(/-beta$/, ''); | ||
| rOperand = rOperand.replace(/-beta$/, ''); | ||
| while (lOperand.length !== rOperand.length) { | ||
| lOperand.length > rOperand.length | ||
| ? rOperand = rOperand.concat('0') | ||
| : lOperand = lOperand.concat('0'); | ||
| } | ||
| lOperand = Number(lOperand.replace(/\./g, '')); | ||
| rOperand = Number(rOperand.replace(/\./g, '')); | ||
| return ( | ||
| operator === '>' ? lOperand > rOperand : | ||
| operator === '>=' ? lOperand >= rOperand : | ||
| operator === '<' ? lOperand < rOperand : | ||
| operator === '<=' ? lOperand <= rOperand : | ||
| operator === '=' ? lOperand === rOperand : | ||
| false | ||
| ); | ||
| }; | ||
| window.Store = Object.assign({}, window.require('WAWebCollections')); | ||
| window.Store.AppState = window.require('WAWebSocketModel').Socket; | ||
| window.Store.BlockContact = window.require('WAWebBlockContactAction'); | ||
| window.Store.Conn = window.require('WAWebConnModel').Conn; | ||
| window.Store.Cmd = window.require('WAWebCmd').Cmd; | ||
| window.Store.DownloadManager = window.require('WAWebDownloadManager').downloadManager; | ||
| window.Store.GroupQueryAndUpdate = window.require('WAWebGroupQueryJob').queryAndUpdateGroupMetadataById; | ||
| window.Store.MediaPrep = window.require('WAWebPrepRawMedia'); | ||
| window.Store.MediaObject = window.require('WAWebMediaStorage'); | ||
| window.Store.MediaTypes = window.require('WAWebMmsMediaTypes'); | ||
| window.Store.MediaUpload = window.require('WAWebMediaMmsV4Upload'); | ||
| window.Store.MsgKey = window.require('WAWebMsgKey'); | ||
| window.Store.OpaqueData = window.require('WAWebMediaOpaqueData'); | ||
| window.Store.QueryProduct = window.require('WAWebBizProductCatalogBridge'); | ||
| window.Store.QueryOrder = window.require('WAWebBizOrderBridge'); | ||
| window.Store.SendClear = window.require('WAWebChatClearBridge'); | ||
| window.Store.SendDelete = window.require('WAWebDeleteChatAction'); | ||
| window.Store.SendMessage = window.require('WAWebSendMsgChatAction'); | ||
| window.Store.EditMessage = window.require('WAWebSendMessageEditAction'); | ||
| window.Store.SendSeen = window.require('WAWebUpdateUnreadChatAction'); | ||
| window.Store.User = window.require('WAWebUserPrefsMeUser'); | ||
| window.Store.ContactMethods = window.require('WAWebContactGetters'); | ||
| window.Store.UserConstructor = window.require('WAWebWid'); | ||
| window.Store.Validators = window.require('WALinkify'); | ||
| window.Store.WidFactory = window.require('WAWebWidFactory'); | ||
| window.Store.ProfilePic = window.require('WAWebContactProfilePicThumbBridge'); | ||
| window.Store.PresenceUtils = window.require('WAWebPresenceChatAction'); | ||
| window.Store.ChatState = window.require('WAWebChatStateBridge'); | ||
| window.Store.findCommonGroups = window.require('WAWebFindCommonGroupsContactAction').findCommonGroups; | ||
| window.Store.StatusUtils = window.require('WAWebContactStatusBridge'); | ||
| window.Store.ConversationMsgs = window.require('WAWebChatLoadMessages'); | ||
| window.Store.sendReactionToMsg = window.require('WAWebSendReactionMsgAction').sendReactionToMsg; | ||
| window.Store.createOrUpdateReactionsModule = window.require('WAWebDBCreateOrUpdateReactions'); | ||
| window.Store.EphemeralFields = window.require('WAWebGetEphemeralFieldsMsgActionsUtils'); | ||
| window.Store.MsgActionChecks = window.require('WAWebMsgActionCapability'); | ||
| window.Store.QuotedMsg = window.require('WAWebQuotedMsgModelUtils'); | ||
| window.Store.LinkPreview = window.require('WAWebLinkPreviewChatAction'); | ||
| window.Store.Socket = window.require('WADeprecatedSendIq'); | ||
| window.Store.SocketWap = window.require('WAWap'); | ||
| window.Store.SearchContext = window.require('WAWebChatMessageSearch'); | ||
| window.Store.DrawerManager = window.require('WAWebDrawerManager').DrawerManager; | ||
| window.Store.LidUtils = window.require('WAWebApiContact'); | ||
| window.Store.WidToJid = window.require('WAWebWidToJid'); | ||
| window.Store.JidToWid = window.require('WAWebJidToWid'); | ||
| window.Store.getMsgInfo = window.require('WAWebApiMessageInfoStore').queryMsgInfo; | ||
| window.Store.QueryExist = window.require('WAWebQueryExistsJob').queryWidExists; | ||
| window.Store.ReplyUtils = window.require('WAWebMsgReply'); | ||
| window.Store.BotSecret = window.require('WAWebBotMessageSecret'); | ||
| window.Store.BotProfiles = window.require('WAWebBotProfileCollection'); | ||
| window.Store.ContactCollection = window.require('WAWebContactCollection').ContactCollection; | ||
| window.Store.DeviceList = window.require('WAWebApiDeviceList'); | ||
| window.Store.HistorySync = window.require('WAWebSendNonMessageDataRequest'); | ||
| window.Store.AddonReactionTable = window.require('WAWebAddonReactionTableMode').reactionTableMode; | ||
| window.Store.AddonPollVoteTable = window.require('WAWebAddonPollVoteTableMode').pollVoteTableMode; | ||
| window.Store.PinnedMsgUtils = window.require('WAWebPinInChatSchema'); | ||
| window.Store.ChatGetters = window.require('WAWebChatGetters'); | ||
| window.Store.PinnedMsgUtils = window.require('WAWebSendPinMessageAction'); | ||
| window.Store.UploadUtils = window.require('WAWebUploadManager'); | ||
| window.Store.WAWebStreamModel = window.require('WAWebStreamModel'); | ||
| window.Store.Settings = { | ||
| ...window.require('WAWebUserPrefsGeneral'), | ||
| ...window.require('WAWebUserPrefsNotifications'), | ||
| setPushname: window.require('WAWebSetPushnameConnAction').setPushname | ||
| }; | ||
| window.Store.NumberInfo = { | ||
| ...window.require('WAPhoneUtils'), | ||
| ...window.require('WAPhoneFindCC') | ||
| }; | ||
| window.Store.ForwardUtils = { | ||
| ...window.require('WAWebChatForwardMessage') | ||
| }; | ||
| window.Store.ScheduledEventMsgUtils = { | ||
| ...window.require('WAWebGenerateEventCallLink'), | ||
| ...window.require('WAWebSendEventEditMsgAction'), | ||
| ...window.require('WAWebSendEventResponseMsgAction') | ||
| }; | ||
| window.Store.VCard = { | ||
| ...window.require('WAWebFrontendVcardUtils'), | ||
| ...window.require('WAWebVcardParsingUtils'), | ||
| ...window.require('WAWebVcardGetNameFromParsed') | ||
| }; | ||
| window.Store.StickerTools = { | ||
| ...window.require('WAWebImageUtils'), | ||
| ...window.require('WAWebAddWebpMetadata') | ||
| }; | ||
| window.Store.GroupUtils = { | ||
| ...window.require('WAWebGroupCreateJob'), | ||
| ...window.require('WAWebGroupModifyInfoJob'), | ||
| ...window.require('WAWebExitGroupAction'), | ||
| ...window.require('WAWebContactProfilePicThumbBridge') | ||
| }; | ||
| window.Store.GroupParticipants = { | ||
| ...window.require('WAWebModifyParticipantsGroupAction'), | ||
| ...window.require('WASmaxGroupsAddParticipantsRPC') | ||
| }; | ||
| window.Store.GroupInvite = { | ||
| ...window.require('WAWebGroupInviteJob'), | ||
| ...window.require('WAWebGroupQueryJob'), | ||
| ...window.require('WAWebMexFetchGroupInviteCodeJob') | ||
| }; | ||
| window.Store.GroupInviteV4 = { | ||
| ...window.require('WAWebGroupInviteV4Job'), | ||
| ...window.require('WAWebChatSendMessages') | ||
| }; | ||
| window.Store.MembershipRequestUtils = { | ||
| ...window.require('WAWebApiMembershipApprovalRequestStore'), | ||
| ...window.require('WASmaxGroupsMembershipRequestsActionRPC') | ||
| }; | ||
| window.Store.ChannelUtils = { | ||
| ...window.require('WAWebLoadNewsletterPreviewChatAction'), | ||
| ...window.require('WAWebNewsletterMetadataQueryJob'), | ||
| ...window.require('WAWebNewsletterCreateQueryJob'), | ||
| ...window.require('WAWebEditNewsletterMetadataAction'), | ||
| ...window.require('WAWebNewsletterDeleteAction'), | ||
| ...window.require('WAWebNewsletterSubscribeAction'), | ||
| ...window.require('WAWebNewsletterUnsubscribeAction'), | ||
| ...window.require('WAWebNewsletterDirectorySearchAction'), | ||
| ...window.require('WAWebNewsletterToggleMuteStateJob'), | ||
| ...window.require('WAWebNewsletterGatingUtils'), | ||
| ...window.require('WAWebNewsletterModelUtils'), | ||
| ...window.require('WAWebMexAcceptNewsletterAdminInviteJob'), | ||
| ...window.require('WAWebMexRevokeNewsletterAdminInviteJob'), | ||
| ...window.require('WAWebChangeNewsletterOwnerAction'), | ||
| ...window.require('WAWebDemoteNewsletterAdminAction'), | ||
| ...window.require('WAWebNewsletterDemoteAdminJob'), | ||
| countryCodesIso: window.require('WAWebCountriesNativeCountryNames'), | ||
| currentRegion: window.require('WAWebL10N').getRegion(), | ||
| }; | ||
| window.Store.SendChannelMessage = { | ||
| ...window.require('WAWebNewsletterUpdateMsgsRecordsJob'), | ||
| ...window.require('WAWebMsgDataFromModel'), | ||
| ...window.require('WAWebNewsletterSendMessageJob'), | ||
| ...window.require('WAWebNewsletterSendMsgAction'), | ||
| ...window.require('WAMediaCalculateFilehash') | ||
| }; | ||
| window.Store.ChannelSubscribers = { | ||
| ...window.require('WAWebMexFetchNewsletterSubscribersJob'), | ||
| ...window.require('WAWebNewsletterSubscriberListAction') | ||
| }; | ||
| window.Store.AddressbookContactUtils = { | ||
| ...window.require('WAWebSaveContactAction'), | ||
| ...window.require('WAWebDeleteContactAction') | ||
| }; | ||
| if (!window.Store.Chat._find || !window.Store.Chat.findImpl) { | ||
| window.Store.Chat._find = e => { | ||
| const target = window.Store.Chat.get(e); | ||
| return target ? Promise.resolve(target) : Promise.resolve({ | ||
| id: e | ||
| }); | ||
| }; | ||
| window.Store.Chat.findImpl = window.Store.Chat._find; | ||
| } | ||
| /** | ||
| * Target options object description | ||
| * @typedef {Object} TargetOptions | ||
| * @property {string|number} module The target module | ||
| * @property {string} function The function name to get from a module | ||
| */ | ||
| /** | ||
| * Function to modify functions | ||
| * @param {TargetOptions} target Options specifying the target function to search for modifying | ||
| * @param {Function} callback Modified function | ||
| */ | ||
| window.injectToFunction = (target, callback) => { | ||
| const module = window.require(target.module); | ||
| const originalFunction = module[target.function]; | ||
| const modifiedFunction = (...args) => callback(originalFunction, ...args); | ||
| module[target.function] = modifiedFunction; | ||
| }; | ||
| window.injectToFunction({ module: 'WAWebBackendJobsCommon', function: 'mediaTypeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage ? null : func(...args); }); | ||
| window.injectToFunction({ module: 'WAWebE2EProtoUtils', function: 'typeAttributeFromProtobuf' }, (func, ...args) => { const [proto] = args; return proto.locationMessage || proto.groupInviteMessage ? 'text' : func(...args); }); | ||
| }; |
| 'use strict'; | ||
| exports.LoadUtils = () => { | ||
| window.WWebJS = {}; | ||
| window.WWebJS.forwardMessage = async (chatId, msgId) => { | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| return window.Store.ForwardUtils.forwardMessages(chat, [msg], true, true); | ||
| }; | ||
| window.WWebJS.sendSeen = async (chatId) => { | ||
| const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| if (chat) { | ||
| window.Store.WAWebStreamModel.Stream.markAvailable(); | ||
| await window.Store.SendSeen.sendSeen(chat); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| window.WWebJS.sendMessage = async (chat, content, options = {}) => { | ||
| const isChannel = window.Store.ChatGetters.getIsNewsletter(chat); | ||
| let mediaOptions = {}; | ||
| if (options.media) { | ||
| mediaOptions = options.sendMediaAsSticker && !isChannel | ||
| ? await window.WWebJS.processStickerData(options.media) | ||
| : await window.WWebJS.processMediaData(options.media, { | ||
| forceSticker: options.sendMediaAsSticker, | ||
| forceGif: options.sendVideoAsGif, | ||
| forceVoice: options.sendAudioAsVoice, | ||
| forceDocument: options.sendMediaAsDocument, | ||
| forceMediaHd: options.sendMediaAsHd, | ||
| sendToChannel: isChannel | ||
| }); | ||
| mediaOptions.caption = options.caption; | ||
| content = options.sendMediaAsSticker ? undefined : mediaOptions.preview; | ||
| mediaOptions.isViewOnce = options.isViewOnce; | ||
| delete options.media; | ||
| delete options.sendMediaAsSticker; | ||
| } | ||
| let quotedMsgOptions = {}; | ||
| if (options.quotedMessageId) { | ||
| let quotedMessage = window.Store.Msg.get(options.quotedMessageId); | ||
| !quotedMessage && (quotedMessage = (await window.Store.Msg.getMessagesById([options.quotedMessageId]))?.messages?.[0]); | ||
| if (quotedMessage) { | ||
| const canReply = window.Store.ReplyUtils | ||
| ? window.Store.ReplyUtils.canReplyMsg(quotedMessage.unsafe()) | ||
| : quotedMessage.canReply(); | ||
| if (canReply) { | ||
| quotedMsgOptions = quotedMessage.msgContextInfo(chat); | ||
| } | ||
| } else { | ||
| if (!options.ignoreQuoteErrors) { | ||
| throw new Error('Could not get the quoted message.'); | ||
| } | ||
| } | ||
| delete options.ignoreQuoteErrors; | ||
| delete options.quotedMessageId; | ||
| } | ||
| if (options.mentionedJidList) { | ||
| options.mentionedJidList = await Promise.all( | ||
| options.mentionedJidList.map(async (id) => { | ||
| const wid = window.Store.WidFactory.createWid(id); | ||
| if (await window.Store.QueryExist(wid)) { | ||
| return wid; | ||
| } | ||
| }) | ||
| ); | ||
| options.mentionedJidList = options.mentionedJidList.filter(Boolean); | ||
| } | ||
| if (options.groupMentions) { | ||
| options.groupMentions = options.groupMentions.map((e) => ({ | ||
| groupSubject: e.subject, | ||
| groupJid: window.Store.WidFactory.createWid(e.id) | ||
| })); | ||
| } | ||
| let locationOptions = {}; | ||
| if (options.location) { | ||
| let { latitude, longitude, description, url } = options.location; | ||
| url = window.Store.Validators.findLink(url)?.href; | ||
| url && !description && (description = url); | ||
| locationOptions = { | ||
| type: 'location', | ||
| loc: description, | ||
| lat: latitude, | ||
| lng: longitude, | ||
| clientUrl: url | ||
| }; | ||
| delete options.location; | ||
| } | ||
| let _pollOptions = {}; | ||
| if (options.poll) { | ||
| const { pollName, pollOptions } = options.poll; | ||
| const { allowMultipleAnswers, messageSecret } = options.poll.options; | ||
| _pollOptions = { | ||
| kind: 'pollCreation', | ||
| type: 'poll_creation', | ||
| pollName: pollName, | ||
| pollOptions: pollOptions, | ||
| pollSelectableOptionsCount: allowMultipleAnswers ? 0 : 1, | ||
| messageSecret: | ||
| Array.isArray(messageSecret) && messageSecret.length === 32 | ||
| ? new Uint8Array(messageSecret) | ||
| : window.crypto.getRandomValues(new Uint8Array(32)) | ||
| }; | ||
| delete options.poll; | ||
| } | ||
| let eventOptions = {}; | ||
| if (options.event) { | ||
| const { name, startTimeTs, eventSendOptions } = options.event; | ||
| const { messageSecret } = eventSendOptions; | ||
| eventOptions = { | ||
| type: 'event_creation', | ||
| eventName: name, | ||
| eventDescription: eventSendOptions.description, | ||
| eventStartTime: startTimeTs, | ||
| eventEndTime: eventSendOptions.endTimeTs, | ||
| eventLocation: eventSendOptions.location && { | ||
| degreesLatitude: 0, | ||
| degreesLongitude: 0, | ||
| name: eventSendOptions.location | ||
| }, | ||
| eventJoinLink: await window.Store.ScheduledEventMsgUtils.createEventCallLink( | ||
| startTimeTs, | ||
| eventSendOptions.callType | ||
| ), | ||
| isEventCanceled: eventSendOptions.isEventCanceled, | ||
| messageSecret: | ||
| Array.isArray(messageSecret) && messageSecret.length === 32 | ||
| ? new Uint8Array(messageSecret) | ||
| : window.crypto.getRandomValues(new Uint8Array(32)), | ||
| }; | ||
| delete options.event; | ||
| } | ||
| let vcardOptions = {}; | ||
| if (options.contactCard) { | ||
| let contact = window.Store.Contact.get(options.contactCard); | ||
| vcardOptions = { | ||
| body: window.Store.VCard.vcardFromContactModel(contact).vcard, | ||
| type: 'vcard', | ||
| vcardFormattedName: contact.formattedName | ||
| }; | ||
| delete options.contactCard; | ||
| } else if (options.contactCardList) { | ||
| let contacts = options.contactCardList.map(c => window.Store.Contact.get(c)); | ||
| let vcards = contacts.map(c => window.Store.VCard.vcardFromContactModel(c)); | ||
| vcardOptions = { | ||
| type: 'multi_vcard', | ||
| vcardList: vcards, | ||
| body: null | ||
| }; | ||
| delete options.contactCardList; | ||
| } else if (options.parseVCards && typeof (content) === 'string' && content.startsWith('BEGIN:VCARD')) { | ||
| delete options.parseVCards; | ||
| delete options.linkPreview; | ||
| try { | ||
| const parsed = window.Store.VCard.parseVcard(content); | ||
| if (parsed) { | ||
| vcardOptions = { | ||
| type: 'vcard', | ||
| vcardFormattedName: window.Store.VCard.vcardGetNameFromParsed(parsed) | ||
| }; | ||
| } | ||
| } catch (_) { | ||
| // not a vcard | ||
| } | ||
| } | ||
| if (options.linkPreview) { | ||
| delete options.linkPreview; | ||
| const link = window.Store.Validators.findLink(content); | ||
| if (link) { | ||
| let preview = await window.Store.LinkPreview.getLinkPreview(link); | ||
| if (preview && preview.data) { | ||
| preview = preview.data; | ||
| preview.preview = true; | ||
| preview.subtype = 'url'; | ||
| options = {...options, ...preview}; | ||
| } | ||
| } | ||
| } | ||
| let buttonOptions = {}; | ||
| if (options.buttons) { | ||
| let caption; | ||
| if (options.buttons.type === 'chat') { | ||
| content = options.buttons.body; | ||
| caption = content; | ||
| } else { | ||
| caption = options.caption ? options.caption : ' '; //Caption can't be empty | ||
| } | ||
| buttonOptions = { | ||
| productHeaderImageRejected: false, | ||
| isFromTemplate: false, | ||
| isDynamicReplyButtonsMsg: true, | ||
| title: options.buttons.title ? options.buttons.title : undefined, | ||
| footer: options.buttons.footer ? options.buttons.footer : undefined, | ||
| dynamicReplyButtons: options.buttons.buttons, | ||
| replyButtons: options.buttons.buttons, | ||
| caption: caption | ||
| }; | ||
| delete options.buttons; | ||
| } | ||
| let listOptions = {}; | ||
| if (options.list) { | ||
| if (window.Store.Conn.platform === 'smba' || window.Store.Conn.platform === 'smbi') { | ||
| throw '[LT01] Whatsapp business can\'t send this yet'; | ||
| } | ||
| listOptions = { | ||
| type: 'list', | ||
| footer: options.list.footer, | ||
| list: { | ||
| ...options.list, | ||
| listType: 1 | ||
| }, | ||
| body: options.list.description | ||
| }; | ||
| delete options.list; | ||
| delete listOptions.list.footer; | ||
| } | ||
| const botOptions = {}; | ||
| if (options.invokedBotWid) { | ||
| botOptions.messageSecret = window.crypto.getRandomValues(new Uint8Array(32)); | ||
| botOptions.botMessageSecret = await window.Store.BotSecret.genBotMsgSecretFromMsgSecret(botOptions.messageSecret); | ||
| botOptions.invokedBotWid = window.Store.WidFactory.createWid(options.invokedBotWid); | ||
| botOptions.botPersonaId = window.Store.BotProfiles.BotProfileCollection.get(options.invokedBotWid).personaId; | ||
| delete options.invokedBotWid; | ||
| } | ||
| const lidUser = window.Store.User.getMaybeMeLidUser(); | ||
| const meUser = window.Store.User.getMaybeMePnUser(); | ||
| const newId = await window.Store.MsgKey.newId(); | ||
| let from = chat.id.isLid() ? lidUser : meUser; | ||
| let participant; | ||
| if (typeof chat.id?.isGroup === 'function' && chat.id.isGroup()) { | ||
| from = chat.groupMetadata && chat.groupMetadata.isLidAddressingMode ? lidUser : meUser; | ||
| participant = window.Store.WidFactory.toUserWidOrThrow(from); | ||
| } | ||
| const newMsgKey = new window.Store.MsgKey({ | ||
| from: from, | ||
| to: chat.id, | ||
| id: newId, | ||
| participant: participant, | ||
| selfDir: 'out', | ||
| }); | ||
| const extraOptions = options.extraOptions || {}; | ||
| delete options.extraOptions; | ||
| const ephemeralFields = window.Store.EphemeralFields.getEphemeralFields(chat); | ||
| const message = { | ||
| ...options, | ||
| id: newMsgKey, | ||
| ack: 0, | ||
| body: content, | ||
| from: meUser, | ||
| to: chat.id, | ||
| local: true, | ||
| self: 'out', | ||
| t: parseInt(new Date().getTime() / 1000), | ||
| isNewMsg: true, | ||
| type: 'chat', | ||
| ...ephemeralFields, | ||
| ...mediaOptions, | ||
| ...(mediaOptions.toJSON ? mediaOptions.toJSON() : {}), | ||
| ...quotedMsgOptions, | ||
| ...locationOptions, | ||
| ..._pollOptions, | ||
| ...eventOptions, | ||
| ...vcardOptions, | ||
| ...buttonOptions, | ||
| ...listOptions, | ||
| ...botOptions, | ||
| ...extraOptions | ||
| }; | ||
| // Bot's won't reply if canonicalUrl is set (linking) | ||
| if (botOptions) { | ||
| delete message.canonicalUrl; | ||
| } | ||
| if (isChannel) { | ||
| const msg = new window.Store.Msg.modelClass(message); | ||
| const msgDataFromMsgModel = window.Store.SendChannelMessage.msgDataFromMsgModel(msg); | ||
| const isMedia = Object.keys(mediaOptions).length > 0; | ||
| await window.Store.SendChannelMessage.addNewsletterMsgsRecords([msgDataFromMsgModel]); | ||
| chat.msgs.add(msg); | ||
| chat.t = msg.t; | ||
| const sendChannelMsgResponse = await window.Store.SendChannelMessage.sendNewsletterMessageJob({ | ||
| msg: msg, | ||
| type: message.type === 'chat' ? 'text' : isMedia ? 'media' : 'pollCreation', | ||
| newsletterJid: chat.id.toJid(), | ||
| ...(isMedia | ||
| ? { | ||
| mediaMetadata: msg.avParams(), | ||
| mediaHandle: isMedia ? mediaOptions.mediaHandle : null, | ||
| } | ||
| : {} | ||
| ) | ||
| }); | ||
| if (sendChannelMsgResponse.success) { | ||
| msg.t = sendChannelMsgResponse.ack.t; | ||
| msg.serverId = sendChannelMsgResponse.serverId; | ||
| } | ||
| msg.updateAck(1, true); | ||
| await window.Store.SendChannelMessage.updateNewsletterMsgRecord(msg); | ||
| return msg; | ||
| } | ||
| const [msgPromise, sendMsgResultPromise] = window.Store.SendMessage.addAndSendMsgToChat(chat, message); | ||
| await msgPromise; | ||
| if (options.waitUntilMsgSent) await sendMsgResultPromise; | ||
| return window.Store.Msg.get(newMsgKey._serialized); | ||
| }; | ||
| window.WWebJS.editMessage = async (msg, content, options = {}) => { | ||
| const extraOptions = options.extraOptions || {}; | ||
| delete options.extraOptions; | ||
| if (options.mentionedJidList) { | ||
| options.mentionedJidList = await Promise.all( | ||
| options.mentionedJidList.map(async (id) => { | ||
| const wid = window.Store.WidFactory.createWid(id); | ||
| if (await window.Store.QueryExist(wid)) { | ||
| return wid; | ||
| } | ||
| }) | ||
| ); | ||
| options.mentionedJidList = options.mentionedJidList.filter(Boolean); | ||
| } | ||
| if (options.groupMentions) { | ||
| options.groupMentions = options.groupMentions.map((e) => ({ | ||
| groupSubject: e.subject, | ||
| groupJid: window.Store.WidFactory.createWid(e.id) | ||
| })); | ||
| } | ||
| if (options.linkPreview) { | ||
| delete options.linkPreview; | ||
| const link = window.Store.Validators.findLink(content); | ||
| if (link) { | ||
| const preview = await window.Store.LinkPreview.getLinkPreview(link); | ||
| preview.preview = true; | ||
| preview.subtype = 'url'; | ||
| options = { ...options, ...preview }; | ||
| } | ||
| } | ||
| const internalOptions = { | ||
| ...options, | ||
| ...extraOptions | ||
| }; | ||
| await window.Store.EditMessage.sendMessageEdit(msg, content, internalOptions); | ||
| return window.Store.Msg.get(msg.id._serialized); | ||
| }; | ||
| window.WWebJS.toStickerData = async (mediaInfo) => { | ||
| if (mediaInfo.mimetype == 'image/webp') return mediaInfo; | ||
| const file = window.WWebJS.mediaInfoToFile(mediaInfo); | ||
| const webpSticker = await window.Store.StickerTools.toWebpSticker(file); | ||
| const webpBuffer = await webpSticker.arrayBuffer(); | ||
| const data = window.WWebJS.arrayBufferToBase64(webpBuffer); | ||
| return { | ||
| mimetype: 'image/webp', | ||
| data | ||
| }; | ||
| }; | ||
| window.WWebJS.processStickerData = async (mediaInfo) => { | ||
| if (mediaInfo.mimetype !== 'image/webp') throw new Error('Invalid media type'); | ||
| const file = window.WWebJS.mediaInfoToFile(mediaInfo); | ||
| let filehash = await window.WWebJS.getFileHash(file); | ||
| let mediaKey = await window.WWebJS.generateHash(32); | ||
| const controller = new AbortController(); | ||
| const uploadedInfo = await window.Store.UploadUtils.encryptAndUpload({ | ||
| blob: file, | ||
| type: 'sticker', | ||
| signal: controller.signal, | ||
| mediaKey | ||
| }); | ||
| const stickerInfo = { | ||
| ...uploadedInfo, | ||
| clientUrl: uploadedInfo.url, | ||
| deprecatedMms3Url: uploadedInfo.url, | ||
| uploadhash: uploadedInfo.encFilehash, | ||
| size: file.size, | ||
| type: 'sticker', | ||
| filehash | ||
| }; | ||
| return stickerInfo; | ||
| }; | ||
| window.WWebJS.processMediaData = async (mediaInfo, { forceSticker, forceGif, forceVoice, forceDocument, forceMediaHd, sendToChannel }) => { | ||
| const file = window.WWebJS.mediaInfoToFile(mediaInfo); | ||
| const opaqueData = await window.Store.OpaqueData.createFromData(file, file.type); | ||
| const mediaParams = { | ||
| asSticker: forceSticker, | ||
| asGif: forceGif, | ||
| isPtt: forceVoice, | ||
| asDocument: forceDocument | ||
| }; | ||
| if (forceMediaHd && file.type.indexOf('image/') === 0) { | ||
| mediaParams.maxDimension = 2560; | ||
| } | ||
| const mediaPrep = window.Store.MediaPrep.prepRawMedia(opaqueData, mediaParams); | ||
| const mediaData = await mediaPrep.waitForPrep(); | ||
| const mediaObject = window.Store.MediaObject.getOrCreateMediaObject(mediaData.filehash); | ||
| const mediaType = window.Store.MediaTypes.msgToMediaType({ | ||
| type: mediaData.type, | ||
| isGif: mediaData.isGif, | ||
| isNewsletter: sendToChannel, | ||
| }); | ||
| if (forceVoice && mediaData.type === 'ptt') { | ||
| const waveform = mediaObject.contentInfo.waveform; | ||
| mediaData.waveform = | ||
| waveform || await window.WWebJS.generateWaveform(file); | ||
| } | ||
| if (!(mediaData.mediaBlob instanceof window.Store.OpaqueData)) { | ||
| mediaData.mediaBlob = await window.Store.OpaqueData.createFromData( | ||
| mediaData.mediaBlob, | ||
| mediaData.mediaBlob.type | ||
| ); | ||
| } | ||
| mediaData.renderableUrl = mediaData.mediaBlob.url(); | ||
| mediaObject.consolidate(mediaData.toJSON()); | ||
| mediaData.mediaBlob.autorelease(); | ||
| const dataToUpload = { | ||
| mimetype: mediaData.mimetype, | ||
| mediaObject, | ||
| mediaType, | ||
| ...(sendToChannel ? { calculateToken: window.Store.SendChannelMessage.getRandomFilehash } : {}) | ||
| }; | ||
| const uploadedMedia = !sendToChannel | ||
| ? await window.Store.MediaUpload.uploadMedia(dataToUpload) | ||
| : await window.Store.MediaUpload.uploadUnencryptedMedia(dataToUpload); | ||
| const mediaEntry = uploadedMedia.mediaEntry; | ||
| if (!mediaEntry) { | ||
| throw new Error('upload failed: media entry was not created'); | ||
| } | ||
| mediaData.set({ | ||
| clientUrl: mediaEntry.mmsUrl, | ||
| deprecatedMms3Url: mediaEntry.deprecatedMms3Url, | ||
| directPath: mediaEntry.directPath, | ||
| mediaKey: mediaEntry.mediaKey, | ||
| mediaKeyTimestamp: mediaEntry.mediaKeyTimestamp, | ||
| filehash: mediaObject.filehash, | ||
| encFilehash: mediaEntry.encFilehash, | ||
| uploadhash: mediaEntry.uploadHash, | ||
| size: mediaObject.size, | ||
| streamingSidecar: mediaEntry.sidecar, | ||
| firstFrameSidecar: mediaEntry.firstFrameSidecar, | ||
| mediaHandle: sendToChannel ? mediaEntry.handle : null, | ||
| }); | ||
| return mediaData; | ||
| }; | ||
| window.WWebJS.getMessageModel = (message) => { | ||
| const msg = message.serialize(); | ||
| msg.isEphemeral = message.isEphemeral; | ||
| msg.isStatusV3 = message.isStatusV3; | ||
| msg.links = (window.Store.Validators.findLinks(message.mediaObject ? message.caption : message.body)).map((link) => ({ | ||
| link: link.href, | ||
| isSuspicious: Boolean(link.suspiciousCharacters && link.suspiciousCharacters.size) | ||
| })); | ||
| if (msg.buttons) { | ||
| msg.buttons = msg.buttons.serialize(); | ||
| } | ||
| if (msg.dynamicReplyButtons) { | ||
| msg.dynamicReplyButtons = JSON.parse(JSON.stringify(msg.dynamicReplyButtons)); | ||
| } | ||
| if (msg.replyButtons) { | ||
| msg.replyButtons = JSON.parse(JSON.stringify(msg.replyButtons)); | ||
| } | ||
| if (typeof msg.id.remote === 'object') { | ||
| msg.id = Object.assign({}, msg.id, { remote: msg.id.remote._serialized }); | ||
| } | ||
| delete msg.pendingAckUpdate; | ||
| return msg; | ||
| }; | ||
| window.WWebJS.getChat = async (chatId, { getAsModel = true } = {}) => { | ||
| const isChannel = /@\w*newsletter\b/.test(chatId); | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| let chat; | ||
| if (isChannel) { | ||
| try { | ||
| chat = window.Store.NewsletterCollection.get(chatId); | ||
| if (!chat) { | ||
| await window.Store.ChannelUtils.loadNewsletterPreviewChat(chatId); | ||
| chat = await window.Store.NewsletterCollection.find(chatWid); | ||
| } | ||
| } catch (err) { | ||
| chat = null; | ||
| } | ||
| } else { | ||
| chat = window.Store.Chat.get(chatWid) || (await window.Store.Chat.find(chatWid)); | ||
| } | ||
| return getAsModel && chat | ||
| ? await window.WWebJS.getChatModel(chat, { isChannel: isChannel }) | ||
| : chat; | ||
| }; | ||
| window.WWebJS.getChannelMetadata = async (inviteCode) => { | ||
| const response = | ||
| await window.Store.ChannelUtils.queryNewsletterMetadataByInviteCode( | ||
| inviteCode, | ||
| window.Store.ChannelUtils.getRoleByIdentifier(inviteCode) | ||
| ); | ||
| const picUrl = response.newsletterPictureMetadataMixin?.picture[0]?.queryPictureDirectPathOrEmptyResponseMixinGroup.value.directPath; | ||
| return { | ||
| id: response.idJid, | ||
| createdAtTs: response.newsletterCreationTimeMetadataMixin.creationTimeValue, | ||
| titleMetadata: { | ||
| title: response.newsletterNameMetadataMixin.nameElementValue, | ||
| updatedAtTs: response.newsletterNameMetadataMixin.nameUpdateTime | ||
| }, | ||
| descriptionMetadata: { | ||
| description: response.newsletterDescriptionMetadataMixin.descriptionQueryDescriptionResponseMixin.elementValue, | ||
| updatedAtTs: response.newsletterDescriptionMetadataMixin.descriptionQueryDescriptionResponseMixin.updateTime | ||
| }, | ||
| inviteLink: `https://whatsapp.com/channel/${response.newsletterInviteLinkMetadataMixin.inviteCode}`, | ||
| membershipType: window.Store.ChannelUtils.getRoleByIdentifier(inviteCode), | ||
| stateType: response.newsletterStateMetadataMixin.stateType, | ||
| pictureUrl: picUrl ? `https://pps.whatsapp.net${picUrl}` : null, | ||
| subscribersCount: response.newsletterSubscribersMetadataMixin.subscribersCount, | ||
| isVerified: response.newsletterVerificationMetadataMixin.verificationState === 'verified' | ||
| }; | ||
| }; | ||
| window.WWebJS.getChats = async () => { | ||
| const chats = window.Store.Chat.getModelsArray(); | ||
| const chatPromises = chats.map(chat => window.WWebJS.getChatModel(chat)); | ||
| return await Promise.all(chatPromises); | ||
| }; | ||
| window.WWebJS.getChannels = async () => { | ||
| const channels = window.Store.NewsletterCollection.getModelsArray(); | ||
| const channelPromises = channels?.map((channel) => window.WWebJS.getChatModel(channel, { isChannel: true })); | ||
| return await Promise.all(channelPromises); | ||
| }; | ||
| window.WWebJS.getChatModel = async (chat, { isChannel = false } = {}) => { | ||
| if (!chat) return null; | ||
| const model = chat.serialize(); | ||
| model.isGroup = false; | ||
| model.isMuted = chat.mute?.expiration !== 0; | ||
| if (isChannel) { | ||
| model.isChannel = window.Store.ChatGetters.getIsNewsletter(chat); | ||
| } else { | ||
| model.formattedTitle = chat.formattedTitle; | ||
| } | ||
| if (chat.groupMetadata) { | ||
| model.isGroup = true; | ||
| const chatWid = window.Store.WidFactory.createWid(chat.id._serialized); | ||
| await window.Store.GroupMetadata.update(chatWid); | ||
| chat.groupMetadata.participants._models | ||
| .filter(x => x.id?._serialized?.endsWith('@lid')) | ||
| .forEach(x => x.contact?.phoneNumber && (x.id = x.contact.phoneNumber)); | ||
| model.groupMetadata = chat.groupMetadata.serialize(); | ||
| model.isReadOnly = chat.groupMetadata.announce; | ||
| } | ||
| if (chat.newsletterMetadata) { | ||
| await window.Store.NewsletterMetadataCollection.update(chat.id); | ||
| model.channelMetadata = chat.newsletterMetadata.serialize(); | ||
| model.channelMetadata.createdAtTs = chat.newsletterMetadata.creationTime; | ||
| } | ||
| model.lastMessage = null; | ||
| if (model.msgs && model.msgs.length) { | ||
| const lastMessage = chat.lastReceivedKey | ||
| ? window.Store.Msg.get(chat.lastReceivedKey._serialized) || (await window.Store.Msg.getMessagesById([chat.lastReceivedKey._serialized]))?.messages?.[0] | ||
| : null; | ||
| lastMessage && (model.lastMessage = window.WWebJS.getMessageModel(lastMessage)); | ||
| } | ||
| delete model.msgs; | ||
| delete model.msgUnsyncedButtonReplyMsgs; | ||
| delete model.unsyncedButtonReplies; | ||
| return model; | ||
| }; | ||
| window.WWebJS.getContactModel = contact => { | ||
| let res = contact.serialize(); | ||
| res.isBusiness = contact.isBusiness === undefined ? false : contact.isBusiness; | ||
| if (contact.businessProfile) { | ||
| res.businessProfile = contact.businessProfile.serialize(); | ||
| } | ||
| res.isMe = window.Store.ContactMethods.getIsMe(contact); | ||
| res.isUser = window.Store.ContactMethods.getIsUser(contact); | ||
| res.isGroup = window.Store.ContactMethods.getIsGroup(contact); | ||
| res.isWAContact = window.Store.ContactMethods.getIsWAContact(contact); | ||
| res.isMyContact = window.Store.ContactMethods.getIsMyContact(contact); | ||
| res.isBlocked = contact.isContactBlocked; | ||
| res.userid = window.Store.ContactMethods.getUserid(contact); | ||
| res.isEnterprise = window.Store.ContactMethods.getIsEnterprise(contact); | ||
| res.verifiedName = window.Store.ContactMethods.getVerifiedName(contact); | ||
| res.verifiedLevel = window.Store.ContactMethods.getVerifiedLevel(contact); | ||
| res.statusMute = window.Store.ContactMethods.getStatusMute(contact); | ||
| res.name = window.Store.ContactMethods.getName(contact); | ||
| res.shortName = window.Store.ContactMethods.getShortName(contact); | ||
| res.pushname = window.Store.ContactMethods.getPushname(contact); | ||
| return res; | ||
| }; | ||
| window.WWebJS.getContact = async contactId => { | ||
| const wid = window.Store.WidFactory.createWid(contactId); | ||
| let contact = await window.Store.Contact.find(wid); | ||
| if (contact.id._serialized.endsWith('@lid')) { | ||
| contact.id = contact.phoneNumber; | ||
| } | ||
| const bizProfile = await window.Store.BusinessProfile.fetchBizProfile(wid); | ||
| bizProfile.profileOptions && (contact.businessProfile = bizProfile); | ||
| return window.WWebJS.getContactModel(contact); | ||
| }; | ||
| window.WWebJS.getContacts = () => { | ||
| const contacts = window.Store.Contact.getModelsArray(); | ||
| return contacts.map(contact => window.WWebJS.getContactModel(contact)); | ||
| }; | ||
| window.WWebJS.mediaInfoToFile = ({ data, mimetype, filename }) => { | ||
| const binaryData = window.atob(data); | ||
| const buffer = new ArrayBuffer(binaryData.length); | ||
| const view = new Uint8Array(buffer); | ||
| for (let i = 0; i < binaryData.length; i++) { | ||
| view[i] = binaryData.charCodeAt(i); | ||
| } | ||
| const blob = new Blob([buffer], { type: mimetype }); | ||
| return new File([blob], filename, { | ||
| type: mimetype, | ||
| lastModified: Date.now() | ||
| }); | ||
| }; | ||
| window.WWebJS.arrayBufferToBase64 = (arrayBuffer) => { | ||
| let binary = ''; | ||
| const bytes = new Uint8Array(arrayBuffer); | ||
| const len = bytes.byteLength; | ||
| for (let i = 0; i < len; i++) { | ||
| binary += String.fromCharCode(bytes[i]); | ||
| } | ||
| return window.btoa(binary); | ||
| }; | ||
| window.WWebJS.arrayBufferToBase64Async = (arrayBuffer) => | ||
| new Promise((resolve, reject) => { | ||
| const blob = new Blob([arrayBuffer], { | ||
| type: 'application/octet-stream', | ||
| }); | ||
| const fileReader = new FileReader(); | ||
| fileReader.onload = () => { | ||
| const [, data] = fileReader.result.split(','); | ||
| resolve(data); | ||
| }; | ||
| fileReader.onerror = (e) => reject(e); | ||
| fileReader.readAsDataURL(blob); | ||
| }); | ||
| window.WWebJS.getFileHash = async (data) => { | ||
| let buffer = await data.arrayBuffer(); | ||
| const hashBuffer = await crypto.subtle.digest('SHA-256', buffer); | ||
| return btoa(String.fromCharCode(...new Uint8Array(hashBuffer))); | ||
| }; | ||
| window.WWebJS.generateHash = async (length) => { | ||
| var result = ''; | ||
| var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
| var charactersLength = characters.length; | ||
| for (var i = 0; i < length; i++) { | ||
| result += characters.charAt(Math.floor(Math.random() * charactersLength)); | ||
| } | ||
| return result; | ||
| }; | ||
| window.WWebJS.generateWaveform = async (audioFile) => { | ||
| try { | ||
| const audioData = await audioFile.arrayBuffer(); | ||
| const audioContext = new AudioContext(); | ||
| const audioBuffer = await audioContext.decodeAudioData(audioData); | ||
| const rawData = audioBuffer.getChannelData(0); | ||
| const samples = 64; | ||
| const blockSize = Math.floor(rawData.length / samples); | ||
| const filteredData = []; | ||
| for (let i = 0; i < samples; i++) { | ||
| const blockStart = blockSize * i; | ||
| let sum = 0; | ||
| for (let j = 0; j < blockSize; j++) { | ||
| sum = sum + Math.abs(rawData[blockStart + j]); | ||
| } | ||
| filteredData.push(sum / blockSize); | ||
| } | ||
| const multiplier = Math.pow(Math.max(...filteredData), -1); | ||
| const normalizedData = filteredData.map((n) => n * multiplier); | ||
| const waveform = new Uint8Array( | ||
| normalizedData.map((n) => Math.floor(100 * n)) | ||
| ); | ||
| return waveform; | ||
| } catch (e) { | ||
| return undefined; | ||
| } | ||
| }; | ||
| window.WWebJS.sendClearChat = async (chatId) => { | ||
| let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| if (chat !== undefined) { | ||
| await window.Store.SendClear.sendClear(chat, false); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| window.WWebJS.sendDeleteChat = async (chatId) => { | ||
| let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| if (chat !== undefined) { | ||
| await window.Store.SendDelete.sendDelete(chat); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| window.WWebJS.sendChatstate = async (state, chatId) => { | ||
| chatId = window.Store.WidFactory.createWid(chatId); | ||
| switch (state) { | ||
| case 'typing': | ||
| await window.Store.ChatState.sendChatStateComposing(chatId); | ||
| break; | ||
| case 'recording': | ||
| await window.Store.ChatState.sendChatStateRecording(chatId); | ||
| break; | ||
| case 'stop': | ||
| await window.Store.ChatState.sendChatStatePaused(chatId); | ||
| break; | ||
| default: | ||
| throw 'Invalid chatstate'; | ||
| } | ||
| return true; | ||
| }; | ||
| window.WWebJS.getLabelModel = label => { | ||
| let res = label.serialize(); | ||
| res.hexColor = label.hexColor; | ||
| return res; | ||
| }; | ||
| window.WWebJS.getLabels = () => { | ||
| const labels = window.Store.Label.getModelsArray(); | ||
| return labels.map(label => window.WWebJS.getLabelModel(label)); | ||
| }; | ||
| window.WWebJS.getLabel = (labelId) => { | ||
| const label = window.Store.Label.get(labelId); | ||
| return window.WWebJS.getLabelModel(label); | ||
| }; | ||
| window.WWebJS.getChatLabels = async (chatId) => { | ||
| const chat = await window.WWebJS.getChat(chatId); | ||
| return (chat.labels || []).map(id => window.WWebJS.getLabel(id)); | ||
| }; | ||
| window.WWebJS.getOrderDetail = async (orderId, token, chatId) => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| return window.Store.QueryOrder.queryOrder(chatWid, orderId, 80, 80, token); | ||
| }; | ||
| window.WWebJS.getProductMetadata = async (productId) => { | ||
| let sellerId = window.Store.Conn.wid; | ||
| let product = await window.Store.QueryProduct.queryProduct(sellerId, productId); | ||
| if (product && product.data) { | ||
| return product.data; | ||
| } | ||
| return undefined; | ||
| }; | ||
| window.WWebJS.rejectCall = async (peerJid, id) => { | ||
| peerJid = peerJid.split('@')[0] + '@s.whatsapp.net'; | ||
| let userId = window.Store.User.getMaybeMePnUser().user + '@s.whatsapp.net'; | ||
| const stanza = window.Store.SocketWap.wap('call', { | ||
| id: window.Store.SocketWap.generateId(), | ||
| from: window.Store.SocketWap.USER_JID(userId), | ||
| to: window.Store.SocketWap.USER_JID(peerJid), | ||
| }, [ | ||
| window.Store.SocketWap.wap('reject', { | ||
| 'call-id': id, | ||
| 'call-creator': window.Store.SocketWap.USER_JID(peerJid), | ||
| count: '0', | ||
| }) | ||
| ]); | ||
| await window.Store.Socket.deprecatedCastStanza(stanza); | ||
| }; | ||
| window.WWebJS.cropAndResizeImage = async (media, options = {}) => { | ||
| if (!media.mimetype.includes('image')) | ||
| throw new Error('Media is not an image'); | ||
| if (options.mimetype && !options.mimetype.includes('image')) | ||
| delete options.mimetype; | ||
| options = Object.assign({ size: 640, mimetype: media.mimetype, quality: .75, asDataUrl: false }, options); | ||
| const img = await new Promise ((resolve, reject) => { | ||
| const img = new Image(); | ||
| img.onload = () => resolve(img); | ||
| img.onerror = reject; | ||
| img.src = `data:${media.mimetype};base64,${media.data}`; | ||
| }); | ||
| const sl = Math.min(img.width, img.height); | ||
| const sx = Math.floor((img.width - sl) / 2); | ||
| const sy = Math.floor((img.height - sl) / 2); | ||
| const canvas = document.createElement('canvas'); | ||
| canvas.width = options.size; | ||
| canvas.height = options.size; | ||
| const ctx = canvas.getContext('2d'); | ||
| ctx.drawImage(img, sx, sy, sl, sl, 0, 0, options.size, options.size); | ||
| const dataUrl = canvas.toDataURL(options.mimetype, options.quality); | ||
| if (options.asDataUrl) | ||
| return dataUrl; | ||
| return Object.assign(media, { | ||
| mimetype: options.mimetype, | ||
| data: dataUrl.replace(`data:${options.mimetype};base64,`, '') | ||
| }); | ||
| }; | ||
| window.WWebJS.setPicture = async (chatId, media) => { | ||
| const thumbnail = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 96 }); | ||
| const profilePic = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 640 }); | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| try { | ||
| const collection = window.Store.ProfilePicThumb.get(chatId) || await window.Store.ProfilePicThumb.find(chatId); | ||
| if (!collection?.canSet()) return false; | ||
| const res = await window.Store.GroupUtils.sendSetPicture(chatWid, thumbnail, profilePic); | ||
| return res ? res.status === 200 : false; | ||
| } catch (err) { | ||
| if (err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
| } | ||
| }; | ||
| window.WWebJS.deletePicture = async (chatid) => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatid); | ||
| try { | ||
| const collection = window.Store.ProfilePicThumb.get(chatid); | ||
| if (!collection.canDelete()) return; | ||
| const res = await window.Store.GroupUtils.requestDeletePicture(chatWid); | ||
| return res ? res.status === 200 : false; | ||
| } catch (err) { | ||
| if(err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
| } | ||
| }; | ||
| window.WWebJS.getProfilePicThumbToBase64 = async (chatWid) => { | ||
| const profilePicCollection = await window.Store.ProfilePicThumb.find(chatWid); | ||
| const _readImageAsBase64 = (imageBlob) => { | ||
| return new Promise((resolve) => { | ||
| const reader = new FileReader(); | ||
| reader.onloadend = function () { | ||
| const base64Image = reader.result; | ||
| if (base64Image == null) { | ||
| resolve(undefined); | ||
| } else { | ||
| const base64Data = base64Image.toString().split(',')[1]; | ||
| resolve(base64Data); | ||
| } | ||
| }; | ||
| reader.readAsDataURL(imageBlob); | ||
| }); | ||
| }; | ||
| if (profilePicCollection?.img) { | ||
| try { | ||
| const response = await fetch(profilePicCollection.img); | ||
| if (response.ok) { | ||
| const imageBlob = await response.blob(); | ||
| if (imageBlob) { | ||
| const base64Image = await _readImageAsBase64(imageBlob); | ||
| return base64Image; | ||
| } | ||
| } | ||
| } catch (error) { /* empty */ } | ||
| } | ||
| return undefined; | ||
| }; | ||
| window.WWebJS.getAddParticipantsRpcResult = async (groupWid, participantWid) => { | ||
| const iqTo = window.Store.WidToJid.widToGroupJid(groupWid); | ||
| const participantArgs = [{ | ||
| participantJid: window.Store.WidToJid.widToUserJid(participantWid) | ||
| }]; | ||
| let rpcResult, resultArgs; | ||
| const data = { | ||
| name: undefined, | ||
| code: undefined, | ||
| inviteV4Code: undefined, | ||
| inviteV4CodeExp: undefined | ||
| }; | ||
| try { | ||
| rpcResult = await window.Store.GroupParticipants.sendAddParticipantsRPC({ participantArgs, iqTo }); | ||
| resultArgs = rpcResult.value.addParticipant[0] | ||
| .addParticipantsParticipantAddedOrNonRegisteredWaUserParticipantErrorLidResponseMixinGroup | ||
| .value | ||
| .addParticipantsParticipantMixins; | ||
| } catch (err) { | ||
| data.code = 400; | ||
| return data; | ||
| } | ||
| if (rpcResult.name === 'AddParticipantsResponseSuccess') { | ||
| const code = resultArgs?.value.error || '200'; | ||
| data.name = resultArgs?.name; | ||
| data.code = +code; | ||
| data.inviteV4Code = resultArgs?.value.addRequestCode; | ||
| data.inviteV4CodeExp = resultArgs?.value.addRequestExpiration?.toString(); | ||
| } | ||
| else if (rpcResult.name === 'AddParticipantsResponseClientError') { | ||
| const { code: code } = rpcResult.value.errorAddParticipantsClientErrors.value; | ||
| data.code = +code; | ||
| } | ||
| else if (rpcResult.name === 'AddParticipantsResponseServerError') { | ||
| const { code: code } = rpcResult.value.errorServerErrors.value; | ||
| data.code = +code; | ||
| } | ||
| return data; | ||
| }; | ||
| window.WWebJS.membershipRequestAction = async (groupId, action, requesterIds, sleep) => { | ||
| const groupWid = window.Store.WidFactory.createWid(groupId); | ||
| const group = await window.Store.Chat.find(groupWid); | ||
| const toApprove = action === 'Approve'; | ||
| let membershipRequests; | ||
| let response; | ||
| let result = []; | ||
| await window.Store.GroupQueryAndUpdate({ id: groupId }); | ||
| if (!requesterIds?.length) { | ||
| membershipRequests = group.groupMetadata.membershipApprovalRequests._models.map(({ id }) => id); | ||
| } else { | ||
| !Array.isArray(requesterIds) && (requesterIds = [requesterIds]); | ||
| membershipRequests = requesterIds.map(r => window.Store.WidFactory.createWid(r)); | ||
| } | ||
| if (!membershipRequests.length) return []; | ||
| const participantArgs = membershipRequests.map(m => ({ | ||
| participantArgs: [ | ||
| { | ||
| participantJid: window.Store.WidToJid.widToUserJid(m) | ||
| } | ||
| ] | ||
| })); | ||
| const groupJid = window.Store.WidToJid.widToGroupJid(groupWid); | ||
| const _getSleepTime = (sleep) => { | ||
| if (!Array.isArray(sleep) || (sleep.length === 2 && sleep[0] === sleep[1])) { | ||
| return sleep; | ||
| } | ||
| if (sleep.length === 1) { | ||
| return sleep[0]; | ||
| } | ||
| sleep[1] - sleep[0] < 100 && (sleep[0] = sleep[1]) && (sleep[1] += 100); | ||
| return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0]; | ||
| }; | ||
| const membReqResCodes = { | ||
| default: `An unknown error occupied while ${toApprove ? 'approving' : 'rejecting'} the participant membership request`, | ||
| 400: 'ParticipantNotFoundError', | ||
| 401: 'ParticipantNotAuthorizedError', | ||
| 403: 'ParticipantForbiddenError', | ||
| 404: 'ParticipantRequestNotFoundError', | ||
| 408: 'ParticipantTemporarilyBlockedError', | ||
| 409: 'ParticipantConflictError', | ||
| 412: 'ParticipantParentLinkedGroupsResourceConstraintError', | ||
| 500: 'ParticipantResourceConstraintError' | ||
| }; | ||
| try { | ||
| for (const participant of participantArgs) { | ||
| response = await window.Store.MembershipRequestUtils.sendMembershipRequestsActionRPC({ | ||
| iqTo: groupJid, | ||
| [toApprove ? 'approveArgs' : 'rejectArgs']: participant | ||
| }); | ||
| if (response.name === 'MembershipRequestsActionResponseSuccess') { | ||
| const value = toApprove | ||
| ? response.value.membershipRequestsActionApprove | ||
| : response.value.membershipRequestsActionReject; | ||
| if (value?.participant) { | ||
| const [_] = value.participant.map(p => { | ||
| const error = toApprove | ||
| ? value.participant[0].membershipRequestsActionAcceptParticipantMixins?.value.error | ||
| : value.participant[0].membershipRequestsActionRejectParticipantMixins?.value.error; | ||
| return { | ||
| requesterId: window.Store.WidFactory.createWid(p.jid)._serialized, | ||
| ...(error | ||
| ? { error: +error, message: membReqResCodes[error] || membReqResCodes.default } | ||
| : { message: `${toApprove ? 'Approved' : 'Rejected'} successfully` }) | ||
| }; | ||
| }); | ||
| _ && result.push(_); | ||
| } | ||
| } else { | ||
| result.push({ | ||
| requesterId: window.Store.JidToWid.userJidToUserWid(participant.participantArgs[0].participantJid)._serialized, | ||
| message: 'ServerStatusCodeError' | ||
| }); | ||
| } | ||
| sleep && | ||
| participantArgs.length > 1 && | ||
| participantArgs.indexOf(participant) !== participantArgs.length - 1 && | ||
| (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep)))); | ||
| } | ||
| return result; | ||
| } catch (err) { | ||
| return []; | ||
| } | ||
| }; | ||
| window.WWebJS.subscribeToUnsubscribeFromChannel = async (channelId, action, options = {}) => { | ||
| const channel = await window.WWebJS.getChat(channelId, { getAsModel: false }); | ||
| if (!channel || channel.newsletterMetadata.membershipType === 'owner') return false; | ||
| options = { eventSurface: 3, deleteLocalModels: options.deleteLocalModels ?? true }; | ||
| try { | ||
| if (action === 'Subscribe') { | ||
| await window.Store.ChannelUtils.subscribeToNewsletterAction(channel, options); | ||
| } else if (action === 'Unsubscribe') { | ||
| await window.Store.ChannelUtils.unsubscribeFromNewsletterAction(channel, options); | ||
| } else return false; | ||
| return true; | ||
| } catch (err) { | ||
| if (err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
| } | ||
| }; | ||
| window.WWebJS.pinUnpinMsgAction = async (msgId, action, duration) => { | ||
| const message = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if (!message) return false; | ||
| if (typeof duration !== 'number') return false; | ||
| const originalFunction = window.require('WAWebPinMsgConstants').getPinExpiryDuration; | ||
| window.require('WAWebPinMsgConstants').getPinExpiryDuration = () => duration; | ||
| const response = await window.Store.PinnedMsgUtils.sendPinInChatMsg(message, action, duration); | ||
| window.require('WAWebPinMsgConstants').getPinExpiryDuration = originalFunction; | ||
| return response.messageSendResult === 'OK'; | ||
| }; | ||
| window.WWebJS.getStatusModel = status => { | ||
| const res = status.serialize(); | ||
| delete res._msgs; | ||
| return res; | ||
| }; | ||
| window.WWebJS.getAllStatuses = () => { | ||
| const statuses = window.Store.Status.getModelsArray(); | ||
| return statuses.map(status => window.WWebJS.getStatusModel(status)); | ||
| }; | ||
| }; |
| /** | ||
| * Expose a function to the page if it does not exist | ||
| * | ||
| * NOTE: | ||
| * Rewrite it to 'upsertFunction' after updating Puppeteer to 20.6 or higher | ||
| * using page.removeExposedFunction | ||
| * https://pptr.dev/api/puppeteer.page.removeExposedFunction | ||
| * | ||
| * @param {import(puppeteer).Page} page | ||
| * @param {string} name | ||
| * @param {Function} fn | ||
| */ | ||
| async function exposeFunctionIfAbsent(page, name, fn) { | ||
| const exist = await page.evaluate((name) => { | ||
| return !!window[name]; | ||
| }, name); | ||
| if (exist) { | ||
| return; | ||
| } | ||
| await page.exposeFunction(name, fn); | ||
| } | ||
| module.exports = {exposeFunctionIfAbsent}; |
| # Release sync for v1.33.2 | ||
| # Release sync for v1.33.2 | ||
| # Release sync for v1.33.2 |
@@ -0,0 +0,0 @@ |
+390
-24
@@ -1,2 +0,2 @@ | ||
| const { Client, Location, List, Buttons, LocalAuth } = require('./index'); | ||
| const { Client, Location, Poll, List, Buttons, LocalAuth } = require('./index'); | ||
@@ -6,8 +6,25 @@ const client = new Client({ | ||
| // proxyAuthentication: { username: 'username', password: 'password' }, | ||
| /** | ||
| * This option changes the browser name from defined in user agent to custom. | ||
| */ | ||
| // deviceName: 'Your custom name', | ||
| /** | ||
| * This option changes browser type from defined in user agent to yours. It affects the browser icon | ||
| * that is displayed in 'linked devices' section. | ||
| * Valid value are: 'Chrome' | 'Firefox' | 'IE' | 'Opera' | 'Safari' | 'Edge'. | ||
| * If another value is provided, the browser icon in 'linked devices' section will be gray. | ||
| */ | ||
| // browserName: 'Firefox', | ||
| puppeteer: { | ||
| // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], | ||
| headless: false | ||
| } | ||
| headless: false, | ||
| }, | ||
| // pairWithPhoneNumber: { | ||
| // phoneNumber: '96170100100' // Pair with phone number (format: <COUNTRY_CODE><PHONE_NUMBER>) | ||
| // showNotification: true, | ||
| // intervalMs: 180000 // Time to renew pairing code in milliseconds, defaults to 3 minutes | ||
| // } | ||
| }); | ||
| // client initialize does not finish at ready now. | ||
| client.initialize(); | ||
@@ -19,3 +36,3 @@ | ||
| client.on('qr', (qr) => { | ||
| client.on('qr', async (qr) => { | ||
| // NOTE: This event will not be fired if a session is specified. | ||
@@ -25,2 +42,6 @@ console.log('QR RECEIVED', qr); | ||
| client.on('code', (code) => { | ||
| console.log('Pairing code:',code); | ||
| }); | ||
| client.on('authenticated', () => { | ||
@@ -35,4 +56,14 @@ console.log('AUTHENTICATED'); | ||
| client.on('ready', () => { | ||
| client.on('ready', async () => { | ||
| console.log('READY'); | ||
| const debugWWebVersion = await client.getWWebVersion(); | ||
| console.log(`WWebVersion = ${debugWWebVersion}`); | ||
| client.pupPage.on('pageerror', function(err) { | ||
| console.log('Page error: ' + err.toString()); | ||
| }); | ||
| client.pupPage.on('error', function(err) { | ||
| console.log('Page error: ' + err.toString()); | ||
| }); | ||
| }); | ||
@@ -73,2 +104,5 @@ | ||
| msg.reply(msg.body.slice(6)); | ||
| } else if (msg.body.startsWith('!preview ')) { | ||
| const text = msg.body.slice(9); | ||
| msg.reply(text, null, { linkPreview: true }); | ||
| } else if (msg.body.startsWith('!desc ')) { | ||
@@ -99,2 +133,74 @@ // Change the group description | ||
| } | ||
| } else if (msg.body.startsWith('!addmembers')) { | ||
| const group = await msg.getChat(); | ||
| const result = await group.addParticipants(['number1@c.us', 'number2@c.us', 'number3@c.us']); | ||
| /** | ||
| * The example of the {@link result} output: | ||
| * | ||
| * { | ||
| * 'number1@c.us': { | ||
| * code: 200, | ||
| * message: 'The participant was added successfully', | ||
| * isInviteV4Sent: false | ||
| * }, | ||
| * 'number2@c.us': { | ||
| * code: 403, | ||
| * message: 'The participant can be added by sending private invitation only', | ||
| * isInviteV4Sent: true | ||
| * }, | ||
| * 'number3@c.us': { | ||
| * code: 404, | ||
| * message: 'The phone number is not registered on WhatsApp', | ||
| * isInviteV4Sent: false | ||
| * } | ||
| * } | ||
| * | ||
| * For more usage examples: | ||
| * @see https://github.com/pedroslopez/whatsapp-web.js/pull/2344#usage-example1 | ||
| */ | ||
| console.log(result); | ||
| } else if (msg.body === '!creategroup') { | ||
| const partitipantsToAdd = ['number1@c.us', 'number2@c.us', 'number3@c.us']; | ||
| const result = await client.createGroup('Group Title', partitipantsToAdd); | ||
| /** | ||
| * The example of the {@link result} output: | ||
| * { | ||
| * title: 'Group Title', | ||
| * gid: { | ||
| * server: 'g.us', | ||
| * user: '1111111111', | ||
| * _serialized: '1111111111@g.us' | ||
| * }, | ||
| * participants: { | ||
| * 'botNumber@c.us': { | ||
| * statusCode: 200, | ||
| * message: 'The participant was added successfully', | ||
| * isGroupCreator: true, | ||
| * isInviteV4Sent: false | ||
| * }, | ||
| * 'number1@c.us': { | ||
| * statusCode: 200, | ||
| * message: 'The participant was added successfully', | ||
| * isGroupCreator: false, | ||
| * isInviteV4Sent: false | ||
| * }, | ||
| * 'number2@c.us': { | ||
| * statusCode: 403, | ||
| * message: 'The participant can be added by sending private invitation only', | ||
| * isGroupCreator: false, | ||
| * isInviteV4Sent: true | ||
| * }, | ||
| * 'number3@c.us': { | ||
| * statusCode: 404, | ||
| * message: 'The phone number is not registered on WhatsApp', | ||
| * isGroupCreator: false, | ||
| * isInviteV4Sent: false | ||
| * } | ||
| * } | ||
| * } | ||
| * | ||
| * For more usage examples: | ||
| * @see https://github.com/pedroslopez/whatsapp-web.js/pull/2344#usage-example2 | ||
| */ | ||
| console.log(result); | ||
| } else if (msg.body === '!groupinfo') { | ||
@@ -149,4 +255,21 @@ let chat = await msg.getChat(); | ||
| } | ||
| if (quotedMsg.hasMedia && quotedMsg.type === 'audio') { | ||
| const audio = await quotedMsg.downloadMedia(); | ||
| await client.sendMessage(msg.from, audio, { sendAudioAsVoice: true }); | ||
| } | ||
| } else if (msg.body === '!isviewonce' && msg.hasQuotedMsg) { | ||
| const quotedMsg = await msg.getQuotedMessage(); | ||
| if (quotedMsg.hasMedia) { | ||
| const media = await quotedMsg.downloadMedia(); | ||
| await client.sendMessage(msg.from, media, { isViewOnce: true }); | ||
| } | ||
| } else if (msg.body === '!location') { | ||
| msg.reply(new Location(37.422, -122.084, 'Googleplex\nGoogle Headquarters')); | ||
| // only latitude and longitude | ||
| await msg.reply(new Location(37.422, -122.084)); | ||
| // location with name only | ||
| await msg.reply(new Location(37.422, -122.084, { name: 'Googleplex' })); | ||
| // location with address only | ||
| await msg.reply(new Location(37.422, -122.084, { address: '1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA' })); | ||
| // location with name, address and url | ||
| await msg.reply(new Location(37.422, -122.084, { name: 'Googleplex', address: '1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA', url: 'https://google.com' })); | ||
| } else if (msg.location) { | ||
@@ -158,8 +281,48 @@ msg.reply(msg.location); | ||
| msg.reply(`Status was updated to *${newStatus}*`); | ||
| } else if (msg.body === '!mention') { | ||
| const contact = await msg.getContact(); | ||
| } else if (msg.body === '!mentionUsers') { | ||
| const chat = await msg.getChat(); | ||
| chat.sendMessage(`Hi @${contact.number}!`, { | ||
| mentions: [contact] | ||
| const userNumber = 'XXXXXXXXXX'; | ||
| /** | ||
| * To mention one user you can pass user's ID to 'mentions' property as is, | ||
| * without wrapping it in Array, and a user's phone number to the message body: | ||
| */ | ||
| await chat.sendMessage(`Hi @${userNumber}`, { | ||
| mentions: userNumber + '@c.us' | ||
| }); | ||
| // To mention a list of users: | ||
| await chat.sendMessage(`Hi @${userNumber}, @${userNumber}`, { | ||
| mentions: [userNumber + '@c.us', userNumber + '@c.us'] | ||
| }); | ||
| } else if (msg.body === '!mentionGroups') { | ||
| const chat = await msg.getChat(); | ||
| const groupId = 'YYYYYYYYYY@g.us'; | ||
| /** | ||
| * Sends clickable group mentions, the same as user mentions. | ||
| * When the mentions are clicked, it opens a chat with the mentioned group. | ||
| * The 'groupMentions.subject' can be custom | ||
| * | ||
| * @note The user that does not participate in the mentioned group, | ||
| * will not be able to click on that mentioned group, the same if the group does not exist | ||
| * | ||
| * To mention one group: | ||
| */ | ||
| await chat.sendMessage(`Check the last message here: @${groupId}`, { | ||
| groupMentions: { subject: 'GroupSubject', id: groupId } | ||
| }); | ||
| // To mention a list of groups: | ||
| await chat.sendMessage(`Check the last message in these groups: @${groupId}, @${groupId}`, { | ||
| groupMentions: [ | ||
| { subject: 'FirstGroup', id: groupId }, | ||
| { subject: 'SecondGroup', id: groupId } | ||
| ] | ||
| }); | ||
| } else if (msg.body === '!getGroupMentions') { | ||
| // To get group mentions from a message: | ||
| const groupId = 'ZZZZZZZZZZ@g.us'; | ||
| const msg = await client.sendMessage('chatId', `Check the last message here: @${groupId}`, { | ||
| groupMentions: { subject: 'GroupSubject', id: groupId } | ||
| }); | ||
| /** {@link groupMentions} is an array of `GroupChat` */ | ||
| const groupMentions = await msg.getGroupMentions(); | ||
| console.log(groupMentions); | ||
| } else if (msg.body === '!delete') { | ||
@@ -207,3 +370,5 @@ if (msg.hasQuotedMsg) { | ||
| } else if (msg.body === '!list') { | ||
| let sections = [{ title: 'sectionTitle', rows: [{ title: 'ListItem1', description: 'desc' }, { title: 'ListItem2' }] }]; | ||
| let sections = [ | ||
| { title: 'sectionTitle', rows: [{ title: 'ListItem1', description: 'desc' }, { title: 'ListItem2' }] } | ||
| ]; | ||
| let list = new List('List body', 'btnText', sections, 'Title', 'footer'); | ||
@@ -213,6 +378,154 @@ client.sendMessage(msg.from, list); | ||
| msg.react('👍'); | ||
| } else if (msg.body === '!sendpoll') { | ||
| /** By default the poll is created as a single choice poll: */ | ||
| await msg.reply(new Poll('Winter or Summer?', ['Winter', 'Summer'])); | ||
| /** If you want to provide a multiple choice poll, add allowMultipleAnswers as true: */ | ||
| await msg.reply(new Poll('Cats or Dogs?', ['Cats', 'Dogs'], { allowMultipleAnswers: true })); | ||
| /** | ||
| * You can provide a custom message secret, it can be used as a poll ID: | ||
| * @note It has to be a unique vector with a length of 32 | ||
| */ | ||
| await msg.reply( | ||
| new Poll('Cats or Dogs?', ['Cats', 'Dogs'], { | ||
| messageSecret: [ | ||
| 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 | ||
| ] | ||
| }) | ||
| ); | ||
| } else if (msg.body === '!edit') { | ||
| if (msg.hasQuotedMsg) { | ||
| const quotedMsg = await msg.getQuotedMessage(); | ||
| if (quotedMsg.fromMe) { | ||
| quotedMsg.edit(msg.body.replace('!edit', '')); | ||
| } else { | ||
| msg.reply('I can only edit my own messages'); | ||
| } | ||
| } | ||
| } else if (msg.body === '!updatelabels') { | ||
| const chat = await msg.getChat(); | ||
| await chat.changeLabels([0, 1]); | ||
| } else if (msg.body === '!addlabels') { | ||
| const chat = await msg.getChat(); | ||
| let labels = (await chat.getLabels()).map((l) => l.id); | ||
| labels.push('0'); | ||
| labels.push('1'); | ||
| await chat.changeLabels(labels); | ||
| } else if (msg.body === '!removelabels') { | ||
| const chat = await msg.getChat(); | ||
| await chat.changeLabels([]); | ||
| } else if (msg.body === '!approverequest') { | ||
| /** | ||
| * Presented an example for membership request approvals, the same examples are for the request rejections. | ||
| * To approve the membership request from a specific user: | ||
| */ | ||
| await client.approveGroupMembershipRequests(msg.from, { requesterIds: 'number@c.us' }); | ||
| /** The same for execution on group object (no need to provide the group ID): */ | ||
| const group = await msg.getChat(); | ||
| await group.approveGroupMembershipRequests({ requesterIds: 'number@c.us' }); | ||
| /** To approve several membership requests: */ | ||
| const approval = await client.approveGroupMembershipRequests(msg.from, { | ||
| requesterIds: ['number1@c.us', 'number2@c.us'] | ||
| }); | ||
| /** | ||
| * The example of the {@link approval} output: | ||
| * [ | ||
| * { | ||
| * requesterId: 'number1@c.us', | ||
| * message: 'Rejected successfully' | ||
| * }, | ||
| * { | ||
| * requesterId: 'number2@c.us', | ||
| * error: 404, | ||
| * message: 'ParticipantRequestNotFoundError' | ||
| * } | ||
| * ] | ||
| * | ||
| */ | ||
| console.log(approval); | ||
| /** To approve all the existing membership requests (simply don't provide any user IDs): */ | ||
| await client.approveGroupMembershipRequests(msg.from); | ||
| /** To change the sleep value to 300 ms: */ | ||
| await client.approveGroupMembershipRequests(msg.from, { | ||
| requesterIds: ['number1@c.us', 'number2@c.us'], | ||
| sleep: 300 | ||
| }); | ||
| /** To change the sleep value to random value between 100 and 300 ms: */ | ||
| await client.approveGroupMembershipRequests(msg.from, { | ||
| requesterIds: ['number1@c.us', 'number2@c.us'], | ||
| sleep: [100, 300] | ||
| }); | ||
| /** To explicitly disable the sleep: */ | ||
| await client.approveGroupMembershipRequests(msg.from, { | ||
| requesterIds: ['number1@c.us', 'number2@c.us'], | ||
| sleep: null | ||
| }); | ||
| } else if (msg.body === '!pinmsg') { | ||
| /** | ||
| * Pins a message in a chat, a method takes a number in seconds for the message to be pinned. | ||
| * WhatsApp default values for duration to pass to the method are: | ||
| * 1. 86400 for 24 hours | ||
| * 2. 604800 for 7 days | ||
| * 3. 2592000 for 30 days | ||
| * You can pass your own value: | ||
| */ | ||
| const result = await msg.pin(60); // Will pin a message for 1 minute | ||
| console.log(result); // True if the operation completed successfully, false otherwise | ||
| } else if (msg.body === '!howManyConnections') { | ||
| /** | ||
| * Get user device count by ID | ||
| * Each WaWeb Connection counts as one device, and the phone (if exists) counts as one | ||
| * So for a non-enterprise user with one WaWeb connection it should return "2" | ||
| */ | ||
| let deviceCount = await client.getContactDeviceCount(msg.from); | ||
| await msg.reply(`You have *${deviceCount}* devices connected`); | ||
| } else if (msg.body === '!syncHistory') { | ||
| const isSynced = await client.syncHistory(msg.from); | ||
| // Or through the Chat object: | ||
| // const chat = await client.getChatById(msg.from); | ||
| // const isSynced = await chat.syncHistory(); | ||
| await msg.reply(isSynced ? 'Historical chat is syncing..' : 'There is no historical chat to sync.'); | ||
| } else if (msg.body === '!statuses') { | ||
| const statuses = await client.getBroadcasts(); | ||
| console.log(statuses); | ||
| const chat = await statuses[0]?.getChat(); // Get user chat of a first status | ||
| console.log(chat); | ||
| } else if (msg.body === '!sendMediaHD' && msg.hasQuotedMsg) { | ||
| const quotedMsg = await msg.getQuotedMessage(); | ||
| if (quotedMsg.hasMedia) { | ||
| const media = await quotedMsg.downloadMedia(); | ||
| await client.sendMessage(msg.from, media, { sendMediaAsHd: true }); | ||
| } | ||
| } else if (msg.body === '!parseVCard') { | ||
| const vCard = | ||
| 'BEGIN:VCARD\n' + | ||
| 'VERSION:3.0\n' + | ||
| 'FN:John Doe\n' + | ||
| 'ORG:Microsoft;\n' + | ||
| 'EMAIL;type=INTERNET:john.doe@gmail.com\n' + | ||
| 'URL:www.johndoe.com\n' + | ||
| 'TEL;type=CELL;type=VOICE;waid=18006427676:+1 (800) 642 7676\n' + | ||
| 'END:VCARD'; | ||
| const vCardExtended = | ||
| 'BEGIN:VCARD\n' + | ||
| 'VERSION:3.0\n' + | ||
| 'FN:John Doe\n' + | ||
| 'ORG:Microsoft;\n' + | ||
| 'item1.TEL:+1 (800) 642 7676\n' + | ||
| 'item1.X-ABLabel:USA Customer Service\n' + | ||
| 'item2.TEL:+55 11 4706 0900\n' + | ||
| 'item2.X-ABLabel:Brazil Customer Service\n' + | ||
| 'PHOTO;BASE64:here you can paste a binary data of a contact photo in Base64 encoding\n' + | ||
| 'END:VCARD'; | ||
| const userId = 'XXXXXXXXXX@c.us'; | ||
| await client.sendMessage(userId, vCard); | ||
| await client.sendMessage(userId, vCardExtended); | ||
| } else if (msg.body === '!changeSync') { | ||
| // NOTE: this action will take effect after you restart the client. | ||
| const backgroundSync = await client.setBackgroundSync(true); | ||
| console.log(backgroundSync); | ||
| } | ||
| }); | ||
| client.on('message_create', (msg) => { | ||
| client.on('message_create', async (msg) => { | ||
| // Fired on all message creations, including your own | ||
@@ -222,4 +535,22 @@ if (msg.fromMe) { | ||
| } | ||
| // Unpins a message | ||
| if (msg.fromMe && msg.body.startsWith('!unpin')) { | ||
| const pinnedMsg = await msg.getQuotedMessage(); | ||
| if (pinnedMsg) { | ||
| // Will unpin a message | ||
| const result = await pinnedMsg.unpin(); | ||
| console.log(result); // True if the operation completed successfully, false otherwise | ||
| } | ||
| } | ||
| }); | ||
| client.on('message_ciphertext', (msg) => { | ||
| // Receiving new incoming messages that have been encrypted | ||
| // msg.type === 'ciphertext' | ||
| msg.body = 'Waiting for this message. Check your phone.'; | ||
| // do stuff here | ||
| }); | ||
| client.on('message_revoke_everyone', async (after, before) => { | ||
@@ -300,24 +631,24 @@ // Fired whenever a message is deleted by anyone (including you) | ||
| /** | ||
| * Information about the {@name message}: | ||
| * Information about the @param {message}: | ||
| * | ||
| * 1. If a notification was emitted due to a group participant changing their phone number: | ||
| * {@name message.author} is a participant's id before the change. | ||
| * {@name message.recipients[0]} is a participant's id after the change (a new one). | ||
| * @param {message.author} is a participant's id before the change. | ||
| * @param {message.recipients[0]} is a participant's id after the change (a new one). | ||
| * | ||
| * 1.1 If the contact who changed their number WAS in the current user's contact list at the time of the change: | ||
| * {@name message.to} is a group chat id the event was emitted in. | ||
| * {@name message.from} is a current user's id that got an notification message in the group. | ||
| * Also the {@name message.fromMe} is TRUE. | ||
| * @param {message.to} is a group chat id the event was emitted in. | ||
| * @param {message.from} is a current user's id that got an notification message in the group. | ||
| * Also the @param {message.fromMe} is TRUE. | ||
| * | ||
| * 1.2 Otherwise: | ||
| * {@name message.from} is a group chat id the event was emitted in. | ||
| * {@name message.to} is @type {undefined}. | ||
| * Also {@name message.fromMe} is FALSE. | ||
| * @param {message.from} is a group chat id the event was emitted in. | ||
| * @param {message.to} is @type {undefined}. | ||
| * Also @param {message.fromMe} is FALSE. | ||
| * | ||
| * 2. If a notification was emitted due to a contact changing their phone number: | ||
| * {@name message.templateParams} is an array of two user's ids: | ||
| * @param {message.templateParams} is an array of two user's ids: | ||
| * the old (before the change) and a new one, stored in alphabetical order. | ||
| * {@name message.from} is a current user's id that has a chat with a user, | ||
| * @param {message.from} is a current user's id that has a chat with a user, | ||
| * whos phone number was changed. | ||
| * {@name message.to} is a user's id (after the change), the current user has a chat with. | ||
| * @param {message.to} is a user's id (after the change), the current user has a chat with. | ||
| */ | ||
@@ -337,1 +668,36 @@ }); | ||
| }); | ||
| client.on('group_membership_request', async (notification) => { | ||
| /** | ||
| * The example of the {@link notification} output: | ||
| * { | ||
| * id: { | ||
| * fromMe: false, | ||
| * remote: 'groupId@g.us', | ||
| * id: '123123123132132132', | ||
| * participant: 'number@c.us', | ||
| * _serialized: 'false_groupId@g.us_123123123132132132_number@c.us' | ||
| * }, | ||
| * body: '', | ||
| * type: 'created_membership_requests', | ||
| * timestamp: 1694456538, | ||
| * chatId: 'groupId@g.us', | ||
| * author: 'number@c.us', | ||
| * recipientIds: [] | ||
| * } | ||
| * | ||
| */ | ||
| console.log(notification); | ||
| /** You can approve or reject the newly appeared membership request: */ | ||
| await client.approveGroupMembershipRequestss(notification.chatId, notification.author); | ||
| await client.rejectGroupMembershipRequests(notification.chatId, notification.author); | ||
| }); | ||
| client.on('message_reaction', async (reaction) => { | ||
| console.log('REACTION RECEIVED', reaction); | ||
| }); | ||
| client.on('vote_update', (vote) => { | ||
| /** The vote that was affected: */ | ||
| console.log(vote); | ||
| }); |
+4
-1
@@ -14,2 +14,3 @@ 'use strict'; | ||
| GroupChat: require('./src/structures/GroupChat'), | ||
| Channel: require('./src/structures/Channel'), | ||
| Message: require('./src/structures/Message'), | ||
@@ -22,5 +23,8 @@ MessageMedia: require('./src/structures/MessageMedia'), | ||
| Location: require('./src/structures/Location'), | ||
| Poll: require('./src/structures/Poll'), | ||
| ScheduledEvent: require('./src/structures/ScheduledEvent'), | ||
| ProductMetadata: require('./src/structures/ProductMetadata'), | ||
| List: require('./src/structures/List'), | ||
| Buttons: require('./src/structures/Buttons'), | ||
| Broadcast: require('./src/structures/Broadcast'), | ||
@@ -31,5 +35,4 @@ // Auth Strategies | ||
| RemoteAuth: require('./src/authStrategies/RemoteAuth'), | ||
| LegacySessionAuth: require('./src/authStrategies/LegacySessionAuth'), | ||
| ...Constants | ||
| }; |
+0
-0
@@ -0,0 +0,0 @@ Apache License |
+10
-10
| { | ||
| "name": "wwebjs-electron", | ||
| "version": "1.21.2", | ||
| "version": "1.33.2", | ||
| "description": "Library for interacting with the WhatsApp Web API through electron", | ||
@@ -11,3 +11,3 @@ "main": "./index.js", | ||
| "shell": "node --experimental-repl-await ./shell.js", | ||
| "generate-docs": "node_modules/.bin/jsdoc --configure .jsdoc.json --verbose" | ||
| "generate-docs": "npx jsdoc --configure .jsdoc.json --verbose" | ||
| }, | ||
@@ -35,7 +35,7 @@ "repository": { | ||
| "@pedroslopez/moduleraid": "^5.0.2", | ||
| "fluent-ffmpeg": "^2.1.2", | ||
| "jsqr": "^1.3.1", | ||
| "fluent-ffmpeg": "2.1.3", | ||
| "mime": "^3.0.0", | ||
| "node-fetch": "^2.6.1", | ||
| "node-webpmux": "^3.1.0", | ||
| "node-fetch": "^2.6.9", | ||
| "node-webpmux": "3.1.7", | ||
| "puppeteer": "^18.2.1", | ||
| "puppeteer-in-electron": "^3.0.5" | ||
@@ -52,13 +52,13 @@ }, | ||
| "jsdoc-baseline": "^0.1.5", | ||
| "mocha": "^10.2.0", | ||
| "sinon": "^15.0.3" | ||
| "mocha": "^9.0.2", | ||
| "sinon": "^13.0.1" | ||
| }, | ||
| "engines": { | ||
| "node": ">=12.0.0" | ||
| "node": ">=18.0.0" | ||
| }, | ||
| "optionalDependencies": { | ||
| "archiver": "^5.3.1", | ||
| "fs-extra": "^11.1.1", | ||
| "fs-extra": "^10.1.0", | ||
| "unzipper": "^0.10.11" | ||
| } | ||
| } |
+117
-59
@@ -1,67 +0,98 @@ | ||
| [](https://www.npmjs.com/package/wwebjs-electron) [](https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765)  [](https://discord.gg/H7DqQs4) | ||
| <div align="center"> | ||
| <br /> | ||
| <p> | ||
| <a href="https://wwebjs.dev"><img src="https://github.com/wwebjs/logos/blob/main/4_Full%20Logo%20Lockup_Small/small_banner_blue.png?raw=true" title="whatsapp-web.js" alt="WWebJS Website" width="500" /></a> | ||
| </p> | ||
| <br /> | ||
| <p> | ||
| <a href="https://www.npmjs.com/package/whatsapp-web.js"><img src="https://img.shields.io/npm/v/whatsapp-web.js.svg" alt="npm" /></a> | ||
| <a href="https://depfu.com/github/pedroslopez/whatsapp-web.js?project_id=9765"><img src="https://badges.depfu.com/badges/4a65a0de96ece65fdf39e294e0c8dcba/overview.svg" alt="Depfu" /></a> | ||
| <img src="https://img.shields.io/badge/WhatsApp_Web-2.3000.1017054665-brightgreen.svg" alt="WhatsApp_Web 2.2346.52" /> | ||
| <a href="https://discord.gg/H7DqQs4"><img src="https://img.shields.io/discord/698610475432411196.svg?logo=discord" alt="Discord server" /></a> | ||
| </p> | ||
| <br /> | ||
| </div> | ||
| ### This is an electron wrapper for [whatsapp-web.js](https://github.com/pedroslopez/whatsapp-web.js) which connect using [puppeteer-in-electron](https://github.com/TrevorSundberg/puppeteer-in-electron). | ||
| ## About | ||
| **A WhatsApp API client that connects through the WhatsApp Web browser app** | ||
| A WhatsApp API client that connects through the WhatsApp Web browser app | ||
| The library works by launching the WhatsApp Web browser application and managing it using Puppeteer to create an instance of WhatsApp Web, thereby mitigating the risk of being blocked. The WhatsApp API client connects through the WhatsApp Web browser app, accessing its internal functions. This grants you access to nearly all the features available on WhatsApp Web, enabling dynamic handling similar to any other Node.js application. | ||
| It uses Puppeteer to run a real instance of Whatsapp Web to avoid getting blocked. | ||
| > [!IMPORTANT] | ||
| > **It is not guaranteed you will not be blocked by using this method. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe.** | ||
| **NOTE:** I can't guarantee you will not be blocked by using this method, although it has worked for me. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe. | ||
| ## Links | ||
| ## whatsapp-web.js Quick Links | ||
| * [Website][website] | ||
| * [Guide][guide] ([source][guide-source]) _(work in progress)_ | ||
| * [Documentation][documentation] ([source][documentation-source]) | ||
| * [WWebJS Discord][discord] | ||
| * [GitHub][gitHub] | ||
| * [npm][npm] | ||
| * [Guide / Getting Started](https://wwebjs.dev/guide) _(work in progress)_ | ||
| * [Reference documentation](https://docs.wwebjs.dev/) | ||
| * [GitHub](https://github.com/pedroslopez/whatsapp-web.js) | ||
| * [npm](https://npmjs.org/package/whatsapp-web.js) | ||
| ## Installation | ||
| The module is now available on [npm](https://npmjs.org/package/wwebjs-electron)! `npm i wwebjs-electron` | ||
| The module is now available on npm! `npm i whatsapp-web.js` | ||
| Please note that Node v12+ is required. | ||
| > [!NOTE] | ||
| > **Node ``v18+`` is required.** | ||
| ## Example usage | ||
| ## QUICK STEPS TO UPGRADE NODE | ||
| ```js | ||
| const { app, BrowserWindow } = require('electron'); | ||
| const puppeteer = require('puppeteer-core'); | ||
| const pie = require('puppeteer-in-electron'); | ||
| const { Client } = require('wwebjs-electron'); | ||
| ### Windows | ||
| const window = new BrowserWindow({ | ||
| // your options... | ||
| }); | ||
| #### Manual | ||
| Just get the latest LTS from the [official node website][nodejs]. | ||
| pie.connect(app, puppeteer).then((pieBrowser) => { | ||
| const client = new Client(pieBrowser, window); | ||
| #### npm | ||
| ```powershell | ||
| sudo npm install -g n | ||
| sudo n stable | ||
| ``` | ||
| // No need to listen for "qr" event as you can scan | ||
| // the qr code directly in electron window | ||
| #### Choco | ||
| ```powershell | ||
| choco install nodejs-lts | ||
| ``` | ||
| client.on('ready', () => { | ||
| console.log('Client is ready!'); | ||
| }); | ||
| #### Winget | ||
| ```powershell | ||
| winget install OpenJS.NodeJS.LTS | ||
| ``` | ||
| client.on('message', msg => { | ||
| if (msg.body == '!ping') { | ||
| msg.reply('pong'); | ||
| } | ||
| }); | ||
| ### Ubuntu / Debian | ||
| ```bash | ||
| curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - &&\ | ||
| sudo apt-get install -y nodejs | ||
| ``` | ||
| client.initialize(); | ||
| ## Example usage | ||
| ```js | ||
| const { Client } = require('whatsapp-web.js'); | ||
| const client = new Client(); | ||
| client.on('qr', (qr) => { | ||
| // Generate and scan this code with your phone | ||
| console.log('QR RECEIVED', qr); | ||
| }); | ||
| ``` | ||
| Take a look at [whatsapp-web.js example.js](https://github.com/pedroslopez/whatsapp-web.js/blob/master/example.js) for another example with more use cases. The only difference should be the initialization process and a few feature (explained below). | ||
| client.on('ready', () => { | ||
| console.log('Client is ready!'); | ||
| }); | ||
| ## Preserving Sessions | ||
| client.on('message', msg => { | ||
| if (msg.body == '!ping') { | ||
| msg.reply('pong'); | ||
| } | ||
| }); | ||
| The original project are using [Authentication Strategies](https://wwebjs.dev/guide/authentication.html) to save and restore sessions. No such thing are needed for electron since it already store its session, so the default `NoAuth` strategy works just fine. | ||
| client.initialize(); | ||
| ``` | ||
| ## Remote Access and Docker | ||
| Take a look at [example.js][examples] for another examples with additional use cases. | ||
| For further details on saving and restoring sessions, explore the provided [Authentication Strategies][auth-strategies]. | ||
| The original [whatsapp-web.js](https://github.com/pedroslopez/whatsapp-web.js) has remote access feature and supports docker which is irrelevant for an electron project, that's why I didn't include it here. | ||
| ## Supported features | ||
@@ -75,3 +106,3 @@ | ||
| | Send media (images/audio/documents) | ✅ | | ||
| | Send media (video) | ✅ [(requires google chrome)](https://wwebjs.dev/guide/handling-attachments.html#caveat-for-sending-videos-and-gifs) | | ||
| | Send media (video) | ✅ [(requires Google Chrome)][google-chrome] | | ||
| | Send stickers | ✅ | | ||
@@ -81,4 +112,4 @@ | Receive media (images/audio/video/documents) | ✅ | | ||
| | Send location | ✅ | | ||
| | Send buttons | ✅ | | ||
| | Send lists | ✅ (business accounts not supported) | | ||
| | Send buttons | ❌ [(DEPRECATED)][deprecated-video] | | ||
| | Send lists | ❌ [(DEPRECATED)][deprecated-video] | | ||
| | Receive location | ✅ | | ||
@@ -94,2 +125,3 @@ | Message replies | ✅ | | ||
| | Mention users | ✅ | | ||
| | Mention groups | ✅ | | ||
| | Mute/unmute chats | ✅ | | ||
@@ -101,2 +133,6 @@ | Block/unblock contacts | ✅ | | ||
| | React to messages | ✅ | | ||
| | Create polls | ✅ | | ||
| | Channels | ✅ | | ||
| | Vote in polls | 🔜 | | ||
| | Communities | 🔜 | | ||
@@ -107,26 +143,48 @@ Something missing? Make an issue and let us know! | ||
| Pull requests are welcome! If you see something you'd like to add, please do. For drastic changes, please open an issue first. | ||
| Feel free to open pull requests; we welcome contributions! However, for significant changes, it's best to open an issue beforehand. Make sure to review our [contribution guidelines][contributing] before creating a pull request. Before creating your own issue or pull request, always check to see if one already exists! | ||
| ## Supporting the project | ||
| You can support the maintainer of original [whatsapp-web.js](https://github.com/pedroslopez/whatsapp-web.js) project through the links below | ||
| You can support the maintainer of this project through the links below | ||
| - [Support via GitHub Sponsors](https://github.com/sponsors/pedroslopez) | ||
| - [Support via PayPal](https://www.paypal.me/psla/) | ||
| - [Sign up for DigitalOcean](https://m.do.co/c/73f906a36ed4) and get $100 in credit when you sign up (Referral) | ||
| - [Support via GitHub Sponsors][gitHub-sponsors] | ||
| - [Support via PayPal][support-payPal] | ||
| - [Sign up for DigitalOcean][digitalocean] and get $200 in credit when you sign up (Referral) | ||
| ## Disclaimer | ||
| This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or its affiliates. The official WhatsApp website can be found at https://whatsapp.com. "WhatsApp" as well as related names, marks, emblems and images are registered trademarks of their respective owners. | ||
| This project is not affiliated, associated, authorized, endorsed by, or in any way officially connected with WhatsApp or any of its subsidiaries or its affiliates. The official WhatsApp website can be found at [whatsapp.com][whatsapp]. "WhatsApp" as well as related names, marks, emblems and images are registered trademarks of their respective owners. Also it is not guaranteed you will not be blocked by using this method. WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe. | ||
| ## License | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this project except in compliance with the License. | ||
| You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. | ||
| Copyright 2019 Pedro S Lopez | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| you may not use this project except in compliance with the License. | ||
| You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. | ||
| Unless required by applicable law or agreed to in writing, software | ||
| distributed under the License is distributed on an "AS IS" BASIS, | ||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| See the License for the specific language governing permissions and | ||
| limitations under the License. | ||
| [website]: https://wwebjs.dev | ||
| [guide]: https://guide.wwebjs.dev/guide | ||
| [guide-source]: https://github.com/wwebjs/wwebjs.dev/tree/main | ||
| [documentation]: https://docs.wwebjs.dev/ | ||
| [documentation-source]: https://github.com/pedroslopez/whatsapp-web.js/tree/main/docs | ||
| [discord]: https://discord.gg/H7DqQs4 | ||
| [gitHub]: https://github.com/pedroslopez/whatsapp-web.js | ||
| [npm]: https://npmjs.org/package/whatsapp-web.js | ||
| [nodejs]: https://nodejs.org/en/download/ | ||
| [examples]: https://github.com/pedroslopez/whatsapp-web.js/blob/master/example.js | ||
| [auth-strategies]: https://wwebjs.dev/guide/creating-your-bot/authentication.html | ||
| [google-chrome]: https://wwebjs.dev/guide/creating-your-bot/handling-attachments.html#caveat-for-sending-videos-and-gifs | ||
| [deprecated-video]: https://www.youtube.com/watch?v=hv1R1rLeVVE | ||
| [gitHub-sponsors]: https://github.com/sponsors/pedroslopez | ||
| [support-payPal]: https://www.paypal.me/psla/ | ||
| [digitalocean]: https://m.do.co/c/73f906a36ed4 | ||
| [contributing]: https://github.com/pedroslopez/whatsapp-web.js/blob/main/CODE_OF_CONDUCT.md | ||
| [whatsapp]: https://whatsapp.com |
+0
-0
@@ -0,0 +0,0 @@ /** |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -12,9 +12,10 @@ 'use strict'; | ||
| * @param {string} options.dataPath - Change the default path for saving session files, default is: "./.wwebjs_auth/" | ||
| * @param {number} options.rmMaxRetries - Sets the maximum number of retries for removing the session directory | ||
| */ | ||
| class LocalAuth extends BaseAuthStrategy { | ||
| constructor({ clientId, dataPath } = {}) { | ||
| constructor({ clientId, dataPath, rmMaxRetries }={}) { | ||
| super(); | ||
| const idRegex = /^[-_\w]+$/i; | ||
| if (clientId && !idRegex.test(clientId)) { | ||
| if(clientId && !idRegex.test(clientId)) { | ||
| throw new Error('Invalid clientId. Only alphanumeric characters, underscores and hyphens are allowed.'); | ||
@@ -25,2 +26,3 @@ } | ||
| this.clientId = clientId; | ||
| this.rmMaxRetries = rmMaxRetries ?? 4; | ||
| } | ||
@@ -33,3 +35,3 @@ | ||
| if (puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) { | ||
| if(puppeteerOpts.userDataDir && puppeteerOpts.userDataDir !== dirPath) { | ||
| throw new Error('LocalAuth is not compatible with a user-supplied userDataDir.'); | ||
@@ -39,3 +41,3 @@ } | ||
| fs.mkdirSync(dirPath, { recursive: true }); | ||
| this.client.options.puppeteer = { | ||
@@ -51,3 +53,6 @@ ...puppeteerOpts, | ||
| if (this.userDataDir) { | ||
| return (fs.rmSync ? fs.rmSync : fs.rmdirSync).call(this, this.userDataDir, { recursive: true, force: true }); | ||
| await fs.promises.rm(this.userDataDir, { recursive: true, force: true, maxRetries: this.rmMaxRetries }) | ||
| .catch((e) => { | ||
| throw new Error(e); | ||
| }); | ||
| } | ||
@@ -58,2 +63,2 @@ } | ||
| module.exports = LocalAuth; | ||
| module.exports = LocalAuth; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -25,5 +25,6 @@ 'use strict'; | ||
| * @param {number} options.backupSyncIntervalMs - Sets the time interval for periodic session backups. Accepts values starting from 60000ms {1 minute} | ||
| * @param {number} options.rmMaxRetries - Sets the maximum number of retries for removing the session directory | ||
| */ | ||
| class RemoteAuth extends BaseAuthStrategy { | ||
| constructor({ clientId, dataPath, store, backupSyncIntervalMs } = {}) { | ||
| constructor({ clientId, dataPath, store, backupSyncIntervalMs, rmMaxRetries } = {}) { | ||
| 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'); | ||
@@ -45,4 +46,5 @@ super(); | ||
| this.dataPath = path.resolve(dataPath || './.wwebjs_auth/'); | ||
| this.tempDir = `${this.dataPath}/wwebjs_temp_session`; | ||
| this.tempDir = `${this.dataPath}/wwebjs_temp_session_${this.clientId}`; | ||
| this.requiredDirs = ['Default', 'IndexedDB', 'Local Storage']; /* => Required Files & Dirs in WWebJS to restore session */ | ||
| this.rmMaxRetries = rmMaxRetries ?? 4; | ||
| } | ||
@@ -85,3 +87,4 @@ | ||
| recursive: true, | ||
| force: true | ||
| force: true, | ||
| maxRetries: this.rmMaxRetries, | ||
| }).catch(() => {}); | ||
@@ -113,3 +116,4 @@ } | ||
| recursive: true, | ||
| force: true | ||
| force: true, | ||
| maxRetries: this.rmMaxRetries, | ||
| }).catch(() => {}); | ||
@@ -127,3 +131,4 @@ if(options && options.emit) this.client.emit(Events.REMOTE_SESSION_SAVED); | ||
| recursive: true, | ||
| force: true | ||
| force: true, | ||
| maxRetries: this.rmMaxRetries, | ||
| }).catch(() => {}); | ||
@@ -185,3 +190,4 @@ } | ||
| recursive: true, | ||
| force: true | ||
| force: true, | ||
| maxRetries: this.rmMaxRetries, | ||
| }).catch(() => {}); | ||
@@ -188,0 +194,0 @@ } else { |
@@ -5,8 +5,13 @@ 'use strict'; | ||
| const GroupChat = require('../structures/GroupChat'); | ||
| const Channel = require('../structures/Channel'); | ||
| class ChatFactory { | ||
| static create(client, data) { | ||
| if(data.isGroup) { | ||
| if (data.isGroup) { | ||
| return new GroupChat(client, data); | ||
| } | ||
| if (data.isChannel) { | ||
| return new Channel(client, data); | ||
| } | ||
@@ -17,2 +22,2 @@ return new PrivateChat(client, data); | ||
| module.exports = ChatFactory; | ||
| module.exports = ChatFactory; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ 'use strict'; |
+43
-10
@@ -82,4 +82,4 @@ 'use strict'; | ||
| */ | ||
| this.lastMessage = data.lastMessage ? new Message(super.client, data.lastMessage) : undefined; | ||
| this.lastMessage = data.lastMessage ? new Message(this.client, data.lastMessage) : undefined; | ||
| return super._patch(data); | ||
@@ -99,3 +99,3 @@ } | ||
| /** | ||
| * Set the message as seen | ||
| * Sets the chat as seen | ||
| * @returns {Promise<Boolean>} result | ||
@@ -109,3 +109,3 @@ */ | ||
| * Clears all messages from the chat | ||
| * @returns {Promise<Boolean>} result | ||
| * @returns {Promise<boolean>} result | ||
| */ | ||
@@ -160,6 +160,10 @@ async clearMessages() { | ||
| * Mutes this chat forever, unless a date is specified | ||
| * @param {?Date} unmuteDate Date at which the Chat will be unmuted, leave as is to mute forever | ||
| * @param {?Date} unmuteDate Date when the chat will be unmuted, don't provide a value to mute forever | ||
| * @returns {Promise<{isMuted: boolean, muteExpiration: number}>} | ||
| */ | ||
| async mute(unmuteDate) { | ||
| return this.client.muteChat(this.id._serialized, unmuteDate); | ||
| const result = await this.client.muteChat(this.id._serialized, unmuteDate); | ||
| this.isMuted = result.isMuted; | ||
| this.muteExpiration = result.muteExpiration; | ||
| return result; | ||
| } | ||
@@ -169,5 +173,9 @@ | ||
| * Unmutes this chat | ||
| * @returns {Promise<{isMuted: boolean, muteExpiration: number}>} | ||
| */ | ||
| async unmute() { | ||
| return this.client.unmuteChat(this.id._serialized); | ||
| const result = await this.client.unmuteChat(this.id._serialized); | ||
| this.isMuted = result.isMuted; | ||
| this.muteExpiration = result.muteExpiration; | ||
| return result; | ||
| } | ||
@@ -178,3 +186,3 @@ | ||
| */ | ||
| async markUnread() { | ||
| async markUnread(){ | ||
| return this.client.markChatUnread(this.id._serialized); | ||
@@ -202,3 +210,3 @@ } | ||
| const chat = window.Store.Chat.get(chatId); | ||
| const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| let msgs = chat.msgs.getModelsArray().filter(msgFilter); | ||
@@ -212,3 +220,3 @@ | ||
| } | ||
| if (msgs.length > searchOptions.limit) { | ||
@@ -272,4 +280,29 @@ msgs.sort((a, b) => (a.t > b.t) ? 1 : -1); | ||
| } | ||
| /** | ||
| * Add or remove labels to this Chat | ||
| * @param {Array<number|string>} labelIds | ||
| * @returns {Promise<void>} | ||
| */ | ||
| async changeLabels(labelIds) { | ||
| return this.client.addOrRemoveLabels(labelIds, [this.id._serialized]); | ||
| } | ||
| /** | ||
| * Gets instances of all pinned messages in a chat | ||
| * @returns {Promise<[Message]|[]>} | ||
| */ | ||
| async getPinnedMessages() { | ||
| return this.client.getPinnedMessages(this.id._serialized); | ||
| } | ||
| /** | ||
| * Sync chat history conversation | ||
| * @return {Promise<boolean>} True if operation completed successfully, false otherwise. | ||
| */ | ||
| async syncHistory() { | ||
| return this.client.syncHistory(this.id._serialized); | ||
| } | ||
| } | ||
| module.exports = Chat; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -159,5 +159,6 @@ 'use strict'; | ||
| const contact = window.Store.Contact.get(contactId); | ||
| await window.Store.BlockContact.blockContact(contact); | ||
| await window.Store.BlockContact.blockContact({contact}); | ||
| }, this.id._serialized); | ||
| this.isBlocked = true; | ||
| return true; | ||
@@ -178,2 +179,3 @@ } | ||
| this.isBlocked = false; | ||
| return true; | ||
@@ -180,0 +182,0 @@ } |
+246
-42
@@ -31,3 +31,3 @@ 'use strict'; | ||
| } | ||
| /** | ||
@@ -58,17 +58,133 @@ * Gets the date at which the group was created | ||
| /** | ||
| * An object that handles the result for {@link addParticipants} method | ||
| * @typedef {Object} AddParticipantsResult | ||
| * @property {number} code The code of the result | ||
| * @property {string} message The result message | ||
| * @property {boolean} isInviteV4Sent Indicates if the inviteV4 was sent to the partitipant | ||
| */ | ||
| /** | ||
| * An object that handles options for adding participants | ||
| * @typedef {Object} AddParticipnatsOptions | ||
| * @property {Array<number>|number} [sleep = [250, 500]] The number of milliseconds to wait before adding the next participant. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500] | ||
| * @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) | ||
| * @property {string} [comment = ''] The comment to be added to an inviteV4 (empty string by default) | ||
| */ | ||
| /** | ||
| * Adds a list of participants by ID to the group | ||
| * @param {Array<string>} participantIds | ||
| * @returns {Promise<Object>} | ||
| * @param {string|Array<string>} participantIds | ||
| * @param {AddParticipnatsOptions} options An object thay handles options for adding participants | ||
| * @returns {Promise<Object.<string, AddParticipantsResult>|string>} Returns an object with the resulting data or an error message as a string | ||
| */ | ||
| async addParticipants(participantIds) { | ||
| return await this.client.pupPage.evaluate(async (chatId, participantIds) => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| const chat = await window.Store.Chat.find(chatWid); | ||
| const participants = await Promise.all(participantIds.map(async p => { | ||
| const wid = window.Store.WidFactory.createWid(p); | ||
| return await window.Store.Contact.get(wid); | ||
| })); | ||
| await window.Store.GroupParticipants.addParticipants(chat, participants); | ||
| return { status: 200 }; | ||
| }, this.id._serialized, participantIds); | ||
| async addParticipants(participantIds, options = {}) { | ||
| return await this.client.pupPage.evaluate(async (groupId, participantIds, options) => { | ||
| const { sleep = [250, 500], autoSendInviteV4 = true, comment = '' } = options; | ||
| const participantData = {}; | ||
| !Array.isArray(participantIds) && (participantIds = [participantIds]); | ||
| const groupWid = window.Store.WidFactory.createWid(groupId); | ||
| const group = window.Store.Chat.get(groupWid) || (await window.Store.Chat.find(groupWid)); | ||
| const participantWids = participantIds.map((p) => window.Store.WidFactory.createWid(p)); | ||
| const errorCodes = { | ||
| default: 'An unknown error occupied while adding a participant', | ||
| isGroupEmpty: 'AddParticipantsError: The participant can\'t be added to an empty group', | ||
| iAmNotAdmin: 'AddParticipantsError: You have no admin rights to add a participant to a group', | ||
| 200: 'The participant was added successfully', | ||
| 403: 'The participant can be added by sending private invitation only', | ||
| 404: 'The phone number is not registered on WhatsApp', | ||
| 408: 'You cannot add this participant because they recently left the group', | ||
| 409: 'The participant is already a group member', | ||
| 417: 'The participant can\'t be added to the community. You can invite them privately to join this group through its invite link', | ||
| 419: 'The participant can\'t be added because the group is full' | ||
| }; | ||
| await window.Store.GroupQueryAndUpdate({ id: groupId }); | ||
| let groupParticipants = group.groupMetadata?.participants.serialize(); | ||
| if (!groupParticipants) { | ||
| return errorCodes.isGroupEmpty; | ||
| } | ||
| if (!group.iAmAdmin()) { | ||
| return errorCodes.iAmNotAdmin; | ||
| } | ||
| groupParticipants.map(({ id }) => { | ||
| return id.server === 'lid' ? window.Store.LidUtils.getPhoneNumber(id) : id; | ||
| }); | ||
| const _getSleepTime = (sleep) => { | ||
| if (!Array.isArray(sleep) || sleep.length === 2 && sleep[0] === sleep[1]) { | ||
| return sleep; | ||
| } | ||
| if (sleep.length === 1) { | ||
| return sleep[0]; | ||
| } | ||
| (sleep[1] - sleep[0]) < 100 && (sleep[0] = sleep[1]) && (sleep[1] += 100); | ||
| return Math.floor(Math.random() * (sleep[1] - sleep[0] + 1)) + sleep[0]; | ||
| }; | ||
| for (let pWid of participantWids) { | ||
| const pId = pWid._serialized; | ||
| pWid = pWid.server === 'lid' ? window.Store.LidUtils.getPhoneNumber(pWid) : pWid; | ||
| participantData[pId] = { | ||
| code: undefined, | ||
| message: undefined, | ||
| isInviteV4Sent: false | ||
| }; | ||
| if (groupParticipants.some(p => p._serialized === pId)) { | ||
| participantData[pId].code = 409; | ||
| participantData[pId].message = errorCodes[409]; | ||
| continue; | ||
| } | ||
| if (!(await window.Store.QueryExist(pWid))?.wid) { | ||
| participantData[pId].code = 404; | ||
| participantData[pId].message = errorCodes[404]; | ||
| continue; | ||
| } | ||
| const rpcResult = | ||
| await window.WWebJS.getAddParticipantsRpcResult(groupWid, pWid); | ||
| const { code: rpcResultCode } = rpcResult; | ||
| participantData[pId].code = rpcResultCode; | ||
| participantData[pId].message = | ||
| errorCodes[rpcResultCode] || errorCodes.default; | ||
| if (autoSendInviteV4 && rpcResultCode === 403) { | ||
| let userChat, isInviteV4Sent = false; | ||
| window.Store.Contact.gadd(pWid, { silent: true }); | ||
| if (rpcResult.name === 'ParticipantRequestCodeCanBeSent' && | ||
| (userChat = window.Store.Chat.get(pWid) || (await window.Store.Chat.find(pWid)))) { | ||
| const groupName = group.formattedTitle || group.name; | ||
| const res = await window.Store.GroupInviteV4.sendGroupInviteMessage( | ||
| userChat, | ||
| group.id._serialized, | ||
| groupName, | ||
| rpcResult.inviteV4Code, | ||
| rpcResult.inviteV4CodeExp, | ||
| comment, | ||
| await window.WWebJS.getProfilePicThumbToBase64(groupWid) | ||
| ); | ||
| isInviteV4Sent = res.messageSendResult === 'OK'; | ||
| } | ||
| participantData[pId].isInviteV4Sent = isInviteV4Sent; | ||
| } | ||
| sleep && | ||
| participantWids.length > 1 && | ||
| participantWids.indexOf(pWid) !== participantWids.length - 1 && | ||
| (await new Promise((resolve) => setTimeout(resolve, _getSleepTime(sleep)))); | ||
| } | ||
| return participantData; | ||
| }, this.id._serialized, participantIds, options); | ||
| } | ||
@@ -79,10 +195,12 @@ | ||
| * @param {Array<string>} participantIds | ||
| * @returns {Promise<Object>} | ||
| * @returns {Promise<{ status: number }>} | ||
| */ | ||
| async removeParticipants(participantIds) { | ||
| return await this.client.pupPage.evaluate(async (chatId, participantIds) => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| const chat = await window.Store.Chat.find(chatWid); | ||
| const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| const participants = participantIds.map(p => { | ||
| return chat.groupMetadata.participants.get(p); | ||
| const wid = window.Store.WidFactory.createWid(p); | ||
| const lid = wid.server!=='lid' ? window.Store.LidUtils.getCurrentLid(wid) : wid; | ||
| const phone = wid.server=='lid' ? window.Store.LidUtils.getPhoneNumber(wid) : wid; | ||
| return chat.groupMetadata.participants.get(lid?._serialized) || chat.groupMetadata.participants.get(phone?._serialized); | ||
| }).filter(p => Boolean(p)); | ||
@@ -101,6 +219,8 @@ await window.Store.GroupParticipants.removeParticipants(chat, participants); | ||
| return await this.client.pupPage.evaluate(async (chatId, participantIds) => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| const chat = await window.Store.Chat.find(chatWid); | ||
| const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| const participants = participantIds.map(p => { | ||
| return chat.groupMetadata.participants.get(p); | ||
| const wid = window.Store.WidFactory.createWid(p); | ||
| const lid = wid.server!=='lid' ? window.Store.LidUtils.getCurrentLid(wid) : wid; | ||
| const phone = wid.server=='lid' ? window.Store.LidUtils.getPhoneNumber(wid) : wid; | ||
| return chat.groupMetadata.participants.get(lid?._serialized) || chat.groupMetadata.participants.get(phone?._serialized); | ||
| }).filter(p => Boolean(p)); | ||
@@ -119,6 +239,8 @@ await window.Store.GroupParticipants.promoteParticipants(chat, participants); | ||
| return await this.client.pupPage.evaluate(async (chatId, participantIds) => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| const chat = await window.Store.Chat.find(chatWid); | ||
| const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| const participants = participantIds.map(p => { | ||
| return chat.groupMetadata.participants.get(p); | ||
| const wid = window.Store.WidFactory.createWid(p); | ||
| const lid = wid.server!=='lid' ? window.Store.LidUtils.getCurrentLid(wid) : wid; | ||
| const phone = wid.server=='lid' ? window.Store.LidUtils.getPhoneNumber(wid) : wid; | ||
| return chat.groupMetadata.participants.get(lid?._serialized) || chat.groupMetadata.participants.get(phone?._serialized); | ||
| }).filter(p => Boolean(p)); | ||
@@ -142,3 +264,3 @@ await window.Store.GroupParticipants.demoteParticipants(chat, participants); | ||
| } catch (err) { | ||
| if (err.name === 'ServerStatusCodeError') return false; | ||
| if(err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
@@ -148,3 +270,3 @@ } | ||
| if (!success) return false; | ||
| if(!success) return false; | ||
| this.name = subject; | ||
@@ -163,2 +285,3 @@ return true; | ||
| let descId = window.Store.GroupMetadata.get(chatWid).descId; | ||
| let newId = await window.Store.MsgKey.newId(); | ||
| try { | ||
@@ -168,3 +291,3 @@ await window.Store.GroupUtils.setGroupDescription(chatWid, description, newId, descId); | ||
| } catch (err) { | ||
| if (err.name === 'ServerStatusCodeError') return false; | ||
| if(err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
@@ -174,7 +297,28 @@ } | ||
| if (!success) return false; | ||
| if(!success) return false; | ||
| this.groupMetadata.desc = description; | ||
| return true; | ||
| } | ||
| /** | ||
| * Updates the group setting to allow only admins to add members to the group. | ||
| * @param {boolean} [adminsOnly=true] Enable or disable this option | ||
| * @returns {Promise<boolean>} Returns true if the setting was properly updated. This can return false if the user does not have the necessary permissions. | ||
| */ | ||
| async setAddMembersAdminsOnly(adminsOnly=true) { | ||
| const success = await this.client.pupPage.evaluate(async (groupId, adminsOnly) => { | ||
| const chatWid = window.Store.WidFactory.createWid(groupId); | ||
| try { | ||
| const response = await window.Store.GroupUtils.setGroupMemberAddMode(chatWid, 'member_add_mode', adminsOnly ? 0 : 1); | ||
| return response.name === 'SetMemberAddModeResponseSuccess'; | ||
| } catch (err) { | ||
| if(err.name === 'SmaxParsingFailure') return false; | ||
| throw err; | ||
| } | ||
| }, this.id._serialized, adminsOnly); | ||
| success && (this.groupMetadata.memberAddMode = adminsOnly ? 'admin_add' : 'all_member_add'); | ||
| return success; | ||
| } | ||
| /** | ||
@@ -185,3 +329,3 @@ * Updates the group settings to only allow admins to send messages. | ||
| */ | ||
| async setMessagesAdminsOnly(adminsOnly = true) { | ||
| async setMessagesAdminsOnly(adminsOnly=true) { | ||
| const success = await this.client.pupPage.evaluate(async (chatId, adminsOnly) => { | ||
@@ -193,3 +337,3 @@ const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| } catch (err) { | ||
| if (err.name === 'ServerStatusCodeError') return false; | ||
| if(err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
@@ -199,3 +343,3 @@ } | ||
| if (!success) return false; | ||
| if(!success) return false; | ||
@@ -211,3 +355,3 @@ this.groupMetadata.announce = adminsOnly; | ||
| */ | ||
| async setInfoAdminsOnly(adminsOnly = true) { | ||
| async setInfoAdminsOnly(adminsOnly=true) { | ||
| const success = await this.client.pupPage.evaluate(async (chatId, adminsOnly) => { | ||
@@ -219,3 +363,3 @@ const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| } catch (err) { | ||
| if (err.name === 'ServerStatusCodeError') return false; | ||
| if(err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
@@ -225,4 +369,4 @@ } | ||
| if (!success) return false; | ||
| if(!success) return false; | ||
| this.groupMetadata.restrict = adminsOnly; | ||
@@ -264,8 +408,18 @@ return true; | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| return window.Store.Invite.queryGroupInviteCode(chatWid); | ||
| try { | ||
| return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.1020730154') | ||
| ? await window.Store.GroupInvite.fetchMexGroupInviteCode(chatId) | ||
| : await window.Store.GroupInvite.queryGroupInviteCode(chatWid, true); | ||
| } | ||
| catch (err) { | ||
| if(err.name === 'ServerStatusCodeError') return undefined; | ||
| throw err; | ||
| } | ||
| }, this.id._serialized); | ||
| return codeRes.code; | ||
| return codeRes?.code | ||
| ? codeRes?.code | ||
| : codeRes; | ||
| } | ||
| /** | ||
@@ -278,3 +432,3 @@ * Invalidates the current group invite code and generates a new one | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| return window.Store.Invite.resetGroupInviteCode(chatWid); | ||
| return window.Store.GroupInvite.resetGroupInviteCode(chatWid); | ||
| }, this.id._serialized); | ||
@@ -284,4 +438,55 @@ | ||
| } | ||
| /** | ||
| * An object that handles the information about the group membership request | ||
| * @typedef {Object} GroupMembershipRequest | ||
| * @property {Object} id The wid of a user who requests to enter the group | ||
| * @property {Object} addedBy The wid of a user who created that request | ||
| * @property {Object|null} parentGroupId The wid of a community parent group to which the current group is linked | ||
| * @property {string} requestMethod The method used to create the request: NonAdminAdd/InviteLink/LinkedGroupJoin | ||
| * @property {number} t The timestamp the request was created at | ||
| */ | ||
| /** | ||
| * Gets an array of membership requests | ||
| * @returns {Promise<Array<GroupMembershipRequest>>} An array of membership requests | ||
| */ | ||
| async getGroupMembershipRequests() { | ||
| return await this.client.getGroupMembershipRequests(this.id._serialized); | ||
| } | ||
| /** | ||
| * An object that handles the result for membership request action | ||
| * @typedef {Object} MembershipRequestActionResult | ||
| * @property {string} requesterId User ID whos membership request was approved/rejected | ||
| * @property {number} error An error code that occurred during the operation for the participant | ||
| * @property {string} message A message with a result of membership request action | ||
| */ | ||
| /** | ||
| * An object that handles options for {@link approveGroupMembershipRequests} and {@link rejectGroupMembershipRequests} methods | ||
| * @typedef {Object} MembershipRequestActionOptions | ||
| * @property {Array<string>|string|null} requesterIds User ID/s who requested to join the group, if no value is provided, the method will search for all membership requests for that group | ||
| * @property {Array<number>|number|null} sleep The number of milliseconds to wait before performing an operation for the next requester. If it is an array, a random sleep time between the sleep[0] and sleep[1] values will be added (the difference must be >=100 ms, otherwise, a random sleep time between sleep[1] and sleep[1] + 100 will be added). If sleep is a number, a sleep time equal to its value will be added. By default, sleep is an array with a value of [250, 500] | ||
| */ | ||
| /** | ||
| * Approves membership requests if any | ||
| * @param {MembershipRequestActionOptions} options Options for performing a membership request action | ||
| * @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were approved and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned | ||
| */ | ||
| async approveGroupMembershipRequests(options = {}) { | ||
| return await this.client.approveGroupMembershipRequests(this.id._serialized, options); | ||
| } | ||
| /** | ||
| * Rejects membership requests if any | ||
| * @param {MembershipRequestActionOptions} options Options for performing a membership request action | ||
| * @returns {Promise<Array<MembershipRequestActionResult>>} Returns an array of requester IDs whose membership requests were rejected and an error for each requester, if any occurred during the operation. If there are no requests, an empty array will be returned | ||
| */ | ||
| async rejectGroupMembershipRequests(options = {}) { | ||
| return await this.client.rejectGroupMembershipRequests(this.id._serialized, options); | ||
| } | ||
| /** | ||
| * Makes the bot leave the group | ||
@@ -292,4 +497,3 @@ * @returns {Promise} | ||
| await this.client.pupPage.evaluate(async chatId => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| const chat = await window.Store.Chat.find(chatWid); | ||
| const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| return window.Store.GroupUtils.sendExitGroup(chat); | ||
@@ -301,2 +505,2 @@ }, this.id._serialized); | ||
| module.exports = GroupChat; | ||
| module.exports = GroupChat; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -8,2 +8,3 @@ module.exports = { | ||
| GroupChat: require('./GroupChat'), | ||
| Channel: require('./Channel'), | ||
| Location: require('./Location'), | ||
@@ -23,2 +24,6 @@ Message: require('./Message'), | ||
| Reaction: require('./Reaction'), | ||
| Poll: require('./Poll'), | ||
| PollVote: require('./PollVote'), | ||
| Broadcast: require('./Broadcast'), | ||
| ScheduledEvent: require('./ScheduledEvent'), | ||
| }; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ 'use strict'; |
| 'use strict'; | ||
| /** | ||
| * Location send options | ||
| * @typedef {Object} LocationSendOptions | ||
| * @property {string} [name] Location name | ||
| * @property {string} [address] Location address | ||
| * @property {string} [url] URL address to be shown within a location message | ||
| * @property {string} [description] Location full description | ||
| */ | ||
| /** | ||
| * Location information | ||
@@ -10,5 +19,5 @@ */ | ||
| * @param {number} longitude | ||
| * @param {?string} description | ||
| * @param {LocationSendOptions} [options] Location send options | ||
| */ | ||
| constructor(latitude, longitude, description) { | ||
| constructor(latitude, longitude, options = {}) { | ||
| /** | ||
@@ -28,5 +37,25 @@ * Location latitude | ||
| * Name for the location | ||
| * @type {?string} | ||
| * @type {string|undefined} | ||
| */ | ||
| this.description = description; | ||
| this.name = options.name; | ||
| /** | ||
| * Location address | ||
| * @type {string|undefined} | ||
| */ | ||
| this.address = options.address; | ||
| /** | ||
| * URL address to be shown within a location message | ||
| * @type {string|undefined} | ||
| */ | ||
| this.url = options.url; | ||
| /** | ||
| * Location full description | ||
| * @type {string|undefined} | ||
| */ | ||
| this.description = this.name && this.address | ||
| ? `${this.name}\n${this.address}` | ||
| : this.name || this.address || ''; | ||
| } | ||
@@ -33,0 +62,0 @@ } |
+209
-43
@@ -9,3 +9,5 @@ 'use strict'; | ||
| const Reaction = require('./Reaction'); | ||
| const {MessageTypes} = require('../util/Constants'); | ||
| const Contact = require('./Contact'); | ||
| const ScheduledEvent = require('./ScheduledEvent'); // eslint-disable-line no-unused-vars | ||
| const { MessageTypes } = require('../util/Constants'); | ||
@@ -48,3 +50,3 @@ /** | ||
| */ | ||
| this.hasMedia = Boolean(data.mediaKey && data.directPath); | ||
| this.hasMedia = Boolean(data.directPath); | ||
@@ -55,3 +57,3 @@ /** | ||
| */ | ||
| this.body = this.hasMedia ? data.caption || '' : data.body || ''; | ||
| this.body = this.hasMedia ? data.caption || '' : data.body || data.pollName || data.eventName || ''; | ||
@@ -156,3 +158,17 @@ /** | ||
| */ | ||
| this.location = data.type === MessageTypes.LOCATION ? new Location(data.lat, data.lng, data.loc) : undefined; | ||
| this.location = (() => { | ||
| if (data.type !== MessageTypes.LOCATION) { | ||
| return undefined; | ||
| } | ||
| let description; | ||
| if (data.loc && typeof data.loc === 'string') { | ||
| let splitted = data.loc.split('\n'); | ||
| description = { | ||
| name: splitted[0], | ||
| address: splitted[1], | ||
| url: data.clientUrl | ||
| }; | ||
| } | ||
| return new Location(data.lat, data.lng, description); | ||
| })(); | ||
@@ -174,4 +190,4 @@ /** | ||
| groupName: data.inviteGrpName, | ||
| fromId: data.from._serialized, | ||
| toId: data.to._serialized | ||
| fromId: typeof data.from === 'object' && '_serialized' in data.from ? data.from._serialized : data.from, | ||
| toId: typeof data.to === 'object' && '_serialized' in data.to ? data.to._serialized : data.to | ||
| } : undefined; | ||
@@ -181,11 +197,19 @@ | ||
| * Indicates the mentions in the message body. | ||
| * @type {Array<string>} | ||
| * @type {string[]} | ||
| */ | ||
| this.mentionedIds = []; | ||
| this.mentionedIds = data.mentionedJidList || []; | ||
| if (data.mentionedJidList) { | ||
| this.mentionedIds = data.mentionedJidList; | ||
| } | ||
| /** | ||
| * @typedef {Object} GroupMention | ||
| * @property {string} groupSubject The name of the group | ||
| * @property {string} groupJid The group ID | ||
| */ | ||
| /** | ||
| * Indicates whether there are group mentions in the message body | ||
| * @type {GroupMention[]} | ||
| */ | ||
| this.groupMentions = data.groupMentions || []; | ||
| /** | ||
| * Order ID for message type ORDER | ||
@@ -233,3 +257,21 @@ * @type {string} | ||
| /** Last edit time */ | ||
| if (data.latestEditSenderTimestampMs) { | ||
| this.latestEditSenderTimestampMs = data.latestEditSenderTimestampMs; | ||
| } | ||
| /** Last edit message author */ | ||
| if (data.latestEditMsgKey) { | ||
| this.latestEditMsgKey = data.latestEditMsgKey; | ||
| } | ||
| /** | ||
| * Protocol message key. | ||
| * Can be used to retrieve the ID of an original message that was revoked. | ||
| */ | ||
| if (data.protocolMessageKey) { | ||
| this.protocolMessageKey = data.protocolMessageKey; | ||
| } | ||
| /** | ||
| * Links included in the message. | ||
@@ -256,2 +298,11 @@ * @type {Array<{link: string, isSuspicious: boolean}>} | ||
| if (this.type === MessageTypes.POLL_CREATION) { | ||
| this.pollName = data.pollName; | ||
| this.pollOptions = data.pollOptions; | ||
| this.allowMultipleAnswers = Boolean(!data.pollSelectableOptionsCount); | ||
| this.pollInvalidated = data.pollInvalidated; | ||
| this.isSentCagPollCreation = data.isSentCagPollCreation; | ||
| this.messageSecret = data.messageSecret ? Object.keys(data.messageSecret).map((key) => data.messageSecret[key]) : []; | ||
| } | ||
| return super._patch(data); | ||
@@ -270,5 +321,5 @@ } | ||
| async reload() { | ||
| const newData = await this.client.pupPage.evaluate((msgId) => { | ||
| const msg = window.Store.Msg.get(msgId); | ||
| if(!msg) return null; | ||
| const newData = await this.client.pupPage.evaluate(async (msgId) => { | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if (!msg) return null; | ||
| return window.WWebJS.getMessageModel(msg); | ||
@@ -314,2 +365,10 @@ }, this.id._serialized); | ||
| } | ||
| /** | ||
| * Returns groups mentioned in this message | ||
| * @returns {Promise<GroupChat[]|[]>} | ||
| */ | ||
| async getGroupMentions() { | ||
| return await Promise.all(this.groupMentions.map(async (m) => await this.client.getChatById(m.groupJid._serialized))); | ||
| } | ||
@@ -323,4 +382,4 @@ /** | ||
| const quotedMsg = await this.client.pupPage.evaluate((msgId) => { | ||
| const msg = window.Store.Msg.get(msgId); | ||
| const quotedMsg = await this.client.pupPage.evaluate(async (msgId) => { | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| const quotedMsg = window.Store.QuotedMsg.getQuotedMsgObj(msg); | ||
@@ -363,5 +422,6 @@ return window.WWebJS.getMessageModel(quotedMsg); | ||
| await this.client.pupPage.evaluate(async (messageId, reaction) => { | ||
| if (!messageId) { return undefined; } | ||
| const msg = await window.Store.Msg.get(messageId); | ||
| if (!messageId) return null; | ||
| const msg = | ||
| window.Store.Msg.get(messageId) || (await window.Store.Msg.getMessagesById([messageId]))?.messages?.[0]; | ||
| if(!msg) return null; | ||
| await window.Store.sendReactionToMsg(msg, reaction); | ||
@@ -389,6 +449,3 @@ }, this.id._serialized, reaction); | ||
| await this.client.pupPage.evaluate(async (msgId, chatId) => { | ||
| let msg = window.Store.Msg.get(msgId); | ||
| let chat = window.Store.Chat.get(chatId); | ||
| return await chat.forwardMessages([msg]); | ||
| return window.WWebJS.forwardMessage(chatId, msgId); | ||
| }, this.id._serialized, chatId); | ||
@@ -407,5 +464,5 @@ } | ||
| const result = await this.client.pupPage.evaluate(async (msgId) => { | ||
| const msg = window.Store.Msg.get(msgId); | ||
| if (!msg) { | ||
| return undefined; | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if (!msg || !msg.mediaData) { | ||
| return null; | ||
| } | ||
@@ -457,15 +514,22 @@ if (msg.mediaData.mediaStage != 'RESOLVED') { | ||
| * @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. | ||
| * @param {?boolean} [clearMedia = true] If true, any associated media will also be deleted from a device. | ||
| */ | ||
| async delete(everyone) { | ||
| await this.client.pupPage.evaluate(async (msgId, everyone) => { | ||
| let msg = window.Store.Msg.get(msgId); | ||
| let chat = await window.Store.Chat.find(msg.id.remote); | ||
| async delete(everyone, clearMedia = true) { | ||
| await this.client.pupPage.evaluate(async (msgId, everyone, clearMedia) => { | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| const chat = window.Store.Chat.get(msg.id.remote) || (await window.Store.Chat.find(msg.id.remote)); | ||
| const canRevoke = window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg); | ||
| const canRevoke = | ||
| window.Store.MsgActionChecks.canSenderRevokeMsg(msg) || window.Store.MsgActionChecks.canAdminRevokeMsg(msg); | ||
| if (everyone && canRevoke) { | ||
| return window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); | ||
| return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') | ||
| ? window.Store.Cmd.sendRevokeMsgs(chat, { list: [msg], type: 'message' }, { clearMedia: clearMedia }) | ||
| : window.Store.Cmd.sendRevokeMsgs(chat, [msg], { clearMedia: true, type: msg.id.fromMe ? 'Sender' : 'Admin' }); | ||
| } | ||
| return window.Store.Cmd.sendDeleteMsgs(chat, [msg], true); | ||
| }, this.id._serialized, everyone); | ||
| return window.compareWwebVersions(window.Debug.VERSION, '>=', '2.3000.0') | ||
| ? window.Store.Cmd.sendDeleteMsgs(chat, { list: [msg], type: 'message' }, clearMedia) | ||
| : window.Store.Cmd.sendDeleteMsgs(chat, [msg], clearMedia); | ||
| }, this.id._serialized, everyone, clearMedia); | ||
| } | ||
@@ -478,4 +542,3 @@ | ||
| await this.client.pupPage.evaluate(async (msgId) => { | ||
| let msg = window.Store.Msg.get(msgId); | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if (window.Store.MsgActionChecks.canStarMsg(msg)) { | ||
@@ -493,4 +556,3 @@ let chat = await window.Store.Chat.find(msg.id.remote); | ||
| await this.client.pupPage.evaluate(async (msgId) => { | ||
| let msg = window.Store.Msg.get(msgId); | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if (window.Store.MsgActionChecks.canStarMsg(msg)) { | ||
@@ -504,2 +566,23 @@ let chat = await window.Store.Chat.find(msg.id.remote); | ||
| /** | ||
| * Pins the message (group admins can pin messages of all group members) | ||
| * @param {number} duration The duration in seconds the message will be pinned in a chat | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async pin(duration) { | ||
| return await this.client.pupPage.evaluate(async (msgId, duration) => { | ||
| return await window.WWebJS.pinUnpinMsgAction(msgId, 1, duration); | ||
| }, this.id._serialized, duration); | ||
| } | ||
| /** | ||
| * Unpins the message (group admins can unpin messages of all group members) | ||
| * @returns {Promise<boolean>} Returns true if the operation completed successfully, false otherwise | ||
| */ | ||
| async unpin() { | ||
| return await this.client.pupPage.evaluate(async (msgId) => { | ||
| return await window.WWebJS.pinUnpinMsgAction(msgId, 2); | ||
| }, this.id._serialized); | ||
| } | ||
| /** | ||
| * Message Info | ||
@@ -516,3 +599,4 @@ * @typedef {Object} MessageInfo | ||
| /** | ||
| * Get information about message delivery status. May return null if the message does not exist or is not sent by you. | ||
| * Get information about message delivery status. | ||
| * May return null if the message does not exist or is not sent by you. | ||
| * @returns {Promise<?MessageInfo>} | ||
@@ -522,6 +606,10 @@ */ | ||
| const info = await this.client.pupPage.evaluate(async (msgId) => { | ||
| const msg = window.Store.Msg.get(msgId); | ||
| if (!msg) return null; | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if (!msg || !msg.id.fromMe) return null; | ||
| return await window.Store.MessageInfo.sendQueryMsgInfo(msg.id); | ||
| return new Promise((resolve) => { | ||
| setTimeout(async () => { | ||
| resolve(await window.Store.getMsgInfo(msg.id)); | ||
| }, (Date.now() - msg.t * 1000 < 1250) && Math.floor(Math.random() * (1200 - 1100 + 1)) + 1100 || 0); | ||
| }); | ||
| }, this.id._serialized); | ||
@@ -553,3 +641,3 @@ | ||
| const msg = await this.client.pupPage.evaluate(async (msgId) => { | ||
| const msg = window.Store.Msg.get(msgId); | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if(!msg) return null; | ||
@@ -600,4 +688,82 @@ return msg.serialize(); | ||
| } | ||
| /** | ||
| * Edits the current message. | ||
| * @param {string} content | ||
| * @param {MessageEditOptions} [options] - Options used when editing the message | ||
| * @returns {Promise<?Message>} | ||
| */ | ||
| async edit(content, options = {}) { | ||
| if (options.mentions) { | ||
| !Array.isArray(options.mentions) && (options.mentions = [options.mentions]); | ||
| if (options.mentions.some((possiblyContact) => possiblyContact instanceof Contact)) { | ||
| console.warn('Mentions with an array of Contact are now deprecated. See more at https://github.com/pedroslopez/whatsapp-web.js/pull/2166.'); | ||
| options.mentions = options.mentions.map((a) => a.id._serialized); | ||
| } | ||
| } | ||
| options.groupMentions && !Array.isArray(options.groupMentions) && (options.groupMentions = [options.groupMentions]); | ||
| let internalOptions = { | ||
| linkPreview: options.linkPreview === false ? undefined : true, | ||
| mentionedJidList: options.mentions || [], | ||
| groupMentions: options.groupMentions, | ||
| extraOptions: options.extra | ||
| }; | ||
| if (!this.fromMe) { | ||
| return null; | ||
| } | ||
| const messageEdit = await this.client.pupPage.evaluate(async (msgId, message, options) => { | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if (!msg) return null; | ||
| let canEdit = window.Store.MsgActionChecks.canEditText(msg) || window.Store.MsgActionChecks.canEditCaption(msg); | ||
| if (canEdit) { | ||
| const msgEdit = await window.WWebJS.editMessage(msg, message, options); | ||
| return msgEdit.serialize(); | ||
| } | ||
| return null; | ||
| }, this.id._serialized, content, internalOptions); | ||
| if (messageEdit) { | ||
| return new Message(this.client, messageEdit); | ||
| } | ||
| return null; | ||
| } | ||
| /** | ||
| * Edits the current ScheduledEvent message. | ||
| * Once the scheduled event is canceled, it can not be edited. | ||
| * @param {ScheduledEvent} editedEventObject | ||
| * @returns {Promise<?Message>} | ||
| */ | ||
| async editScheduledEvent(editedEventObject) { | ||
| if (!this.fromMe) { | ||
| return null; | ||
| } | ||
| const edittedEventMsg = await this.client.pupPage.evaluate(async (msgId, editedEventObject) => { | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| if (!msg) return null; | ||
| const { name, startTimeTs, eventSendOptions } = editedEventObject; | ||
| const eventOptions = { | ||
| name: name, | ||
| description: eventSendOptions.description, | ||
| startTime: startTimeTs, | ||
| endTime: eventSendOptions.endTimeTs, | ||
| location: eventSendOptions.location, | ||
| callType: eventSendOptions.callType, | ||
| isEventCanceled: eventSendOptions.isEventCanceled, | ||
| }; | ||
| await window.Store.ScheduledEventMsgUtils.sendEventEditMessage(eventOptions, msg); | ||
| const editedMsg = window.Store.Msg.get(msg.id._serialized); | ||
| return editedMsg?.serialize(); | ||
| }, this.id._serialized, editedEventObject); | ||
| return edittedEventMsg && new Message(this.client, edittedEventMsg); | ||
| } | ||
| } | ||
| module.exports = Message; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ const Base = require('./Base'); |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -0,0 +0,0 @@ const Base = require('./Base'); |
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -10,3 +10,3 @@ 'use strict'; | ||
| }, | ||
| webVersion: '2.2322.15', | ||
| webVersion: '2.3000.1017054665', | ||
| webVersionCache: { | ||
@@ -22,3 +22,8 @@ type: 'local', | ||
| bypassCSP: false, | ||
| proxyAuthentication: undefined | ||
| proxyAuthentication: undefined, | ||
| pairWithPhoneNumber: { | ||
| phoneNumber: '', | ||
| showNotification: true, | ||
| intervalMs: 180000, | ||
| }, | ||
| }; | ||
@@ -49,2 +54,3 @@ | ||
| MESSAGE_RECEIVED: 'message', | ||
| MESSAGE_CIPHERTEXT: 'message_ciphertext', | ||
| MESSAGE_CREATE: 'message_create', | ||
@@ -54,2 +60,3 @@ MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone', | ||
| MESSAGE_ACK: 'message_ack', | ||
| MESSAGE_EDIT: 'message_edit', | ||
| UNREAD_COUNT: 'unread_count', | ||
@@ -62,4 +69,6 @@ MESSAGE_REACTION: 'message_reaction', | ||
| GROUP_ADMIN_CHANGED: 'group_admin_changed', | ||
| GROUP_MEMBERSHIP_REQUEST: 'group_membership_request', | ||
| GROUP_UPDATE: 'group_update', | ||
| QR_RECEIVED: 'qr', | ||
| CODE_RECEIVED: 'code', | ||
| LOADING_SCREEN: 'loading_screen', | ||
@@ -70,3 +79,4 @@ DISCONNECTED: 'disconnected', | ||
| INCOMING_CALL: 'call', | ||
| REMOTE_SESSION_SAVED: 'remote_session_saved' | ||
| REMOTE_SESSION_SAVED: 'remote_session_saved', | ||
| VOTE_UPDATE: 'vote_update' | ||
| }; | ||
@@ -115,2 +125,4 @@ | ||
| TEMPLATE_BUTTON_REPLY: 'template_button_reply', | ||
| POLL_CREATION: 'poll_creation', | ||
| SCHEDULED_EVENT_CREATION: 'scheduled_event_creation', | ||
| }; | ||
@@ -117,0 +129,0 @@ |
@@ -17,6 +17,5 @@ 'use strict'; | ||
| async openChatWindow(chatId) { | ||
| await this.pupPage.evaluate(async chatId => { | ||
| let chatWid = window.Store.WidFactory.createWid(chatId); | ||
| let chat = await window.Store.Chat.find(chatWid); | ||
| await window.Store.Cmd.openChatAt(chat); | ||
| await this.pupPage.evaluate(async (chatId) => { | ||
| const chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| await window.Store.Cmd.openChatBottom(chat); | ||
| }, chatId); | ||
@@ -31,3 +30,3 @@ } | ||
| await this.pupPage.evaluate(async chatId => { | ||
| let chat = await window.Store.Chat.get(chatId); | ||
| let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| await window.Store.Cmd.openDrawerMid(chat); | ||
@@ -43,3 +42,3 @@ }, chatId); | ||
| await this.pupPage.evaluate(async chatId => { | ||
| let chat = await window.Store.Chat.get(chatId); | ||
| let chat = await window.WWebJS.getChat(chatId, { getAsModel: false }); | ||
| await window.Store.Cmd.chatSearch(chat); | ||
@@ -54,7 +53,7 @@ }, chatId); | ||
| async openChatWindowAt(msgId) { | ||
| await this.pupPage.evaluate(async msgId => { | ||
| let msg = await window.Store.Msg.get(msgId); | ||
| let chat = await window.Store.Chat.find(msg.id.remote); | ||
| let searchContext = await window.Store.SearchContext(chat,msg); | ||
| await window.Store.Cmd.openChatAt(chat, searchContext); | ||
| await this.pupPage.evaluate(async (msgId) => { | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| const chat = window.Store.Chat.get(msg.id.remote) ?? await window.Store.Chat.find(msg.id.remote); | ||
| const searchContext = await window.Store.SearchContext.getSearchContext(chat, msg.id); | ||
| await window.Store.Cmd.openChatAt({ chat: chat, msgContext: searchContext }); | ||
| }, msgId); | ||
@@ -69,3 +68,3 @@ } | ||
| await this.pupPage.evaluate(async msgId => { | ||
| let msg = await window.Store.Msg.get(msgId); | ||
| const msg = window.Store.Msg.get(msgId) || (await window.Store.Msg.getMessagesById([msgId]))?.messages?.[0]; | ||
| await window.Store.Cmd.msgInfoDrawer(msg); | ||
@@ -72,0 +71,0 @@ }, msgId); |
+0
-0
@@ -0,0 +0,0 @@ 'use strict'; |
@@ -32,7 +32,4 @@ const path = require('path'); | ||
| async persist(indexHtml) { | ||
| // extract version from index (e.g. manifest-2.2206.9.json -> 2.2206.9) | ||
| const version = indexHtml.match(/manifest-([\d\\.]+)\.json/)[1]; | ||
| if(!version) return; | ||
| async persist(indexHtml, version) { | ||
| // version = (version+'').replace(/[^0-9.]/g,''); | ||
| const filePath = path.join(this.path, `${version}.html`); | ||
@@ -44,2 +41,2 @@ fs.mkdirSync(this.path, { recursive: true }); | ||
| module.exports = LocalWebCache; | ||
| module.exports = LocalWebCache; |
@@ -0,0 +0,0 @@ const fetch = require('node-fetch'); |
@@ -9,6 +9,4 @@ /** | ||
| class VersionResolveError extends Error { } | ||
| module.exports = { | ||
@@ -15,0 +13,0 @@ WebCache, |
@@ -0,0 +0,0 @@ const RemoteWebCache = require('./RemoteWebCache'); |
| 'use strict'; | ||
| const BaseAuthStrategy = require('./BaseAuthStrategy'); | ||
| /** | ||
| * Legacy session auth strategy | ||
| * Not compatible with multi-device accounts. | ||
| * @param {object} options - options | ||
| * @param {string} options.restartOnAuthFail - Restart client with a new session (i.e. use null 'session' var) if authentication fails | ||
| * @param {object} options.session - Whatsapp session to restore. If not set, will start a new session | ||
| * @param {string} options.session.WABrowserId | ||
| * @param {string} options.session.WASecretBundle | ||
| * @param {string} options.session.WAToken1 | ||
| * @param {string} options.session.WAToken2 | ||
| */ | ||
| class LegacySessionAuth extends BaseAuthStrategy { | ||
| constructor({ session, restartOnAuthFail }={}) { | ||
| super(); | ||
| this.session = session; | ||
| this.restartOnAuthFail = restartOnAuthFail; | ||
| } | ||
| async afterBrowserInitialized() { | ||
| if(this.session) { | ||
| await this.client.pupPage.evaluateOnNewDocument(session => { | ||
| if (document.referrer === 'https://whatsapp.com/') { | ||
| localStorage.clear(); | ||
| localStorage.setItem('WABrowserId', session.WABrowserId); | ||
| localStorage.setItem('WASecretBundle', session.WASecretBundle); | ||
| localStorage.setItem('WAToken1', session.WAToken1); | ||
| localStorage.setItem('WAToken2', session.WAToken2); | ||
| } | ||
| localStorage.setItem('remember-me', 'true'); | ||
| }, this.session); | ||
| } | ||
| } | ||
| async onAuthenticationNeeded() { | ||
| if(this.session) { | ||
| this.session = null; | ||
| return { | ||
| failed: true, | ||
| restart: this.restartOnAuthFail, | ||
| failureEventPayload: 'Unable to log in. Are the session details valid?' | ||
| }; | ||
| } | ||
| return { failed: false }; | ||
| } | ||
| async getAuthEventPayload() { | ||
| const isMD = await this.client.pupPage.evaluate(() => { | ||
| return window.Store.MDBackend; | ||
| }); | ||
| if(isMD) throw new Error('Authenticating via JSON session is not supported for MultiDevice-enabled WhatsApp accounts.'); | ||
| const localStorage = JSON.parse(await this.client.pupPage.evaluate(() => { | ||
| return JSON.stringify(window.localStorage); | ||
| })); | ||
| return { | ||
| WABrowserId: localStorage.WABrowserId, | ||
| WASecretBundle: localStorage.WASecretBundle, | ||
| WAToken1: localStorage.WAToken1, | ||
| WAToken2: localStorage.WAToken2 | ||
| }; | ||
| } | ||
| } | ||
| module.exports = LegacySessionAuth; |
| 'use strict'; | ||
| // Exposes the internal Store to the WhatsApp Web client | ||
| exports.ExposeStore = (moduleRaidStr) => { | ||
| eval('var moduleRaid = ' + moduleRaidStr); | ||
| // eslint-disable-next-line no-undef | ||
| window.mR = moduleRaid(); | ||
| window.Store = Object.assign({}, window.mR.findModule(m => m.default && m.default.Chat)[0].default); | ||
| window.Store.AppState = window.mR.findModule('Socket')[0].Socket; | ||
| window.Store.Conn = window.mR.findModule('Conn')[0].Conn; | ||
| window.Store.BlockContact = window.mR.findModule('blockContact')[0]; | ||
| window.Store.Call = window.mR.findModule((module) => module.default && module.default.Call)[0].default.Call; | ||
| window.Store.Cmd = window.mR.findModule('Cmd')[0].Cmd; | ||
| window.Store.CryptoLib = window.mR.findModule('decryptE2EMedia')[0]; | ||
| window.Store.DownloadManager = window.mR.findModule('downloadManager')[0].downloadManager; | ||
| window.Store.GroupMetadata = window.mR.findModule('GroupMetadata')[0].default.GroupMetadata; | ||
| window.Store.Invite = window.mR.findModule('resetGroupInviteCode')[0]; | ||
| window.Store.InviteInfo = window.mR.findModule('queryGroupInvite')[0]; | ||
| window.Store.Label = window.mR.findModule('LabelCollection')[0].LabelCollection; | ||
| window.Store.MediaPrep = window.mR.findModule('prepRawMedia')[0]; | ||
| window.Store.MediaObject = window.mR.findModule('getOrCreateMediaObject')[0]; | ||
| window.Store.NumberInfo = window.mR.findModule('formattedPhoneNumber')[0]; | ||
| window.Store.MediaTypes = window.mR.findModule('msgToMediaType')[0]; | ||
| window.Store.MediaUpload = window.mR.findModule('uploadMedia')[0]; | ||
| window.Store.MsgKey = window.mR.findModule((module) => module.default && module.default.fromString)[0].default; | ||
| window.Store.MessageInfo = window.mR.findModule('sendQueryMsgInfo')[0]; | ||
| window.Store.OpaqueData = window.mR.findModule(module => module.default && module.default.createFromData)[0].default; | ||
| window.Store.QueryExist = window.mR.findModule('queryExists')[0] ? window.mR.findModule('queryExists')[0].queryExists : window.mR.findModule('queryExist')[0].queryWidExists; | ||
| window.Store.QueryProduct = window.mR.findModule('queryProduct')[0]; | ||
| window.Store.QueryOrder = window.mR.findModule('queryOrder')[0]; | ||
| window.Store.SendClear = window.mR.findModule('sendClear')[0]; | ||
| window.Store.SendDelete = window.mR.findModule('sendDelete')[0]; | ||
| window.Store.SendMessage = window.mR.findModule('addAndSendMsgToChat')[0]; | ||
| window.Store.EditMessage = window.mR.findModule('addAndSendMessageEdit')[0]; | ||
| window.Store.SendSeen = window.mR.findModule('sendSeen')[0]; | ||
| window.Store.User = window.mR.findModule('getMaybeMeUser')[0]; | ||
| window.Store.ContactMethods = window.mR.findModule('getUserid')[0]; | ||
| window.Store.BusinessProfileCollection = window.mR.findModule('BusinessProfileCollection')[0].BusinessProfileCollection; | ||
| window.Store.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default; | ||
| window.Store.UserConstructor = window.mR.findModule((module) => (module.default && module.default.prototype && module.default.prototype.isServer && module.default.prototype.isUser) ? module.default : null)[0].default; | ||
| window.Store.Validators = window.mR.findModule('findLinks')[0]; | ||
| window.Store.VCard = window.mR.findModule('vcardFromContactModel')[0]; | ||
| window.Store.WidFactory = window.mR.findModule('createWid')[0]; | ||
| window.Store.ProfilePic = window.mR.findModule('profilePicResync')[0]; | ||
| window.Store.PresenceUtils = window.mR.findModule('sendPresenceAvailable')[0]; | ||
| window.Store.ChatState = window.mR.findModule('sendChatStateComposing')[0]; | ||
| window.Store.GroupParticipants = window.mR.findModule('promoteParticipants')[0]; | ||
| window.Store.JoinInviteV4 = window.mR.findModule('queryGroupInviteV4')[0]; | ||
| window.Store.findCommonGroups = window.mR.findModule('findCommonGroups')[0].findCommonGroups; | ||
| window.Store.StatusUtils = window.mR.findModule('setMyStatus')[0]; | ||
| window.Store.ConversationMsgs = window.mR.findModule('loadEarlierMsgs')[0]; | ||
| window.Store.sendReactionToMsg = window.mR.findModule('sendReactionToMsg')[0].sendReactionToMsg; | ||
| window.Store.createOrUpdateReactionsModule = window.mR.findModule('createOrUpdateReactions')[0]; | ||
| window.Store.EphemeralFields = window.mR.findModule('getEphemeralFields')[0]; | ||
| window.Store.ReplyUtils = window.mR.findModule('canReplyMsg').length > 0 && window.mR.findModule('canReplyMsg')[0]; | ||
| window.Store.MsgActionChecks = window.mR.findModule('canSenderRevokeMsg')[0]; | ||
| window.Store.QuotedMsg = window.mR.findModule('getQuotedMsgObj')[0]; | ||
| window.Store.Socket = window.mR.findModule('deprecatedSendIq')[0]; | ||
| window.Store.SocketWap = window.mR.findModule('wap')[0]; | ||
| window.Store.SearchContext = window.mR.findModule('getSearchContext')[0].getSearchContext; | ||
| window.Store.DrawerManager = window.mR.findModule('DrawerManager')[0].DrawerManager; | ||
| window.Store.StickerTools = { | ||
| ...window.mR.findModule('toWebpSticker')[0], | ||
| ...window.mR.findModule('addWebpMetadata')[0] | ||
| }; | ||
| window.Store.GroupUtils = { | ||
| ...window.mR.findModule('createGroup')[0], | ||
| ...window.mR.findModule('setGroupDescription')[0], | ||
| ...window.mR.findModule('sendExitGroup')[0], | ||
| ...window.mR.findModule('sendSetPicture')[0] | ||
| }; | ||
| if (!window.Store.Chat._find) { | ||
| window.Store.Chat._find = e => { | ||
| const target = window.Store.Chat.get(e); | ||
| return target ? Promise.resolve(target) : Promise.resolve({ | ||
| id: e | ||
| }); | ||
| }; | ||
| } | ||
| // TODO remove these once everybody has been updated to WWebJS with legacy sessions removed | ||
| const _linkPreview = window.mR.findModule('queryLinkPreview'); | ||
| if (_linkPreview && _linkPreview[0] && _linkPreview[0].default) { | ||
| window.Store.Wap = _linkPreview[0].default; | ||
| } | ||
| const _isMDBackend = window.mR.findModule('isMDBackend'); | ||
| if(_isMDBackend && _isMDBackend[0] && _isMDBackend[0].isMDBackend) { | ||
| window.Store.MDBackend = _isMDBackend[0].isMDBackend(); | ||
| } else { | ||
| window.Store.MDBackend = true; | ||
| } | ||
| const _features = window.mR.findModule('FEATURE_CHANGE_EVENT')[0]; | ||
| if(_features) { | ||
| window.Store.Features = _features.LegacyPhoneFeatures; | ||
| } | ||
| }; | ||
| exports.LoadUtils = () => { | ||
| window.WWebJS = {}; | ||
| window.WWebJS.sendSeen = async (chatId) => { | ||
| let chat = window.Store.Chat.get(chatId); | ||
| if (chat !== undefined) { | ||
| await window.Store.SendSeen.sendSeen(chat, false); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| window.WWebJS.sendMessage = async (chat, content, options = {}) => { | ||
| let attOptions = {}; | ||
| if (options.attachment) { | ||
| attOptions = options.sendMediaAsSticker | ||
| ? await window.WWebJS.processStickerData(options.attachment) | ||
| : await window.WWebJS.processMediaData(options.attachment, { | ||
| forceVoice: options.sendAudioAsVoice, | ||
| forceDocument: options.sendMediaAsDocument, | ||
| forceGif: options.sendVideoAsGif | ||
| }); | ||
| if (options.caption){ | ||
| attOptions.caption = options.caption; | ||
| } | ||
| content = options.sendMediaAsSticker ? undefined : attOptions.preview; | ||
| attOptions.isViewOnce = options.isViewOnce; | ||
| delete options.attachment; | ||
| delete options.sendMediaAsSticker; | ||
| } | ||
| let quotedMsgOptions = {}; | ||
| if (options.quotedMessageId) { | ||
| let quotedMessage = window.Store.Msg.get(options.quotedMessageId); | ||
| // TODO remove .canReply() once all clients are updated to >= v2.2241.6 | ||
| const canReply = window.Store.ReplyUtils ? | ||
| window.Store.ReplyUtils.canReplyMsg(quotedMessage.unsafe()) : | ||
| quotedMessage.canReply(); | ||
| if (canReply) { | ||
| quotedMsgOptions = quotedMessage.msgContextInfo(chat); | ||
| } | ||
| delete options.quotedMessageId; | ||
| } | ||
| if (options.mentionedJidList) { | ||
| options.mentionedJidList = options.mentionedJidList.map(cId => window.Store.Contact.get(cId).id); | ||
| } | ||
| let locationOptions = {}; | ||
| if (options.location) { | ||
| locationOptions = { | ||
| type: 'location', | ||
| loc: options.location.description, | ||
| lat: options.location.latitude, | ||
| lng: options.location.longitude | ||
| }; | ||
| delete options.location; | ||
| } | ||
| let vcardOptions = {}; | ||
| if (options.contactCard) { | ||
| let contact = window.Store.Contact.get(options.contactCard); | ||
| vcardOptions = { | ||
| body: window.Store.VCard.vcardFromContactModel(contact).vcard, | ||
| type: 'vcard', | ||
| vcardFormattedName: contact.formattedName | ||
| }; | ||
| delete options.contactCard; | ||
| } else if (options.contactCardList) { | ||
| let contacts = options.contactCardList.map(c => window.Store.Contact.get(c)); | ||
| let vcards = contacts.map(c => window.Store.VCard.vcardFromContactModel(c)); | ||
| vcardOptions = { | ||
| type: 'multi_vcard', | ||
| vcardList: vcards, | ||
| body: undefined | ||
| }; | ||
| delete options.contactCardList; | ||
| } else if (options.parseVCards && typeof (content) === 'string' && content.startsWith('BEGIN:VCARD')) { | ||
| delete options.parseVCards; | ||
| try { | ||
| const parsed = window.Store.VCard.parseVcard(content); | ||
| if (parsed) { | ||
| vcardOptions = { | ||
| type: 'vcard', | ||
| vcardFormattedName: window.Store.VCard.vcardGetNameFromParsed(parsed) | ||
| }; | ||
| } | ||
| } catch (_) { | ||
| // not a vcard | ||
| } | ||
| } | ||
| if (options.linkPreview) { | ||
| delete options.linkPreview; | ||
| // Not supported yet by WhatsApp Web on MD | ||
| if(!window.Store.MDBackend) { | ||
| const link = window.Store.Validators.findLink(content); | ||
| if (link) { | ||
| const preview = await window.Store.Wap.queryLinkPreview(link.url); | ||
| preview.preview = true; | ||
| preview.subtype = 'url'; | ||
| options = { ...options, ...preview }; | ||
| } | ||
| } | ||
| } | ||
| let buttonOptions = {}; | ||
| if(options.buttons){ | ||
| let caption; | ||
| if (options.buttons.type === 'chat') { | ||
| content = options.buttons.body; | ||
| caption = content; | ||
| } else { | ||
| caption = options.caption ? options.caption : ' '; //Caption can't be empty | ||
| } | ||
| buttonOptions = { | ||
| productHeaderImageRejected: false, | ||
| isFromTemplate: false, | ||
| isDynamicReplyButtonsMsg: true, | ||
| title: options.buttons.title ? options.buttons.title : undefined, | ||
| footer: options.buttons.footer ? options.buttons.footer : undefined, | ||
| dynamicReplyButtons: options.buttons.buttons, | ||
| replyButtons: options.buttons.buttons, | ||
| caption: caption | ||
| }; | ||
| delete options.buttons; | ||
| } | ||
| let listOptions = {}; | ||
| if (options.list) { | ||
| if (window.Store.Conn.platform === 'smba' || window.Store.Conn.platform === 'smbi') { | ||
| throw '[LT01] Whatsapp business can\'t send this yet'; | ||
| } | ||
| listOptions = { | ||
| type: 'list', | ||
| footer: options.list.footer, | ||
| list: { | ||
| ...options.list, | ||
| listType: 1 | ||
| }, | ||
| body: options.list.description | ||
| }; | ||
| delete options.list; | ||
| delete listOptions.list.footer; | ||
| } | ||
| const meUser = window.Store.User.getMaybeMeUser(); | ||
| const isMD = window.Store.MDBackend; | ||
| const newId = await window.Store.MsgKey.newId(); | ||
| const newMsgId = new window.Store.MsgKey({ | ||
| from: meUser, | ||
| to: chat.id, | ||
| id: newId, | ||
| participant: isMD && chat.id.isGroup() ? meUser : undefined, | ||
| selfDir: 'out', | ||
| }); | ||
| const extraOptions = options.extraOptions || {}; | ||
| delete options.extraOptions; | ||
| const ephemeralFields = window.Store.EphemeralFields.getEphemeralFields(chat); | ||
| const message = { | ||
| ...options, | ||
| id: newMsgId, | ||
| ack: 0, | ||
| body: content, | ||
| from: meUser, | ||
| to: chat.id, | ||
| local: true, | ||
| self: 'out', | ||
| t: parseInt(new Date().getTime() / 1000), | ||
| isNewMsg: true, | ||
| type: 'chat', | ||
| ...ephemeralFields, | ||
| ...locationOptions, | ||
| ...attOptions, | ||
| ...(attOptions.toJSON ? attOptions.toJSON() : {}), | ||
| ...quotedMsgOptions, | ||
| ...vcardOptions, | ||
| ...buttonOptions, | ||
| ...listOptions, | ||
| ...extraOptions | ||
| }; | ||
| await window.Store.SendMessage.addAndSendMsgToChat(chat, message); | ||
| return window.Store.Msg.get(newMsgId._serialized); | ||
| }; | ||
| window.WWebJS.editMessage = async (msg, content, options = {}) => { | ||
| const extraOptions = options.extraOptions || {}; | ||
| delete options.extraOptions; | ||
| if (options.mentionedJidList) { | ||
| options.mentionedJidList = options.mentionedJidList.map(cId => window.Store.Contact.get(cId).id); | ||
| } | ||
| if (options.linkPreview) { | ||
| options.linkPreview = null; | ||
| // Not supported yet by WhatsApp Web on MD | ||
| if(!window.Store.MDBackend) { | ||
| const link = window.Store.Validators.findLink(content); | ||
| if (link) { | ||
| const preview = await window.Store.Wap.queryLinkPreview(link.url); | ||
| preview.preview = true; | ||
| preview.subtype = 'url'; | ||
| options = { ...options, ...preview }; | ||
| } | ||
| } | ||
| } | ||
| const internalOptions = { | ||
| ...options, | ||
| ...extraOptions | ||
| }; | ||
| await window.Store.EditMessage.sendMessageEdit(msg, content, internalOptions); | ||
| return window.Store.Msg.get(msg.id._serialized); | ||
| }; | ||
| window.WWebJS.toStickerData = async (mediaInfo) => { | ||
| if (mediaInfo.mimetype == 'image/webp') return mediaInfo; | ||
| const file = window.WWebJS.mediaInfoToFile(mediaInfo); | ||
| const webpSticker = await window.Store.StickerTools.toWebpSticker(file); | ||
| const webpBuffer = await webpSticker.arrayBuffer(); | ||
| const data = window.WWebJS.arrayBufferToBase64(webpBuffer); | ||
| return { | ||
| mimetype: 'image/webp', | ||
| data | ||
| }; | ||
| }; | ||
| window.WWebJS.processStickerData = async (mediaInfo) => { | ||
| if (mediaInfo.mimetype !== 'image/webp') throw new Error('Invalid media type'); | ||
| const file = window.WWebJS.mediaInfoToFile(mediaInfo); | ||
| let filehash = await window.WWebJS.getFileHash(file); | ||
| let mediaKey = await window.WWebJS.generateHash(32); | ||
| const controller = new AbortController(); | ||
| const uploadedInfo = await window.Store.UploadUtils.encryptAndUpload({ | ||
| blob: file, | ||
| type: 'sticker', | ||
| signal: controller.signal, | ||
| mediaKey | ||
| }); | ||
| const stickerInfo = { | ||
| ...uploadedInfo, | ||
| clientUrl: uploadedInfo.url, | ||
| deprecatedMms3Url: uploadedInfo.url, | ||
| uploadhash: uploadedInfo.encFilehash, | ||
| size: file.size, | ||
| type: 'sticker', | ||
| filehash | ||
| }; | ||
| return stickerInfo; | ||
| }; | ||
| window.WWebJS.processMediaData = async (mediaInfo, { forceVoice, forceDocument, forceGif }) => { | ||
| const file = window.WWebJS.mediaInfoToFile(mediaInfo); | ||
| const mData = await window.Store.OpaqueData.createFromData(file, file.type); | ||
| const mediaPrep = window.Store.MediaPrep.prepRawMedia(mData, { asDocument: forceDocument }); | ||
| const mediaData = await mediaPrep.waitForPrep(); | ||
| const mediaObject = window.Store.MediaObject.getOrCreateMediaObject(mediaData.filehash); | ||
| const mediaType = window.Store.MediaTypes.msgToMediaType({ | ||
| type: mediaData.type, | ||
| isGif: mediaData.isGif | ||
| }); | ||
| if (forceVoice && mediaData.type === 'audio') { | ||
| mediaData.type = 'ptt'; | ||
| } | ||
| if (forceGif && mediaData.type === 'video') { | ||
| mediaData.isGif = true; | ||
| } | ||
| if (forceDocument) { | ||
| mediaData.type = 'document'; | ||
| } | ||
| if (!(mediaData.mediaBlob instanceof window.Store.OpaqueData)) { | ||
| mediaData.mediaBlob = await window.Store.OpaqueData.createFromData(mediaData.mediaBlob, mediaData.mediaBlob.type); | ||
| } | ||
| mediaData.renderableUrl = mediaData.mediaBlob.url(); | ||
| mediaObject.consolidate(mediaData.toJSON()); | ||
| mediaData.mediaBlob.autorelease(); | ||
| const uploadedMedia = await window.Store.MediaUpload.uploadMedia({ | ||
| mimetype: mediaData.mimetype, | ||
| mediaObject, | ||
| mediaType | ||
| }); | ||
| const mediaEntry = uploadedMedia.mediaEntry; | ||
| if (!mediaEntry) { | ||
| throw new Error('upload failed: media entry was not created'); | ||
| } | ||
| mediaData.set({ | ||
| clientUrl: mediaEntry.mmsUrl, | ||
| deprecatedMms3Url: mediaEntry.deprecatedMms3Url, | ||
| directPath: mediaEntry.directPath, | ||
| mediaKey: mediaEntry.mediaKey, | ||
| mediaKeyTimestamp: mediaEntry.mediaKeyTimestamp, | ||
| filehash: mediaObject.filehash, | ||
| encFilehash: mediaEntry.encFilehash, | ||
| uploadhash: mediaEntry.uploadHash, | ||
| size: mediaObject.size, | ||
| streamingSidecar: mediaEntry.sidecar, | ||
| firstFrameSidecar: mediaEntry.firstFrameSidecar | ||
| }); | ||
| return mediaData; | ||
| }; | ||
| window.WWebJS.getMessageModel = message => { | ||
| const msg = message.serialize(); | ||
| msg.isEphemeral = message.isEphemeral; | ||
| msg.isStatusV3 = message.isStatusV3; | ||
| msg.links = (message.getRawLinks()).map(link => ({ | ||
| link: link.href, | ||
| isSuspicious: Boolean(link.suspiciousCharacters && link.suspiciousCharacters.size) | ||
| })); | ||
| if (msg.buttons) { | ||
| msg.buttons = msg.buttons.serialize(); | ||
| } | ||
| if (msg.dynamicReplyButtons) { | ||
| msg.dynamicReplyButtons = JSON.parse(JSON.stringify(msg.dynamicReplyButtons)); | ||
| } | ||
| if (msg.replyButtons) { | ||
| msg.replyButtons = JSON.parse(JSON.stringify(msg.replyButtons)); | ||
| } | ||
| if (typeof msg.id.remote === 'object') { | ||
| msg.id = Object.assign({}, msg.id, { remote: msg.id.remote._serialized }); | ||
| } | ||
| delete msg.pendingAckUpdate; | ||
| return msg; | ||
| }; | ||
| window.WWebJS.getChatModel = async chat => { | ||
| let res = chat.serialize(); | ||
| res.isGroup = chat.isGroup; | ||
| res.formattedTitle = chat.formattedTitle; | ||
| res.isMuted = chat.mute && chat.mute.isMuted; | ||
| if (chat.groupMetadata) { | ||
| const chatWid = window.Store.WidFactory.createWid((chat.id._serialized)); | ||
| await window.Store.GroupMetadata.update(chatWid); | ||
| res.groupMetadata = chat.groupMetadata.serialize(); | ||
| } | ||
| res.lastMessage = null; | ||
| if (res.msgs && res.msgs.length) { | ||
| const lastMessage = chat.lastReceivedKey ? window.Store.Msg.get(chat.lastReceivedKey._serialized) : null; | ||
| if (lastMessage) { | ||
| res.lastMessage = window.WWebJS.getMessageModel(lastMessage); | ||
| } | ||
| } | ||
| delete res.msgs; | ||
| delete res.msgUnsyncedButtonReplyMsgs; | ||
| delete res.unsyncedButtonReplies; | ||
| return res; | ||
| }; | ||
| window.WWebJS.getChat = async chatId => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| const chat = await window.Store.Chat.find(chatWid); | ||
| return await window.WWebJS.getChatModel(chat); | ||
| }; | ||
| window.WWebJS.getChats = async () => { | ||
| const chats = window.Store.Chat.getModelsArray(); | ||
| const chatPromises = chats.map(chat => window.WWebJS.getChatModel(chat)); | ||
| return await Promise.all(chatPromises); | ||
| }; | ||
| window.WWebJS.getContactModel = contact => { | ||
| let res = contact.serialize(); | ||
| res.isBusiness = contact.isBusiness === undefined ? false : contact.isBusiness; | ||
| if (contact.businessProfile) { | ||
| res.businessProfile = contact.businessProfile.serialize(); | ||
| } | ||
| // TODO: remove useOldImplementation and its checks once all clients are updated to >= v2.2327.4 | ||
| const useOldImplementation | ||
| = window.compareWwebVersions(window.Debug.VERSION, '<', '2.2327.4'); | ||
| res.isMe = useOldImplementation | ||
| ? contact.isMe | ||
| : window.Store.ContactMethods.getIsMe(contact); | ||
| res.isUser = useOldImplementation | ||
| ? contact.isUser | ||
| : window.Store.ContactMethods.getIsUser(contact); | ||
| res.isGroup = useOldImplementation | ||
| ? contact.isGroup | ||
| : window.Store.ContactMethods.getIsGroup(contact); | ||
| res.isWAContact = useOldImplementation | ||
| ? contact.isWAContact | ||
| : window.Store.ContactMethods.getIsWAContact(contact); | ||
| res.isMyContact = useOldImplementation | ||
| ? contact.isMyContact | ||
| : window.Store.ContactMethods.getIsMyContact(contact); | ||
| res.isBlocked = contact.isContactBlocked; | ||
| res.userid = useOldImplementation | ||
| ? contact.userid | ||
| : window.Store.ContactMethods.getUserid(contact); | ||
| res.isEnterprise = useOldImplementation | ||
| ? contact.isEnterprise | ||
| : window.Store.ContactMethods.getIsEnterprise(contact); | ||
| res.verifiedName = useOldImplementation | ||
| ? contact.verifiedName | ||
| : window.Store.ContactMethods.getVerifiedName(contact); | ||
| res.verifiedLevel = useOldImplementation | ||
| ? contact.verifiedLevel | ||
| : window.Store.ContactMethods.getVerifiedLevel(contact); | ||
| res.statusMute = useOldImplementation | ||
| ? contact.statusMute | ||
| : window.Store.ContactMethods.getStatusMute(contact); | ||
| res.name = useOldImplementation | ||
| ? contact.name | ||
| : window.Store.ContactMethods.getName(contact); | ||
| res.shortName = useOldImplementation | ||
| ? contact.shortName | ||
| : window.Store.ContactMethods.getShortName(contact); | ||
| res.pushname = useOldImplementation | ||
| ? contact.pushname | ||
| : window.Store.ContactMethods.getPushname(contact); | ||
| return res; | ||
| }; | ||
| window.WWebJS.getContact = async contactId => { | ||
| const wid = window.Store.WidFactory.createWid(contactId); | ||
| const contact = await window.Store.Contact.find(wid); | ||
| const bizProfile = await window.Store.BusinessProfileCollection.fetchBizProfile(wid); | ||
| bizProfile.profileOptions && (contact.businessProfile = bizProfile); | ||
| return window.WWebJS.getContactModel(contact); | ||
| }; | ||
| window.WWebJS.getContacts = () => { | ||
| const contacts = window.Store.Contact.getModelsArray(); | ||
| return contacts.map(contact => window.WWebJS.getContactModel(contact)); | ||
| }; | ||
| window.WWebJS.mediaInfoToFile = ({ data, mimetype, filename }) => { | ||
| const binaryData = window.atob(data); | ||
| const buffer = new ArrayBuffer(binaryData.length); | ||
| const view = new Uint8Array(buffer); | ||
| for (let i = 0; i < binaryData.length; i++) { | ||
| view[i] = binaryData.charCodeAt(i); | ||
| } | ||
| const blob = new Blob([buffer], { type: mimetype }); | ||
| return new File([blob], filename, { | ||
| type: mimetype, | ||
| lastModified: Date.now() | ||
| }); | ||
| }; | ||
| window.WWebJS.arrayBufferToBase64 = (arrayBuffer) => { | ||
| let binary = ''; | ||
| const bytes = new Uint8Array(arrayBuffer); | ||
| const len = bytes.byteLength; | ||
| for (let i = 0; i < len; i++) { | ||
| binary += String.fromCharCode(bytes[i]); | ||
| } | ||
| return window.btoa(binary); | ||
| }; | ||
| window.WWebJS.arrayBufferToBase64Async = (arrayBuffer) => | ||
| new Promise((resolve, reject) => { | ||
| const blob = new Blob([arrayBuffer], { | ||
| type: 'application/octet-stream', | ||
| }); | ||
| const fileReader = new FileReader(); | ||
| fileReader.onload = () => { | ||
| const [, data] = fileReader.result.split(','); | ||
| resolve(data); | ||
| }; | ||
| fileReader.onerror = (e) => reject(e); | ||
| fileReader.readAsDataURL(blob); | ||
| }); | ||
| window.WWebJS.getFileHash = async (data) => { | ||
| let buffer = await data.arrayBuffer(); | ||
| const hashBuffer = await crypto.subtle.digest('SHA-256', buffer); | ||
| return btoa(String.fromCharCode(...new Uint8Array(hashBuffer))); | ||
| }; | ||
| window.WWebJS.generateHash = async (length) => { | ||
| var result = ''; | ||
| var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; | ||
| var charactersLength = characters.length; | ||
| for (var i = 0; i < length; i++) { | ||
| result += characters.charAt(Math.floor(Math.random() * charactersLength)); | ||
| } | ||
| return result; | ||
| }; | ||
| window.WWebJS.sendClearChat = async (chatId) => { | ||
| let chat = window.Store.Chat.get(chatId); | ||
| if (chat !== undefined) { | ||
| await window.Store.SendClear.sendClear(chat, false); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| window.WWebJS.sendDeleteChat = async (chatId) => { | ||
| let chat = window.Store.Chat.get(chatId); | ||
| if (chat !== undefined) { | ||
| await window.Store.SendDelete.sendDelete(chat); | ||
| return true; | ||
| } | ||
| return false; | ||
| }; | ||
| window.WWebJS.sendChatstate = async (state, chatId) => { | ||
| if (window.Store.MDBackend) { | ||
| chatId = window.Store.WidFactory.createWid(chatId); | ||
| } | ||
| switch (state) { | ||
| case 'typing': | ||
| await window.Store.ChatState.sendChatStateComposing(chatId); | ||
| break; | ||
| case 'recording': | ||
| await window.Store.ChatState.sendChatStateRecording(chatId); | ||
| break; | ||
| case 'stop': | ||
| await window.Store.ChatState.sendChatStatePaused(chatId); | ||
| break; | ||
| default: | ||
| throw 'Invalid chatstate'; | ||
| } | ||
| return true; | ||
| }; | ||
| window.WWebJS.getLabelModel = label => { | ||
| let res = label.serialize(); | ||
| res.hexColor = label.hexColor; | ||
| return res; | ||
| }; | ||
| window.WWebJS.getLabels = () => { | ||
| const labels = window.Store.Label.getModelsArray(); | ||
| return labels.map(label => window.WWebJS.getLabelModel(label)); | ||
| }; | ||
| window.WWebJS.getLabel = (labelId) => { | ||
| const label = window.Store.Label.get(labelId); | ||
| return window.WWebJS.getLabelModel(label); | ||
| }; | ||
| window.WWebJS.getChatLabels = async (chatId) => { | ||
| const chat = await window.WWebJS.getChat(chatId); | ||
| return (chat.labels || []).map(id => window.WWebJS.getLabel(id)); | ||
| }; | ||
| window.WWebJS.getOrderDetail = async (orderId, token, chatId) => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatId); | ||
| return window.Store.QueryOrder.queryOrder(chatWid, orderId, 80, 80, token); | ||
| }; | ||
| window.WWebJS.getProductMetadata = async (productId) => { | ||
| let sellerId = window.Store.Conn.wid; | ||
| let product = await window.Store.QueryProduct.queryProduct(sellerId, productId); | ||
| if (product && product.data) { | ||
| return product.data; | ||
| } | ||
| return undefined; | ||
| }; | ||
| window.WWebJS.rejectCall = async (peerJid, id) => { | ||
| peerJid = peerJid.split('@')[0] + '@s.whatsapp.net'; | ||
| let userId = window.Store.User.getMaybeMeUser().user + '@s.whatsapp.net'; | ||
| const stanza = window.Store.SocketWap.wap('call', { | ||
| id: window.Store.SocketWap.generateId(), | ||
| from: window.Store.SocketWap.USER_JID(userId), | ||
| to: window.Store.SocketWap.USER_JID(peerJid), | ||
| }, [ | ||
| window.Store.SocketWap.wap('reject', { | ||
| 'call-id': id, | ||
| 'call-creator': window.Store.SocketWap.USER_JID(peerJid), | ||
| count: '0', | ||
| }) | ||
| ]); | ||
| await window.Store.Socket.deprecatedCastStanza(stanza); | ||
| }; | ||
| window.WWebJS.cropAndResizeImage = async (media, options = {}) => { | ||
| if (!media.mimetype.includes('image')) | ||
| throw new Error('Media is not an image'); | ||
| if (options.mimetype && !options.mimetype.includes('image')) | ||
| delete options.mimetype; | ||
| options = Object.assign({ size: 640, mimetype: media.mimetype, quality: .75, asDataUrl: false }, options); | ||
| const img = await new Promise ((resolve, reject) => { | ||
| const img = new Image(); | ||
| img.onload = () => resolve(img); | ||
| img.onerror = reject; | ||
| img.src = `data:${media.mimetype};base64,${media.data}`; | ||
| }); | ||
| const sl = Math.min(img.width, img.height); | ||
| const sx = Math.floor((img.width - sl) / 2); | ||
| const sy = Math.floor((img.height - sl) / 2); | ||
| const canvas = document.createElement('canvas'); | ||
| canvas.width = options.size; | ||
| canvas.height = options.size; | ||
| const ctx = canvas.getContext('2d'); | ||
| ctx.drawImage(img, sx, sy, sl, sl, 0, 0, options.size, options.size); | ||
| const dataUrl = canvas.toDataURL(options.mimetype, options.quality); | ||
| if (options.asDataUrl) | ||
| return dataUrl; | ||
| return Object.assign(media, { | ||
| mimetype: options.mimeType, | ||
| data: dataUrl.replace(`data:${options.mimeType};base64,`, '') | ||
| }); | ||
| }; | ||
| window.WWebJS.setPicture = async (chatid, media) => { | ||
| const thumbnail = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 96 }); | ||
| const profilePic = await window.WWebJS.cropAndResizeImage(media, { asDataUrl: true, mimetype: 'image/jpeg', size: 640 }); | ||
| const chatWid = window.Store.WidFactory.createWid(chatid); | ||
| try { | ||
| const collection = window.Store.ProfilePicThumb.get(chatid); | ||
| if (!collection.canSet()) return; | ||
| const res = await window.Store.GroupUtils.sendSetPicture(chatWid, thumbnail, profilePic); | ||
| return res ? res.status === 200 : false; | ||
| } catch (err) { | ||
| if(err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
| } | ||
| }; | ||
| window.WWebJS.deletePicture = async (chatid) => { | ||
| const chatWid = window.Store.WidFactory.createWid(chatid); | ||
| try { | ||
| const collection = window.Store.ProfilePicThumb.get(chatid); | ||
| if (!collection.canDelete()) return; | ||
| const res = await window.Store.GroupUtils.requestDeletePicture(chatWid); | ||
| return res ? res.status === 200 : false; | ||
| } catch (err) { | ||
| if(err.name === 'ServerStatusCodeError') return false; | ||
| throw err; | ||
| } | ||
| }; | ||
| }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 3 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
452233
62.38%57
21.28%9900
57.24%186
45.31%36
50%10
11.11%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated