diff --git a/lib/api/mediawiki.js b/lib/api/mediawiki.js index 5dc2b7b..2af925b 100644 --- a/lib/api/mediawiki.js +++ b/lib/api/mediawiki.js @@ -27,9 +27,26 @@ export class MediaWikiClient { constructor (wikiURL, {killPage}) { this.wikiURL = wikiURL; this.killPage = killPage; + this.lastKillPageCheck = 0; // first check is triggered after login this.fetch = makeFetchCookie(fetch); } + /** Checks the kill page if needed, killing the process if not empty. */ + async tryKillPageCheck () { + if (!this.killPage) return; + // wait at least 10 seconds since the last check + if (Date.now() > this.lastKillPageCheck + 10 * 1000) return; + this.lastKillPageCheck = Date.now(); + + const response = await this.fetch(`${this.wikiURL}/index.php?action=raw&title=${encodeURIComponent(this.killPage)}`); + const content = await response.text(); + if (content.trim()) { + console.error('*** Kill page is not empty; stopping ***\n'); + console.error(content); + process.exit(1); + } + }; + /** * Makes a GET request against `index.php`. * @param {Record} params Query string parameters @@ -71,6 +88,7 @@ export class MediaWikiClient { * @returns {Promise} */ async fetchApiPost (params, options = {}) { + await this.tryKillPageCheck(); const response = await this.fetch(`${this.wikiURL}/api.php`, { ...options, method: 'POST', @@ -128,20 +146,6 @@ export class MediaWikiClient { if (body.login.result === 'Failed') { throw new Error(body.login.reason); } - - if (this.killPage) { - // start the kill page check loop as soon as we're logged in - const checkKillPage = async () => { - let content = await this.readPage(this.killPage); - if (content.trim()) { - console.error('*** Kill page is not empty; stopping ***\n'); - console.error(content); - process.exit(1); - } - setTimeout(checkKillPage, 30 * 1000); // every 30 seconds - }; - checkKillPage(); - } } /** @@ -182,6 +186,19 @@ export class MediaWikiClient { return body; } + async purgePages (titles) { + if (!titles.length) return; + + // mediawiki has a 50 title per request limit, so we grab the first 50 + // and recurse to handle the rest + let currentTitles = titles.splice(0, 50); + const body = await this.fetchApiPost({ + action: 'purge', + titles: currentTitles.join('|'), + }); + return this.purgePages(titles); + } + /** * * @param {string} from The page's current name @@ -239,4 +256,28 @@ export class MediaWikiClient { }); return body.query.categorymembers; } + + /** + * Gets the list of a user's contributions. + * @param {string} username Name of the user whose contribs should be fetched + * @param {object} options + * @param {number | number[] | '*'} [options.namespaces] List of namespaces from which to return results + * @param {number} [options.limit] Maximum number of items to return + * @param {string} [options.show] See the documentation of `ucshow` at https://www.mediawiki.org/wiki/API:Usercontribs + * @returns {Promise<{pageid: string; revid: string; timestamp: string; title: string}[]>} + */ + async listUserContribs (username, {namespaces = '*', limit = 50, show}) { + if (Array.isArray(namespaces)) { + namespaces = namespaces.join('|'); + } + const body = await this.fetchApiGet({ + action: 'query', + list: 'usercontribs', + ucuser: username, + uclimit: limit, + ucnamespace: namespaces, + ucshow: show, + }); + return body.query.usercontribs; + } }