Участник:A particle for world to form/purge.js

/* Добавляет кнопку «Очистить кэш» в меню «инструменты»; её поведение зависит
 * от пространства имён:
 * — Для категорий сбрасывает кэш на всех входящих в них страницах.
 * — Для файлов и шаблонов сбрасиывает кэш во всех включениях.
 * — В основном пространстве сбрасывает кэш на страницах, ссылающихся на данную.
 * — Для подстраниц пространств имён «Участник» и «Проект» сбрасывает кэш на
 *   всех страницах, на которые ссылается данная. Можно использовать для сброса
 *   кэша на произвольном списке страниц, собранного, например, через PetScan.
 * Может также делать нулевую правку (ctrl+клик).
 * 
 * По-умолчанию кэш сбрасывается с максимально разрешённой скоростью. Изменить
 * это можно, задав глобальную переменную purgejs_ratelimit:
 *     var purgejs_ratelimit = { hits: страниц_за_запрос, seconds: задержка };
 * Например:
 *     var purgejs_ratelimit = { hits: 50, seconds: 0 }; // 50 страниц за запрос
 *     var purgejs_ratelimit = { hits: 1, seconds: 1 }; // запрос в секунду
 * Учтите, что при совершении нулевой правки будет сбрасываться одна страница
 * на запрос независимо от присвоенных значений.
 * 
 * Вдохновлено скриптом IKhitron:
 * https://ru.wikipedia.org/wiki/Участник:IKhitron/purge.js
 * 
 * Copyright (c) 2017 Facenapalm
 * Licensed under the terms of the MIT license.
 */

