Jump to content

User:GeneralNotability/sharedhelpermethods.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// <nowiki>
// @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<string>} The text of the page, '' if the page does not exist.
 */
async function helperCommon_getPageText(title, show, sectionId = null) {
	const $statusLine = $('<li>');
	if (show) {
		// Actually display the statusLine
		$('#progress', document).append($statusLine);
	}
	// Build the link element (use JQuery so we get escapes and such)
	const $link = $('<a>').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('<b>Failed to get ' + $link.html() + '</b>: ' + 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 = $('<li>').appendTo($('#progress', document));
	const $link = $('<a>').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('<b>Edit failed on ' + $link.html() + '</b>: ' + 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 = $('<li>').appendTo($('#progress', document));
	const $sourceLink = $('<a>').attr('href', mw.util.getUrl(sourcePage)).attr('title', sourcePage).text(sourcePage);
	const $destLink = $('<a>').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('<b>Failed to move ' + $sourceLink.prop('outerHTML') + ' to ' + $destLink.prop('outerHTML') + '</b>: ' + 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 = $('<li>').appendTo($('#progress', document));
	const $link = $('<a>').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('<b>Failed to purge ' + $link.prop('outerHTML') + '</b>: ' + 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 = $('<li>').appendTo($('#progress', document));
	const $link = $('<a>').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('<b>Failed to block ' + $link.prop('outerHTML') + '</b>: ' + error);
	}
}

/**
 * Get whether a user is currently blocked
 *
 * @param {string} user Username
 * @return {Promise<string>} 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<number>} 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 = $('<li>').appendTo($('#progress', document));
	const $link = $('<a>').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('<b>Failed to delete ' + $link.prop('outerHTML') + '</b>: ' + 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 = $('<li>').appendTo($('#progress', document));
	const $link = $('<a>').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('<b>Failed to undelete ' + $link.prop('outerHTML') + '</b>: ' + error);
	}
}

/**
 * Render a snippet of wikitext
 *
 * @param {string} title Page title
 * @param {string} text Text to render
 * @return {Promise<string>} 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;
	}
}
// </nowiki>