wiki.js 9.2 KB

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