Skip to content

Commit

Permalink
refactor: Moved the escapeX functions from discord.js to @discord.js/…
Browse files Browse the repository at this point in the history
…formatters (#8957)

* refactor: moved escapeX funcs from discord.js to @discord.js/formatters

- moved escapeX functions from discord.js to @discord.js/formatters
- converted code from JS to TS (including JSDoc and TSDoc)
- made linter happy
- modified the escapeHeading's RegExp to pass the RegExp safety test
- escapeBulletedList now conserves the bullet style (- or *)

* fix: removed useless exports and eslint command

removed useless exports and eslint command

* fix(escapeX): emojis with underlines

porting the fix made in 2c4c5c2 into the refactorization PR

Co-authored-by: space <spaceeec@yahoo.com>
  • Loading branch information
Cl00e9ment and SpaceEEC committed Jan 13, 2023
1 parent b803a9a commit 13ce78a
Show file tree
Hide file tree
Showing 6 changed files with 575 additions and 506 deletions.
239 changes: 0 additions & 239 deletions packages/discord.js/src/util/Util.js
Expand Up @@ -53,233 +53,6 @@ function flatten(obj, ...props) {
return out;
}

/**
* Options used to escape markdown.
* @typedef {Object} EscapeMarkdownOptions
* @property {boolean} [codeBlock=true] Whether to escape code blocks
* @property {boolean} [inlineCode=true] Whether to escape inline code
* @property {boolean} [bold=true] Whether to escape bolds
* @property {boolean} [italic=true] Whether to escape italics
* @property {boolean} [underline=true] Whether to escape underlines
* @property {boolean} [strikethrough=true] Whether to escape strikethroughs
* @property {boolean} [spoiler=true] Whether to escape spoilers
* @property {boolean} [codeBlockContent=true] Whether to escape text inside code blocks
* @property {boolean} [inlineCodeContent=true] Whether to escape text inside inline code
* @property {boolean} [escape=true] Whether to escape escape characters
* @property {boolean} [heading=false] Whether to escape headings
* @property {boolean} [bulletedList=false] Whether to escape bulleted lists
* @property {boolean} [numberedList=false] Whether to escape numbered lists
* @property {boolean} [maskedLink=false] Whether to escape masked links
*/

/**
* Escapes any Discord-flavour markdown in a string.
* @param {string} text Content to escape
* @param {EscapeMarkdownOptions} [options={}] Options for escaping the markdown
* @returns {string}
*/
function escapeMarkdown(
text,
{
codeBlock = true,
inlineCode = true,
bold = true,
italic = true,
underline = true,
strikethrough = true,
spoiler = true,
codeBlockContent = true,
inlineCodeContent = true,
escape = true,
heading = false,
bulletedList = false,
numberedList = false,
maskedLink = false,
} = {},
) {
if (!codeBlockContent) {
return text
.split('```')
.map((subString, index, array) => {
if (index % 2 && index !== array.length - 1) return subString;
return escapeMarkdown(subString, {
inlineCode,
bold,
italic,
underline,
strikethrough,
spoiler,
inlineCodeContent,
escape,
heading,
bulletedList,
numberedList,
maskedLink,
});
})
.join(codeBlock ? '\\`\\`\\`' : '```');
}
if (!inlineCodeContent) {
return text
.split(/(?<=^|[^`])`(?=[^`]|$)/g)
.map((subString, index, array) => {
if (index % 2 && index !== array.length - 1) return subString;
return escapeMarkdown(subString, {
codeBlock,
bold,
italic,
underline,
strikethrough,
spoiler,
escape,
heading,
bulletedList,
numberedList,
maskedLink,
});
})
.join(inlineCode ? '\\`' : '`');
}
if (escape) text = escapeEscape(text);
if (inlineCode) text = escapeInlineCode(text);
if (codeBlock) text = escapeCodeBlock(text);
if (italic) text = escapeItalic(text);
if (bold) text = escapeBold(text);
if (underline) text = escapeUnderline(text);
if (strikethrough) text = escapeStrikethrough(text);
if (spoiler) text = escapeSpoiler(text);
if (heading) text = escapeHeading(text);
if (bulletedList) text = escapeBulletedList(text);
if (numberedList) text = escapeNumberedList(text);
if (maskedLink) text = escapeMaskedLink(text);
return text;
}

/**
* Escapes code block markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeCodeBlock(text) {
return text.replaceAll('```', '\\`\\`\\`');
}

/**
* Escapes inline code markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeInlineCode(text) {
return text.replace(/(?<=^|[^`])``?(?=[^`]|$)/g, match => (match.length === 2 ? '\\`\\`' : '\\`'));
}

/**
* Escapes italic markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeItalic(text) {
let i = 0;
text = text.replace(/(?<=^|[^*])\*([^*]|\*\*|$)/g, (_, match) => {
if (match === '**') return ++i % 2 ? `\\*${match}` : `${match}\\*`;
return `\\*${match}`;
});
i = 0;
return text.replace(/(?<=^|[^_])(?<!<a?:.+)_(?!:\d+>)([^_]|__|$)/g, (_, match) => {
if (match === '__') return ++i % 2 ? `\\_${match}` : `${match}\\_`;
return `\\_${match}`;
});
}

/**
* Escapes bold markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeBold(text) {
let i = 0;
return text.replace(/\*\*(\*)?/g, (_, match) => {
if (match) return ++i % 2 ? `${match}\\*\\*` : `\\*\\*${match}`;
return '\\*\\*';
});
}

/**
* Escapes underline markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeUnderline(text) {
let i = 0;
return text.replace(/(?<!<a?:.+)__(_)?(?!:\d+>)/g, (_, match) => {
if (match) return ++i % 2 ? `${match}\\_\\_` : `\\_\\_${match}`;
return '\\_\\_';
});
}

/**
* Escapes strikethrough markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeStrikethrough(text) {
return text.replaceAll('~~', '\\~\\~');
}

/**
* Escapes spoiler markdown in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeSpoiler(text) {
return text.replaceAll('||', '\\|\\|');
}

/**
* Escapes escape characters in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeEscape(text) {
return text.replaceAll('\\', '\\\\');
}

/**
* Escapes heading characters in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeHeading(text) {
return text.replaceAll(/^( {0,2}[*-] +)?(#{1,3} )/gm, '$1\\$2');
}

/**
* Escapes bulleted list characters in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeBulletedList(text) {
return text.replaceAll(/^( *)[*-]( +)/gm, '$1\\-$2');
}

/**
* Escapes numbered list characters in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeNumberedList(text) {
return text.replaceAll(/^( *\d+)\./gm, '$1\\.');
}

/**
* Escapes masked link characters in a string.
* @param {string} text Content to escape
* @returns {string}
*/
function escapeMaskedLink(text) {
return text.replaceAll(/\[.+\]\(.+\)/gm, '\\$&');
}

/**
* @typedef {Object} FetchRecommendedShardCountOptions
* @property {number} [guildsPerShard=1000] Number of guilds assigned per shard
Expand Down Expand Up @@ -600,18 +373,6 @@ function parseWebhookURL(url) {

module.exports = {
flatten,
escapeMarkdown,
escapeCodeBlock,
escapeInlineCode,
escapeItalic,
escapeBold,
escapeUnderline,
escapeStrikethrough,
escapeSpoiler,
escapeHeading,
escapeBulletedList,
escapeNumberedList,
escapeMaskedLink,
fetchRecommendedShardCount,
parseEmoji,
resolvePartialEmoji,
Expand Down

2 comments on commit 13ce78a

@vercel
Copy link

@vercel vercel bot commented on 13ce78a Jan 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 13ce78a Jan 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.