From e0d955f130ee1dcb9fa3e9cc71e3c4158a82d9a7 Mon Sep 17 00:00:00 2001 From: alechkos <93551621+alechkos@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:33:10 +0200 Subject: [PATCH] feat: group mentions + improved user mentions (#2602) * added logic to mention groups + fixed user mentions * usage example updated * docs fixed * window.injectToFunction clarifications * usage example clarifications * fix: typo * docs: added missed docs --- example.js | 54 ++++++++++++++++++++++++++++++++++----- index.d.ts | 24 ++++++++++++++--- src/Client.js | 25 ++++++++++++++---- src/structures/Message.js | 52 ++++++++++++++++++++++++++++++------- src/util/Injected.js | 36 +++++++++++++++++++++++--- 5 files changed, 165 insertions(+), 26 deletions(-) diff --git a/example.js b/example.js index e922579..dcefe32 100644 --- a/example.js +++ b/example.js @@ -240,12 +240,52 @@ client.on('message', async msg => { const newStatus = msg.body.split(' ')[1]; await client.setStatus(newStatus); 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') { if (msg.hasQuotedMsg) { const quotedMsg = await msg.getQuotedMessage(); @@ -288,7 +328,9 @@ client.on('message', async msg => { let button = new Buttons('Button body', [{ body: 'bt1' }, { body: 'bt2' }, { body: 'bt3' }], 'title', 'footer'); client.sendMessage(msg.from, button); } 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'); client.sendMessage(msg.from, list); } else if (msg.body === '!reaction') { @@ -323,7 +365,7 @@ client.on('message', async msg => { await chat.changeLabels([0, 1]); } else if (msg.body === '!addlabels') { const chat = await msg.getChat(); - let labels = (await chat.getLabels()).map(l => l.id); + let labels = (await chat.getLabels()).map((l) => l.id); labels.push('0'); labels.push('1'); await chat.changeLabels(labels); diff --git a/index.d.ts b/index.d.ts index 864ec63..e50c3db 100644 --- a/index.d.ts +++ b/index.d.ts @@ -857,7 +857,16 @@ declare namespace WAWebJS { /** MediaKey that represents the sticker 'ID' */ mediaKey?: string, /** Indicates the mentions in the message body. */ - mentionedIds: [], + mentionedIds: ChatId[], + /** Indicates whether there are group mentions in the message body */ + groupMentions: { + groupSubject: string; + groupJid: { + server: string; + user: string; + _serialized: string; + }; + }[], /** Unix timestamp for when the message was created */ timestamp: number, /** @@ -917,6 +926,8 @@ declare namespace WAWebJS { getContact: () => Promise, /** Returns the Contacts mentioned in this message */ getMentions: () => Promise, + /** Returns groups mentioned in this message */ + getGroupMentions: () => Promise, /** Returns the quoted message, if any */ getQuotedMessage: () => Promise, /** @@ -1039,8 +1050,15 @@ declare namespace WAWebJS { caption?: string /** Id of the message that is being quoted (or replied to) */ quotedMessageId?: string - /** Contacts that are being mentioned in the message */ - mentions?: Contact[] + /** User IDs to mention in the message */ + mentions?: string[] + /** An array of object that handle group mentions */ + groupMentions?: { + /** The name of a group to mention (can be custom) */ + subject: string, + /** The group ID, e.g.: 'XXXXXXXXXX@g.us' */ + id: string + }[] /** Send 'seen' status */ sendSeen?: boolean /** Media to be sent */ diff --git a/src/Client.js b/src/Client.js index fb3b9b8..522d79a 100644 --- a/src/Client.js +++ b/src/Client.js @@ -810,6 +810,13 @@ class Client extends EventEmitter { return result; } + /** + * An object representing mentions of groups + * @typedef {Object} GroupMention + * @property {string} subject - The name of a group to mention (can be custom) + * @property {string} id - The group ID, e.g.: 'XXXXXXXXXX@g.us' + */ + /** * Message options. * @typedef {Object} MessageSendOptions @@ -822,7 +829,8 @@ class Client extends EventEmitter { * @property {boolean} [parseVCards=true] - Automatically parse vCards and send them as contacts * @property {string} [caption] - Image or video caption * @property {string} [quotedMessageId] - Id of the message that is being quoted (or replied to) - * @property {Contact[]} [mentions] - Contacts that are being mentioned in the message + * @property {GroupMention[]} [groupMentions] - An array of object that handle group mentions + * @property {string[]} [mentions] - User IDs to mention in the message * @property {boolean} [sendSeen=true] - Mark the conversation as seen after sending the message * @property {string} [stickerAuthor=undefined] - Sets the author of the sticker, (if sendMediaAsSticker is true). * @property {string} [stickerName=undefined] - Sets the name of the sticker, (if sendMediaAsSticker is true). @@ -839,10 +847,16 @@ class Client extends EventEmitter { * @returns {Promise} Message that was just sent */ async sendMessage(chatId, content, options = {}) { - if (options.mentions && 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); + 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, sendAudioAsVoice: options.sendAudioAsVoice, @@ -852,7 +866,8 @@ class Client extends EventEmitter { caption: options.caption, quotedMessageId: options.quotedMessageId, parseVCards: options.parseVCards === false ? false : true, - mentionedJidList: Array.isArray(options.mentions) ? options.mentions : [], + mentionedJidList: options.mentions || [], + groupMentions: options.groupMentions, extraOptions: options.extra }; diff --git a/src/structures/Message.js b/src/structures/Message.js index ba5c7c4..a761699 100644 --- a/src/structures/Message.js +++ b/src/structures/Message.js @@ -187,14 +187,32 @@ class Message extends Base { } : undefined; /** - * Indicates the mentions in the message body. - * @type {Array} + * @typedef {Object} Mention + * @property {string} server + * @property {string} user + * @property {string} _serialized */ - this.mentionedIds = []; - if (data.mentionedJidList) { - this.mentionedIds = data.mentionedJidList; - } + /** + * Indicates the mentions in the message body. + * @type {Mention[]} + */ + this.mentionedIds = data.mentionedJidList || []; + + /** + * @typedef {Object} GroupMention + * @property {string} groupSubject The name of the group + * @property {Object} groupJid The group ID + * @property {string} groupJid.server + * @property {string} groupJid.user + * @property {string} groupJid._serialized + */ + + /** + * Indicates whether there are group mentions in the message body + * @type {GroupMention[]} + */ + this.groupMentions = data.groupMentions || []; /** * Order ID for message type ORDER @@ -341,6 +359,14 @@ class Message extends Base { async getMentions() { return await Promise.all(this.mentionedIds.map(async m => await this.client.getContactById(m))); } + + /** + * Returns groups mentioned in this message + * @returns {Promise} + */ + async getGroupMentions() { + return await Promise.all(this.groupMentions.map(async (m) => await this.client.getChatById(m.groupJid._serialized))); + } /** * Returns the quoted message, if any @@ -649,12 +675,20 @@ class Message extends Base { * @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); + 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: Array.isArray(options.mentions) ? options.mentions : [], + mentionedJidList: options.mentions || [], + groupMentions: options.groupMentions, extraOptions: options.extra }; diff --git a/src/util/Injected.js b/src/util/Injected.js index eeb49f5..dd32a9d 100644 --- a/src/util/Injected.js +++ b/src/util/Injected.js @@ -58,12 +58,12 @@ 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.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]; - window.Store.getMsgInfo = (window.mR.findModule('sendQueryMsgInfo')[0] || {}).sendQueryMsgInfo || window.mR.findModule('queryMsgInfo')[0].queryMsgInfo; /* eslint-enable no-undef, no-cond-assign */ window.Store.Settings = { @@ -195,7 +195,22 @@ exports.LoadUtils = () => { } if (options.mentionedJidList) { - options.mentionedJidList = options.mentionedJidList.map(cId => window.Store.Contact.get(cId).id); + 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 = {}; @@ -368,7 +383,22 @@ exports.LoadUtils = () => { delete options.extraOptions; if (options.mentionedJidList) { - options.mentionedJidList = options.mentionedJidList.map(cId => window.Store.Contact.get(cId).id); + 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) {