functions.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. const htmlparser = require('htmlparser2');
  2. const got = require('got').extend( {
  3. throwHttpErrors: false,
  4. timeout: 5000,
  5. headers: {
  6. 'User-Agent': 'Wiki-Bot/' + ( isDebug ? 'testing' : process.env.npm_package_version ) + ' (Discord; ' + process.env.npm_package_name + ')'
  7. },
  8. responseType: 'json'
  9. } );
  10. /**
  11. * Parse infobox content
  12. * @param {Object} infobox - The content of the infobox.
  13. * @param {import('discord.js').MessageEmbed} embed - The message embed.
  14. * @param {String} [thumbnail] - The default thumbnail for the wiki.
  15. */
  16. function parse_infobox(infobox, embed, thumbnail) {
  17. if ( embed.fields.length >= 25 ) return;
  18. switch ( infobox.type ) {
  19. case 'data':
  20. var {label = '', value = ''} = infobox.data;
  21. label = htmlToPlain(label).trim();
  22. value = htmlToPlain(value).trim();
  23. if ( label && value ) embed.addField( label, value, true );
  24. break;
  25. case 'group':
  26. infobox.data.value.forEach( group => {
  27. parse_infobox(group, embed, thumbnail);
  28. } );
  29. break;
  30. case 'header':
  31. var {value = ''} = infobox.data;
  32. value = htmlToPlain(value).trim();
  33. if ( value ) embed.addField( '\u200b', '__**' + value + '**__', false );
  34. break;
  35. case 'image':
  36. if ( embed.thumbnail?.url !== thumbnail ) return;
  37. var image = infobox.data.find( img => {
  38. return ( /^(?:https?:)?\/\//.test(img.url) && /\.(?:png|jpg|jpeg|gif)$/.test(img.name) );
  39. } );
  40. if ( image ) embed.setThumbnail( image.url );
  41. break;
  42. }
  43. }
  44. /**
  45. * Make wikitext formatting usage.
  46. * @param {String} [text] - The text to modify.
  47. * @param {Boolean} [showEmbed] - If the text is used in an embed.
  48. * @param {import('./wiki.js')} [wiki] - The wiki.
  49. * @param {String} [title] - The page title.
  50. * @param {Boolean} [fullWikitext] - If the text can contain full wikitext.
  51. * @returns {String}
  52. */
  53. function toFormatting(text = '', showEmbed = false, wiki, title = '', fullWikitext = false) {
  54. if ( showEmbed ) return toMarkdown(text, wiki, title, fullWikitext);
  55. else return toPlaintext(text, fullWikitext);
  56. };
  57. /**
  58. * Turns wikitext formatting into markdown.
  59. * @param {String} [text] - The text to modify.
  60. * @param {import('./wiki.js')} wiki - The wiki.
  61. * @param {String} [title] - The page title.
  62. * @param {Boolean} [fullWikitext] - If the text can contain full wikitext.
  63. * @returns {String}
  64. */
  65. function toMarkdown(text = '', wiki, title = '', fullWikitext = false) {
  66. text = text.replace( /[()\\]/g, '\\$&' );
  67. var link = null;
  68. var regex = /\[\[(?:([^\|\]]+)\|)?([^\]]+)\]\]([a-z]*)/g;
  69. while ( ( link = regex.exec(text) ) !== null ) {
  70. var pagetitle = ( link[1] || link[2] );
  71. var page = wiki.toLink(( /^[#\/]/.test(pagetitle) ? title + ( pagetitle.startsWith( '/' ) ? pagetitle : '' ) : pagetitle ), '', ( pagetitle.startsWith( '#' ) ? pagetitle.substring(1) : '' ), true);
  72. text = text.replaceSave( link[0], '[' + link[2] + link[3] + '](' + page + ')' );
  73. }
  74. if ( title !== '' ) {
  75. regex = /\/\*\s*([^\*]+?)\s*\*\/\s*(.)?/g;
  76. while ( ( link = regex.exec(text) ) !== null ) {
  77. text = text.replaceSave( link[0], '[→' + link[1] + '](' + wiki.toLink(title, '', link[1], true) + ')' + ( link[2] ? ': ' + link[2] : '' ) );
  78. }
  79. }
  80. if ( fullWikitext ) {
  81. regex = /\[(?:https?:)?\/\/([^ ]+) ([^\]]+)\]/g;
  82. while ( ( link = regex.exec(text) ) !== null ) {
  83. text = text.replaceSave( link[0], '[' + link[2] + '](https://' + link[1] + ')' );
  84. }
  85. return htmlToDiscord( text, true, true ).replaceSave( /'''/g, '**' ).replaceSave( /''/g, '*' );
  86. }
  87. return escapeFormatting(text, true);
  88. };
  89. /**
  90. * Removes wikitext formatting.
  91. * @param {String} [text] - The text to modify.
  92. * @param {Boolean} [fullWikitext] - If the text can contain full wikitext.
  93. * @returns {String}
  94. */
  95. function toPlaintext(text = '', fullWikitext = false) {
  96. text = text.replace( /\[\[(?:[^\|\]]+\|)?([^\]]+)\]\]/g, '$1' ).replace( /\/\*\s*([^\*]+?)\s*\*\//g, '→$1:' );
  97. if ( fullWikitext ) {
  98. return htmlToPlain( text.replace( /\[(?:https?:)?\/\/(?:[^ ]+) ([^\]]+)\]/g, '$1' ) );
  99. }
  100. else return escapeFormatting(text);
  101. };
  102. /**
  103. * Change HTML text to plain text.
  104. * @param {String} html - The text in HTML.
  105. * @returns {String}
  106. */
  107. function htmlToPlain(html) {
  108. var text = '';
  109. var reference = false;
  110. var parser = new htmlparser.Parser( {
  111. onopentag: (tagname, attribs) => {
  112. if ( tagname === 'sup' && attribs.class === 'reference' ) reference = true;
  113. if ( tagname === 'br' ) text += '\n';
  114. },
  115. ontext: (htmltext) => {
  116. if ( !reference ) text += escapeFormatting(htmltext);
  117. },
  118. onclosetag: (tagname) => {
  119. if ( tagname === 'sup' ) reference = false;
  120. }
  121. } );
  122. parser.write( html );
  123. parser.end();
  124. return text;
  125. };
  126. /**
  127. * Change HTML text to markdown text.
  128. * @param {String} html - The text in HTML.
  129. * @param {Boolean[]} [escapeArgs] - Arguments for the escaping of text formatting.
  130. * @returns {String}
  131. */
  132. function htmlToDiscord(html, ...escapeArgs) {
  133. var text = '';
  134. var parser = new htmlparser.Parser( {
  135. onopentag: (tagname, attribs) => {
  136. switch (tagname) {
  137. case 'b':
  138. text += '**';
  139. break;
  140. case 'i':
  141. text += '*';
  142. break;
  143. case 's':
  144. text += '~~';
  145. break;
  146. case 'u':
  147. text += '__';
  148. break;
  149. }
  150. },
  151. ontext: (htmltext) => {
  152. text += escapeFormatting(htmltext, ...escapeArgs);
  153. },
  154. onclosetag: (tagname) => {
  155. switch (tagname) {
  156. case 'b':
  157. text += '**';
  158. break;
  159. case 'i':
  160. text += '*';
  161. break;
  162. case 's':
  163. text += '~~';
  164. break;
  165. case 'u':
  166. text += '__';
  167. break;
  168. }
  169. }
  170. } );
  171. parser.write( html );
  172. parser.end();
  173. return text;
  174. };
  175. /**
  176. * Escapes formatting.
  177. * @param {String} [text] - The text to modify.
  178. * @param {Boolean} [isMarkdown] - The text contains markdown links.
  179. * @param {Boolean} [keepLinks] - Don't escape non-markdown links.
  180. * @returns {String}
  181. */
  182. function escapeFormatting(text = '', isMarkdown = false, keepLinks = false) {
  183. if ( !isMarkdown ) text = text.replace( /[()\\]/g, '\\$&' );
  184. if ( !keepLinks ) text = text.replace( /\/\//g, '\\$&' );
  185. return text.replace( /[`_*~:<>{}@|]/g, '\\$&' );
  186. };
  187. module.exports = {
  188. got,
  189. parse_infobox,
  190. toFormatting,
  191. toMarkdown,
  192. toPlaintext,
  193. htmlToPlain,
  194. htmlToDiscord,
  195. escapeFormatting
  196. };