From 9c8aaf1bbcc220299e65cadb6ac54e1a7f208576 Mon Sep 17 00:00:00 2001 From: Papa Date: Sat, 29 Feb 2020 06:20:39 -0700 Subject: [PATCH] feat: reimplement disableEveryone into disableMentions * User input sanitation: reimplement disableEveryone into disableMentions * Change default value of ClientOptions#disableMentions to 'none' * Update type declarations of disableMentions to include default * update for compliance with ESLint * Overlooked these files. Updated for complete compliance with ESLint --- src/client/Client.js | 4 +- src/structures/APIMessage.js | 10 ++++- src/structures/Webhook.js | 4 +- src/structures/interfaces/TextBasedChannel.js | 4 +- src/util/Constants.js | 5 ++- src/util/Util.js | 43 ++++++++++++------- test/disableMentions.js | 4 +- typings/index.d.ts | 6 +-- 8 files changed, 51 insertions(+), 29 deletions(-) diff --git a/src/client/Client.js b/src/client/Client.js index e2abbfa77..a23e5a691 100644 --- a/src/client/Client.js +++ b/src/client/Client.js @@ -386,8 +386,8 @@ class Client extends BaseClient { if (typeof options.fetchAllMembers !== 'boolean') { throw new TypeError('CLIENT_INVALID_OPTION', 'fetchAllMembers', 'a boolean'); } - if (typeof options.disableMentions !== 'boolean') { - throw new TypeError('CLIENT_INVALID_OPTION', 'disableMentions', 'a boolean'); + if (typeof options.disableMentions !== 'string') { + throw new TypeError('CLIENT_INVALID_OPTION', 'disableMentions', 'a string'); } if (!Array.isArray(options.partials)) { throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array'); diff --git a/src/structures/APIMessage.js b/src/structures/APIMessage.js index 3a52a4608..385640a89 100644 --- a/src/structures/APIMessage.js +++ b/src/structures/APIMessage.js @@ -91,8 +91,16 @@ class APIMessage { const disableMentions = typeof this.options.disableMentions === 'undefined' ? this.target.client.options.disableMentions : this.options.disableMentions; - if (disableMentions) { + if (disableMentions === 'all') { content = Util.removeMentions(content || ''); + } else if (disableMentions === 'everyone') { + content = (content || '').replace(/@([^<>@ ]*)/gsmu, (match, target) => { + if (target.match(/^[&!]?\d+$/)) { + return `@${target}`; + } else { + return `@\u200b${target}`; + } + }); } const isSplit = typeof this.options.split !== 'undefined' && this.options.split !== false; diff --git a/src/structures/Webhook.js b/src/structures/Webhook.js index c76045994..6a5be72bf 100644 --- a/src/structures/Webhook.js +++ b/src/structures/Webhook.js @@ -85,8 +85,8 @@ class Webhook { * @property {string} [nonce=''] The nonce for the message * @property {Object[]} [embeds] An array of embeds for the message * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) - * @property {boolean} [disableMentions=this.client.options.disableMentions] Whether or not a zero width space - * should be placed after every @ character to prevent unexpected mentions + * @property {'none' | 'all' | 'everyone'} [disableMentions=this.client.options.disableMentions] Whether or not + * all mentions or everyone/here mentions should be sanitized to prevent unexpected mentions * @property {FileOptions[]|string[]} [files] Files to send with the message * @property {string|boolean} [code] Language for optional codeblock formatting to apply * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if diff --git a/src/structures/interfaces/TextBasedChannel.js b/src/structures/interfaces/TextBasedChannel.js index 38a9dcb31..b2abf50af 100644 --- a/src/structures/interfaces/TextBasedChannel.js +++ b/src/structures/interfaces/TextBasedChannel.js @@ -57,8 +57,8 @@ class TextBasedChannel { * @property {string} [content=''] The content for the message * @property {MessageEmbed|Object} [embed] An embed for the message * (see [here](https://discordapp.com/developers/docs/resources/channel#embed-object) for more details) - * @property {boolean} [disableMentions=this.client.options.disableMentions] Whether or not a zero width space - * should be placed after every @ character to prevent unexpected mentions + * @property {'none' | 'all' | 'everyone'} [disableMentions=this.client.options.disableMentions] Whether or not + * all mentionsor everyone/here mentions should be sanitized to prevent unexpected mentions * @property {FileOptions[]|BufferResolvable[]} [files] Files to send with the message * @property {string|boolean} [code] Language for optional codeblock formatting to apply * @property {boolean|SplitOptions} [split=false] Whether or not the message should be split into multiple messages if diff --git a/src/util/Constants.js b/src/util/Constants.js index 547ffde1d..604a13f9e 100644 --- a/src/util/Constants.js +++ b/src/util/Constants.js @@ -21,7 +21,8 @@ const browser = exports.browser = typeof window !== 'undefined'; * the message cache lifetime (in seconds, 0 for never) * @property {boolean} [fetchAllMembers=false] Whether to cache all guild members and users upon startup, as well as * upon joining a guild (should be avoided whenever possible) - * @property {boolean} [disableMentions=false] Default value for {@link MessageOptions#disableMentions} + * @property {'none' | 'all' | 'everyone'} [disableMentions='none'] Default value + * for {@link MessageOptions#disableMentions} * @property {PartialType[]} [partials] Structures allowed to be partial. This means events can be emitted even when * they're missing all the data for a particular structure. See the "Partials" topic listed in the sidebar for some * important usage information, as partials require you to put checks in place when handling data. @@ -47,7 +48,7 @@ exports.DefaultOptions = { messageCacheLifetime: 0, messageSweepInterval: 0, fetchAllMembers: false, - disableMentions: false, + disableMentions: 'none', partials: [], restWsBridgeTimeout: 5000, disabledEvents: [], diff --git a/src/util/Util.js b/src/util/Util.js index bc2f0f811..a7c2a23d1 100644 --- a/src/util/Util.js +++ b/src/util/Util.js @@ -534,22 +534,30 @@ class Util { * @returns {string} */ static cleanContent(str, message) { - return Util.removeMentions(str - .replace(/<@!?[0-9]+>/g, input => { - const id = input.replace(/<|!|>|@/g, ''); - if (message.channel.type === 'dm') { - const user = message.client.users.cache.get(id); - return user ? `@${user.username}` : input; - } - - const member = message.channel.guild.members.cache.get(id); - if (member) { - return `@${member.displayName}`; + if (message.client.options.disableMentions === 'everyone') { + str = str.replace(/@([^<>@ ]*)/gsmu, (match, target) => { + if (target.match(/^[&!]?\d+$/)) { + return `@${target}`; } else { - const user = message.client.users.cache.get(id); - return user ? `@${user.username}` : input; + return `@\u200b${target}`; } - }) + }); + } + str = str.replace(/<@!?[0-9]+>/g, input => { + const id = input.replace(/<|!|>|@/g, ''); + if (message.channel.type === 'dm') { + const user = message.client.users.cache.get(id); + return user ? `@${user.username}` : input; + } + + const member = message.channel.guild.members.cache.get(id); + if (member) { + return `@${member.displayName}`; + } else { + const user = message.client.users.cache.get(id); + return user ? `@${user.username}` : input; + } + }) .replace(/<#[0-9]+>/g, input => { const channel = message.client.channels.cache.get(input.replace(/<|#|>/g, '')); return channel ? `#${channel.name}` : input; @@ -558,7 +566,12 @@ class Util { if (message.channel.type === 'dm') return input; const role = message.guild.roles.cache.get(input.replace(/<|@|>|&/g, '')); return role ? `@${role.name}` : input; - })); + }); + if (message.client.options.disableMentions === 'all') { + return Util.removeMentions(str); + } else { + return str; + } } /** diff --git a/test/disableMentions.js b/test/disableMentions.js index 281633d6e..f227a0b49 100644 --- a/test/disableMentions.js +++ b/test/disableMentions.js @@ -6,7 +6,7 @@ const client = new Discord.Client({ // To see a difference, comment out disableMentions and run the same tests using disableEveryone // You will notice that all messages will mention @everyone //disableEveryone: true - disableMentions: true + disableMentions: 'everyone' }); const tests = [ @@ -44,4 +44,4 @@ client.on('message', message => { message.reply(tests[2]); }); -client.login(token).catch(console.error); \ No newline at end of file +client.login(token).catch(console.error); diff --git a/typings/index.d.ts b/typings/index.d.ts index 659a1b89c..cac46ed04 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -2062,7 +2062,7 @@ declare module 'discord.js' { messageCacheLifetime?: number; messageSweepInterval?: number; fetchAllMembers?: boolean; - disableMentions?: boolean; + disableMentions?: 'none' | 'all' | 'everyone'; partials?: PartialTypes[]; restWsBridgeTimeout?: number; restTimeOffset?: number; @@ -2514,7 +2514,7 @@ declare module 'discord.js' { nonce?: string; content?: string; embed?: MessageEmbed | MessageEmbedOptions; - disableMentions?: boolean; + disableMentions?: 'none' | 'all' | 'everyone'; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; code?: string | boolean; split?: boolean | SplitOptions; @@ -2763,7 +2763,7 @@ declare module 'discord.js' { tts?: boolean; nonce?: string; embeds?: (MessageEmbed | object)[]; - disableMentions?: boolean; + disableMentions?: 'none' | 'all' | 'everyone'; files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[]; code?: string | boolean; split?: boolean | SplitOptions;