User:GeneralNotability/sharedhelpermethods.js

// // @ts-check // "Building-block" functions to wrap basic API calls // These are shared by spihelper and arbhelper

// Intentionally non-const - set this when you import to get an ad for whatever script you're using let helperCommon_ADVERT = '';

/** * Get the text of a page. Not that complicated. * * @param {string} title Title of the page to get the contents of * @param {boolean} show Whether to show page fetch progress on-screen * @param {?number} [sectionId=null] Section to retrieve, setting this to null will *                                  retrieve the entire page * * @return {Promise } The text of the page, '' if the page does not exist. */ async function helperCommon_getPageText(title, show, sectionId = null) { const $statusLine = $(''); if (show) { // Actually display the statusLine $('#progress', document).append($statusLine); }	// Build the link element (use JQuery so we get escapes and such) const $link = $('').attr('href', mw.util.getUrl(title)).attr('title', title).text(title); $statusLine.html('Getting page ' + $link.prop('outerHTML'));

const finalTitle = helperCommon_stripXWikiPrefix(title);

const request = { action: 'query', prop: 'revisions', rvprop: 'content', rvslots: 'main', indexpageids: true, titles: finalTitle };

if (sectionId) { request.rvsection = sectionId; }

try { const response = await helperCommon_getAPI(title).get(request); const pageid = response.query.pageids[0];

if (pageid === '-1') { $statusLine.html('Page ' + $link.html + ' does not exist'); return ''; }		$statusLine.html('Got ' + $link.html); return response.query.pages[pageid].revisions[0].slots.main['*']; } catch (error) { $statusLine.addClass('helperCommon-errortext').html('Failed to get ' + $link.html + ': ' + error); return ''; } }

/** * * @param {string} title Title of the page to edit * @param {string} newtext New content of the page * @param {string} summary Edit summary to use for the edit * @param {boolean} createonly Only try to create the page - if false, *                            will fail if the page already exists * @param {string} watch What watchlist setting to use when editing - decides *                      whether the edited page will be watched * @param {?number} baseRevId Base revision ID, used to detect edit conflicts. If 0, *                          we'll grab the current page ID. * @param {?number} [sectionId=null] Section to edit - if null, edits the whole page */ async function helperCommon_editPage(title, newtext, summary, createonly, watch, baseRevId = null, sectionId = null) { const $statusLine = $('').appendTo($('#progress', document)); const $link = $('').attr('href', mw.util.getUrl(title)).attr('title', title).text(title);

$statusLine.html('Editing ' + $link.prop('outerHTML'));

if (!baseRevId) { baseRevId = await helperCommon_getPageRev(title); }	const api = helperCommon_getAPI(title); const finalTitle = helperCommon_stripXWikiPrefix(title);

const request = { action: 'edit', watchlist: watch, summary: summary + helperCommon_ADVERT, text: newtext, title: finalTitle, createonly: createonly, baserevid: baseRevId };	if (sectionId) { request.section = sectionId; }	try { await api.postWithToken('csrf', request); $statusLine.html('Saved ' + $link.prop('outerHTML')); } catch (error) { $statusLine.addClass('spiHelper-errortext').html('Edit failed on ' + $link.html + ': ' + error); } } /** * Moves a page. Exactly what it sounds like. * * @param {string} sourcePage Title of the source page (page we're moving) * @param {string} destPage Title of the destination page (page we're moving to) * @param {string} summary Edit summary to use for the move * @param {boolean} ignoreWarnings Whether to ignore warnings on move (used to force-move one page over another) */ async function helperCommon_movePage(sourcePage, destPage, summary, ignoreWarnings) { // Move a page from sourcePage to destPage. Not that complicated. 'use strict';

const api = helperCommon_getAPI(sourcePage);

const $statusLine = $('').appendTo($('#progress', document)); const $sourceLink = $('').attr('href', mw.util.getUrl(sourcePage)).attr('title', sourcePage).text(sourcePage); const $destLink = $('').attr('href', mw.util.getUrl(destPage)).attr('title', destPage).text(destPage);

$statusLine.html('Moving ' + $sourceLink.prop('outerHTML') + ' to ' + $destLink.prop('outerHTML'));

try { await api.postWithToken('csrf', {			action: 'move',			from: sourcePage,			to: destPage,			reason: summary + helperCommon_ADVERT,			noredirect: true,			movesubpages: true,			ignoreWarnings: ignoreWarnings		}); $statusLine.html('Moved ' + $sourceLink.prop('outerHTML') + ' to ' + $destLink.prop('outerHTML')); } catch (error) { $statusLine.addClass('spihelper-errortext').html('Failed to move ' + $sourceLink.prop('outerHTML') + ' to ' + $destLink.prop('outerHTML') + ': ' + error); } }

/** * Purges a page's cache * * * @param {string} title Title of the page to purge */ async function helperCommon_purgePage(title) { // Forces a cache purge on the selected page 'use strict'; const $statusLine = $('').appendTo($('#progress', document)); const $link = $('').attr('href', mw.util.getUrl(title)).attr('title', title).text(title); $statusLine.html('Purging ' + $link.prop('outerHTML')); const strippedTitle = helperCommon_stripXWikiPrefix(title);

const api = helperCommon_getAPI(title); try { await api.postWithToken('csrf', {			action: 'purge',			titles: strippedTitle		}); $statusLine.html('Purged ' + $link.prop('outerHTML')); } catch (error) { $statusLine.addClass('spihelper-errortext').html('Failed to purge ' + $link.prop('outerHTML') + ': ' + error); } }

/** * Blocks a user. * * @param {string} user Username to block * @param {string} duration Duration of the block * @param {string} reason Reason to log for the block * @param {boolean} reblock Whether to reblock - if false, nothing will happen if the *                         target user is already blocked * @param {boolean} anononly For IPs, whether this is an anonymous-only block (alternative is *                          that logged-in users with the IP are also blocked) * @param {boolean} accountcreation Whether to permit the user to create new accounts * @param {boolean} autoblock Whether to apply an autoblock to the user's IP * @param {boolean} talkpage Whether to revoke talkpage access * @param {boolean} email Whether to block email * @param {string} watchBlockedUser Watchlist setting for whether to watch the newly-blocked user */ async function helperCommon_blockUser(user, duration, reason, reblock, anononly, accountcreation, autoblock, talkpage, email, watchBlockedUser) { 'use strict'; const userPage = 'User:' + user; const $statusLine = $('').appendTo($('#progress', document)); const $link = $('').attr('href', mw.util.getUrl(userPage)).attr('title', userPage).text(user); $statusLine.html('Blocking ' + $link.prop('outerHTML'));

const api = helperCommon_getAPI(user); try { await api.postWithToken('csrf', {			action: 'block',			expiry: duration,			reason: reason,			reblock: reblock,			anononly: anononly,			nocreate: accountcreation,			autoblock: autoblock,			allowusertalk: !talkpage,			noemail: email,			watchuser: watchBlockedUser,			user: user		}); $statusLine.html('Blocked ' + $link.prop('outerHTML')); } catch (error) { $statusLine.addClass('spihelper-errortext').html('Failed to block ' + $link.prop('outerHTML') + ': ' + error); } }

/** * Get whether a user is currently blocked * * @param {string} user Username * @return {Promise } Block reason, empty string if not blocked */ async function helperCommon_getUserBlockReason(user) { 'use strict'; // This is not something which should ever be cross-wiki const api = helperCommon_getAPI(user); try { const response = await api.get({			action: 'query',			list: 'blocks',			bklimit: '1',			bkusers: user,			bkprop: 'user|reason'		}); if (response.query.blocks.length === 0) { // If the length is 0, then the user isn't blocked return ''; }		return response.query.blocks[0].reason; } catch (error) { return ''; } }

/** * Get a page's latest revision ID - useful for preventing edit conflicts * * @param {string} title Title of the page * @return {Promise } Latest revision of a page, 0 if it doesn't exist */ async function helperCommon_getPageRev(title) { 'use strict';

const finalTitle = helperCommon_stripXWikiPrefix(title); const request = { action: 'query', prop: 'revisions', rvslots: 'main', indexpageids: true, titles: finalTitle };

try { const response = await helperCommon_getAPI(title).get(request); const pageid = response.query.pageids[0]; if (pageid === '-1') { return 0; }		return response.query.pages[pageid].revisions[0].revid; } catch (error) { return 0; } }

/** * Delete a page. Admin-only function. * * @param {string} title Title of the page to delete * @param {string} reason Reason to log for the page deletion */ async function helperCommon_deletePage(title, reason) { 'use strict';

const $statusLine = $('').appendTo($('#progress', document)); const $link = $('').attr('href', mw.util.getUrl(title)).attr('title', title).text(title); $statusLine.html('Deleting ' + $link.prop('outerHTML'));

const api = helperCommon_getAPI(title); try { await api.postWithToken('csrf', {			action: 'delete',			title: title,			reason: reason		}); $statusLine.html('Deleted ' + $link.prop('outerHTML')); } catch (error) { $statusLine.addClass('spihelper-errortext').html('Failed to delete ' + $link.prop('outerHTML') + ': ' + error); } }

/** * Undelete a page (or, if the page exists, undelete deleted revisions). Admin-only function * * @param {string} title Title of the pgae to undelete * @param {string} reason Reason to log for the page undeletion */ async function helperCommon_undeletePage(title, reason) { 'use strict'; const $statusLine = $('').appendTo($('#progress', document)); const $link = $('').attr('href', mw.util.getUrl(title)).attr('title', title).text(title); $statusLine.html('Undeleting ' + $link.prop('outerHTML'));

const api = helperCommon_getAPI(title); try { await api.postWithToken('csrf', {			action: 'undelete',			title: title,			reason: reason		}); $statusLine.html('Undeleted ' + $link.prop('outerHTML')); } catch (error) { $statusLine.addClass('spihelper-errortext').html('Failed to undelete ' + $link.prop('outerHTML') + ': ' + error); } }

/** * Render a snippet of wikitext * * @param {string} title Page title * @param {string} text Text to render * @return {Promise } Rendered version of the text */ async function helperCommon_renderText(title, text) { 'use strict';

const request = { action: 'parse', prop: 'text', pst: 'true', text: text, title: title };

try { const response = await helperCommon_getAPI(title).get(request); return response.parse.text['*']; } catch (error) { console.error('Error rendering text: ' + error); return ''; } }

/** * Given a page title, get an API to operate on that page * * @param {string} title Title of the page we want the API for * @return {Object} MediaWiki Api/ForeignAPI for the target page's wiki */ function helperCommon_getAPI(title) { 'use strict'; if (title.startsWith('m:') || title.startsWith('meta:')) { return new mw.ForeignApi('https://meta.wikimedia.org/w/api.php'); } else { return new mw.Api; } }

/** * Removes the interwiki prefix from a page title * * @param {*} title Page name including interwiki prefix * @return {string} Just the page name */ function helperCommon_stripXWikiPrefix(title) { // TODO: This only works with single-colon names, make it more robust 'use strict'; if (title.startsWith('m:') || title.startsWith('meta:')) { return title.slice(title.indexOf(':') + 1); } else { return title; } } //