diff --git a/example.js b/example.js index a3ade0e0aa..040e53fe1e 100644 --- a/example.js +++ b/example.js @@ -5,7 +5,7 @@ const client = new Client({ // proxyAuthentication: { username: 'username', password: 'password' }, puppeteer: { // args: ['--proxy-server=proxy-server-that-requires-authentication.example.com'], - headless: false + headless: false, } }); @@ -418,6 +418,20 @@ client.on('message', async msg => { requesterIds: ['number1@c.us', 'number2@c.us'], sleep: null }); + } else if (msg.author) { + /** + * Let's say the message was sent in a group + * and you want to forward it to its author: + */ + + // 1. By default the message will be forwarded with a caption (if provided): + await msg.forward(msg.author); + + // 2. To forward without a caption text: + await msg.forward(msg.author, { withCaption: false }); + + // 3. To forward without a 'Forwarded' tag: + await msg.forward(msg.author, { displayAsForwarded: false }); } else { /** * Pins a message in a chat, a method takes a number in seconds for the message to be pinned. diff --git a/index.d.ts b/index.d.ts index c192b4aa83..d6ba8665e5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -946,10 +946,8 @@ declare namespace WAWebJS { reply: (content: MessageContent, chatId?: string, options?: MessageSendOptions) => Promise, /** React to this message with an emoji*/ react: (reaction: string) => Promise, - /** - * Forwards this message to another chat (that you chatted before, otherwise it will fail) - */ - forward: (chat: Chat | string) => Promise, + /** Forwards this message to another chat */ + forward: (chat: Chat | string, options?: MessageForwardOptions) => Promise, /** Star this message */ star: () => Promise, /** Unstar this message */ @@ -1109,6 +1107,20 @@ declare namespace WAWebJS { stickerCategories?: string[] } + /** Options for forwarding a message */ + export interface MessageForwardOptions { + /** + * Forwards this message with the caption text of the original message if provided + * @default true + */ + withCaption?: boolean + /** + * If false, the forwarded message will be displayed withour a 'Forwarded' tag, + * by default the default WhatsApp behavior is used + */ + displayAsForwarded?: boolean + } + /** Options for editing a message */ export interface MessageEditOptions { /** Show links preview. Has no effect on multi-device accounts. */ diff --git a/src/structures/Message.js b/src/structures/Message.js index b61563af68..e5ac672057 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -423,22 +423,31 @@ class Message extends Base { async acceptGroupV4Invite() { return await this.client.acceptGroupV4Invite(this.inviteV4); } + + /** + * Message forward options + * @typedef {Object} MessageForwardOptions + * @property {boolean} [withCaption=true] Forwards this message with the caption text of the original message if provided + * @property {boolean} [displayAsForwarded] If false, the forwarded message will be displayed withour a 'Forwarded' tag, by default the default WhatsApp behavior is used + */ /** - * Forwards this message to another chat (that you chatted before, otherwise it will fail) - * + * Forwards this message to another chat * @param {string|Chat} chat Chat model or chat ID to which the message will be forwarded - * @returns {Promise} + * @param {?MessageForwardOptions} options Options used when forwarding the message + * @returns {Promise} Returns true if a message was forwarded successfully, false otherwise */ - async forward(chat) { + async forward(chat, options = {}) { const chatId = typeof chat === 'string' ? chat : chat.id._serialized; - 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]); - }, this.id._serialized, chatId); + return await this.client.pupPage.evaluate(async (msgId, chatId, options) => { + const chatWid = window.Store.WidFactory.createWid(chatId); + const chat = await window.Store.Chat.find(chatWid); + const msg = window.Store.Msg.get(msgId); + if (!chat || !msg) return false; + + return await window.WWebJS.forwardMessage(chat, msg, options); + }, this.id._serialized, chatId, options); } /** diff --git a/src/util/Injected.js b/src/util/Injected.js index c0df187fc0..5a72d6b101 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -47,7 +47,6 @@ exports.ExposeStore = (moduleRaidStr) => { 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]; @@ -58,6 +57,7 @@ exports.ExposeStore = (moduleRaidStr) => { 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.MessageFieldGetters = window.mR.findModule('getShouldDisplayAsForwarded')[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; @@ -66,6 +66,10 @@ exports.ExposeStore = (moduleRaidStr) => { window.Store.ReplyUtils = (m = window.mR.findModule('canReplyMsg')).length > 0 && m[0]; /* eslint-enable no-undef, no-cond-assign */ + window.Store.EphemeralFields = { + ...window.mR.findModule('getEphemeralFields')[0], + ...window.mR.findModule('isEphemeralSettingOn')[0] + }; window.Store.Settings = { ...window.mR.findModule('ChatlistPanelState')[0], setPushname: window.mR.findModule((m) => m.setPushname && !m.ChatlistPanelState)[0].setPushname @@ -96,6 +100,11 @@ exports.ExposeStore = (moduleRaidStr) => { ...window.mR.findModule('getMembershipApprovalRequests')[0], ...window.mR.findModule('sendMembershipRequestsActionRPC')[0] }; + window.Store.ForwardMessageUtils = { + ...window.mR.findModule('getAsMms')[0], + ...window.mR.findModule('getForwardedMessageFields')[0], + ...window.mR.findModule('getNewsletterContextForForwardedMsg')[0] + }; if (!window.Store.Chat._find) { window.Store.Chat._find = e => { @@ -376,6 +385,104 @@ exports.LoadUtils = () => { await window.Store.SendMessage.addAndSendMsgToChat(chat, message); return window.Store.Msg.get(newMsgId._serialized); }; + + window.WWebJS.forwardMessage = async (chat, msg, options = {}) => { + if (chat.isUser && chat.contact.isContactBlocked) return false; + const isMediaMsg = Boolean(window.Store.ForwardMessageUtils.getAsMms(msg) && !msg.ctwaContext); + if (isMediaMsg) { + const result = await window.WWebJS.forwardMediaMessage(chat, msg, options); + return result.messageSendResult === 'OK'; + } + const forwardedMsgFields = window.Store.ForwardMessageUtils.getForwardedMessageFields(msg, chat); + const meUser = window.Store.User.getMaybeMeUser(); + let participant; + chat.isGroup && (participant = window.Store.WidFactory.toUserWid(meUser)); + const newId = await window.Store.MsgKey.newId(); + const isMD = window.Store.MDBackend; + const newMsgKey = new window.Store.MsgKey({ + from: meUser, + to: chat.id, + id: newId, + participant: isMD && participant, + selfDir: 'out', + }); + if (msg.ctwaContext) { + forwardedMsgFields.body = msg.ctwaContext.sourceUrl; + forwardedMsgFields.type = 'chat'; + delete forwardedMsgFields.mediaObject; + } + forwardedMsgFields.forwardedNewsletterMessageInfo = window.Store.ForwardMessageUtils.getNewsletterContextForForwardedMsg(msg); + window.Store.EphemeralFields.isEphemeralSettingOn(chat) && (forwardedMsgFields.ephemeralDuration = window.Store.EphemeralFields.getEphemeralSetting(chat)); + forwardedMsgFields.ephemeralSettingTimestamp = window.Store.EphemeralFields.getEphemeralSettingTimestamp(chat); + forwardedMsgFields.disappearingModeInitiator = window.Store.EphemeralFields.getDisappearingModeInitiator(chat); + forwardedMsgFields.disappearingModeTrigger = window.Store.EphemeralFields.getDisappearingModeTrigger(chat); + forwardedMsgFields.disappearingModeInitiatedByMe = window.Store.EphemeralFields.getDisappearingModeInitiatedByMe(chat); + const newMessage = { + ...forwardedMsgFields, + id: newMsgKey, + from: meUser, + t: parseInt(new Date().getTime() / 1000), + to: chat.id, + ack: 0, + participant: undefined, + local: true, + self: 'out', + isNewMsg: true, + star: false, + isForwarded: options.displayAsForwarded ?? window.Store.MessageFieldGetters.getShouldDisplayAsForwarded(msg), + forwardedFromWeb: true, + forwardingScore: msg.getForwardingScoreWhenForwarded(), + multicast: true + }; + const [, result] = await Promise.all(window.Store.SendMessage.addAndSendMsgToChat(chat, newMessage)); + return result.messageSendResult === 'OK'; + }; + + window.WWebJS.forwardMediaMessage = async (chat, msg, options = {}) => { + const { withCaption = true } = options; + const mediaData = msg.mediaData.toJSON(); + mediaData.preview && (mediaData.preview = msg.mediaObject.contentInfo._preview); + mediaData.mediaBlob instanceof window.Store.OpaqueData && mediaData.mediaBlob.retain(); + let placeholderProps = { mimetype: mediaData.mimetype }; + mediaData.isGif && (placeholderProps = { ...placeholderProps, isGif: true }); + mediaData.type === 'ptt' && (mediaData.type = 'audio'); + const productOptions = { + businessOwnerJid: msg.businessOwnerJid, + productId: msg.productId, + currencyCode: msg.currencyCode, + priceAmount1000: msg.priceAmount1000, + salePriceAmount1000: msg.salePriceAmount1000, + retailerId: msg.retailerId, + url: msg.url, + productImageCount: msg.productImageCount, + title: msg.title, + description: msg.description + }; + const isImageOrVideo = mediaData.type === 'image' || mediaData.type === 'video'; + const isProduct = mediaData.type === 'product'; + const caption = (withCaption && (isImageOrVideo || msg.isCaptionByUser)) || isProduct + ? msg.caption + : undefined; + const mediaPrep = new window.Store.MediaPrep.MediaPrep(mediaData.type, Promise.resolve(mediaData)); + return await mediaPrep.sendToChat( + chat, + { + forwardedFromWeb: true, + caption: caption, + mentionedJidList: msg.mentionedJidList, + groupMentions: msg.groupMentions, + footer: isProduct && msg.footer, + addEvenWhilePreparing: true, + placeholderProps: placeholderProps, + isForwarded: options.displayAsForwarded ?? window.Store.MessageFieldGetters.getShouldDisplayAsForwarded(msg), + forwardingScore: msg.getForwardingScoreWhenForwarded(), + multicast: true, + productMsgOptions: productOptions, + isAvatar: msg.isAvatar, + forwardedNewsletterMessageInfo: window.Store.ForwardMessageUtils.getNewsletterContextForForwardedMsg(msg) + } + ); + }; window.WWebJS.editMessage = async (msg, content, options = {}) => {