mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-19 21:13:30 +01:00
refactor: Moved the escapeX functions from discord.js to @discord.js/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 2c4c5c23d6 into the refactorization PR
Co-authored-by: space <spaceeec@yahoo.com>
This commit is contained in:
@@ -53,233 +53,6 @@ function flatten(obj, ...props) {
|
|||||||
return out;
|
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
|
* @typedef {Object} FetchRecommendedShardCountOptions
|
||||||
* @property {number} [guildsPerShard=1000] Number of guilds assigned per shard
|
* @property {number} [guildsPerShard=1000] Number of guilds assigned per shard
|
||||||
@@ -600,18 +373,6 @@ function parseWebhookURL(url) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
flatten,
|
flatten,
|
||||||
escapeMarkdown,
|
|
||||||
escapeCodeBlock,
|
|
||||||
escapeInlineCode,
|
|
||||||
escapeItalic,
|
|
||||||
escapeBold,
|
|
||||||
escapeUnderline,
|
|
||||||
escapeStrikethrough,
|
|
||||||
escapeSpoiler,
|
|
||||||
escapeHeading,
|
|
||||||
escapeBulletedList,
|
|
||||||
escapeNumberedList,
|
|
||||||
escapeMaskedLink,
|
|
||||||
fetchRecommendedShardCount,
|
fetchRecommendedShardCount,
|
||||||
parseEmoji,
|
parseEmoji,
|
||||||
resolvePartialEmoji,
|
resolvePartialEmoji,
|
||||||
|
|||||||
@@ -1,254 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
/* eslint-env jest */
|
|
||||||
/* eslint-disable max-len */
|
|
||||||
|
|
||||||
const Util = require('../src/util/Util');
|
|
||||||
const testString = "`_Behold!_`\n||___~~***```js\n`use strict`;\nrequire('discord.js');```***~~___||";
|
|
||||||
const testStringForums =
|
|
||||||
'# Title\n## Subtitle\n### Subsubtitle\n- Bullet list\n - # Title with bullet\n * Subbullet\n1. Number list\n 1. Sub number list';
|
|
||||||
|
|
||||||
describe('escapeCodeblock', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeCodeBlock(testString)).toEqual(
|
|
||||||
"`_Behold!_`\n||___~~***\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`***~~___||",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeCodeBlock('```test```')).toEqual('\\`\\`\\`test\\`\\`\\`');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeInlineCode', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeInlineCode(testString)).toEqual(
|
|
||||||
"\\`_Behold!_\\`\n||___~~***```js\n\\`use strict\\`;\nrequire('discord.js');```***~~___||",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeInlineCode('`test`')).toEqual('\\`test\\`');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeBold', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeBold(testString)).toEqual(
|
|
||||||
"`_Behold!_`\n||___~~*\\*\\*```js\n`use strict`;\nrequire('discord.js');```\\*\\**~~___||",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeBold('**test**')).toEqual('\\*\\*test\\*\\*');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeItalic', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeItalic(testString)).toEqual(
|
|
||||||
"`\\_Behold!\\_`\n||\\___~~\\***```js\n`use strict`;\nrequire('discord.js');```**\\*~~__\\_||",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic (_)', () => {
|
|
||||||
expect(Util.escapeItalic('_test_')).toEqual('\\_test\\_');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic (*)', () => {
|
|
||||||
expect(Util.escapeItalic('*test*')).toEqual('\\*test\\*');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('emoji', () => {
|
|
||||||
const testOne = 'This is a test with _emojis_ <:Frost_ed_Wreath:1053399941210443826> and **bold text**.';
|
|
||||||
expect(Util.escapeItalic(testOne)).toEqual(
|
|
||||||
'This is a test with \\_emojis\\_ <:Frost_ed_Wreath:1053399941210443826> and **bold text**.',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeUnderline', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeUnderline(testString)).toEqual(
|
|
||||||
"`_Behold!_`\n||_\\_\\_~~***```js\n`use strict`;\nrequire('discord.js');```***~~\\_\\__||",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeUnderline('__test__')).toEqual('\\_\\_test\\_\\_');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('emoji', () => {
|
|
||||||
const testTwo = 'This is a test with __emojis__ <:Frost__ed__Wreath:1053399939654352978> and **bold text**.';
|
|
||||||
expect(Util.escapeUnderline(testTwo)).toBe(
|
|
||||||
'This is a test with \\_\\_emojis\\_\\_ <:Frost__ed__Wreath:1053399939654352978> and **bold text**.',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeStrikethrough', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeStrikethrough(testString)).toEqual(
|
|
||||||
"`_Behold!_`\n||___\\~\\~***```js\n`use strict`;\nrequire('discord.js');```***\\~\\~___||",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeStrikethrough('~~test~~')).toEqual('\\~\\~test\\~\\~');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeSpoiler', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeSpoiler(testString)).toEqual(
|
|
||||||
"`_Behold!_`\n\\|\\|___~~***```js\n`use strict`;\nrequire('discord.js');```***~~___\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeSpoiler('||test||')).toEqual('\\|\\|test\\|\\|');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeHeading', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeHeading(testStringForums)).toEqual(
|
|
||||||
'\\# Title\n\\## Subtitle\n\\### Subsubtitle\n- Bullet list\n - \\# Title with bullet\n * Subbullet\n1. Number list\n 1. Sub number list',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeHeading('# test')).toEqual('\\# test');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeBulletedList', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeBulletedList(testStringForums)).toEqual(
|
|
||||||
'# Title\n## Subtitle\n### Subsubtitle\n\\- Bullet list\n \\- # Title with bullet\n \\* Subbullet\n1. Number list\n 1. Sub number list',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeBulletedList('- test')).toEqual('\\- test');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeNumberedList', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeNumberedList(testStringForums)).toEqual(
|
|
||||||
'# Title\n## Subtitle\n### Subsubtitle\n- Bullet list\n - # Title with bullet\n * Subbullet\n1\\. Number list\n 1\\. Sub number list',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeNumberedList('1. test')).toEqual('1\\. test');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeMaskedLink', () => {
|
|
||||||
test('basic', () => {
|
|
||||||
expect(Util.escapeMaskedLink('[test](https://discord.js.org)')).toEqual('\\[test](https://discord.js.org)');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('escapeMarkdown', () => {
|
|
||||||
test('shared', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString)).toEqual(
|
|
||||||
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no codeBlock', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { codeBlock: false })).toEqual(
|
|
||||||
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*```js\n\\`use strict\\`;\nrequire('discord.js');```\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no inlineCode', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { inlineCode: false })).toEqual(
|
|
||||||
"`\\_Behold!\\_`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no bold', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { bold: false })).toEqual(
|
|
||||||
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\***\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`**\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no italic', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { italic: false })).toEqual(
|
|
||||||
"\\`_Behold!_\\`\n\\|\\|_\\_\\_\\~\\~*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\**\\~\\~\\_\\__\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no underline', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { underline: false })).toEqual(
|
|
||||||
"\\`\\_Behold!\\_\\`\n\\|\\|\\___\\~\\~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~__\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no strikethrough', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { strikethrough: false })).toEqual(
|
|
||||||
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_~~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*~~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no spoiler', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { spoiler: false })).toEqual(
|
|
||||||
"\\`\\_Behold!\\_\\`\n||\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_||",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('code content', () => {
|
|
||||||
test('no code block content', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { codeBlockContent: false })).toEqual(
|
|
||||||
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('no inline code content', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { inlineCodeContent: false })).toEqual(
|
|
||||||
"\\`_Behold!_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('neither inline code or code block content', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { inlineCodeContent: false, codeBlockContent: false }))
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
.toEqual(
|
|
||||||
"\\`_Behold!_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('neither code blocks or code block content', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { codeBlock: false, codeBlockContent: false })).toEqual(
|
|
||||||
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*```js\n`use strict`;\nrequire('discord.js');```\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('neither inline code or inline code content', () => {
|
|
||||||
expect(Util.escapeMarkdown(testString, { inlineCode: false, inlineCodeContent: false })).toEqual(
|
|
||||||
"`_Behold!_`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('edge-case odd number of fences with no code block content', () => {
|
|
||||||
expect(
|
|
||||||
Util.escapeMarkdown('**foo** ```**bar**``` **fizz** ``` **buzz**', {
|
|
||||||
codeBlock: false,
|
|
||||||
codeBlockContent: false,
|
|
||||||
}),
|
|
||||||
).toEqual('\\*\\*foo\\*\\* ```**bar**``` \\*\\*fizz\\*\\* ``` \\*\\*buzz\\*\\*');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('edge-case odd number of backticks with no inline code content', () => {
|
|
||||||
expect(
|
|
||||||
Util.escapeMarkdown('**foo** `**bar**` **fizz** ` **buzz**', { inlineCode: false, inlineCodeContent: false }),
|
|
||||||
).toEqual('\\*\\*foo\\*\\* `**bar**` \\*\\*fizz\\*\\* ` \\*\\*buzz\\*\\*');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/* eslint-enable max-len, no-undef */
|
|
||||||
13
packages/discord.js/typings/index.d.ts
vendored
13
packages/discord.js/typings/index.d.ts
vendored
@@ -3051,19 +3051,6 @@ export function cleanContent(str: string, channel: TextBasedChannel): string;
|
|||||||
export function discordSort<K, V extends { rawPosition: number; id: Snowflake }>(
|
export function discordSort<K, V extends { rawPosition: number; id: Snowflake }>(
|
||||||
collection: Collection<K, V>,
|
collection: Collection<K, V>,
|
||||||
): Collection<K, V>;
|
): Collection<K, V>;
|
||||||
export function escapeMarkdown(text: string, options?: EscapeMarkdownOptions): string;
|
|
||||||
export function escapeCodeBlock(text: string): string;
|
|
||||||
export function escapeInlineCode(text: string): string;
|
|
||||||
export function escapeBold(text: string): string;
|
|
||||||
export function escapeItalic(text: string): string;
|
|
||||||
export function escapeUnderline(text: string): string;
|
|
||||||
export function escapeStrikethrough(text: string): string;
|
|
||||||
export function escapeSpoiler(text: string): string;
|
|
||||||
export function escapeEscape(text: string): string;
|
|
||||||
export function escapeHeading(text: string): string;
|
|
||||||
export function escapeBulletedList(text: string): string;
|
|
||||||
export function escapeNumberedList(text: string): string;
|
|
||||||
export function escapeMaskedLink(text: string): string;
|
|
||||||
export function cleanCodeBlockContent(text: string): string;
|
export function cleanCodeBlockContent(text: string): string;
|
||||||
export function fetchRecommendedShardCount(token: string, options?: FetchRecommendedShardCountOptions): Promise<number>;
|
export function fetchRecommendedShardCount(token: string, options?: FetchRecommendedShardCountOptions): Promise<number>;
|
||||||
export function flatten(obj: unknown, ...props: Record<string, boolean | string>[]): unknown;
|
export function flatten(obj: unknown, ...props: Record<string, boolean | string>[]): unknown;
|
||||||
|
|||||||
264
packages/formatters/__tests__/escapers.test.ts
Normal file
264
packages/formatters/__tests__/escapers.test.ts
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
import { describe, test, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
escapeCodeBlock,
|
||||||
|
escapeInlineCode,
|
||||||
|
escapeItalic,
|
||||||
|
escapeBold,
|
||||||
|
escapeUnderline,
|
||||||
|
escapeStrikethrough,
|
||||||
|
escapeMaskedLink,
|
||||||
|
escapeSpoiler,
|
||||||
|
escapeHeading,
|
||||||
|
escapeBulletedList,
|
||||||
|
escapeNumberedList,
|
||||||
|
escapeMarkdown,
|
||||||
|
} from '../src/index.js';
|
||||||
|
|
||||||
|
const testString = "`_Behold!_`\n||___~~***```js\n`use strict`;\nrequire('discord.js');```***~~___||";
|
||||||
|
const testStringForums =
|
||||||
|
'# Title\n## Subtitle\n### Subsubtitle\n- Bullet list\n - # Title with bullet\n * Subbullet\n1. Number list\n 1. Sub number list';
|
||||||
|
|
||||||
|
describe('Markdown escapers', () => {
|
||||||
|
describe('escapeCodeblock', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeCodeBlock(testString)).toEqual(
|
||||||
|
"`_Behold!_`\n||___~~***\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`***~~___||",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeCodeBlock('```test```')).toEqual('\\`\\`\\`test\\`\\`\\`');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeInlineCode', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeInlineCode(testString)).toEqual(
|
||||||
|
"\\`_Behold!_\\`\n||___~~***```js\n\\`use strict\\`;\nrequire('discord.js');```***~~___||",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeInlineCode('`test`')).toEqual('\\`test\\`');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeBold', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeBold(testString)).toEqual(
|
||||||
|
"`_Behold!_`\n||___~~*\\*\\*```js\n`use strict`;\nrequire('discord.js');```\\*\\**~~___||",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeBold('**test**')).toEqual('\\*\\*test\\*\\*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeItalic', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeItalic(testString)).toEqual(
|
||||||
|
"`\\_Behold!\\_`\n||\\___~~\\***```js\n`use strict`;\nrequire('discord.js');```**\\*~~__\\_||",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic (_)', () => {
|
||||||
|
expect(escapeItalic('_test_')).toEqual('\\_test\\_');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic (*)', () => {
|
||||||
|
expect(escapeItalic('*test*')).toEqual('\\*test\\*');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emoji', () => {
|
||||||
|
const testOne = 'This is a test with _emojis_ <:Frost_ed_Wreath:1053399941210443826> and **bold text**.';
|
||||||
|
expect(escapeItalic(testOne)).toEqual(
|
||||||
|
'This is a test with \\_emojis\\_ <:Frost_ed_Wreath:1053399941210443826> and **bold text**.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeUnderline', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeUnderline(testString)).toEqual(
|
||||||
|
"`_Behold!_`\n||_\\_\\_~~***```js\n`use strict`;\nrequire('discord.js');```***~~\\_\\__||",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeUnderline('__test__')).toEqual('\\_\\_test\\_\\_');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emoji', () => {
|
||||||
|
const testTwo = 'This is a test with __emojis__ <:Frost__ed__Wreath:1053399939654352978> and **bold text**.';
|
||||||
|
expect(escapeUnderline(testTwo)).toBe(
|
||||||
|
'This is a test with \\_\\_emojis\\_\\_ <:Frost__ed__Wreath:1053399939654352978> and **bold text**.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeStrikethrough', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeStrikethrough(testString)).toEqual(
|
||||||
|
"`_Behold!_`\n||___\\~\\~***```js\n`use strict`;\nrequire('discord.js');```***\\~\\~___||",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeStrikethrough('~~test~~')).toEqual('\\~\\~test\\~\\~');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeSpoiler', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeSpoiler(testString)).toEqual(
|
||||||
|
"`_Behold!_`\n\\|\\|___~~***```js\n`use strict`;\nrequire('discord.js');```***~~___\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeSpoiler('||test||')).toEqual('\\|\\|test\\|\\|');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeHeading', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeHeading(testStringForums)).toEqual(
|
||||||
|
'\\# Title\n\\## Subtitle\n\\### Subsubtitle\n- Bullet list\n - \\# Title with bullet\n * Subbullet\n1. Number list\n 1. Sub number list',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeHeading('# test')).toEqual('\\# test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeBulletedList', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeBulletedList(testStringForums)).toEqual(
|
||||||
|
'# Title\n## Subtitle\n### Subsubtitle\n\\- Bullet list\n \\- # Title with bullet\n \\* Subbullet\n1. Number list\n 1. Sub number list',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeBulletedList('- test')).toEqual('\\- test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeNumberedList', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeNumberedList(testStringForums)).toEqual(
|
||||||
|
'# Title\n## Subtitle\n### Subsubtitle\n- Bullet list\n - # Title with bullet\n * Subbullet\n1\\. Number list\n 1\\. Sub number list',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeNumberedList('1. test')).toEqual('1\\. test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeMaskedLink', () => {
|
||||||
|
test('basic', () => {
|
||||||
|
expect(escapeMaskedLink('[test](https://discord.js.org)')).toEqual('\\[test](https://discord.js.org)');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('escapeMarkdown', () => {
|
||||||
|
test('shared', () => {
|
||||||
|
expect(escapeMarkdown(testString)).toEqual(
|
||||||
|
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no codeBlock', () => {
|
||||||
|
expect(escapeMarkdown(testString, { codeBlock: false })).toEqual(
|
||||||
|
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*```js\n\\`use strict\\`;\nrequire('discord.js');```\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no inlineCode', () => {
|
||||||
|
expect(escapeMarkdown(testString, { inlineCode: false })).toEqual(
|
||||||
|
"`\\_Behold!\\_`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no bold', () => {
|
||||||
|
expect(escapeMarkdown(testString, { bold: false })).toEqual(
|
||||||
|
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\***\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`**\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no italic', () => {
|
||||||
|
expect(escapeMarkdown(testString, { italic: false })).toEqual(
|
||||||
|
"\\`_Behold!_\\`\n\\|\\|_\\_\\_\\~\\~*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\**\\~\\~\\_\\__\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no underline', () => {
|
||||||
|
expect(escapeMarkdown(testString, { underline: false })).toEqual(
|
||||||
|
"\\`\\_Behold!\\_\\`\n\\|\\|\\___\\~\\~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~__\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no strikethrough', () => {
|
||||||
|
expect(escapeMarkdown(testString, { strikethrough: false })).toEqual(
|
||||||
|
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_~~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*~~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no spoiler', () => {
|
||||||
|
expect(escapeMarkdown(testString, { spoiler: false })).toEqual(
|
||||||
|
"\\`\\_Behold!\\_\\`\n||\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_||",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('code content', () => {
|
||||||
|
test('no code block content', () => {
|
||||||
|
expect(escapeMarkdown(testString, { codeBlockContent: false })).toEqual(
|
||||||
|
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no inline code content', () => {
|
||||||
|
expect(escapeMarkdown(testString, { inlineCodeContent: false })).toEqual(
|
||||||
|
"\\`_Behold!_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n\\`use strict\\`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('neither inline code or code block content', () => {
|
||||||
|
expect(escapeMarkdown(testString, { inlineCodeContent: false, codeBlockContent: false }))
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
.toEqual(
|
||||||
|
"\\`_Behold!_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('neither code blocks or code block content', () => {
|
||||||
|
expect(escapeMarkdown(testString, { codeBlock: false, codeBlockContent: false })).toEqual(
|
||||||
|
"\\`\\_Behold!\\_\\`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*```js\n`use strict`;\nrequire('discord.js');```\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('neither inline code or inline code content', () => {
|
||||||
|
expect(escapeMarkdown(testString, { inlineCode: false, inlineCodeContent: false })).toEqual(
|
||||||
|
"`_Behold!_`\n\\|\\|\\_\\_\\_\\~\\~\\*\\*\\*\\`\\`\\`js\n`use strict`;\nrequire('discord.js');\\`\\`\\`\\*\\*\\*\\~\\~\\_\\_\\_\\|\\|",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('edge-case odd number of fences with no code block content', () => {
|
||||||
|
expect(
|
||||||
|
escapeMarkdown('**foo** ```**bar**``` **fizz** ``` **buzz**', {
|
||||||
|
codeBlock: false,
|
||||||
|
codeBlockContent: false,
|
||||||
|
}),
|
||||||
|
).toEqual('\\*\\*foo\\*\\* ```**bar**``` \\*\\*fizz\\*\\* ``` \\*\\*buzz\\*\\*');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('edge-case odd number of backticks with no inline code content', () => {
|
||||||
|
expect(
|
||||||
|
escapeMarkdown('**foo** `**bar**` **fizz** ` **buzz**', { inlineCode: false, inlineCodeContent: false }),
|
||||||
|
).toEqual('\\*\\*foo\\*\\* `**bar**` \\*\\*fizz\\*\\* ` \\*\\*buzz\\*\\*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
310
packages/formatters/src/escapers.ts
Normal file
310
packages/formatters/src/escapers.ts
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
/* eslint-disable prefer-named-capture-group */
|
||||||
|
|
||||||
|
export interface EscapeMarkdownOptions {
|
||||||
|
/**
|
||||||
|
* Whether to escape bolds
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
bold?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape bulleted lists
|
||||||
|
*
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
bulletedList?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape code blocks
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
codeBlock?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape text inside code blocks
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
codeBlockContent?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape escape characters
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
escape?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape headings
|
||||||
|
*
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
heading?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape inline code
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
inlineCode?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape text inside inline code
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
inlineCodeContent?: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to escape italics
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
italic?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape masked links
|
||||||
|
*
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
maskedLink?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape numbered lists
|
||||||
|
*
|
||||||
|
* @defaultValue false
|
||||||
|
*/
|
||||||
|
numberedList?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape spoilers
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
spoiler?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape strikethroughs
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
strikethrough?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to escape underlines
|
||||||
|
*
|
||||||
|
* @defaultValue true
|
||||||
|
*/
|
||||||
|
underline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes any Discord-flavour markdown in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
* @param options - Options for escaping the markdown
|
||||||
|
*/
|
||||||
|
export function escapeMarkdown(text: string, options: EscapeMarkdownOptions = {}): string {
|
||||||
|
const {
|
||||||
|
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,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
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 ? '\\`' : '`');
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = text;
|
||||||
|
if (escape) res = escapeEscape(res);
|
||||||
|
if (inlineCode) res = escapeInlineCode(res);
|
||||||
|
if (codeBlock) res = escapeCodeBlock(res);
|
||||||
|
if (italic) res = escapeItalic(res);
|
||||||
|
if (bold) res = escapeBold(res);
|
||||||
|
if (underline) res = escapeUnderline(res);
|
||||||
|
if (strikethrough) res = escapeStrikethrough(res);
|
||||||
|
if (spoiler) res = escapeSpoiler(res);
|
||||||
|
if (heading) res = escapeHeading(res);
|
||||||
|
if (bulletedList) res = escapeBulletedList(res);
|
||||||
|
if (numberedList) res = escapeNumberedList(res);
|
||||||
|
if (maskedLink) res = escapeMaskedLink(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes code block markdown in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeCodeBlock(text: string): string {
|
||||||
|
return text.replaceAll('```', '\\`\\`\\`');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes inline code markdown in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeInlineCode(text: string): string {
|
||||||
|
return text.replaceAll(/(?<=^|[^`])``?(?=[^`]|$)/g, (match) => (match.length === 2 ? '\\`\\`' : '\\`'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes italic markdown in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeItalic(text: string): string {
|
||||||
|
let idx = 0;
|
||||||
|
const newText = text.replaceAll(/(?<=^|[^*])\*([^*]|\*\*|$)/g, (_, match) => {
|
||||||
|
if (match === '**') return ++idx % 2 ? `\\*${match}` : `${match}\\*`;
|
||||||
|
return `\\*${match}`;
|
||||||
|
});
|
||||||
|
idx = 0;
|
||||||
|
return newText.replaceAll(/(?<=^|[^_])(?<!<a?:.+)_(?!:\d+>)([^_]|__|$)/g, (_, match) => {
|
||||||
|
if (match === '__') return ++idx % 2 ? `\\_${match}` : `${match}\\_`;
|
||||||
|
return `\\_${match}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes bold markdown in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeBold(text: string): string {
|
||||||
|
let idx = 0;
|
||||||
|
return text.replaceAll(/\*\*(\*)?/g, (_, match) => {
|
||||||
|
if (match) return ++idx % 2 ? `${match}\\*\\*` : `\\*\\*${match}`;
|
||||||
|
return '\\*\\*';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes underline markdown in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeUnderline(text: string): string {
|
||||||
|
let idx = 0;
|
||||||
|
return text.replaceAll(/(?<!<a?:.+)__(_)?(?!:\d+>)/g, (_, match) => {
|
||||||
|
if (match) return ++idx % 2 ? `${match}\\_\\_` : `\\_\\_${match}`;
|
||||||
|
return '\\_\\_';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes strikethrough markdown in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeStrikethrough(text: string): string {
|
||||||
|
return text.replaceAll('~~', '\\~\\~');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes spoiler markdown in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeSpoiler(text: string): string {
|
||||||
|
return text.replaceAll('||', '\\|\\|');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes escape characters in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeEscape(text: string): string {
|
||||||
|
return text.replaceAll('\\', '\\\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes heading characters in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeHeading(text: string): string {
|
||||||
|
return text.replaceAll(/^( {0,2})([*-] )?( *)(#{1,3} )/gm, '$1$2$3\\$4');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes bulleted list characters in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeBulletedList(text: string): string {
|
||||||
|
return text.replaceAll(/^( *)([*-])( +)/gm, '$1\\$2$3');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes numbered list characters in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeNumberedList(text: string): string {
|
||||||
|
return text.replaceAll(/^( *\d+)\./gm, '$1\\.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes masked link characters in a string.
|
||||||
|
*
|
||||||
|
* @param text - Content to escape
|
||||||
|
*/
|
||||||
|
export function escapeMaskedLink(text: string): string {
|
||||||
|
return text.replaceAll(/\[.+]\(.+\)/gm, '\\$&');
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
export * from './escapers.js';
|
||||||
export * from './formatters.js';
|
export * from './formatters.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user