Procházet zdrojové kódy

Add /inline slash command

Markus-Rost před 4 roky
rodič
revize
541d2160ef

+ 3 - 3
README.md

@@ -1,11 +1,11 @@
 # Wiki-Bot[<img src="https://translate.wikibot.de/widgets/wiki-bot/-/svg-badge.svg" alt="Translation status" align="right" />](#translations)[<img src="https://github.com/Markus-Rost/discord-wiki-bot/workflows/Node.js CI/badge.svg" alt="Node.js CI" align="right" />](https://github.com/Markus-Rost/discord-wiki-bot/actions)
-[<img src="/dashboard/src/icon.png" alt="Wiki-Bot" align="right" />](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot)
+[<img src="/dashboard/src/icon.png" alt="Wiki-Bot" align="right" />](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot%20applications.commands)
 
 **Wiki-Bot** is a bot for [Discord](https://discord.com/) with the purpose to easily link and search [MediaWiki](https://www.mediawiki.org/wiki/MediaWiki) sites like [Gamepedia](https://www.gamepedia.com/) and [Fandom](https://www.fandom.com/) wikis. **Wiki-Bot** shows short descriptions and additional info about pages and is able to resolve redirects and follow interwiki links.
 
 **Wiki-Bot** has translations for English, Bengali, German, French, Hindi, Dutch, Polish, Portuguese, Russian, Turkish and Chinese.
 
-[Use this link to invite **Wiki-Bot** to your Discord server.](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot)
+[Use this link to invite **Wiki-Bot** to your Discord server.](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot%20applications.commands)
 
 [Change the server settings for **Wiki-Bot** using the dashboard.](https://settings.wikibot.de/)
 
@@ -20,7 +20,7 @@ Support server: [https://discord.gg/v77RTk5](https://discord.gg/v77RTk5)
 * [Voice Channel](#voice-channel)
 
 ## Setup
-After [inviting](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot) **Wiki-Bot** to your server you need to set the wiki you want to search by default. You do this with the `!wiki settings` command.
+After [inviting](https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot%20applications.commands) **Wiki-Bot** to your server you need to set the wiki you want to search by default. You do this with the `!wiki settings` command.
 * Change the wiki with `!wiki settings wiki <url>`
   * Example: `!wiki settings wiki https://minecraft.gamepedia.com/Minecraft_Wiki`
 * Change the language with `!wiki settings lang <language>`

+ 90 - 0
bot.js

@@ -205,6 +205,96 @@ String.prototype.hasPrefix = function(prefix, flags = '') {
 	return regex.test(this.replace( /\u200b/g, '' ).toLowerCase());
 };
 
+const Wiki = require('./util/wiki.js');
+const slash_inline = require('./interactions/inline.js');
+const fs = require('fs');
+var slash = {};
+fs.readdir( './interactions', (error, files) => {
+	if ( error ) return error;
+	files.filter( file => file.endsWith('.js') ).forEach( file => {
+		var command = require('./interactions/' + file);
+		slash[command.name] = command.run;
+	} );
+} );
+/*
+!test eval got.post(`https://discord.com/api/v8/applications/${msg.client.user.id}/commands`, {
+	headers:{Authorization: `Bot ${process.env.token}`},
+	json: require('../interactions/commands.json')[0]
+}).then(response=>console.log(response.statusCode,response.body))
+*/
+client.on( 'raw', rawEvent => {
+	if ( rawEvent.t !== 'INTERACTION_CREATE' ) return;
+	var interaction = rawEvent.d;
+	if ( interaction.version !== 1 || interaction.type !== 2 ) return;
+	interaction.application_id = client.user.id;
+	if ( !slash.hasOwnProperty(interaction.data.name) ) {
+		consol.log( '- Slash: Unknown command: ' + interaction.data.name );
+		return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
+			json: {
+				//type: 4,
+				type: 3,
+				data: {
+					content: '[<:error:440871715938238494> Unknown Command! <:error:440871715938238494>](<' + process.env.invite + '>)',
+					allowed_mentions: {
+						parse: []
+					},
+					flags: 64
+				}
+			}
+		} ).then( response => {
+			if ( response.statusCode !== 204 ) {
+				console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
+			}
+		}, log_error );
+	}
+	if ( !interaction.guild_id ) return slash[interaction.data.name](interaction, new Lang(), new Wiki());
+	var guild = client.guilds.cache.get(interaction.guild_id);
+	db.get( 'SELECT wiki, lang, role FROM discord WHERE guild = ? AND (channel = ? OR channel = ? OR channel IS NULL) ORDER BY channel DESC', [interaction.guild_id, interaction.channel_id, '#' + guild?.channels.cache.get(interaction.channel_id)?.parentID], (dberror, row) => {
+		if ( dberror ) {
+			console.log( '- Error while getting the wiki: ' + dberror );
+			return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
+				json: {
+					//type: 4,
+					type: 3,
+					data: {
+						content: '[<:error:440871715938238494> Error! <:error:440871715938238494>](<' + process.env.invite + '>)',
+						allowed_mentions: {
+							parse: []
+						},
+						flags: 64
+					}
+				}
+			} ).then( response => {
+				if ( response.statusCode !== 204 ) {
+					console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
+				}
+			}, log_error );
+		}
+		var lang = new Lang(row.lang || defaultSettings.lang);
+		if ( row.role && !interaction.member.roles.includes( row.role ) && guild?.roles.cache.has(row.role) && ( !interaction.member.roles.length || !interaction.member.roles.some( role => guild.roles.cache.get(role)?.comparePositionTo(row.role) >= 0 ) ) ) {
+			return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
+				json: {
+					//type: 4,
+					type: 3,
+					data: {
+						content: lang.get('interaction.missingrole', '<@&' + row.role + '>'),
+						allowed_mentions: {
+							parse: []
+						},
+						flags: 64
+					}
+				}
+			} ).then( response => {
+				if ( response.statusCode !== 204 ) {
+					console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
+				}
+			}, log_error );
+		}
+		var wiki = new Wiki(row.wiki || defaultSettings.wiki);
+		return slash[interaction.data.name](interaction, lang, wiki, guild);
+	} );
+} );
+
 client.on( 'message', msg => {
 	if ( isStop || msg.type !== 'DEFAULT' || msg.system || msg.webhookID || msg.author.bot || msg.author.id === msg.client.user.id ) return;
 	if ( !msg.content.hasPrefix(( msg.channel.isGuild() && patreons[msg.guild.id] || process.env.prefix ), 'm') ) {

+ 1 - 1
cmds/invite.js

@@ -15,7 +15,7 @@ function cmd_invite(lang, msg, args, line, wiki) {
 		msg.client.generateInvite({
 			permissions: defaultPermissions
 		}).then( invite => {
-			msg.sendChannel( lang.get('invite.bot') + '\n<' + invite + '>' );
+			msg.sendChannel( lang.get('invite.bot') + '\n<' + invite + '%20applications.commands' + '>' );
 		}, log_error );
 	}
 }

+ 1 - 1
cmds/patreon.js

@@ -21,7 +21,7 @@ function cmd_patreon(lang, msg, args, line, wiki) {
 			permissions: defaultPermissions,
 			guild: args[1]
 		}).then( invite => {
-			msg.replyMsg( 'I\'m not on a server with the id `' + args[1] + '`.\n<' + invite + '>', {}, true )
+			msg.replyMsg( 'I\'m not on a server with the id `' + args[1] + '`.\n<' + invite + '%20applications.commands' + '>', {}, true )
 		}, log_error );
 		if ( patreons[args[1]] ) return msg.replyMsg( '"' + guild + '" has the patreon features already enabled.', {}, true );
 		db.get( 'SELECT count, COUNT(guild) guilds FROM patreons LEFT JOIN discord ON discord.patreon = patreons.patreon WHERE patreons.patreon = ? GROUP BY patreons.patreon', [msg.author.id], (dberror, row) => {

+ 2 - 2
dashboard/guilds.js

@@ -61,7 +61,7 @@ function dashboard_guilds(res, dashboardLang, state, reqURL, action, actionArgs)
 	$('#logout img').attr('src', settings.user.avatar);
 	$('#logout span').text(`${settings.user.username} #${settings.user.discriminator}`);
 	$('.guild#invite a').attr('href', oauth.generateAuthUrl( {
-		scope: ['identify', 'guilds', 'bot'],
+		scope: ['identify', 'guilds', 'bot', 'applications.commands'],
 		permissions: defaultPermissions, state
 	} ));
 	$('.guild#refresh a').attr('href', '/refresh?return=' + reqURL.pathname);
@@ -118,7 +118,7 @@ function dashboard_guilds(res, dashboardLang, state, reqURL, action, actionArgs)
 		$('head title').text(`${guild.name} – ` + $('head title').text());
 		res.setHeader('Set-Cookie', [`guild="${guild.id}/settings"; HttpOnly; Path=/`]);
 		let url = oauth.generateAuthUrl( {
-			scope: ['identify', 'guilds', 'bot'],
+			scope: ['identify', 'guilds', 'bot', 'applications.commands'],
 			permissions: defaultPermissions,
 			guildId: guild.id, state
 		} );

+ 1 - 1
dashboard/index.html

@@ -51,7 +51,7 @@
 		<div id="guildlist">
 			<div class="guild" id="invite">
 				<div class="bar"></div>
-				<a href="https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot" alt="Invite Wiki-Bot">
+				<a href="https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot%20applications.commands" alt="Invite Wiki-Bot">
 					<div class="avatar svg-avatar">
 						<svg width="24" height="24" viewBox="0 0 24 24">
 							<path fill="currentColor" d="M20 11.1111H12.8889V4H11.1111V11.1111H4V12.8889H11.1111V20H12.8889V12.8889H20V11.1111Z"></path>

+ 2 - 2
dashboard/login.html

@@ -41,7 +41,7 @@
 				<img src="/src/settings.svg" alt="Settings">
 				<div>Login</div>
 			</a>
-			<a class="channel" id="invite-wikibot" href="https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot">
+			<a class="channel" id="invite-wikibot" href="https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot%20applications.commands">
 				<img src="/src/channel.svg" alt="Channel">
 				<div>Invite Wiki-Bot</div>
 			</a>
@@ -57,7 +57,7 @@
 		<div id="guildlist">
 			<div class="guild" id="invite">
 				<div class="bar"></div>
-				<a href="https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot" alt="Invite Wiki-Bot">
+				<a href="https://discord.com/oauth2/authorize?client_id=461189216198590464&permissions=939904064&scope=bot%20applications.commands" alt="Invite Wiki-Bot">
 					<div class="avatar svg-avatar">
 						<svg width="24" height="24" viewBox="0 0 24 24">
 							<path fill="currentColor" d="M20 11.1111H12.8889V4H11.1111V11.1111H4V12.8889H11.1111V20H12.8889V12.8889H20V11.1111Z"></path>

+ 1 - 1
dashboard/oauth.js

@@ -55,7 +55,7 @@ function dashboard_login(res, dashboardLang, state, action) {
 		state = crypto.randomBytes(16).toString("hex");
 	}
 	let invite = oauth.generateAuthUrl( {
-		scope: ['identify', 'guilds', 'bot'],
+		scope: ['identify', 'guilds', 'bot', 'applications.commands'],
 		permissions: defaultPermissions, state
 	} );
 	$('.guild#invite a, .channel#invite-wikibot').attr('href', invite);

+ 1 - 0
dashboard/util.js

@@ -244,6 +244,7 @@ const permissions = {
 	EMBED_LINKS: 1 << 14,
 	ATTACH_FILES: 1 << 15,
 	READ_MESSAGE_HISTORY: 1 << 16,
+	MENTION_EVERYONE: 1 << 17,
 	USE_EXTERNAL_EMOJIS: 1 << 18,
 	MANAGE_NICKNAMES: 1 << 27,
 	MANAGE_ROLES: 1 << 28,

+ 1 - 1
functions/parse_page.js

@@ -69,7 +69,7 @@ const removeClassesExceptions = [
 
 /**
  * Parses a wiki page to get it's description.
- * @param {import('../../util/i18n.js')} lang - The user language.
+ * @param {import('../util/i18n.js')} lang - The user language.
  * @param {import('discord.js').Message} msg - The Discord message.
  * @param {String} content - The content for the message.
  * @param {import('discord.js').MessageEmbed} embed - The embed for the message.

+ 4 - 0
i18n/en.json

@@ -390,6 +390,10 @@
         "noadmin": "you need the `Manage Server` permission for these commands!",
         "pause": "**I'm currently paused on this server!**\nOnly these commands can be performed:"
     },
+    "interaction": {
+        "inline": "Please provide some text with [[wikitext]] links to use this command.",
+        "missingrole": "You need to have the $1 role or any higher one to use this command."
+    },
     "invite": {
         "bot": "Use this link to invite me to another server:"
     },

+ 14 - 0
interactions/commands.json

@@ -0,0 +1,14 @@
+[
+	{
+		"name": "inline",
+		"description": "Post a message with inline wiki links.",
+		"options": [
+			{
+				"type": 3,
+				"name": "text",
+				"description": "Text including wikitext links.",
+				"required": true
+			}
+		]
+	}
+]

+ 246 - 0
interactions/inline.js

@@ -0,0 +1,246 @@
+const logging = require('../util/logging.js');
+const Wiki = require('../util/wiki.js');
+const {limitLength, partialURIdecode} = require('../util/functions.js');
+
+/**
+ * Post a message with inline wiki links.
+ * @param {Object} interaction - The interaction.
+ * @param {import('../util/i18n.js')} lang - The user language.
+ * @param {import('../util/wiki.js')} wiki - The wiki for the interaction.
+ * @param {import('discord.js').Guild} [guild] - The guild for the interaction.
+ */
+function slash_inline(interaction, lang, wiki, guild) {
+	var text = ( interaction.data.options?.[0]?.value || '' ).replace( /\]\(/g, ']\\(' ).trim();
+	console.log( ( interaction.guild_id || '@' + interaction.user.id ) + ': Slash: ' + text );
+	logging(wiki, interaction.guild_id, 'slash', 'inline');
+	if ( !text ) {
+		return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
+			json: {
+				//type: 4,
+				type: 3,
+				data: {
+					content: lang.get('interaction.inline'),
+					allowed_mentions: {
+						parse: []
+					},
+					flags: 64
+				}
+			}
+		} ).then( response => {
+			if ( response.statusCode !== 204 ) {
+				console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
+			}
+		}, log_error );
+	}
+	var allowed_mentions = {
+		parse: ['users']
+	};
+	if ( interaction.guild_id ) {
+		if ( ( (interaction.member.permissions & 1 << 3) === 1 << 3 ) // ADMINISTRATOR
+		|| ( (interaction.member.permissions & 1 << 17) === 1 << 17 ) ) { // MENTION_EVERYONE
+			allowed_mentions.parse = ['users', 'roles', 'everyone'];
+		}
+		else if ( guild ) {
+			allowed_mentions.roles = guild.roles.cache.filter( role => role.mentionable ).map( role => role.id );
+			if ( allowed_mentions.roles.length > 100 ) {
+				allowed_mentions.roles = allowed_mentions.roles.slice(0, 100);
+			}
+		}
+	}
+	if ( text.length > 1800 ) text = text.substring(0, 1800) + '\u2026';
+	return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
+		json: {
+			type: 4,
+			data: {
+				content: text,
+				allowed_mentions,
+				flags: 0
+			}
+		}
+	} ).then( response => {
+		if ( response.statusCode !== 204 ) {
+			console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
+			return;
+		}
+		if ( !text.includes( '{{' ) && !( text.includes( '[[' ) && text.includes( ']]' ) ) ) return;
+		var textReplacement = [];
+		var replacedText = text.replace( /\u200b/g, '' ).replace( /(?<!\\)(?:<a?(:\w+:)\d+>|```.+?```|`.+?`)/gs, (replacement, arg) => {
+			textReplacement.push(replacement);
+			return '\u200b<replacement' + ( arg ? '\u200b' + textReplacement.length + '\u200b' + arg : '' ) + '>\u200b';
+		} );
+		var templates = [];
+		var links = [];
+		var breakInline = false;
+		replacedText.replace( /\u200b<replacement\u200b\d+\u200b(.+?)>\u200b/g, '$1' ).replace( /(?:%[\dA-F]{2})+/g, partialURIdecode ).split('\n').forEach( line => {
+			if ( line.startsWith( '>>> ' ) ) breakInline = true;
+			if ( line.startsWith( '> ' ) || breakInline ) return;
+			var inlineLink = null;
+			var regex = /(?<!\\|\{)\{\{(?:\s*(?:subst|safesubst|raw|msg|msgnw):)?([^<>\[\]\|\{\}\x01-\x1F\x7F#]+)(?<!\\)(?:\||\}\})/g;
+			while ( ( inlineLink = regex.exec(line) ) !== null ) {
+				let title = inlineLink[1].trim();
+				if ( !title.replace( /:/g, '' ).trim().length || title.startsWith( '/' ) ) continue;
+				if ( title.startsWith( 'int:' ) ) templates.push({
+					raw: title,
+					title: title.replace( /^int:/, 'MediaWiki:' ),
+					template: title.replace( /^int:/, 'MediaWiki:' )
+				});
+				else templates.push({raw: title, title, template: 'Template:' + title});
+			}
+			inlineLink = null;
+			regex = /(?<!\\)\[\[([^<>\[\]\|\{\}\x01-\x1F\x7F]+)(?:\|(?:(?!\[\[).)*?)?(?<!\\)\]\]/g;
+			while ( ( inlineLink = regex.exec(line) ) !== null ) {
+				inlineLink[1] = inlineLink[1].trim();
+				let title = inlineLink[1].split('#')[0].trim();
+				let section = inlineLink[1].split('#').slice(1).join('#');
+				if ( !title.replace( /:/g, '' ).trim().length || title.startsWith( '/' ) ) continue;
+				links.push({raw: title, title, section});
+			}
+		} );
+		if ( !templates.length && !links.length ) return;
+		return got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&iwurl=true&titles=' + encodeURIComponent( [
+			...templates.map( link => link.title + '|' + link.template ),
+			...links.map( link => link.title )
+		].join('|') ) + '&format=json' ).then( response => {
+			var body = response.body;
+			if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query ) {
+				if ( wiki.noWiki(response.url, response.statusCode) ) {
+					console.log( '- This wiki doesn\'t exist!' );
+				}
+				else {
+					console.log( '- ' + response.statusCode + ': Error while following the links: ' + body?.error?.info );
+				}
+				return;
+			}
+			wiki.updateWiki(body.query.general);
+			if ( body.query.normalized ) {
+				body.query.normalized.forEach( title => {
+					templates.filter( link => link.title === title.from ).forEach( link => link.title = title.to );
+					templates.filter( link => link.template === title.from ).forEach( link => link.template = title.to );
+					links.filter( link => link.title === title.from ).forEach( link => link.title = title.to );
+				} );
+			}
+			if ( body.query.interwiki ) {
+				body.query.interwiki.forEach( interwiki => {
+					templates.filter( link => link.title === interwiki.title ).forEach( link => {
+						link.url = interwiki.url;
+					} );
+					links.filter( link => link.title === interwiki.title ).forEach( link => {
+						link.url = ( link.section ? interwiki.url.split('#')[0] + Wiki.toSection(link.section) : interwiki.url );
+					} );
+				} );
+			}
+			if ( body.query.pages ) {
+				Object.values(body.query.pages).forEach( page => {
+					templates.filter( link => link.title === page.title ).forEach( link => {
+						if ( page.invalid !== undefined || ( page.missing !== undefined && page.known === undefined ) ) {
+							link.title = '';
+						}
+						else if ( page.ns === 0 && !link.raw.startsWith( ':' ) ) {
+							link.title = '';
+						}
+					} );
+					templates.filter( link => link.template === page.title ).forEach( link => {
+						if ( page.invalid !== undefined || ( page.missing !== undefined && page.known === undefined ) ) {
+							link.template = '';
+						}
+					} );
+					links.filter( link => link.title === page.title ).forEach( link => {
+						link.ns = page.ns;
+						if ( page.invalid !== undefined ) return links.splice(links.indexOf(link), 1);
+						if ( page.missing !== undefined && page.known === undefined ) {
+							if ( ( page.ns === 2 || page.ns === 202 ) && !page.title.includes( '/' ) ) {
+								return;
+							}
+							if ( wiki.isMiraheze() && page.ns === 0 && /^Mh:[a-z\d]+:/.test(page.title) ) {
+								var iw_parts = page.title.split(':');
+								var iw = new Wiki('https://' + iw_parts[1] + '.miraheze.org/w/');
+								link.url = iw.toLink(iw_parts.slice(2).join(':'), '', link.section, true);
+								return;
+							}
+							return links.splice(links.indexOf(link), 1);
+						}
+					} );
+				} );
+			}
+			templates = templates.filter( link => link.title || link.template );
+			if ( templates.length || links.length ) {
+				breakInline = false;
+				replacedText = replacedText.split('\n').map( line => {
+					if ( line.startsWith( '>>> ' ) ) breakInline = true;
+					if ( line.startsWith( '> ' ) || breakInline ) return line;
+					let emojiReplacements = 1;
+					let regex = /(?<!\\|\{)(\{\{(?:\s*(?:subst|safesubst|raw|msg|msgnw):)?\s*)((?:[^<>\[\]\|\{\}\x01-\x1F\x7F#]|\u200b<replacement\u200b\d+\u200b.+?>\u200b)+?)(\s*(?<!\\)\||\}\})/g;
+					line = line.replace( regex, (fullLink, linkprefix, title, linktrail) => {
+						title = title.replace( /(?:%[\dA-F]{2})+/g, partialURIdecode );
+						let rawTitle = title.replace( /\u200b<replacement\u200b\d+\u200b(.+?)>\u200b/g, '$1' ).trim();
+						let link = templates.find( link => link.raw === rawTitle );
+						if ( !link ) return fullLink;
+						title = title.replace( /\u200b<replacement\u200b(\d+)\u200b(.+?)>\u200b/g, (replacement, id, arg) => {
+							links.splice(id - emojiReplacements, 1);
+							emojiReplacements++;
+							return arg;
+						} );
+						if ( title.startsWith( 'int:' ) ) {
+							title = title.replace( /^int:\s*/, replacement => {
+								linkprefix += replacement;
+								return '';
+							} );
+						}
+						return linkprefix + '[' + title + '](<' + ( link.url || wiki.toLink(link.title || link.template, '', '', true) ) + '>)' + linktrail;
+					} );
+					regex = new RegExp( '([' + body.query.general.linkprefixcharset.replace( /\\x([a-fA-f0-9]{4,6}|\{[a-fA-f0-9]{4,6}\})/g, '\\u$1' ) + ']+)?' + '(?<!\\\\)\\[\\[' + '((?:[^' + "<>\\[\\]\\|\{\}\\x01-\\x1F\\x7F" + ']|' + '\\u200b<replacement\\u200b\\d+\\u200b.+?>\\u200b' + ')+)' + '(?:\\|((?:(?!\\[\\[|\\]\\().)*?))?' + '(?<!\\\\)\\]\\]' + body.query.general.linktrail.replace( /\\x([a-fA-f0-9]{4,6}|\{[a-fA-f0-9]{4,6}\})/g, '\\u$1' ).replace( /^\/\^(\(\[.+?\]\+\))\(\.\*\)\$\/sDu?$/, '$1?' ), 'gu' );
+					line = line.replace( regex, (fullLink, linkprefix = '', title, display, linktrail = '') => {
+						title = title.replace( /(?:%[\dA-F]{2})+/g, partialURIdecode );
+						let rawTitle = title.replace( /\u200b<replacement\u200b\d+\u200b(.+?)>\u200b/g, '$1' ).split('#')[0].trim();
+						let link = links.find( link => link.raw === rawTitle );
+						if ( !link ) return fullLink;
+						title = title.replace( /\u200b<replacement\u200b(\d+)\u200b(.+?)>\u200b/g, (replacement, id, arg) => {
+							links.splice(id - emojiReplacements, 1);
+							emojiReplacements++;
+							return arg;
+						} );
+						if ( display === undefined ) display = title.replace( /^\s*:?/, '' );
+						if ( !display.trim() ) {
+							display = title.replace( /^\s*:/, '' );
+							if ( display.includes( ',' ) && !/ ([^\(\)]+)$/.test(display) ) {
+								display = display.replace( /^([^,]+), .*$/, '$1' );
+							}
+							display = display.replace( / ([^\(\)]+)$/, '' );
+							if ( link.url || link.ns  !== 0 ) {
+								display = display.split(':').slice(1).join(':');
+							}
+						}
+						return '[' + ( linkprefix + display + linktrail ).replace( /\[\]\(\)/g, '\\$&' ) + '](<' + ( link.url || wiki.toLink(link.title, '', link.section, true) ) + '>)';
+					} );
+					return line;
+				} ).join('\n');
+				text = replacedText.replace( /\u200b<replacement(?:\u200b\d+\u200b.+?)?>\u200b/g, replacement => {
+					return textReplacement.shift();
+				} );
+				if ( text.length > 1900 ) text = limitLength(text, 1900, 100);
+				return got.patch( `https://discord.com/api/v8/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`, {
+					json: {
+						content: text,
+						allowed_mentions
+					}
+				} ).then( response => {
+					if ( response.statusCode !== 200 ) {
+						console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
+					}
+				}, log_error );
+			}
+		}, error => {
+			if ( wiki.noWiki(error.message) ) {
+				console.log( '- This wiki doesn\'t exist!' );
+			}
+			else {
+				console.log( '- Error while following the links: ' + error );
+			}
+		} );
+	}, log_error );
+}
+
+module.exports = {
+	name: 'inline',
+	run: slash_inline
+};