mw.loader.using(['mediawiki.api']).then(function() {
	"use strict";

	var api = new mw.Api();
	var element = null;

	var ratelimit = null;
	var request = null;
	var count = 0;
	
	mw.messages.set( {
		'purgejs-link': 'Purge',
		'purgejs-done': 'Done.',
		'purgejs-error': 'Error.',
		'purgejs-purged': 'Purged: $1',
		'purgejs-tooltip-ctrl': 'Ctrl+Click to make a null edit.',
		'purgejs-tooltip-backlinks': 'Purge the cache on pages that link to this page.',
		'purgejs-tooltip-category': 'Purge the cache on pages in this category.',
		'purgejs-tooltip-file': 'Purge the cache on pages that use this file.',
		'purgejs-tooltip-template': 'Purge the cache on pages that use this template.',
		'purgejs-tooltip-linked': 'Purge the cache on pages that are linked from this page.'
	} );
	
	if ( mw.config.get('wgUserLanguage' ) === 'ru' ) {
		mw.messages.set( {
			'purgejs-link': 'Сбросить кэш',
			'purgejs-done': 'Готово.',
			'purgejs-error': 'Ошибка.',
			'purgejs-purged': 'Очищено: $1',
			'purgejs-tooltip-ctrl': 'Ctrl+клик — сделать нулевую правку.',
			'purgejs-tooltip-backlinks': 'Сбросить кэш на страницах, ссылающихся на эту.',
			'purgejs-tooltip-category': 'Сбросить кэш на страницах, включённых в эту категорию.',
			'purgejs-tooltip-file': 'Сбросить кэш на страницах с этим файлом.',
			'purgejs-tooltip-template': 'Сбросить кэш на страницах, включающих этот шаблон.',
			'purgejs-tooltip-linked': 'Сбросить кэш на страницах, на которые ведут ссылки с этой.'
		} );
	}

	function status(message) {
		message = message === undefined ? mw.message( 'purgejs-purged', count ).text() : message;
		if (element)
			element.text(message);
	}

	function error(message) {
		status( mw.message( 'purgejs-error' ).text() );
		message = message === undefined ? "unknown error" : message;
		console.log("purge.js: " + message);
	}

	function calcGCD(x, y) {
		if (x < y) {
			var tmp = x;
			x = y;
			y = tmp;
		}
		while (true) {
			if (y === 0)
				return x;
			x %= y;
			if (x === 0)
				return y;
			y %= x;
		}
	}

	function updateRatelimit(callback) {
		if (typeof purgejs_ratelimit !== "undefined") {
			ratelimit = purgejs_ratelimit;
			if (callback)
				callback();
			return;
		}

		api.get({
			meta: 'userinfo',
			uiprop: 'rights|ratelimits'
		}).done(function(data) {
			if (data && data.query && data.query.userinfo) {
				var ratelimits = data.query.userinfo.ratelimits;
				var rights = data.query.userinfo.rights;
				if (ratelimits && ratelimits.purge) {
					if (ratelimits.purge.user) {
						ratelimit = ratelimits.purge.user;
					} else if (ratelimits.purge.ip) {
						ratelimit = ratelimits.purge.ip;
					} else {
						return error("unable to parse ratelimits");
					}
					var gcd = calcGCD(ratelimit.hits, ratelimit.seconds);
					if (gcd > 1) {
						ratelimit.hits /= gcd;
						ratelimit.seconds /= gcd;
					}
				} else {
					if (rights && rights.indexOf("apihighlimits") !== -1) {
						ratelimit = { hits: 500, seconds: 0 };
					} else {
						ratelimit = { hits: 50, seconds: 0 };
					}
				}
			} else {
				return error("unable to get userinfo");
			}
			if (callback)
				callback();
		});
	}

	function processPurge(continueinfo) {
		api.post($.extend({}, request, continueinfo))
			.done(function(data) {
				if (data && data.purge && data.purge.length) {
					count += data.purge.length;
				} else {
					return error("empty response");
				}
				if (data.warnings) {
					error("warning received");
					console.log(data.warnings);
					return;
				}
				if (data["continue"]) {
					status();
					setTimeout(function() {
						processPurge(data["continue"]);
					}, 1000 * ratelimit.seconds);
				} else {
					status( mw.message( 'purgejs-done' ).text() );
				}
			})
			.fail(function() {
				error("request failed");
			});
	}

	function startPurge(prefix, generator, linkupdate) {
		request = { "action": "purge" };
		if (linkupdate) {
			request.forcelinkupdate = "1";
		}
		$.extend(request, generator);

		updateRatelimit(function() {
			var limit = prefix + "limit";
			if (linkupdate) {
				request[limit] = 1;
			} else {
				request[limit] = ratelimit.hits;
			}
			count = 0;
			status();
			processPurge(null);
		});
	}

	function addButton() {
		var pagename = mw.config.get("wgPageName");
		var namespace = mw.config.get("wgCanonicalNamespace");
		var action = mw.config.get("wgAction");
		var exists = mw.config.get("wgArticleId") !== 0;

		if (!(
			action === "view" ||
			namespace === "Category" && action === "edit" && !exists
		)) {
			return;
		}

		var prefix, generator;
		var comment = "";
		if (namespace === "") {
			prefix = "gbl";
			generator = {
				"generator": "backlinks",
				"gbltitle": pagename
			};
			comment = mw.message( 'purgejs-tooltip-backlinks' ).text();
		} else if (namespace === "File") {
			prefix = "gfu";
			generator = {
				"generator": "fileusage",
				"titles": pagename
			};
			comment = mw.message( 'purgejs-tooltip-file' ).text();
		} else if (namespace === "Template" || namespace === "Module") {
			prefix = "gti";
			generator = {
				"generator": "transcludedin",
				"titles": pagename
			};
			comment = mw.message( 'purgejs-tooltip-template' ).text();
		} else if (namespace === "Category") {
			prefix = "gcm";
			generator = {
				"generator": "categorymembers",
				"gcmtitle": pagename
			};
			comment = mw.message( 'purgejs-tooltip-category' ).text();
		} else if (
			(namespace === "User" || namespace === "Wikiproject") &&
			pagename.indexOf("/") != -1
		) {
			prefix = "gpl";
			generator = {
				"generator": "links",
				"titles": pagename
			};
			comment = mw.message( 'purgejs-tooltip-linked' ).text();
		}

		if (prefix && generator) {
			mw.loader.using( 'mediawiki.util', function() {
				var link = mw.util.addPortletLink(
					'p-tb',
					'#',
					mw.message( 'purgejs-link' ).text(),
					't-purge',
					comment + ' ' + mw.message( 'purgejs-tooltip-ctrl' ).text()
				);
				$( link ).find( 'a' ).on( 'click', function( ev ) {
					ev.preventDefault();
					startPurge(prefix, generator, ev.ctrlKey);
				} );
				
				element = $( '<div>' );
				$( link ).append( element );
			} );
		}
	}

	addButton();
});