inline.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. const logging = require('../util/logging.js');
  2. const Wiki = require('../util/wiki.js');
  3. const {limitLength, partialURIdecode} = 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').Guild} [guild] - The guild for the interaction.
  10. */
  11. function slash_inline(interaction, lang, wiki, guild) {
  12. var text = ( interaction.data.options?.[0]?.value || '' ).replace( /\]\(/g, ']\\(' ).trim();
  13. if ( !text ) {
  14. return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
  15. json: {
  16. //type: 4,
  17. type: 3,
  18. data: {
  19. content: lang.get('interaction.inline'),
  20. allowed_mentions: {
  21. parse: []
  22. },
  23. flags: 64
  24. }
  25. }
  26. } ).then( response => {
  27. if ( response.statusCode !== 204 ) {
  28. console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
  29. }
  30. }, log_error );
  31. }
  32. var allowed_mentions = {
  33. parse: ['users']
  34. };
  35. if ( interaction.guild_id ) {
  36. if ( ( (interaction.member.permissions & 1 << 3) === 1 << 3 ) // ADMINISTRATOR
  37. || ( (interaction.member.permissions & 1 << 17) === 1 << 17 ) ) { // MENTION_EVERYONE
  38. allowed_mentions.parse = ['users', 'roles', 'everyone'];
  39. }
  40. else if ( guild ) {
  41. allowed_mentions.roles = guild.roles.cache.filter( role => role.mentionable ).map( role => role.id );
  42. if ( allowed_mentions.roles.length > 100 ) {
  43. allowed_mentions.roles = allowed_mentions.roles.slice(0, 100);
  44. }
  45. }
  46. }
  47. if ( text.length > 1800 ) text = text.substring(0, 1800) + '\u2026';
  48. return got.post( `https://discord.com/api/v8/interactions/${interaction.id}/${interaction.token}/callback`, {
  49. json: {
  50. type: 4,
  51. data: {
  52. content: text,
  53. allowed_mentions,
  54. flags: 0
  55. }
  56. }
  57. } ).then( response => {
  58. if ( response.statusCode !== 204 ) {
  59. console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
  60. return;
  61. }
  62. if ( !text.includes( '{{' ) && !( text.includes( '[[' ) && text.includes( ']]' ) ) ) return;
  63. var textReplacement = [];
  64. var replacedText = text.replace( /\u200b/g, '' ).replace( /(?<!\\)(?:<a?(:\w+:)\d+>|```.+?```|`.+?`)/gs, (replacement, arg) => {
  65. textReplacement.push(replacement);
  66. return '\u200b<replacement' + ( arg ? '\u200b' + textReplacement.length + '\u200b' + arg : '' ) + '>\u200b';
  67. } );
  68. var templates = [];
  69. var links = [];
  70. var breakInline = false;
  71. replacedText.replace( /\u200b<replacement\u200b\d+\u200b(.+?)>\u200b/g, '$1' ).replace( /(?:%[\dA-F]{2})+/g, partialURIdecode ).split('\n').forEach( line => {
  72. if ( line.startsWith( '>>> ' ) ) breakInline = true;
  73. if ( line.startsWith( '> ' ) || breakInline ) return;
  74. var inlineLink = null;
  75. var regex = /(?<!\\|\{)\{\{(?:\s*(?:subst|safesubst|raw|msg|msgnw):)?([^<>\[\]\|\{\}\x01-\x1F\x7F#]+)(?<!\\)(?:\||\}\})/g;
  76. while ( ( inlineLink = regex.exec(line) ) !== null ) {
  77. let title = inlineLink[1].trim();
  78. if ( !title.replace( /:/g, '' ).trim().length || title.startsWith( '/' ) ) continue;
  79. if ( title.startsWith( 'int:' ) ) templates.push({
  80. raw: title,
  81. title: title.replace( /^int:/, 'MediaWiki:' ),
  82. template: title.replace( /^int:/, 'MediaWiki:' )
  83. });
  84. else templates.push({raw: title, title, template: 'Template:' + title});
  85. }
  86. inlineLink = null;
  87. regex = /(?<!\\)\[\[([^<>\[\]\|\{\}\x01-\x1F\x7F]+)(?:\|(?:(?!\[\[).)*?)?(?<!\\)\]\]/g;
  88. while ( ( inlineLink = regex.exec(line) ) !== null ) {
  89. inlineLink[1] = inlineLink[1].trim();
  90. let title = inlineLink[1].split('#')[0].trim();
  91. let section = inlineLink[1].split('#').slice(1).join('#');
  92. if ( !title.replace( /:/g, '' ).trim().length || title.startsWith( '/' ) ) continue;
  93. links.push({raw: title, title, section});
  94. }
  95. } );
  96. if ( !templates.length && !links.length ) return;
  97. return got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&iwurl=true&titles=' + encodeURIComponent( [
  98. ...templates.map( link => link.title + '|' + link.template ),
  99. ...links.map( link => link.title )
  100. ].join('|') ) + '&format=json' ).then( response => {
  101. var body = response.body;
  102. if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query ) {
  103. if ( wiki.noWiki(response.url, response.statusCode) ) {
  104. console.log( '- This wiki doesn\'t exist!' );
  105. }
  106. else {
  107. console.log( '- ' + response.statusCode + ': Error while following the links: ' + body?.error?.info );
  108. }
  109. return;
  110. }
  111. logging(wiki, interaction.guild_id, 'slash', 'inline');
  112. wiki.updateWiki(body.query.general);
  113. if ( body.query.normalized ) {
  114. body.query.normalized.forEach( title => {
  115. templates.filter( link => link.title === title.from ).forEach( link => link.title = title.to );
  116. templates.filter( link => link.template === title.from ).forEach( link => link.template = title.to );
  117. links.filter( link => link.title === title.from ).forEach( link => link.title = title.to );
  118. } );
  119. }
  120. if ( body.query.interwiki ) {
  121. body.query.interwiki.forEach( interwiki => {
  122. templates.filter( link => link.title === interwiki.title ).forEach( link => {
  123. link.url = decodeURI(interwiki.url)
  124. } );
  125. links.filter( link => link.title === interwiki.title ).forEach( link => {
  126. link.url = ( link.section ? decodeURI(interwiki.url.split('#')[0]) + Wiki.toSection(link.section) : decodeURI(interwiki.url) );
  127. } );
  128. } );
  129. }
  130. if ( body.query.pages ) {
  131. Object.values(body.query.pages).forEach( page => {
  132. templates.filter( link => link.title === page.title ).forEach( link => {
  133. if ( page.invalid !== undefined || ( page.missing !== undefined && page.known === undefined ) ) {
  134. link.title = '';
  135. }
  136. else if ( page.ns === 0 && !link.raw.startsWith( ':' ) ) {
  137. link.title = '';
  138. }
  139. } );
  140. templates.filter( link => link.template === page.title ).forEach( link => {
  141. if ( page.invalid !== undefined || ( page.missing !== undefined && page.known === undefined ) ) {
  142. link.template = '';
  143. }
  144. } );
  145. links.filter( link => link.title === page.title ).forEach( link => {
  146. link.ns = page.ns;
  147. if ( page.invalid !== undefined ) return links.splice(links.indexOf(link), 1);
  148. if ( page.missing !== undefined && page.known === undefined ) {
  149. if ( ( page.ns === 2 || page.ns === 202 ) && !page.title.includes( '/' ) ) {
  150. return;
  151. }
  152. if ( wiki.isMiraheze() && page.ns === 0 && /^Mh:[a-z\d]+:/.test(page.title) ) {
  153. var iw_parts = page.title.split(':');
  154. var iw = new Wiki('https://' + iw_parts[1] + '.miraheze.org/w/');
  155. link.url = iw.toLink(iw_parts.slice(2).join(':'), '', link.section, true);
  156. return;
  157. }
  158. return links.splice(links.indexOf(link), 1);
  159. }
  160. } );
  161. } );
  162. }
  163. templates = templates.filter( link => link.title || link.template );
  164. if ( templates.length || links.length ) {
  165. breakInline = false;
  166. replacedText = replacedText.split('\n').map( line => {
  167. if ( line.startsWith( '>>> ' ) ) breakInline = true;
  168. if ( line.startsWith( '> ' ) || breakInline ) return line;
  169. let emojiReplacements = 1;
  170. let regex = /(?<!\\|\{)(\{\{(?:\s*(?:subst|safesubst|raw|msg|msgnw):)?\s*)((?:[^<>\[\]\|\{\}\x01-\x1F\x7F#]|\u200b<replacement\u200b\d+\u200b.+?>\u200b)+?)(\s*(?<!\\)\||\}\})/g;
  171. line = line.replace( regex, (fullLink, linkprefix, title, linktrail) => {
  172. title = title.replace( /(?:%[\dA-F]{2})+/g, partialURIdecode );
  173. let rawTitle = title.replace( /\u200b<replacement\u200b\d+\u200b(.+?)>\u200b/g, '$1' ).trim();
  174. let link = templates.find( link => link.raw === rawTitle );
  175. if ( !link ) return fullLink;
  176. console.log( ( interaction.guild_id || '@' + interaction.user.id ) + ': Slash: ' + fullLink );
  177. title = title.replace( /\u200b<replacement\u200b(\d+)\u200b(.+?)>\u200b/g, (replacement, id, arg) => {
  178. links.splice(id - emojiReplacements, 1);
  179. emojiReplacements++;
  180. return arg;
  181. } );
  182. if ( title.startsWith( 'int:' ) ) {
  183. title = title.replace( /^int:\s*/, replacement => {
  184. linkprefix += replacement;
  185. return '';
  186. } );
  187. }
  188. return linkprefix + '[' + title + '](<' + ( link.url || wiki.toLink(link.title || link.template, '', '', true) ) + '>)' + linktrail;
  189. } );
  190. 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' );
  191. line = line.replace( regex, (fullLink, linkprefix = '', title, display, linktrail = '') => {
  192. title = title.replace( /(?:%[\dA-F]{2})+/g, partialURIdecode );
  193. let rawTitle = title.replace( /\u200b<replacement\u200b\d+\u200b(.+?)>\u200b/g, '$1' ).split('#')[0].trim();
  194. let link = links.find( link => link.raw === rawTitle );
  195. if ( !link ) return fullLink;
  196. console.log( ( interaction.guild_id || '@' + interaction.user.id ) + ': Slash: ' + fullLink );
  197. title = title.replace( /\u200b<replacement\u200b(\d+)\u200b(.+?)>\u200b/g, (replacement, id, arg) => {
  198. links.splice(id - emojiReplacements, 1);
  199. emojiReplacements++;
  200. return arg;
  201. } );
  202. if ( display === undefined ) display = title.replace( /^\s*:?/, '' );
  203. if ( !display.trim() ) {
  204. display = title.replace( /^\s*:/, '' );
  205. if ( display.includes( ',' ) && !/ ([^\(\)]+)$/.test(display) ) {
  206. display = display.replace( /^([^,]+), .*$/, '$1' );
  207. }
  208. display = display.replace( / ([^\(\)]+)$/, '' );
  209. if ( link.url || link.ns !== 0 ) {
  210. display = display.split(':').slice(1).join(':');
  211. }
  212. }
  213. return '[' + ( linkprefix + display + linktrail ).replace( /\[\]\(\)/g, '\\$&' ) + '](<' + ( link.url || wiki.toLink(link.title, '', link.section, true) ) + '>)';
  214. } );
  215. return line;
  216. } ).join('\n');
  217. text = replacedText.replace( /\u200b<replacement(?:\u200b\d+\u200b.+?)?>\u200b/g, replacement => {
  218. return textReplacement.shift();
  219. } );
  220. if ( text.length > 1900 ) text = limitLength(text, 1900, 100);
  221. return got.patch( `https://discord.com/api/v8/webhooks/${interaction.application_id}/${interaction.token}/messages/@original`, {
  222. json: {
  223. content: text,
  224. allowed_mentions
  225. }
  226. } ).then( response => {
  227. if ( response.statusCode !== 200 ) {
  228. console.log( '- Slash: ' + response.statusCode + ': Error while sending the response: ' + response.body?.message );
  229. }
  230. }, log_error );
  231. }
  232. }, error => {
  233. if ( wiki.noWiki(error.message) ) {
  234. console.log( '- This wiki doesn\'t exist!' );
  235. }
  236. else {
  237. console.log( '- Error while following the links: ' + error );
  238. }
  239. } );
  240. }, log_error );
  241. }
  242. module.exports = {
  243. name: 'inline',
  244. run: slash_inline
  245. };