wiki.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. const util = require('util');
  2. const {defaultSettings, wikiProjects} = require('./default.json');
  3. /**
  4. * A wiki.
  5. * @class Wiki
  6. */
  7. class Wiki extends URL {
  8. /**
  9. * Creates a new wiki.
  10. * @param {String|URL|Wiki} [wiki] - The wiki script path.
  11. * @param {String|URL|Wiki} [base] - The base for the wiki.
  12. * @constructs Wiki
  13. */
  14. constructor(wiki = defaultSettings.wiki, base = defaultSettings.wiki) {
  15. super(wiki, base);
  16. this.protocol = 'https';
  17. let articlepath = '/index.php?title=$1';
  18. if ( this.isFandom() ) articlepath = this.pathname + 'wiki/$1';
  19. if ( this.isGamepedia() ) articlepath = '/$1';
  20. let project = wikiProjects.find( project => this.hostname.endsWith( project.name ) );
  21. if ( project ) {
  22. let regex = ( this.host + this.pathname ).match( new RegExp( '^' + project.regex + project.scriptPath + '$' ) );
  23. if ( regex ) articlepath = 'https://' + regex[1] + project.articlePath + '$1';
  24. }
  25. this.articlepath = articlepath;
  26. this.mainpage = '';
  27. this.centralauth = 'local';
  28. this.miraheze = this.hostname.endsWith( '.miraheze.org' );
  29. }
  30. /**
  31. * @type {String}
  32. */
  33. get articlepath() {
  34. return this.articleURL.pathname + this.articleURL.search;
  35. }
  36. set articlepath(path) {
  37. this.articleURL = new articleURL(path, this);
  38. }
  39. /**
  40. * @type {String}
  41. */
  42. get mainpage() {
  43. return this.articleURL.mainpage;
  44. }
  45. set mainpage(title) {
  46. this.articleURL.mainpage = title;
  47. }
  48. /**
  49. * Updates the wiki url.
  50. * @param {Object} siteinfo - Siteinfo from the wiki API.
  51. * @param {String} siteinfo.server - Server of the wiki with protocol. (For legacy Fandom wikis)
  52. * @param {String} siteinfo.servername - Hostname of the wiki.
  53. * @param {String} siteinfo.scriptpath - Scriptpath of the wiki.
  54. * @param {String} siteinfo.articlepath - Articlepath of the wiki.
  55. * @param {String} siteinfo.mainpage - Main page of the wiki.
  56. * @param {String} siteinfo.centralidlookupprovider - Central auth of the wiki.
  57. * @param {String} siteinfo.logo - Logo of the wiki.
  58. * @returns {Wiki}
  59. */
  60. updateWiki({server, servername, scriptpath, articlepath, mainpage, centralidlookupprovider, logo}) {
  61. if ( servername ) this.hostname = servername;
  62. else this.hostname = server.replace( /^(?:https?:)?\/\//, '' );
  63. this.pathname = scriptpath + '/';
  64. this.articlepath = articlepath;
  65. this.mainpage = mainpage;
  66. this.centralauth = centralidlookupprovider;
  67. this.miraheze = /^(?:https?:)?\/\/static\.miraheze\.org\//.test(logo);
  68. return this;
  69. }
  70. /**
  71. * Check for a Fandom wiki.
  72. * @param {Boolean} [includeGP] - If Gamepedia wikis are included.
  73. * @returns {Boolean}
  74. */
  75. isFandom(includeGP = true) {
  76. return ( this.hostname.endsWith( '.fandom.com' ) || this.hostname.endsWith( '.wikia.org' )
  77. || ( includeGP && this.hostname.endsWith( '.gamepedia.com' ) ) );
  78. }
  79. /**
  80. * Check for a Gamepedia wiki.
  81. * @returns {Boolean}
  82. */
  83. isGamepedia() {
  84. return this.hostname.endsWith( '.gamepedia.com' );
  85. }
  86. /**
  87. * Check for a Miraheze wiki.
  88. * @returns {Boolean}
  89. */
  90. isMiraheze() {
  91. return this.miraheze;
  92. }
  93. /**
  94. * Check for CentralAuth.
  95. * @returns {Boolean}
  96. */
  97. hasCentralAuth() {
  98. return this.centralauth === 'CentralAuth';
  99. }
  100. /**
  101. * Check if a wiki is missing.
  102. * @param {String} [message] - Error message or response url.
  103. * @param {Number} [statusCode] - Status code of the response.
  104. * @returns {Boolean}
  105. */
  106. noWiki(message = '', statusCode = 0) {
  107. if ( statusCode === 410 || statusCode === 404 ) return true;
  108. if ( !this.isFandom() ) return false;
  109. if ( this.hostname.startsWith( 'www.' ) || message.startsWith( 'https://www.' ) ) return true;
  110. return [
  111. 'https://community.fandom.com/wiki/Community_Central:Not_a_valid_community?from=' + this.hostname,
  112. this + 'language-wikis'
  113. ].includes( message.replace( /Unexpected token < in JSON at position 0 in "([^ ]+)"/, '$1' ) );
  114. }
  115. /**
  116. * Get an URI encoded link.
  117. * @param {String} [title] - Name of the page.
  118. * @returns {String}
  119. */
  120. toDescLink(title = this.mainpage) {
  121. return this.articleURL.href.replace( '$1', encodeURIComponent( title.replace( / /g, '_' ) ) );
  122. }
  123. /**
  124. * Get a page link.
  125. * @param {String} [title] - Name of the page.
  126. * @param {URLSearchParams} [querystring] - Query arguments of the page.
  127. * @param {String} [fragment] - Fragment of the page.
  128. * @param {Boolean} [isMarkdown] - Use the link in markdown.
  129. * @returns {String}
  130. */
  131. toLink(title = '', querystring = '', fragment = '', isMarkdown = false) {
  132. querystring = new URLSearchParams(querystring);
  133. if ( !querystring.toString().length ) title = ( title || this.mainpage );
  134. title = title.replace( / /g, '_' );
  135. let link = new URL(this.articleURL);
  136. link.pathname = link.pathname.replace( '$1', title.replace( /\\/g, '%5C' ) );
  137. link.searchParams.forEach( (value, name, searchParams) => {
  138. if ( value.includes( '$1' ) ) {
  139. if ( !title ) searchParams.delete(name);
  140. else searchParams.set(name, value.replace( '$1', title ));
  141. }
  142. } );
  143. querystring.forEach( (value, name) => {
  144. link.searchParams.append(name, value);
  145. } );
  146. let output = decodeURI( link ).replace( /\\/g, '%5C' ).replace( /@(here|everyone)/g, '%40$1' );
  147. if ( isMarkdown ) output = output.replace( /([\(\)])/g, '\\$1' );
  148. return output + Wiki.toSection(fragment);
  149. }
  150. /**
  151. * Encode a page title.
  152. * @param {String} [title] - Title of the page.
  153. * @returns {String}
  154. * @static
  155. */
  156. static toTitle(title = '') {
  157. return title.replace( / /g, '_' ).replace( /[?&%\\]/g, (match) => {
  158. return '%' + match.charCodeAt().toString(16).toUpperCase();
  159. } ).replace( /@(here|everyone)/g, '%40$1' ).replace( /[()]/g, '\\$&' );
  160. };
  161. /**
  162. * Encode a link section.
  163. * @param {String} [fragment] - Fragment of the page.
  164. * @returns {String}
  165. * @static
  166. */
  167. static toSection(fragment = '') {
  168. if ( !fragment ) return '';
  169. fragment = fragment.replace( / /g, '_' );
  170. if ( !/['"`^{}<>|\\]|@(everyone|here)/.test(fragment) ) return '#' + fragment;
  171. return '#' + encodeURIComponent( fragment ).replace( /[!'()*~]/g, (match) => {
  172. return '%' + match.charCodeAt().toString(16).toUpperCase();
  173. } ).replace( /%3A/g, ':' ).replace( /%/g, '.' );
  174. }
  175. [util.inspect.custom](depth, opts) {
  176. if ( typeof depth === 'number' && depth < 0 ) return this;
  177. const wiki = {
  178. href: this.href,
  179. origin: this.origin,
  180. protocol: this.protocol,
  181. username: this.username,
  182. password: this.password,
  183. host: this.host,
  184. hostname: this.hostname,
  185. port: this.port,
  186. pathname: this.pathname,
  187. search: this.search,
  188. searchParams: this.searchParams,
  189. hash: this.hash,
  190. articlepath: this.articlepath,
  191. articleURL: this.articleURL,
  192. mainpage: this.mainpage
  193. }
  194. return 'Wiki ' + util.inspect(wiki, opts);
  195. }
  196. }
  197. /**
  198. * An article URL.
  199. * @class articleURL
  200. */
  201. class articleURL extends URL {
  202. /**
  203. * Creates a new article URL.
  204. * @param {String|URL|Wiki} [articlepath] - The article path.
  205. * @param {String|URL|Wiki} [wiki] - The wiki.
  206. * @constructs articleURL
  207. */
  208. constructor(articlepath = '/index.php?title=$1', wiki) {
  209. super(articlepath, wiki);
  210. this.protocol = 'https';
  211. this.mainpage = '';
  212. }
  213. [util.inspect.custom](depth, opts) {
  214. if ( typeof depth === 'number' && depth < 0 ) return this;
  215. if ( typeof depth === 'number' && depth < 2 ) {
  216. var link = this.href;
  217. var mainpage = link.replace( '$1', ( this.mainpage || 'Main Page' ).replace( / /g, '_' ) );
  218. return 'articleURL { ' + util.inspect(link, opts) + ' => ' + util.inspect(mainpage, opts) + ' }';
  219. }
  220. return super[util.inspect.custom](depth, opts);
  221. }
  222. }
  223. module.exports = Wiki;