From 8e8d0c9b3ea05d297da725d604eca340a5767e60 Mon Sep 17 00:00:00 2001 From: tofers Date: Thu, 15 Jun 2023 18:49:38 +0300 Subject: [PATCH] feat: Edit message (#2254) * function edit message --- example.js | 9 ++++++++ index.d.ts | 27 ++++++++++++++++++++++ src/Client.js | 22 +++++++++++++++--- src/structures/Message.js | 47 +++++++++++++++++++++++++++++++++++++++ src/util/Constants.js | 1 + src/util/Injected.js | 35 +++++++++++++++++++++++++++++ 6 files changed, 138 insertions(+), 3 deletions(-) diff --git a/example.js b/example.js index 065fd12..529a8c4 100644 --- a/example.js +++ b/example.js @@ -201,6 +201,15 @@ client.on('message', async msg => { client.sendMessage(msg.from, list); } else if (msg.body === '!reaction') { msg.react('👍'); + } 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'); + } + } } }); diff --git a/index.d.ts b/index.d.ts index 4b686a6..a9864d4 100644 --- a/index.d.ts +++ b/index.d.ts @@ -242,6 +242,16 @@ declare namespace WAWebJS { ack: MessageAck ) => void): this + /** Emitted when an ack event occurrs on message type */ + on(event: 'message_edit', listener: ( + /** The message that was affected */ + message: Message, + /** New text message */ + newBody: String, + /** Prev text message */ + prevBody: String + ) => void): this + /** Emitted when a chat unread count changes */ on(event: 'unread_count', listener: ( /** The chat that was affected */ @@ -567,6 +577,7 @@ declare namespace WAWebJS { MESSAGE_REVOKED_EVERYONE = 'message_revoke_everyone', MESSAGE_REVOKED_ME = 'message_revoke_me', MESSAGE_ACK = 'message_ack', + MESSAGE_EDIT = 'message_edit', MEDIA_UPLOADED = 'media_uploaded', CONTACT_CHANGED = 'contact_changed', GROUP_JOIN = 'group_join', @@ -789,6 +800,10 @@ declare namespace WAWebJS { businessOwnerJid?: string, /** Product JID */ productId?: string, + /** Last edit time */ + latestEditSenderTimestampMs?: number, + /** Last edit message author */ + latestEditMsgKey?: MessageId, /** Message buttons */ dynamicReplyButtons?: object, /** Selected button ID */ @@ -846,6 +861,8 @@ declare namespace WAWebJS { * Gets the reactions associated with the given message */ getReactions: () => Promise, + /** Edits the current message */ + edit: (content: MessageContent, options?: MessageEditOptions) => Promise, } /** ID that represents a message */ @@ -911,6 +928,16 @@ declare namespace WAWebJS { stickerCategories?: string[] } + /** Options for editing a message */ + export interface MessageEditOptions { + /** Show links preview. Has no effect on multi-device accounts. */ + linkPreview?: boolean + /** Contacts that are being mentioned in the message */ + mentions?: Contact[] + /** Extra options */ + extra?: any + } + export interface MediaFromURLOptions { client?: Client filename?: string diff --git a/src/Client.js b/src/Client.js index d5eb7b7..0a5b5d1 100644 --- a/src/Client.js +++ b/src/Client.js @@ -584,12 +584,28 @@ class Client extends EventEmitter { this.emit(Events.CHAT_ARCHIVED, new Chat(this, chat), currState, prevState); }); + await page.exposeFunction('onEditMessageEvent', (msg, newBody, prevBody) => { + + if(msg.type === 'revoked'){ + return; + } + /** + * Emitted when messages are edited + * @event Client#message_edit + * @param {Message} message + * @param {string} newBody + * @param {string} prevBody + */ + this.emit(Events.MESSAGE_EDIT, new Message(this, msg), newBody, prevBody); + }); + await page.evaluate(() => { window.Store.Msg.on('change', (msg) => { window.onChangeMessageEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('change:type', (msg) => { window.onChangeMessageTypeEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('change:ack', (msg, ack) => { window.onMessageAckEvent(window.WWebJS.getMessageModel(msg), ack); }); window.Store.Msg.on('change:isUnsentMedia', (msg, unsent) => { if (msg.id.fromMe && !unsent) window.onMessageMediaUploadedEvent(window.WWebJS.getMessageModel(msg)); }); window.Store.Msg.on('remove', (msg) => { if (msg.isNewMsg) window.onRemoveMessageEvent(window.WWebJS.getMessageModel(msg)); }); + window.Store.Msg.on('change:body', (msg, newBody, prevBody) => { window.onEditMessageEvent(window.WWebJS.getMessageModel(msg), newBody, prevBody); }); window.Store.AppState.on('change:state', (_AppState, state) => { window.onAppStateChangedEvent(state); }); window.Store.Conn.on('change:battery', (state) => { window.onBatteryStateChangedEvent(state); }); window.Store.Call.on('add', (call) => { window.onIncomingCall(call); }); @@ -732,7 +748,7 @@ class Client extends EventEmitter { * @property {string[]} [stickerCategories=undefined] - Sets the categories of the sticker, (if sendMediaAsSticker is true). Provide emoji char array, can be null. * @property {MessageMedia} [media] - Media to be sent */ - + /** * Send a message to a specific chatId * @param {string} chatId @@ -785,7 +801,7 @@ class Client extends EventEmitter { internalOptions.list = content; content = ''; } - + if (internalOptions.sendMediaAsSticker && internalOptions.attachment) { internalOptions.attachment = await Util.formatToWebpSticker( internalOptions.attachment, { @@ -811,7 +827,7 @@ class Client extends EventEmitter { return new Message(this, newMessage); } - + /** * Searches for messages * @param {string} query diff --git a/src/structures/Message.js b/src/structures/Message.js index a3b3117..9c9e051 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -7,6 +7,7 @@ const Order = require('./Order'); const Payment = require('./Payment'); const Reaction = require('./Reaction'); const {MessageTypes} = require('../util/Constants'); +const {Contact} = require('./Contact'); /** * Represents a Message on WhatsApp @@ -224,6 +225,16 @@ class Message extends Base { this.productId = data.productId; } + /** Last edit time */ + if (data.latestEditSenderTimestampMs) { + this.latestEditSenderTimestampMs = data.latestEditSenderTimestampMs; + } + + /** Last edit message author */ + if (data.latestEditMsgKey) { + this.latestEditMsgKey = data.latestEditMsgKey; + } + /** * Links included in the message. * @type {Array<{link: string, isSuspicious: boolean}>} @@ -576,6 +587,42 @@ class Message extends Base { return reaction; }); } + + /** + * Edits the current message. + * @param {string} content + * @param {MessageEditOptions} [options] - Options used when editing the message + * @returns {Promise} + */ + async edit(content, options = {}) { + if (options.mentions && options.mentions.some(possiblyContact => possiblyContact instanceof Contact)) { + options.mentions = options.mentions.map(a => a.id._serialized); + } + let internalOptions = { + linkPreview: options.linkPreview === false ? undefined : true, + mentionedJidList: Array.isArray(options.mentions) ? options.mentions : [], + extraOptions: options.extra + }; + + if (!this.fromMe) { + return null; + } + const messageEdit = await this.client.pupPage.evaluate(async (msgId, message, options) => { + let msg = window.Store.Msg.get(msgId); + if (!msg) return null; + + let catEdit = (msg.type === 'chat' && window.Store.MsgActionChecks.canEditText(msg)); + if (catEdit) { + 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; + } } module.exports = Message; diff --git a/src/util/Constants.js b/src/util/Constants.js index 5a0efc7..f1c2197 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -48,6 +48,7 @@ exports.Events = { MESSAGE_REVOKED_EVERYONE: 'message_revoke_everyone', MESSAGE_REVOKED_ME: 'message_revoke_me', MESSAGE_ACK: 'message_ack', + MESSAGE_EDIT: 'message_edit', UNREAD_COUNT: 'unread_count', MESSAGE_REACTION: 'message_reaction', MEDIA_UPLOADED: 'media_uploaded', diff --git a/src/util/Injected.js b/src/util/Injected.js index 3f5aa96..73c00dd 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -31,6 +31,7 @@ exports.ExposeStore = (moduleRaidStr) => { 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.UploadUtils = window.mR.findModule((module) => (module.default && module.default.encryptAndUpload) ? module.default : null)[0].default; @@ -289,6 +290,40 @@ exports.LoadUtils = () => { 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;