From 436acf72990ccdb73babd45831ff334773cfc8b7 Mon Sep 17 00:00:00 2001 From: ewin Date: Thu, 4 Sep 2025 23:04:54 -0400 Subject: [PATCH] improve killpage logic relying on an interval causes the process to never exit, because the interval is still running in the background. this is bad for oneshot scripts like ours. instead the check works by comparing the current date to the last known date any time a post request is sent --- lib/api/mediawiki.js | 69 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 14 deletions(-) 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; + } }