special_page.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. const {Util} = require('discord.js');
  2. const logging = require('../util/logging.js');
  3. const {timeoptions} = require('../util/default.json');
  4. const {toMarkdown, escapeFormatting} = require('../util/functions.js');
  5. const overwrites = {
  6. randompage: (fn, lang, msg, wiki, reaction, spoiler) => {
  7. fn.random(lang, msg, wiki, reaction, spoiler);
  8. },
  9. statistics: (fn, lang, msg, wiki, reaction, spoiler) => {
  10. fn.overview(lang, msg, wiki, reaction, spoiler);
  11. },
  12. diff: (fn, lang, msg, wiki, reaction, spoiler, args, embed) => {
  13. fn.diff(lang, msg, args, wiki, reaction, spoiler, embed);
  14. }
  15. }
  16. const queryfunctions = {
  17. title: (query, wiki) => query.querypage.results.map( result => {
  18. return '[' + escapeFormatting(result.title) + '](' + wiki.toLink(result.title, '', '', true) + ')';
  19. } ).join('\n'),
  20. times: (query, wiki, lang) => query.querypage.results.map( result => {
  21. return parseInt(result.value, 10).toLocaleString(lang.get('dateformat')) + '× [' + escapeFormatting(result.title) + '](' + wiki.toLink(result.title, '', '', true) + ')';
  22. } ).join('\n'),
  23. size: (query, wiki, lang) => query.querypage.results.map( result => {
  24. return lang.get('diff.info.bytes', parseInt(result.value, 10).toLocaleString(lang.get('dateformat')), result.value) + ': [' + escapeFormatting(result.title) + '](' + wiki.toLink(result.title, '', '', true) + ')';
  25. } ).join('\n'),
  26. redirect: (query, wiki) => query.querypage.results.map( result => {
  27. return '[' + escapeFormatting(result.title) + '](' + wiki.toLink(result.title, 'redirect=no', '', true) + ')' + ( result.databaseResult && result.databaseResult.rd_title ? ' → ' + escapeFormatting(result.databaseResult.rd_title) : '' );
  28. } ).join('\n'),
  29. doubleredirect: (query, wiki) => query.querypage.results.map( result => {
  30. return '[' + escapeFormatting(result.title) + '](' + wiki.toLink(result.title, 'redirect=no', '', true) + ')' + ( result.databaseResult && result.databaseResult.b_title && result.databaseResult.c_title ? ' → ' + escapeFormatting(result.databaseResult.b_title) + ' → ' + escapeFormatting(result.databaseResult.c_title) : '' );
  31. } ).join('\n'),
  32. timestamp: (query, wiki, lang) => query.querypage.results.map( result => {
  33. try {
  34. var dateformat = new Intl.DateTimeFormat(lang.get('dateformat'), Object.assign({
  35. timeZone: body.query.general.timezone
  36. }, timeoptions));
  37. }
  38. catch ( error ) {
  39. var dateformat = new Intl.DateTimeFormat(lang.get('dateformat'), Object.assign({
  40. timeZone: 'UTC'
  41. }, timeoptions));
  42. }
  43. let lastEditDate = new Date(result.timestamp);
  44. return dateformat.format(lastEditDate) + ' <t:' + Math.trunc(lastEditDate.getTime() / 1000) + ':R>: [' + escapeFormatting(result.title) + '](' + wiki.toLink(result.title, '', '', true) + ')';
  45. } ).join('\n'),
  46. media: (query, wiki, lang) => query.querypage.results.map( result => {
  47. var ms = result.title.split(';');
  48. return '**' + ms[1] + '**: ' + lang.get('search.category.files', parseInt(ms[2], 10).toLocaleString(lang.get('dateformat')), parseInt(ms[2], 10)) + ' (' + lang.get('diff.info.bytes', parseInt(ms[3], 10).toLocaleString(lang.get('dateformat')), parseInt(ms[3], 10)) + ')';
  49. } ).join('\n'),
  50. category: (query, wiki, lang) => query.querypage.results.map( result => {
  51. return parseInt(result.value, 10).toLocaleString(lang.get('dateformat')) + '× [' + escapeFormatting(result.title) + '](' + wiki.toLink('Category:' + result.title, '', '', true) + ')';
  52. } ).join('\n'),
  53. gadget: (query, wiki, lang) => query.querypage.results.map( result => {
  54. result.title = result.title.replace( /^(?:.*:)?gadget-/, '' );
  55. return '**' + escapeFormatting(result.title) + '**: ' + parseInt(result.value, 10).toLocaleString(lang.get('dateformat')) + ' users (' + result.ns.toLocaleString(lang.get('dateformat')) + ' active)';
  56. } ).join('\n'),
  57. recentchanges: (query, wiki) => query.recentchanges.map( result => {
  58. return '[' + escapeFormatting(result.title) + '](' + wiki.toLink(result.title, ( result.type === 'edit' ? {diff:result.revid,oldid:result.old_revid} : '' ), '', true) + ')';
  59. } ).join('\n')
  60. }
  61. const querypages = {
  62. ancientpages: ['&list=querypage&qplimit=10&qppage=Ancientpages', queryfunctions.timestamp],
  63. brokenredirects: ['&list=querypage&qplimit=10&qppage=BrokenRedirects', queryfunctions.redirect],
  64. deadendpages: ['&list=querypage&qplimit=10&qppage=Deadendpages', queryfunctions.title],
  65. doubleredirects: ['&list=querypage&qplimit=10&qppage=DoubleRedirects', queryfunctions.doubleredirect],
  66. fewestrevisions: ['&list=querypage&qplimit=10&qppage=Fewestrevisions', queryfunctions.times],
  67. listduplicatedfiles: ['&list=querypage&qplimit=10&qppage=ListDuplicatedFiles', queryfunctions.times],
  68. listredirects: ['&list=querypage&qplimit=10&qppage=Listredirects', queryfunctions.redirect],
  69. lonelypages: ['&list=querypage&qplimit=10&qppage=Lonelypages', queryfunctions.title],
  70. longpages: ['&list=querypage&qplimit=10&qppage=Longpages', queryfunctions.size],
  71. mediastatistics: ['&list=querypage&qplimit=10&qppage=MediaStatistics', queryfunctions.media],
  72. mostcategories: ['&list=querypage&qplimit=10&qppage=Mostcategories', queryfunctions.times],
  73. mostimages: ['&list=querypage&qplimit=10&qppage=Mostimages', queryfunctions.times],
  74. mostinterwikis: ['&list=querypage&qplimit=10&qppage=Mostinterwikis', queryfunctions.times],
  75. mostlinked: ['&list=querypage&qplimit=10&qppage=Mostlinked', queryfunctions.times],
  76. mostlinkedcategories: ['&list=querypage&qplimit=10&qppage=Mostlinkedcategories', queryfunctions.times],
  77. mostlinkedtemplates: ['&list=querypage&qplimit=10&qppage=Mostlinkedtemplates', queryfunctions.times],
  78. mostrevisions: ['&list=querypage&qplimit=10&qppage=Mostrevisions', queryfunctions.times],
  79. shortpages: ['&list=querypage&qplimit=10&qppage=Shortpages', queryfunctions.size],
  80. uncategorizedcategories: ['&list=querypage&qplimit=10&qppage=Uncategorizedcategories', queryfunctions.title],
  81. uncategorizedpages: ['&list=querypage&qplimit=10&qppage=Uncategorizedpages', queryfunctions.title],
  82. uncategorizedimages: ['&list=querypage&qplimit=10&qppage=Uncategorizedimages', queryfunctions.title],
  83. uncategorizedtemplates: ['&list=querypage&qplimit=10&qppage=Uncategorizedtemplates', queryfunctions.title],
  84. unusedcategories: ['&list=querypage&qplimit=10&qppage=Unusedcategories', queryfunctions.title],
  85. unusedimages: ['&list=querypage&qplimit=10&qppage=Unusedimages', queryfunctions.title],
  86. unusedtemplates: ['&list=querypage&qplimit=10&qppage=Unusedtemplates', queryfunctions.title],
  87. unwatchedpages: ['&list=querypage&qplimit=10&qppage=Unwatchedpages', queryfunctions.title],
  88. wantedcategories: ['&list=querypage&qplimit=10&qppage=Wantedcategories', queryfunctions.times],
  89. wantedfiles: ['&list=querypage&qplimit=10&qppage=Wantedfiles', queryfunctions.times],
  90. wantedpages: ['&list=querypage&qplimit=10&qppage=Wantedpages', queryfunctions.times],
  91. wantedtemplates: ['&list=querypage&qplimit=10&qppage=Wantedtemplates', queryfunctions.times],
  92. withoutinterwiki: ['&list=querypage&qplimit=10&qppage=Withoutinterwiki', queryfunctions.title],
  93. gadgetusage: ['&list=querypage&qplimit=10&qppage=GadgetUsage', queryfunctions.gadget],
  94. recentchanges: ['&list=recentchanges&rctype=edit|new|log&rclimit=10', queryfunctions.recentchanges],
  95. disambiguations: ['&list=querypage&qplimit=10&qppage=Disambiguations', queryfunctions.title],
  96. mostpopularcategories: ['&list=querypage&qplimit=10&qppage=Mostpopularcategories', queryfunctions.category],
  97. mostlinkedfilesincontent: ['&list=querypage&qplimit=10&qppage=MostLinkedFilesInContent', queryfunctions.times],
  98. unusedvideos: ['&list=querypage&qplimit=10&qppage=UnusedVideos', queryfunctions.title],
  99. withoutimages: ['&list=querypage&qplimit=10&qppage=Withoutimages', queryfunctions.title],
  100. nonportableinfoboxes: ['&list=querypage&qplimit=10&qppage=Nonportableinfoboxes', queryfunctions.title],
  101. popularpages: ['&list=querypage&qplimit=10&qppage=Popularpages', queryfunctions.title],
  102. pageswithoutinfobox: ['&list=querypage&qplimit=10&qppage=Pageswithoutinfobox', queryfunctions.title],
  103. templateswithouttype: ['&list=querypage&qplimit=10&qppage=Templateswithouttype', queryfunctions.title],
  104. allinfoboxes: ['&list=querypage&qplimit=10&qppage=AllInfoboxes', queryfunctions.title]
  105. }
  106. const descriptions = {
  107. block: 'blockiptext&amargs=16|19',
  108. checkuser: 'checkuser-summary&amargs=16|19',
  109. resettokens: 'resettokens-text',
  110. allmessages: 'allmessagestext',
  111. expandtemplates: 'expand_templates_intro',
  112. apisandbox: 'apisandbox-intro',
  113. abusefilter: 'abusefilter-intro',
  114. gadgets: 'gadgets-pagetext',
  115. categorytree: 'categorytree-header',
  116. drafts: 'drafts-view-summary&amargs=30',
  117. analytics: 'analytics_confidential',
  118. mostlinkedfilesincontent: 'mostimagesincontent-summary',
  119. popularpages: 'insights-list-description-popularpages'
  120. }
  121. /**
  122. * Processes special pages.
  123. * @param {import('../util/i18n.js')} lang - The user language.
  124. * @param {import('discord.js').Message} msg - The Discord message.
  125. * @param {Object} querypage - The details of the special page.
  126. * @param {String} querypage.title - The title of the special page.
  127. * @param {String} querypage.uselang - The language of the special page.
  128. * @param {String} specialpage - The canonical name of the special page.
  129. * @param {import('discord.js').MessageEmbed} embed - The embed for the page.
  130. * @param {import('../util/wiki.js')} wiki - The wiki for the page.
  131. * @param {import('discord.js').MessageReaction} reaction - The reaction on the message.
  132. * @param {String} spoiler - If the response is in a spoiler.
  133. */
  134. function special_page(lang, msg, {title, uselang = lang.lang}, specialpage, embed, wiki, reaction, spoiler) {
  135. if ( overwrites.hasOwnProperty(specialpage) ) {
  136. var args = title.split('/').slice(1,3);
  137. overwrites[specialpage](this, lang, msg, wiki, reaction, spoiler, args, embed);
  138. return;
  139. }
  140. logging(wiki, msg.guild?.id, 'general', 'special');
  141. if ( specialpage === 'recentchanges' && msg.isAdmin() ) {
  142. embed.addField( lang.get('rcscript.title'), lang.get('rcscript.ad', ( patreons[msg?.guild?.id] || process.env.prefix ), '[RcGcDw](https://gitlab.com/piotrex43/RcGcDw)') );
  143. }
  144. got.get( wiki + 'api.php?uselang=' + uselang + '&action=query&meta=allmessages|siteinfo&siprop=general&amenableparser=true&amtitle=' + encodeURIComponent( title ) + '&ammessages=' + encodeURIComponent( specialpage ) + '|' + ( descriptions.hasOwnProperty(specialpage) ? descriptions[specialpage] : encodeURIComponent( specialpage ) + '-summary' ) + ( querypages.hasOwnProperty(specialpage) ? querypages[specialpage][0] : '' ) + '&format=json' ).then( response => {
  145. var body = response.body;
  146. if ( body && body.warnings ) log_warn(body.warnings);
  147. if ( response.statusCode !== 200 || body?.batchcomplete === undefined ) {
  148. console.log( '- ' + response.statusCode + ': Error while getting the special page: ' + ( body && body.error && body.error.info ) );
  149. return;
  150. }
  151. if ( body.query.allmessages?.[0]?.['*']?.trim?.() ) {
  152. let displaytitle = escapeFormatting(body.query.allmessages[0]['*'].trim());
  153. if ( displaytitle.length > 250 ) displaytitle = displaytitle.substring(0, 250) + '\u2026';
  154. embed.setTitle( displaytitle );
  155. }
  156. if ( body.query.allmessages?.[1]?.['*']?.trim?.() ) {
  157. var description = toMarkdown(body.query.allmessages[1]['*'], wiki, title, true);
  158. if ( description.length > 1000 ) description = description.substring(0, 1000) + '\u2026';
  159. embed.setDescription( description );
  160. }
  161. if ( msg.channel.isGuild() && patreons[msg.guild?.id] && querypages.hasOwnProperty(specialpage) ) {
  162. var text = Util.splitMessage( querypages[specialpage][1](body.query, wiki, lang), {maxLength:1000} )[0];
  163. embed.addField( lang.get('search.special'), ( text || lang.get('search.empty') ) );
  164. if ( body.query.querypage.cached !== undefined ) {
  165. embed.setFooter( lang.get('search.cached') ).setTimestamp(new Date(body.query.querypage.cachedtimestamp));
  166. }
  167. }
  168. }, error => {
  169. console.log( '- Error while getting the special page: ' + error );
  170. } ).finally( () => {
  171. msg.sendChannel( spoiler + '<' + embed.url + '>' + spoiler, {embed} );
  172. if ( reaction ) reaction.removeEmoji();
  173. } );
  174. }
  175. module.exports = special_page;