inline.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. const logging = require('../util/logging.js');
  2. const Wiki = require('../util/wiki.js');
  3. const {limitLength, partialURIdecode, allowDelete} = require('../util/functions.js');
  4. /**
  5. * Post a message with inline wiki links.
  6. * @param {Object} interaction - The interaction.
  7. * @param {import('../util/i18n.js')} lang - The user language.
  8. * @param {import('../util/wiki.js')} wiki - The wiki for the interaction.
  9. * @param {import('discord.js').TextChannel} [channel] - The channel for the interaction.
  10. */
  11. function slash_inline(interaction, lang, wiki, channel) {
  12. var text = ( interaction.data.options?.[0]?.value || '' ).replace( /\]\(/g, ']\\(' );
  13. text = text.replace( /\x1F/g, '' ).replace( /(?<!@)\u200b/g, '' ).trim();
  14. if ( !text ) {
  15. return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
  16. json: {
  17. //type: 4,
  18. type: 3,
  19. data: {
  20. content: lang.get('interaction.inline'),
  21. allowed_mentions: {
  22. parse: []
  23. },
  24. flags: 64
  25. }
  26. }
  27. } ).then( response => {
  28. if ( response.statusCode !== 204 ) {
  29. console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
  30. }
  31. }, log_error );
  32. }
  33. var allowed_mentions = {
  34. parse: ['users']
  35. };
  36. if ( interaction.guild_id ) {
  37. if ( ( (interaction.member.permissions & 1 << 3) === 1 << 3 ) // ADMINISTRATOR
  38. || ( (interaction.member.permissions & 1 << 17) === 1 << 17 ) ) { // MENTION_EVERYONE
  39. allowed_mentions.parse = ['users', 'roles', 'everyone'];
  40. }
  41. else if ( channel?.guild ) {
  42. allowed_mentions.roles = channel.guild.roles.cache.filter( role => role.mentionable ).map( role => role.id ).slice(0, 100);
  43. }
  44. if ( channel?.guild && ( (interaction.member.permissions & 1 << 3) !== 1 << 3 ) // ADMINISTRATOR
  45. && ( (interaction.member.permissions & 1 << 18) !== 1 << 18 ) ) { // USE_EXTERNAL_EMOJIS
  46. text = text.replace( /(?<!\\)<a?(:\w+:)\d+>/g, (replacement, emoji, id) => {
  47. if ( channel.guild.emojis.cache.has(id) ) {
  48. return replacement;
  49. }
  50. return emoji;
  51. } );
  52. }
  53. }
  54. if ( text.length > 1800 ) text = text.substring(0, 1800) + '\u2026';
  55. return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
  56. json: {
  57. type: 4,
  58. data: {
  59. content: text.replace( /(?<!\\)<a?(:\w+:)\d+>/g, (replacement, emoji, id) => {
  60. if ( channel?.guild?.emojis.cache.has(id) ) {
  61. return replacement;
  62. }
  63. return emoji;
  64. } ),
  65. allowed_mentions,
  66. flags: 0
  67. }
  68. }
  69. } ).then( aresponse => {
  70. if ( aresponse.statusCode !== 204 ) {
  71. console.log( '- Slash: ' + aresponse.statusCode + ': Error while sending the response: ' + aresponse.body?.message );
  72. return;
  73. }
  74. if ( !text.includes( '{{' ) && !( text.includes( '[[' ) && text.includes( ']]' ) ) ) {
  75. return got.patch( `https://discord.com/api/v8/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`, {
  76. json: {}
  77. } ).then( presponse => {
  78. if ( presponse.statusCode === 200 && presponse.body?.id ) {
  79. channel?.messages.fetch(presponse.body.id).then( msg => {
  80. allowDelete(msg, ( interaction.member?.user.id || interaction.user.id ));
  81. }, () => {} );
  82. }
  83. }, () => {} );
  84. }
  85. var textReplacement = [];
  86. var replacedText = text.replace( /(?<!\\)(?:<a?(:\w+:)\d+>|<#(\d+)>|<@!?(\d+)>|<@&(\d+)>|```.+?```|``.+?``|`.+?`)/gs, (replacement, emoji, textchannel, user, role) => {
  87. textReplacement.push(replacement);
  88. var arg = '';
  89. if ( emoji ) arg = emoji;
  90. if ( channel ) {
  91. if ( textchannel ) {
  92. let tempchannel = channel.client.channels.cache.get(textchannel);
  93. if ( tempchannel ) arg = '#' + tempchannel.name;
  94. }
  95. if ( user ) {
  96. let tempuser = channel.guild?.members.cache.get(user);
  97. if ( tempuser ) arg = '@' + tempuser.displayName;
  98. else {
  99. tempuser = channel.client.users.cache.get(user);
  100. if ( tempuser ) arg = '@' + tempuser.username;
  101. }
  102. }
  103. if ( role ) {
  104. let temprole = channel.guild?.roles.cache.get(role);
  105. if ( temprole ) arg = '@' + temprole.name;
  106. }
  107. }
  108. return '\x1F<replacement' + ( arg ? '\x1F' + textReplacement.length + '\x1F' + arg : '' ) + '>\x1F';
  109. } );
  110. var templates = [];
  111. var links = [];
  112. var breakInline = false;
  113. replacedText.replace( /\x1F<replacement\x1F\d+\x1F(.+?)>\x1F/g, '$1' ).replace( /(?:%[\dA-F]{2})+/g, partialURIdecode ).split('\n').forEach( line => {
  114. if ( line.startsWith( '>>> ' ) ) breakInline = true;
  115. if ( line.startsWith( '> ' ) || breakInline ) return;
  116. var inlineLink = null;
  117. var regex = /(?<!\\|\{)\{\{(?:\s*(?:subst|safesubst|raw|msg|msgnw):)?([^<>\[\]\|\{\}\x01-\x1F\x7F#]+)(?<!\\)(?:\||\}\})/g;
  118. while ( ( inlineLink = regex.exec(line) ) !== null ) {
  119. let title = inlineLink[1].trim();
  120. if ( !title.replace( /:/g, '' ).trim().length || title.startsWith( '/' ) ) continue;
  121. if ( title.startsWith( 'int:' ) ) templates.push({
  122. raw: title,
  123. title: title.replace( /^int:/, 'MediaWiki:' ),
  124. template: title.replace( /^int:/, 'MediaWiki:' )
  125. });
  126. else templates.push({raw: title, title, template: 'Template:' + title});
  127. }
  128. inlineLink = null;
  129. regex = /(?<!\\)\[\[([^<>\[\]\|\{\}\x01-\x1F\x7F]+)(?:\|(?:(?!\[\[|\]\\\]).)*?)?(?<!\\)\]\]/g;
  130. while ( ( inlineLink = regex.exec(line) ) !== null ) {
  131. inlineLink[1] = inlineLink[1].trim();
  132. let title = inlineLink[1].split('#')[0].trim();
  133. let section = inlineLink[1].split('#').slice(1).join('#');
  134. if ( !title.replace( /:/g, '' ).trim().length || title.startsWith( '/' ) ) continue;
  135. links.push({raw: title, title, section});
  136. }
  137. } );
  138. if ( !templates.length && !links.length ) {
  139. return got.patch( `https://discord.com/api/v8/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`, {
  140. json: {}
  141. } ).then( presponse => {
  142. if ( presponse.statusCode === 200 && presponse.body?.id ) {
  143. channel?.messages.fetch(presponse.body.id).then( msg => {
  144. allowDelete(msg, ( interaction.member?.user.id || interaction.user.id ));
  145. }, () => {} );
  146. }
  147. }, () => {} );
  148. }
  149. return got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&iwurl=true&titles=' + encodeURIComponent( [
  150. ...templates.map( link => link.title + '|' + link.template ),
  151. ...links.map( link => link.title )
  152. ].join('|') ) + '&format=json' ).then( response => {
  153. var body = response.body;
  154. if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query ) {
  155. if ( wiki.noWiki(response.url, response.statusCode) ) {
  156. console.log( '- This wiki doesn\'t exist!' );
  157. }
  158. else {
  159. console.log( '- ' + response.statusCode + ': Error while following the links: ' + body?.error?.info );
  160. }
  161. return got.patch( `https://discord.com/api/v8/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`, {
  162. json: {}
  163. } ).then( presponse => {
  164. if ( presponse.statusCode === 200 && presponse.body?.id ) {
  165. channel?.messages.fetch(presponse.body.id).then( msg => {
  166. allowDelete(msg, ( interaction.member?.user.id || interaction.user.id ));
  167. }, () => {} );
  168. }
  169. }, () => {} );
  170. }
  171. logging(wiki, interaction.guild_id, 'slash', 'inline');
  172. wiki.updateWiki(body.query.general);
  173. if ( body.query.normalized ) {
  174. body.query.normalized.forEach( title => {
  175. templates.filter( link => link.title === title.from ).forEach( link => link.title = title.to );
  176. templates.filter( link => link.template === title.from ).forEach( link => link.template = title.to );
  177. links.filter( link => link.title === title.from ).forEach( link => link.title = title.to );
  178. } );
  179. }
  180. if ( body.query.interwiki ) {
  181. body.query.interwiki.forEach( interwiki => {
  182. templates.filter( link => link.title === interwiki.title ).forEach( link => {
  183. link.url = decodeURI(interwiki.url)
  184. } );
  185. links.filter( link => link.title === interwiki.title ).forEach( link => {
  186. link.url = ( link.section ? decodeURI(interwiki.url.split('#')[0]) + Wiki.toSection(link.section) : decodeURI(interwiki.url) );
  187. } );
  188. } );
  189. }
  190. if ( body.query.pages ) {
  191. Object.values(body.query.pages).forEach( page => {
  192. templates.filter( link => link.title === page.title ).forEach( link => {
  193. if ( page.invalid !== undefined || ( page.missing !== undefined && page.known === undefined ) ) {
  194. link.title = '';
  195. }
  196. else if ( page.ns === 0 && !link.raw.startsWith( ':' ) ) {
  197. link.title = '';
  198. }
  199. } );
  200. templates.filter( link => link.template === page.title ).forEach( link => {
  201. if ( page.invalid !== undefined || ( page.missing !== undefined && page.known === undefined ) ) {
  202. link.template = '';
  203. }
  204. } );
  205. links.filter( link => link.title === page.title ).forEach( link => {
  206. link.ns = page.ns;
  207. if ( page.invalid !== undefined ) return links.splice(links.indexOf(link), 1);
  208. if ( page.missing !== undefined && page.known === undefined ) {
  209. if ( ( page.ns === 2 || page.ns === 202 ) && !page.title.includes( '/' ) ) {
  210. return;
  211. }
  212. if ( wiki.isMiraheze() && page.ns === 0 && /^Mh:[a-z\d]+:/.test(page.title) ) {
  213. var iw_parts = page.title.split(':');
  214. var iw = new Wiki('https://' + iw_parts[1] + '.miraheze.org/w/');
  215. link.url = iw.toLink(iw_parts.slice(2).join(':'), '', link.section, true);
  216. return;
  217. }
  218. return links.splice(links.indexOf(link), 1);
  219. }
  220. } );
  221. } );
  222. }
  223. templates = templates.filter( link => link.title || link.template );
  224. if ( templates.length || links.length ) {
  225. breakInline = false;
  226. replacedText = replacedText.split('\n').map( line => {
  227. if ( line.startsWith( '>>> ' ) ) breakInline = true;
  228. if ( line.startsWith( '> ' ) || breakInline ) return line;
  229. let linkReplacements = 1;
  230. let regex = /(?<!\\|\{)(\{\{(?:\s*(?:subst|safesubst|raw|msg|msgnw):)?\s*)((?:[^<>\[\]\|\{\}\x01-\x1F\x7F#]|\x1F<replacement\x1F\d+\x1F.+?>\x1F)+?)(\s*(?<!\\)\||\}\})/g;
  231. line = line.replace( regex, (fullLink, linkprefix, title, linktrail) => {
  232. title = title.replace( /(?:%[\dA-F]{2})+/g, partialURIdecode );
  233. let rawTitle = title.replace( /\x1F<replacement\x1F\d+\x1F(.+?)>\x1F/g, '$1' ).trim();
  234. let link = templates.find( link => link.raw === rawTitle );
  235. if ( !link ) return fullLink;
  236. console.log( ( interaction.guild_id || '@' + interaction.user.id ) + ': Slash: ' + fullLink );
  237. title = title.replace( /\x1F<replacement\x1F(\d+)\x1F(.+?)>\x1F/g, (replacement, id, arg) => {
  238. links.splice(id - linkReplacements, 1);
  239. linkReplacements++;
  240. return arg;
  241. } );
  242. if ( title.startsWith( 'int:' ) ) {
  243. title = title.replace( /^int:\s*/, replacement => {
  244. linkprefix += replacement;
  245. return '';
  246. } );
  247. }
  248. return linkprefix + '[' + title + '](<' + ( link.url || wiki.toLink(link.title || link.template, '', '', true) ) + '>)' + linktrail;
  249. } );
  250. 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" + ']|' + '\\x1F<replacement\\x1F\\d+\\x1F.+?>\\x1F' + ')+)' + '(?:\\|((?:(?!\\[\\[|\\]\\(|\\]\\\\\\]).)*?))?' + '(?<!\\\\)\\]\\]' + 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' );
  251. line = line.replace( regex, (fullLink, linkprefix = '', title, display, linktrail = '') => {
  252. title = title.replace( /(?:%[\dA-F]{2})+/g, partialURIdecode );
  253. let rawTitle = title.replace( /\x1F<replacement\x1F\d+\x1F(.+?)>\x1F/g, '$1' ).split('#')[0].trim();
  254. let link = links.find( link => link.raw === rawTitle );
  255. if ( !link ) return fullLink;
  256. console.log( ( interaction.guild_id || '@' + interaction.user.id ) + ': Slash: ' + fullLink );
  257. title = title.replace( /\x1F<replacement\x1F(\d+)\x1F(.+?)>\x1F/g, (replacement, id, arg) => {
  258. links.splice(id - linkReplacements, 1);
  259. linkReplacements++;
  260. return arg;
  261. } );
  262. if ( display === undefined ) display = title.replace( /^\s*:?/, '' );
  263. if ( !display.trim() ) {
  264. display = title.replace( /^\s*:/, '' );
  265. if ( display.includes( ',' ) && !/ ([^\(\)]+)$/.test(display) ) {
  266. display = display.replace( /^([^,]+), .*$/, '$1' );
  267. }
  268. display = display.replace( / \([^\(\)]+\)$/, '' );
  269. if ( link.url || link.ns !== 0 ) {
  270. display = display.split(':').slice(1).join(':');
  271. }
  272. }
  273. return '[' + ( linkprefix + display + linktrail ).replace( /\[\]\(\)/g, '\\$&' ) + '](<' + ( link.url || wiki.toLink(link.title, '', link.section, true) ) + '>)';
  274. } );
  275. return line;
  276. } ).join('\n');
  277. text = replacedText.replace( /\x1F<replacement(?:\x1F\d+\x1F.+?)?>\x1F/g, replacement => {
  278. return textReplacement.shift();
  279. } );
  280. if ( text.length > 1900 ) text = limitLength(text, 1900, 100);
  281. return got.patch( `https://discord.com/api/v8/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`, {
  282. json: {
  283. content: text,
  284. allowed_mentions
  285. }
  286. } ).then( presponse => {
  287. if ( presponse.statusCode !== 200 ) {
  288. console.log( '- Slash: ' + presponse.statusCode + ': Error while sending the response: ' + presponse.body?.message );
  289. }
  290. channel?.messages.fetch(presponse.body.id).then( msg => {
  291. allowDelete(msg, ( interaction.member?.user.id || interaction.user.id ));
  292. }, () => {} );
  293. }, log_error );
  294. }
  295. else return got.patch( `https://discord.com/api/v8/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`, {
  296. json: {}
  297. } ).then( presponse => {
  298. if ( presponse.statusCode === 200 && presponse.body?.id ) {
  299. channel?.messages.fetch(presponse.body.id).then( msg => {
  300. allowDelete(msg, ( interaction.member?.user.id || interaction.user.id ));
  301. }, () => {} );
  302. }
  303. }, () => {} );
  304. }, error => {
  305. if ( wiki.noWiki(error.message) ) {
  306. console.log( '- This wiki doesn\'t exist!' );
  307. }
  308. else {
  309. console.log( '- Error while following the links: ' + error );
  310. }
  311. return got.patch( `https://discord.com/api/v8/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`, {
  312. json: {}
  313. } ).then( presponse => {
  314. if ( presponse.statusCode === 200 && presponse.body?.id ) {
  315. channel?.messages.fetch(presponse.body.id).then( msg => {
  316. allowDelete(msg, ( interaction.member?.user.id || interaction.user.id ));
  317. }, () => {} );
  318. }
  319. }, () => {} );
  320. } );
  321. }, log_error );
  322. }
  323. module.exports = {
  324. name: 'inline',
  325. run: slash_inline
  326. };