MediaWiki:TopicBlockLog/code.js

// TopicBlockLog (v0.9.0 - test) // @author: The JoTS //   Creates an interwiki block report from wikis of similar topic. //   This allows an administrator to more easily identify editors who may be //    making edits with ill intent or identify potential "raiders".

// "Constants" var DELAY = 40, // 50 requests in 2 secs REQS_BEFORE_PAUSE = 60, // tbh, I don't know how many requests per sec is too many. PAUSE_DELAY = 80, DebugMsg = { // These messages are (mostly) not user facing.

// Debug warnings (not all are stored here) HAD_LOCAL_BLOCK: "Target user had been blocked on local wiki. Pre-existing logs available.", NO_LOCAL_BLOCK: "Target user has never been blocked on local wiki. Create log.", NOT_BLOCK_PAGE: "Page is not Special:Block or user does not have permission to view it.", // Script errors STATIC_USED_IN_INSTANCE: "Attempt to use a class static function from a class instance", // User configuration errors NO_SETUP_VARS: "No wiki group has been defined (or invalid). " + "An interwiki block report cannot be generated." }; // Run only on Special:Block if (mw.config.get('wgCanonicalSpecialPageName') === 'Block'		&& !$(".permissions-errors").length)

// Script body (function($, mw, window) {	if (window.TBL_DEBOUNCE) return;	window.TBL_DEBOUNCE = true;   ///********/	//* Data *//	/********///    var    config = mw.config.get([ "wgPageName", "wgUserLanguage", "wgDBname" //		"wgServer" ]),   requestMessages = [		// general		"pipe-separator",		"word-separator",		// user		"wall-message-wall-shorten",		"contribslink",		// block		"blocklog-showlog",		"blocklogentry",		"unblocklogentry"	];    config = $.extend(config, { usingLang: mw.util.getParamValue("uselang") || config.wgUserLanguage.split('-')[0], targetUser: config.wgPageName.split("/")[1] });   // No target (no /Username nor wpTarget=Username)    if (!(config.targetUser = config.targetUser        || mw.util.getParamValue("wpTarget"))) return;	/* Load system messages */    mw.loader.using('mediawiki.api').done(function { function loadMessages( messages ) { return (new mw.Api).get( {               action:     'query',                meta:       'allmessages',                ammessages:  messages.join('|'),                amlang:      config.usingLang            } ).then( function (data) {                $.each( data.query.allmessages, function ( i, message ) { if ( message.missing !== '' ) { mw.messages.set( message.name, message['*'] ); }               } );            } );        }        loadMessages(requestMessages).then(function {            $(blockedNotice).text(mw.message("blocklog-showlog").text);        }); });	/* Load topics dictionary */	/*importArticles({ type: 'script', articles: "TopicBlockLog/topics.js", });*/	///*******************/	//* Element Factory *//	/*******************///	function Make(tag, classes) {		// I found out too late that I cannot use the class declaration for JS on wikia		// So... here's a class of different syntax with fake static methods.		this.e = document.createElement(tag); // even if a class declaration could be used, cannot extend the class from DOM elements, yet.		if (typeof classes !== "undefined")			this.setClass(classes);	}	Make.prototype.isObject = true; // for static function checking	// Static class constructors	Make.prototype.link = function(index, page, ltext) {		if (this.__proto__.isObject) throw STATIC_USED_IN_INSTANCE;		var a = new Make('a');		a.e.setAttribute("href", index+encodeURIComponent(page));		a.e.setAttribute("title", page);		if (ltext) a.e.innerHTML = ltext; // don't user ternary. couldn't get it to work here.		else a.e.innerHTML = page;		return a;	} Make.prototype.toolLinks = function(mlinks) { if (this.__proto__.isObject) throw STATIC_USED_IN_INSTANCE; var tools = new Make('span', 'mw-usertoollinks') .contains('(');		for (i = 0; i < mlinks.length;)			tools.append(mlinks[i] + (++i !== mlinks.length ? (' '+mw.message("pipe-separator")+' ') : ')')); return tools; }	Make.prototype.comment = function(wiki, _comment) { if (this.__proto__.isObject) throw STATIC_USED_IN_INSTANCE; return new Make('i') .contains('(' + _comment.replace(/\[\[(.+?)(\|(.+?))?\]\]/g, function(full, wlink, _, wtext) {					return Make.link(wiki, wlink, wtext)}) + ')');	}	// Object methods Make.prototype.setClass = function(classes) { this.e.setAttribute("class", classes); return this; }	Make.prototype.contains = function(content) { this.e.innerHTML = content; return this; }	Make.prototype.append = function(content) { this.e.innerHTML += content; return this; }	// @override Make.prototype.toString = function { return this.e.outerHTML; } ///****************************/	//* Report Container & Flags *// /****************************///   var $blockLog = $(".mw-warning-with-logexcerpt"), blockedNotice, hadAnyBlock, nWikisParsed = 0, // # of wikis finished parsing nFinalParsed = 0; // # of wikis to be parsed at finish //		currParseIntv; if($blockLog.length) { // Target user had been blocked on local wiki. Pre-existing logs available. mw.log(DebugMsg.HAD_LOCAL_BLOCK); $blockLog.append(document.createElement("hr")); hadAnyBlock = true; } else { // Target user has never been blocked on local wiki. Create log. mw.log(DebugMsg.NO_LOCAL_BLOCK); $("#mw-content-text").append(           $blockLog = $(document.createElement("div"))                .addClass("mw-warning-with-logexcerpt")                .append(blockedNotice = document.createElement("p"))        ); hadAnyBlock = false; }	$loading = $(		// http://fandomdesignsystem.com/#/components/progress-indicators		' \			 \				 \			 \		 ') .appendTo("#mw-content-text"); $blockLog.hide; // while loading ///*****************/	//* Log Retrieval *// /*****************///   function getInterwikiLog(wiki, callback) { // e.g. http://campcamp.wikia.com/api.php?action=query&list=logevents&letype=block&letitle=User:The_JoTS&format=json if (wiki == config.wgDBname) return; mw.log("Checking user's block log at wiki " + wiki); $.get("http://" + wiki + ".wikia.com/api.php", {           action: "query",            format: "json",            // Block logs            list:   "logevents",            letype: "block",            letitle: "User:" + config.targetUser,            // SiteInfo            meta:   "siteinfo",            siprop: "general"        }, function(result) {            if (result !== null && result.query.logevents.length !== 0)				callback(result.query, recordParsedWiki);			else recordParsedWiki;			if (result === null)				// I'm not sure if this result is a possible case?				mw.log("Null result returned from query. Sending in queries too quickly?");       }, "jsonp"); }	///********************/	//* Make log entries *// /********************///	function makeWikiEntries(result, callback) { var $localLogs, indexURL = result.general.server + "/index.php?title="; mw.log("Creating block entries for current user from " + result.general.server); // New wiki section $blockLog.append($(document.createElement("div"))           .prepend($localLogs = $(document.createElement("ul")))            .prepend(new Make("h4") .contains( Make.prototype.link(result.general.server, '', result.general.sitename) ).e			)       ); // Log entry result.logevents.reverse.forEach(function(entry) {			hadAnyBlock = true;           var liEntry = new Make("li"),            blocked = entry.action !== "unblock", // there's a "change block" or something apparently            blockedOn  = new Date(entry.timestamp);            blockedOn = $.extend(blockedOn, { // this would've been a *great* time for toLocaleFormat to be supported... hour:  String(blockedOn.getHours).padStart(2,'0'), minute: String(blockedOn.getMinutes).padStart(2,'0'), date:  blockedOn.getDate, month: mw.config.get("wgMonthNames")[blockedOn.getMonth + 1], // the lazy way, may differ for uselang year:  blockedOn.getFullYear });			//			var target = entry.title.replace(/^.+?:/,''),			targetUserLinks = Make.prototype.link(indexURL, entry.title, target) + ' '				+ Make.prototype.toolLinks([ Make.prototype.link(indexURL, "Message Wall:"+target, mw.message("wall-message-wall-shorten")), Make.prototype.link(indexURL, "Special:Contributions/"+target, mw.message("contribslink")) ]);			liEntry.contains( // Note: mw.message(loadedmsg).parse didn't seem to work on my end and thus was not used in this script. // Because of this, some languages' messages will have some errors. blockedOn.hour + ':' + blockedOn.minute + ', ' + blockedOn.month + ' ' + blockedOn.date + ", " + blockedOn.year + ' ' + Make.prototype.link(indexURL, "User:"+entry.user, entry.user) + ' ' + Make.prototype.toolLinks([					Make.prototype.link(indexURL, "Message Wall:"+entry.user, mw.message("wall-message-wall-shorten")),					Make.prototype.link(indexURL, "Special:Contributions/"+entry.user, mw.message("contribslink"))				]) + ' ' + (blocked					? mw.message("blocklogentry", targetUserLinks, entry.block.duration, )						.plain.replace(/\[|\]/g,)					: mw.message("unblocklogentry", targetUserLinks)				) + ' ' + Make.prototype.comment(indexURL, entry.comment) );           $(liEntry.e)                .addClass("mw-logline-block")                .prependTo($localLogs);        }); callback; }	///***************/	//* Async Tasks *// /***************///	function recordParsedWiki { if (++nWikisParsed === nFinalParsed) { mw.log("A parse task (Last/" + nFinalParsed + ") finished."); // Finished parsing! //clearInterval(currParseIntv); if (hadAnyBlock) $blockLog.show; else $blockLog.remove; $loading.remove; mw.log("TopicBlockLog parse tasks completed."); mw.log(hadAnyBlock				? "Target user was blocked locally and/or blocked on sister wikis."				: "Target user does not have any block history on wikis of this topic."); }		else mw.log("A parse task (" + nWikisParsed + "/" + nFinalParsed + ") finished."); }	function getAndParseLogs(wikiIter, finalParseCount) { nFinalParsed = finalParseCount; getAndParseLogF(wikiIter); }	function getAndParseLogF(wikiIter) { return function { var reqSent = 0, requestIntv = setInterval(function {				//if (reqSent === -1) return;				var wiki = wikiIter.next;				if (wiki.done) {					mw.log("Last ajax query sent for TopicBlockLog.");					clearInterval(requestIntv);					//reqSent = -1;					return;				}				getInterwikiLog(wiki.value, makeWikiEntries);				if (++reqSent % REQS_BEFORE_PAUSE === 0) {					mw.log("Pausing TopicBlockLog ajax requests for " + REQS_BEFORE_PAUSE + "ms.");					setTimeout(getAndParseLogF(wikiIter), PAUSE_DELAY);					clearInterval(requestIntv);				}			}, DELAY); };	}	///*********/	//* Start *// /*********///	var userDefinedWikiList =   window.TBL_WIKIS, userSelectedWikiTopic = window.TBL_GROUP, TBL_GROUP_DICT =        window.TBL_GROUP_DICT; if (typeof userDefinedWikiList !== "undefined"			&& Array.isArray(userDefinedWikiList)) //		wikis = TBL_WIKIS.values; getAndParseLogs(			userDefinedWikiList[Symbol.iterator],			userDefinedWikiList.length ); else if (typeof userSelectedWikiTopic === "string"			&& TBL_GROUP_DICT[userSelectedWikiTopic]) getAndParseLogs(			TBL_GROUP_DICT[userSelectedWikiTopic][Symbol.iterator],			TBL_GROUP_DICT[userSelectedWikiTopic].length ); // todo: automatically search for wiki w/in TBL_GROUP_DICT else { $loading.remove; throw DebugMsg.NO_SETUP_VARS; } })(jQuery, mediaWiki, this);

else mw.log(DebugMsg.WARN_NOT_BLOCK_PAGE);