newMessage.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import { readdir } from 'fs';
  2. import { domainToASCII } from 'url';
  3. import { Util } from 'discord.js';
  4. import Wiki from './wiki.js';
  5. import logging from './logging.js';
  6. import { got, partialURIdecode } from './functions.js';
  7. import check_wiki_general from '../cmds/wiki/general.js';
  8. import check_wiki_test from '../cmds/test.js';
  9. import { createRequire } from 'module';
  10. const require = createRequire(import.meta.url);
  11. const {limit: {command: commandLimit}, defaultSettings, wikiProjects} = require('./default.json');
  12. const check_wiki = {
  13. general: check_wiki_general,
  14. test: check_wiki_test.run
  15. };
  16. var cmdmap = {};
  17. var pausecmdmap = {};
  18. var ownercmdmap = {};
  19. readdir( './cmds', (error, files) => {
  20. if ( error ) return error;
  21. files.filter( file => file.endsWith('.js') ).forEach( file => {
  22. import('../cmds/' + file).then( ({default: command}) => {
  23. if ( command.everyone ) cmdmap[command.name] = command.run;
  24. if ( command.pause ) pausecmdmap[command.name] = command.run;
  25. if ( command.owner ) ownercmdmap[command.name] = command.run;
  26. } );
  27. } );
  28. } );
  29. /**
  30. * Processes new messages.
  31. * @param {import('discord.js').Message} msg - The Discord message.
  32. * @param {import('./i18n.js').default} lang - The user language.
  33. * @param {Wiki} [wiki] - The default wiki.
  34. * @param {String} [prefix] - The prefix for the message.
  35. * @param {Boolean} [noInline] - Parse inline commands?
  36. * @param {String} [content] - Overwrite for the message content.
  37. */
  38. export default function newMessage(msg, lang, wiki = defaultSettings.wiki, prefix = process.env.prefix, noInline = null, content = '') {
  39. wiki = new Wiki(wiki);
  40. msg.noInline = noInline;
  41. var cont = ( content || msg.content );
  42. var cleanCont = ( content ? Util.cleanContent(content, msg) : msg.cleanContent );
  43. if ( msg.isOwner() && cont.hasPrefix(prefix) ) {
  44. let invoke = cont.substring(prefix.length).split(' ')[0].split('\n')[0].toLowerCase();
  45. let aliasInvoke = ( lang.aliases[invoke] || invoke );
  46. if ( ownercmdmap.hasOwnProperty(aliasInvoke) ) {
  47. cont = cont.substring(prefix.length);
  48. let args = cont.split(' ').slice(1);
  49. if ( cont.split(' ')[0].split('\n')[1] ) args.unshift( '', cont.split(' ')[0].split('\n')[1] );
  50. console.log( ( msg.guildId || '@' + msg.author.id ) + ': ' + prefix + cont );
  51. return ownercmdmap[aliasInvoke](lang, msg, args, cont, wiki);
  52. }
  53. }
  54. var count = 0;
  55. var maxcount = commandLimit[( patreonGuildsPrefix.has(msg.guildId) ? 'patreon' : 'default' )];
  56. var breakLines = false;
  57. cleanCont.replace( /\u200b/g, '' ).replace( /<a?(:\w+:)\d+>/g, '$1' ).replace( /(?<!\\)```.+?```/gs, '<codeblock>' ).split('\n').forEach( line => {
  58. if ( line.startsWith( '>>> ' ) ) breakLines = true;
  59. if ( !line.hasPrefix(prefix) || breakLines || count > maxcount ) return;
  60. if ( count === maxcount ) {
  61. count++;
  62. console.log( '- Message contains too many commands!' );
  63. msg.reactEmoji('⚠️');
  64. msg.sendChannelError( {
  65. content: lang.get('general.limit', msg.author.toString()),
  66. reply: {messageReference: msg.id},
  67. allowedMentions: {
  68. users: [msg.author.id],
  69. repliedUser: true
  70. }
  71. } );
  72. return;
  73. }
  74. count++;
  75. line = line.substring(prefix.length);
  76. var invoke = line.split(' ')[0].toLowerCase();
  77. var args = line.split(' ').slice(1);
  78. var aliasInvoke = ( lang.aliases[invoke] || invoke );
  79. var ownercmd = ( msg.isOwner() && ownercmdmap.hasOwnProperty(aliasInvoke) );
  80. var pausecmd = ( msg.isAdmin() && pausedGuilds.has(msg.guildId) && pausecmdmap.hasOwnProperty(aliasInvoke) );
  81. if ( msg.onlyVerifyCommand && !( aliasInvoke === 'verify' || pausecmd || ownercmd ) ) return;
  82. if ( msg.inGuild() && pausedGuilds.has(msg.guildId) && !( pausecmd || ownercmd ) ) {
  83. return console.log( msg.guildId + ': Paused' );
  84. }
  85. console.log( ( msg.guildId || '@' + msg.author.id ) + ': ' + prefix + line );
  86. if ( ownercmd ) return ownercmdmap[aliasInvoke](lang, msg, args, line, wiki);
  87. if ( pausecmd ) return pausecmdmap[aliasInvoke](lang, msg, args, line, wiki);
  88. if ( cmdmap.hasOwnProperty(aliasInvoke) ) return cmdmap[aliasInvoke](lang, msg, args, line, wiki);
  89. if ( invoke.startsWith( '!' ) && /^![a-z\d-]{1,50}$/.test(invoke) ) {
  90. return cmdmap.LINK(lang, msg, args.join(' '), new Wiki('https://' + invoke.substring(1) + '.gamepedia.com/'), invoke + ' ');
  91. }
  92. if ( invoke.startsWith( '?' ) && /^\?(?:[a-z-]{2,12}\.)?[a-z\d-]{1,50}$/.test(invoke) ) {
  93. let invokeWiki = wiki;
  94. if ( invoke.includes( '.' ) ) invokeWiki = 'https://' + invoke.split('.')[1] + '.fandom.com/' + invoke.substring(1).split('.')[0] + '/';
  95. else invokeWiki = 'https://' + invoke.substring(1) + '.fandom.com/';
  96. return cmdmap.LINK(lang, msg, args.join(' '), new Wiki(invokeWiki), invoke + ' ');
  97. }
  98. if ( invoke.startsWith( '??' ) && /^\?\?(?:[a-z-]{2,12}\.)?[a-z\d-]{1,50}$/.test(invoke) ) {
  99. let invokeWiki = wiki;
  100. if ( invoke.includes( '.' ) ) invokeWiki = 'https://' + invoke.split('.')[1] + '.wikia.org/' + invoke.substring(2).split('.')[0] + '/';
  101. else invokeWiki = 'https://' + invoke.substring(2) + '.wikia.org/';
  102. return cmdmap.LINK(lang, msg, args.join(' '), new Wiki(invokeWiki), invoke + ' ');
  103. }
  104. if ( invoke.startsWith( '!!' ) && /^!!(?:[a-z\d-]{1,50}\.)?[a-z\d-]{1,50}\.[a-z\d-]{1,10}(?:\/|$)/.test(domainToASCII(invoke)) ) {
  105. let project = wikiProjects.find( project => invoke.split('/')[0].endsWith( project.name ) );
  106. if ( project ) {
  107. let regex = invoke.match( new RegExp( project.regex ) );
  108. if ( regex && invoke === '!!' + regex[1] ) return cmdmap.LINK(lang, msg, args.join(' '), new Wiki('https://' + regex[1] + project.scriptPath), invoke + ' ');
  109. }
  110. }
  111. return cmdmap.LINK(lang, msg, line, wiki);
  112. } );
  113. if ( msg.onlyVerifyCommand ) return;
  114. if ( ( !msg.inGuild() || !pausedGuilds.has(msg.guildId) ) && !noInline && ( cont.includes( '[[' ) || cont.includes( '{{' ) ) ) {
  115. var links = [];
  116. var embeds = [];
  117. var linkcount = 0;
  118. var linkmaxcount = maxcount + 5;
  119. var breakInline = false;
  120. cleanCont.replace( /\u200b/g, '' ).replace( /<a?(:\w+:)\d+>/g, '$1' ).replace( /(?<!\\)```.+?```/gs, '<codeblock>' ).replace( /(?<!\\)``.+?``/gs, '<code>' ).replace( /(?<!\\)`.+?`/gs, '<code>' ).split('\n').forEach( line => {
  121. if ( line.startsWith( '>>> ' ) ) breakInline = true;
  122. if ( line.startsWith( '> ' ) || breakInline ) return;
  123. if ( line.hasPrefix(prefix) || !( line.includes( '[[' ) || line.includes( '{{' ) ) ) return;
  124. line = line.replace( /(?:%[\dA-F]{2})+/g, partialURIdecode );
  125. if ( line.includes( '[[' ) && line.includes( ']]' ) && linkcount <= linkmaxcount ) {
  126. let regex = new RegExp( '(?<!\\\\)(|\\|\\|)\\[\\[([^' + "<>\\[\\]\\|{}\\x01-\\x1F\\x7F" + ']+)(?<!\\\\)\\]\\]\\1', 'g' );
  127. let entry = null;
  128. while ( ( entry = regex.exec(line) ) !== null ) {
  129. if ( linkcount < linkmaxcount ) {
  130. linkcount++;
  131. console.log( ( msg.guildId || '@' + msg.author.id ) + ': ' + entry[0] );
  132. let title = entry[2].split('#')[0];
  133. let section = entry[2].split('#').slice(1).join('#');
  134. links.push({title,section,spoiler:entry[1]});
  135. }
  136. else if ( linkcount === linkmaxcount ) {
  137. linkcount++;
  138. console.log( '- Message contains too many links!' );
  139. msg.reactEmoji('⚠️');
  140. break;
  141. }
  142. }
  143. }
  144. if ( line.includes( '{{' ) && line.includes( '}}' ) && count <= maxcount ) {
  145. let regex = new RegExp( '(?<!\\\\)(|\\|\\|)(?<!\\{)\\{\\{([^' + "<>\\[\\]\\|{}\\x01-\\x1F\\x7F" + ']+)(?<!\\\\)\\}\\}\\1', 'g' );
  146. let entry = null;
  147. while ( ( entry = regex.exec(line) ) !== null ) {
  148. if ( count < maxcount ) {
  149. count++;
  150. console.log( ( msg.guildId || '@' + msg.author.id ) + ': ' + entry[0] );
  151. let title = entry[2].split('#')[0];
  152. let section = entry[2].split('#').slice(1).join('#');
  153. embeds.push({title,section,spoiler:entry[1]});
  154. }
  155. else if ( count === maxcount ) {
  156. count++;
  157. console.log( '- Message contains too many links!' );
  158. msg.reactEmoji('⚠️');
  159. break;
  160. }
  161. }
  162. }
  163. } );
  164. if ( links.length ) got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general&iwurl=true&titles=' + encodeURIComponent( links.map( link => link.title ).join('|') ) + '&format=json' ).then( response => {
  165. var body = response.body;
  166. if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query ) {
  167. if ( wiki.noWiki(response.url, response.statusCode) ) {
  168. console.log( '- This wiki doesn\'t exist!' );
  169. msg.reactEmoji('nowiki');
  170. return;
  171. }
  172. console.log( '- ' + response.statusCode + ': Error while following the links: ' + ( body && body.error && body.error.info ) );
  173. return;
  174. }
  175. wiki.updateWiki(body.query.general);
  176. if ( body.query.normalized ) {
  177. body.query.normalized.forEach( title => links.filter( link => link.title === title.from ).forEach( link => link.title = title.to ) );
  178. }
  179. if ( body.query.interwiki ) {
  180. body.query.interwiki.forEach( interwiki => links.filter( link => link.title === interwiki.title ).forEach( link => {
  181. logging(wiki, msg.guildId, 'inline', 'interwiki');
  182. link.url = ( link.section ? decodeURI(interwiki.url.split('#')[0]) + Wiki.toSection(link.section) : decodeURI(interwiki.url) );
  183. } ) );
  184. }
  185. if ( body.query.pages ) {
  186. var querypages = Object.values(body.query.pages);
  187. querypages.filter( page => page.invalid !== undefined ).forEach( page => links.filter( link => link.title === page.title ).forEach( link => {
  188. links.splice(links.indexOf(link), 1);
  189. } ) );
  190. querypages.filter( page => page.missing !== undefined && page.known === undefined ).forEach( page => links.filter( link => link.title === page.title ).forEach( link => {
  191. if ( ( page.ns === 2 || page.ns === 202 ) && !page.title.includes( '/' ) ) return;
  192. if ( wiki.isMiraheze() && page.ns === 0 && /^Mh:[a-z\d]+:/.test(page.title) ) {
  193. logging(wiki, msg.guildId, 'inline', 'interwiki');
  194. var iw_parts = page.title.split(':');
  195. var iw = new Wiki('https://' + iw_parts[1] + '.miraheze.org/w/');
  196. link.url = iw.toLink(iw_parts.slice(2).join(':'), '', link.section);
  197. return;
  198. }
  199. logging(wiki, msg.guildId, 'inline', 'redlink');
  200. link.url = wiki.toLink(link.title, 'action=edit&redlink=1');
  201. } ) );
  202. }
  203. if ( links.length ) Util.splitMessage( links.map( link => {
  204. if ( !link.url ) logging(wiki, msg.guildId, 'inline');
  205. return link.spoiler + '<' + ( link.url || wiki.toLink(link.title, '', link.section) ) + '>' + link.spoiler;
  206. } ).join('\n') ).forEach( textpart => msg.sendChannel( textpart ) );
  207. }, error => {
  208. if ( wiki.noWiki(error.message) ) {
  209. console.log( '- This wiki doesn\'t exist!' );
  210. msg.reactEmoji('nowiki');
  211. }
  212. else {
  213. console.log( '- Error while following the links: ' + error );
  214. }
  215. } );
  216. if ( embeds.length ) got.get( wiki + 'api.php?action=query&meta=siteinfo&siprop=general' + ( wiki.isFandom() ? '' : '|variables' ) + '&titles=' + encodeURIComponent( embeds.map( embed => embed.title + '|Template:' + embed.title ).join('|') ) + '&format=json' ).then( response => {
  217. var body = response.body;
  218. if ( response.statusCode !== 200 || body?.batchcomplete === undefined || !body?.query ) {
  219. if ( wiki.noWiki(response.url, response.statusCode) ) {
  220. console.log( '- This wiki doesn\'t exist!' );
  221. msg.reactEmoji('nowiki');
  222. return;
  223. }
  224. console.log( '- ' + response.statusCode + ': Error while following the links: ' + ( body && body.error && body.error.info ) );
  225. return;
  226. }
  227. wiki.updateWiki(body.query.general);
  228. if ( body.query.normalized ) {
  229. body.query.normalized.forEach( title => embeds.filter( embed => embed.title === title.from ).forEach( embed => embed.title = title.to ) );
  230. }
  231. if ( body.query.pages ) {
  232. var querypages = Object.values(body.query.pages);
  233. querypages.filter( page => page.invalid !== undefined ).forEach( page => embeds.filter( embed => embed.title === page.title ).forEach( embed => {
  234. embeds.splice(embeds.indexOf(embed), 1);
  235. } ) );
  236. var missing = [];
  237. querypages.filter( page => page.missing !== undefined && page.known === undefined ).forEach( page => embeds.filter( embed => embed.title === page.title ).forEach( embed => {
  238. if ( ( page.ns === 2 || page.ns === 202 ) && !page.title.includes( '/' ) ) return;
  239. if ( wiki.isMiraheze() && page.ns === 0 && /^Mh:[a-z\d]+:/.test(page.title) ) return;
  240. embeds.splice(embeds.indexOf(embed), 1);
  241. if ( page.ns === 0 && !embed.section ) {
  242. var template = querypages.find( template => template.ns === 10 && template.title.split(':').slice(1).join(':') === embed.title );
  243. if ( template && template.missing === undefined ) embed.template = wiki.toLink(template.title);
  244. }
  245. if ( embed.template || !body.query.variables || !body.query.variables.some( variable => variable.toUpperCase() === embed.title ) ) missing.push(embed);
  246. } ) );
  247. if ( missing.length ) Util.splitMessage( missing.map( embed => {
  248. if ( embed.template ) logging(wiki, msg.guildId, 'inline', 'template');
  249. else logging(wiki, msg.guildId, 'inline', 'redlink');
  250. return embed.spoiler + '<' + ( embed.template || wiki.toLink(embed.title, 'action=edit&redlink=1') ) + '>' + embed.spoiler;
  251. } ).join('\n') ).forEach( textpart => msg.sendChannel( textpart ) );
  252. }
  253. if ( embeds.length ) embeds.forEach( embed => msg.reactEmoji('⏳').then( reaction => {
  254. logging(wiki, msg.guildId, 'inline', 'embed');
  255. check_wiki.general(lang, msg, embed.title, wiki, '', reaction, embed.spoiler, false, new URLSearchParams(), embed.section);
  256. } ) );
  257. }, error => {
  258. if ( wiki.noWiki(error.message) ) {
  259. console.log( '- This wiki doesn\'t exist!' );
  260. msg.reactEmoji('nowiki');
  261. }
  262. else {
  263. console.log( '- Error while following the links: ' + error );
  264. }
  265. } );
  266. }
  267. }