feat: replace disableEveryone with disableMentions (#3830)

* add ClientOptions#disableMentions and MessageOptions#disableMentions

* provide tests

* don't sanitize controlled mentions

* add @here mentions to tests

* fix indents (6 spaces instead of 8)

* add Util#cleanContent tests

* add typings for removeMentions

* replace @ with @\u200b AFTER cleaning content as suggested instead of using removeMentions

* better explanation of this option

* no newline in Util.removeMentions

* fix long line

* remove double space

* remove comments (change has been reverted)

* Use Util.removeMentions to remove mentions

* use Util.removeMentions in Util.cleanContent
This commit is contained in:
Timo
2020-02-26 12:13:23 +01:00
committed by GitHub
parent c4bda746c8
commit 9cb306c823
8 changed files with 77 additions and 21 deletions

View File

@@ -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.disableEveryone !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'disableEveryone', 'a boolean');
if (typeof options.disableMentions !== 'boolean') {
throw new TypeError('CLIENT_INVALID_OPTION', 'disableMentions', 'a boolean');
}
if (!Array.isArray(options.partials)) {
throw new TypeError('CLIENT_INVALID_OPTION', 'partials', 'an Array');

View File

@@ -88,6 +88,13 @@ class APIMessage {
content = Util.resolveString(this.options.content);
}
const disableMentions = typeof this.options.disableMentions === 'undefined' ?
this.target.client.options.disableMentions :
this.options.disableMentions;
if (disableMentions) {
content = Util.removeMentions(content || '');
}
const isSplit = typeof this.options.split !== 'undefined' && this.options.split !== false;
const isCode = typeof this.options.code !== 'undefined' && this.options.code !== false;
const splitOptions = isSplit ? { ...this.options.split } : undefined;
@@ -113,13 +120,6 @@ class APIMessage {
content = `${mentionPart}${content || ''}`;
}
const disableEveryone = typeof this.options.disableEveryone === 'undefined' ?
this.target.client.options.disableEveryone :
this.options.disableEveryone;
if (disableEveryone) {
content = (content || '').replace(/@(everyone|here)/g, '@\u200b$1');
}
if (isSplit) {
content = Util.splitMessage(content || '', splitOptions);
}

View File

@@ -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} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here
* should be replaced with plain-text
* @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 {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

View File

@@ -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} [disableEveryone=this.client.options.disableEveryone] Whether or not @everyone and @here
* should be replaced with plain-text
* @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 {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

View File

@@ -21,7 +21,7 @@ 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} [disableEveryone=false] Default value for {@link MessageOptions#disableEveryone}
* @property {boolean} [disableMentions=false] 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 +47,7 @@ exports.DefaultOptions = {
messageCacheLifetime: 0,
messageSweepInterval: 0,
fetchAllMembers: false,
disableEveryone: false,
disableMentions: false,
partials: [],
restWsBridgeTimeout: 5000,
disabledEvents: [],

View File

@@ -518,6 +518,15 @@ class Util {
return dec;
}
/**
* Breaks user, role and everyone/here mentions by adding a zero width space after every @ character
* @param {string} str The string to sanitize
* @returns {string}
*/
static removeMentions(str) {
return str.replace(/@/g, '@\u200b');
}
/**
* The content to have all mentions replaced by the equivalent text.
* @param {string} str The string to be converted
@@ -525,8 +534,7 @@ class Util {
* @returns {string}
*/
static cleanContent(str, message) {
return str
.replace(/@(everyone|here)/g, '@\u200b$1')
return Util.removeMentions(str
.replace(/<@!?[0-9]+>/g, input => {
const id = input.replace(/<|!|>|@/g, '');
if (message.channel.type === 'dm') {
@@ -550,7 +558,7 @@ class Util {
if (message.channel.type === 'dm') return input;
const role = message.guild.roles.cache.get(input.replace(/<|@|>|&/g, ''));
return role ? `@${role.name}` : input;
});
}));
}
/**

47
test/disableMentions.js Normal file
View File

@@ -0,0 +1,47 @@
const Discord = require('../src');
const { Util } = Discord;
const { token, prefix } = require('./auth');
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
});
const tests = [
// Test 1
// See https://github.com/discordapp/discord-api-docs/issues/1189
'@\u202eeveryone @\u202ehere',
// Test 2
// See https://github.com/discordapp/discord-api-docs/issues/1241
// TL;DR: Characters like \u0300 will only be stripped if more than 299 are present
'\u0300@'.repeat(150) + '@\u0300everyone @\u0300here',
// Test 3
// Normal @everyone/@here mention
'@everyone @here',
];
client.on('ready', () => console.log('Ready!'));
client.on('message', message => {
// Check if message starts with prefix
if (!message.content.startsWith(prefix)) return;
const [command, ...args] = message.content.substr(prefix.length).split(' ');
// Clean content and log each character
console.log(Util.cleanContent(args.join(' '), message).split(''));
if (command === 'test1')
message.reply(tests[0]);
else if (command === 'test2')
message.reply(tests[1]);
else if (command === 'test3')
message.reply(tests[2]);
});
client.login(token).catch(console.error);

7
typings/index.d.ts vendored
View File

@@ -1427,6 +1427,7 @@ declare module 'discord.js' {
public static basename(path: string, ext?: string): string;
public static binaryToID(num: string): Snowflake;
public static cleanContent(str: string, message: Message): string;
public static removeMentions(str: string): string;
public static cloneObject(obj: object): object;
public static convertToBuffer(ab: ArrayBuffer | string): Buffer;
public static delayFor(ms: number): Promise<void>;
@@ -2068,7 +2069,7 @@ declare module 'discord.js' {
messageCacheLifetime?: number;
messageSweepInterval?: number;
fetchAllMembers?: boolean;
disableEveryone?: boolean;
disableMentions?: boolean;
partials?: PartialTypes[];
restWsBridgeTimeout?: number;
restTimeOffset?: number;
@@ -2464,7 +2465,7 @@ declare module 'discord.js' {
nonce?: string;
content?: string;
embed?: MessageEmbed | MessageEmbedOptions;
disableEveryone?: boolean;
disableMentions?: boolean;
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
code?: string | boolean;
split?: boolean | SplitOptions;
@@ -2693,7 +2694,7 @@ declare module 'discord.js' {
tts?: boolean;
nonce?: string;
embeds?: (MessageEmbed | object)[];
disableEveryone?: boolean;
disableMentions?: boolean;
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
code?: string | boolean;
split?: boolean | SplitOptions;