MediaWiki:Gadget-cats-LookupUser.js

From Meta Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
const {i18n: {LookupUser: i18n}, wikiList: wikis} = require( './cats-utils.js' );
const config = mw.config.get([
	'wgCanonicalSpecialPageName',
	'wgRelevantUserName',
	'wgArticlePath',
	'wgMonthNames',
	'wgTitle',
	'wgLegalTitleChars'
]);

var output = [],
	elements = {},
	input,
	button,
	widget,
	length,
	count = 0,
	found = 0,
	username = '',
	globalgroups = ['autoconfirmed', 'cats', 'global-interface-maintainer', 'checkuser', 'staff'],
	regex = new RegExp('^[' + config.wgLegalTitleChars + ']+$');

function log(msg) {
	console.log('[LookupContribs]', msg);
}

function wikiLink(wiki, page, text, title, query) {
	return mw.html.element( 'a', {
		href: wiki + mw.util.getUrl( page ) + (query || ''),
		title: title
	}, text );
}

function formatDate(date) {
	if (date.length) {
		const d = new Date(date);
		return ('0' + d.getHours()).slice(-2) + ':' + ('0' + d.getMinutes()).slice(-2) + ', ' + // Time
		d.getDate() + ' ' + config.wgMonthNames[d.getMonth()+1] + ' ' + d.getFullYear(); // Date
	} else {
		return '';
	}
}

function filterEntry(groups) {
	return !['*', 'user'].concat(globalgroups).includes(groups);
}

function done() {
	const table = document.createElement('table');
	table.classList = 'wikitable';
	table.style.width = '100%';
	table.innerHTML =
		'<thead>' +
			'<tr>' +
				mw.html.element( 'th', {}, i18n['wikis-title'] ) +
				mw.html.element( 'th', {}, i18n['wikis-last-edited'] ) +
				mw.html.element( 'th', {}, i18n['wikis-edits'] ) +
				( mw.util.isIPAddress(username, true) && !mw.util.isIPAddress(username, false) ? '' : 
					mw.html.element( 'th', {}, i18n['wikis-logs'] ) +
					mw.html.element( 'th', {}, i18n['wikis-deleted'] ) +
					mw.html.element( 'th', {}, i18n['wikis-abuselog'] )
				) +
				( mw.util.isIPAddress(username, true) ? '' : 
					mw.html.element( 'th', {}, i18n['wikis-rights'] )
				) +
				mw.html.element( 'th', {}, i18n['wikis-blocked'] ) +
			'</tr>' +
		'</thead>' +
		'<tbody>' +
		'</tbody>';
	const tbody = table.querySelector('tbody');
	
	var globalEditCount = 0;

	for (var i=0; i<output.length; i++) {
		const row = tbody.insertRow(),
			url = row.insertCell(),
			timestamp = row.insertCell(),
			edits = row.insertCell(),
			logs = row.insertCell(),
			deleted = row.insertCell(),
			abuselog = row.insertCell(),
			rights = row.insertCell(),
			blocks = row.insertCell();

		if (output[i].timestamp.length || output[i].blocks[0] !== '—' || output[i].blocks.length > 1) {
			found++;
		}
		
		url.dataset.sortValue = (wikis[i].id === 'meta' ? 'zz-meta' : wikis[i].id);
		url.innerHTML = wikiLink(
			output[i].name,
			( mw.util.isIPAddress(username, true) ? 'Special:Contributions/' : 'User:' ) + output[i].username,
			output[i].name,
			mw.format(i18n['wikis-url-content'], output[i].username, output[i].name)
		);
		timestamp.dataset.sortValue = output[i].timestamp;
		timestamp.textContent = formatDate(output[i].timestamp);
		edits.dataset.sortValue = output[i].usercontribs;
		edits.innerHTML = wikiLink(
			output[i].name,
			'Special:Contributions/' + output[i].username,
			mw.language.convertNumber(output[i].usercontribs),
			mw.format(
				mw.language.convertPlural(output[i].usercontribs, i18n['wikis-edits-content']),
				mw.language.convertNumber(output[i].usercontribs),
				output[i].name
			)
		);
		if ( mw.util.isIPAddress(username, true) && !mw.util.isIPAddress(username, false) ) {
			logs.remove();
			deleted.remove();
			abuselog.remove();
		}
		else {
			logs.dataset.sortValue = output[i].logevents;
			logs.innerHTML = wikiLink(
				output[i].name,
				'Special:Logs/User:' + output[i].username,
				mw.language.convertNumber(output[i].logevents),
				mw.format(
					mw.language.convertPlural(output[i].logevents, i18n['wikis-logs-content']),
					mw.language.convertNumber(output[i].logevents),
					output[i].name
				)
			);
			deleted.dataset.sortValue = output[i].deletedcontribs;
			deleted.innerHTML = wikiLink(
				output[i].name,
				'Special:DeletedContributions/' + output[i].username,
				mw.language.convertNumber(output[i].deletedcontribs),
				mw.format(
					mw.language.convertPlural(output[i].deletedcontribs, i18n['wikis-deleted-content']),
					mw.language.convertNumber(output[i].deletedcontribs),
					output[i].name
				)
			);
			abuselog.dataset.sortValue = output[i].abuselog;
			abuselog.innerHTML = wikiLink(
				output[i].name,
				'Special:AbuseLog',
				mw.language.convertNumber(output[i].abuselog),
				mw.format(
					mw.language.convertPlural(output[i].abuselog, i18n['wikis-abuselog-content']),
					mw.language.convertNumber(output[i].abuselog),
					output[i].name
				),
				'?wpSearchUser=' + output[i].username
			);
		}
		if ( mw.util.isIPAddress(username, true) ) rights.remove();
		else {
			rights.dataset.sortValue = output[i].groups.filter(filterEntry).length;
			rights.textContent = output[i].groups.filter(filterEntry).join(', ');
		}
		blocks.dataset.sortValue = output[i].blocks.filter( function(block) {
			return block !== '—';
		} ).length;
		blocks.innerHTML = output[i].blocks.map( function(block, j) {
			return wikiLink(
				output[i].name,
				'Special:Log/block',
				block,
				i18n['wikis-blocked-content'],
				'?page=User:' + ( j ? output[i].username.split('/')[0] + block : output[i].username )
			);
		} ).join(', ');
		globalEditCount += output[i].usercontribs + output[i].deletedcontribs;
	}
	if ( mw.util.isIPAddress(username, true) ) {
		elements.userinfo.querySelector('#global-editcount').innerHTML = 
			mw.html.element( 'strong', {}, i18n['global-editcount'] ) + ' ' + mw.html.escape( mw.language.convertNumber( globalEditCount ) );
	}
	widget.setNotices( [] );
	widget.setSuccess( [ mw.format(mw.language.convertPlural(found, i18n['wikis-found']), mw.language.convertNumber(found)) ] );
	$(table).tablesorter();
	elements.wikis.append(table);
	input.setReadOnly(false);
	button.setDisabled(false);
}

function updateCount() {
	count++;
	widget.setNotices( [ mw.format(i18n['wikis-checking'], mw.language.convertNumber(count + 1), mw.language.convertNumber(length)) ] );
	if (count === length) done();
}

function sendRequest(host, id) {
	var queryArgs = {
		action: 'query',
		list: ['usercontribs'],
		uclimit: 'max',
		ucprop: ['timestamp'],
		formatversion: 2
	};
	if ( mw.util.isIPAddress(username, true) ) {
		queryArgs.list.push( 'blocks' );
		if ( mw.util.isIPAddress(username, false) ) {
			queryArgs.list.push( 'logevents', 'alldeletedrevisions', 'abuselog' );
			queryArgs.ucuser = username;
			queryArgs.leuser = username;
			queryArgs.lelimit = 'max';
			queryArgs.leprop = ['timestamp'];
			queryArgs.adruser = username;
			queryArgs.adrlimit = 'max';
			queryArgs.adrprop = ['timestamp'];
			queryArgs.afluser = username;
			queryArgs.afllimit = 'max';
			queryArgs.aflprop = ['timestamp', 'wiki'];
		}
		else {
			queryArgs.uciprange = username;
		}
		queryArgs.bkip = username;
		queryArgs.bklimit = 'max';
		queryArgs.bkprop = ['user'];
	}
	else {
		queryArgs.list.push( 'users', 'logevents', 'alldeletedrevisions', 'abuselog' );
		if ( /^#\d+$/.test(username) ) {
			var userid = username.slice(1);
			queryArgs.ususerids = userid;
			queryArgs.ucuserids = userid;
		}
		else {
			queryArgs.ususers = username;
			queryArgs.ucuser = username;
		}
		queryArgs.usprop = ['groups', 'blockinfo'];
		queryArgs.leuser = username;
		queryArgs.lelimit = 'max';
		queryArgs.leprop = ['timestamp'];
		queryArgs.adruser = username;
		queryArgs.adrlimit = 'max';
		queryArgs.adrprop = ['timestamp'];
		queryArgs.afluser = username;
		queryArgs.afllimit = 'max';
		queryArgs.aflprop = ['timestamp', 'wiki'];
	}
	var api = new mw.ForeignApi( host + '/api.php', {
		parameters: queryArgs
	} );
	var result = {
		name: host,
		username: username,
		usercontribs: 0,
		logevents: 0,
		deletedcontribs: 0,
		abuselog: 0,
		groups: [],
		timestamp: '',
		blocks: ['—'],
		id: id
	};
	output[id] = result;
	sendSingleRequest(api, result);
}

function sendSingleRequest(api, result, datacontinue) {
	api.get( datacontinue ).then(function(data) {
		if ( data.query.usercontribs ) result.usercontribs += data.query.usercontribs.length;
		if ( data.query.logevents ) result.logevents += data.query.logevents.length;
		if ( data.query.alldeletedrevisions ) result.deletedcontribs += data.query.alldeletedrevisions.reduce( function(sum, cur) {
			return sum + cur.revisions.length;
		}, 0 );
		if ( data.query.abuselog ) result.abuselog += data.query.abuselog.length;
		if ( data.query.blocks && data.query.blocks.length ) {
			data.query.blocks.forEach( function(block) {
				if ( block.user.split('/')[1] === username.split('/')[1] ) result.blocks[0] = 'Y';
				else result.blocks.push('/' + block.user.split('/')[1]);
			} );
		}
		if ( api.defaults.parameters.uciprange && data.query.usercontribs ) {
			result.timestamp = data.query.usercontribs.reduce(function(acc, cur) {
				if ( acc > cur.timestamp ) return acc;
				return cur.timestamp;
			}, result.timestamp);
		}
		if ( !datacontinue ) {
			if ( data.query.users && data.query.users[0] ) {
				result.username = data.query.users[0].name;
				result.groups = data.query.users[0].groups;
				if ( data.query.users[0].blockid ) result.blocks[0] = 'Y';
			}
			if ( data.query.usercontribs && data.query.usercontribs[0]
			&& data.query.usercontribs[0].timestamp > result.timestamp ) {
				result.timestamp = data.query.usercontribs[0].timestamp;
			}
			if ( data.query.logevents && data.query.logevents[0]
			&& data.query.logevents[0].timestamp > result.timestamp ) {
				result.timestamp = data.query.logevents[0].timestamp;
			}
			if ( data.query.alldeletedrevisions && data.query.alldeletedrevisions[0] && data.query.alldeletedrevisions[0].revisions[0]
			&& data.query.alldeletedrevisions[0].revisions[0].timestamp > result.timestamp ) {
				result.timestamp = data.query.alldeletedrevisions[0].revisions[0].timestamp;
			}
			if ( data.query.abuselog && data.query.abuselog[0]
			&& data.query.abuselog[0].timestamp > result.timestamp ) {
				let abuselog = data.query.abuselog.find( logentry => !logentry.wiki );
				if ( abuselog && abuselog.timestamp > result.timestamp ) {
					result.timestamp = abuselog.timestamp;
				}
			}
		}
		if ( data.continue ) {
			sendSingleRequest(api, result, data.continue);
		}
		else updateCount();
	}).catch(function(error) {
		log(error);
		updateCount();
	});
}

/**
 * Initiate wiki list.
 */
function lookupWikis() {
	length = wikis.length;
	for (var i=0; i<length; i++) {
		sendRequest(wikis[i].url, i);
	}
}

/**
 * Button action to start the process.
 */
function start() {
	if (input.isReadOnly() || button.isDisabled()) return;

	// Reset
	username = '';
	count = 0;
	found = 0;
	output = [];
	elements.userinfo.textContent = '';
	elements.wikis.textContent = '';
	widget.setSuccess( [] );
	widget.setNotices( [] );
	widget.setWarnings( [] );
	widget.setErrors( [] );
	username = input.getValue().trim();

	if (!username.length) return;
	input.setReadOnly(true);
	button.setDisabled(true);
	widget.setNotices( [ i18n['main-loading'] ] );
	
	var api = new mw.Api();
	
	if ( mw.util.isIPAddress(username, true) ) {
		username = mw.util.sanitizeIP(username);
		api.get( {
			action: 'query',
			list: ['globalblocks'],
			bgip: username,
			bglimit: 'max',
			bgprop: ['address', 'expiry'],
			formatversion: 2
		} ).then(function(data) {
			elements.userinfo.innerHTML =
				'<fieldset>' +
					mw.html.element( 'legend', {}, i18n['global-header'] ) +
					'<div>' + mw.html.element( 'strong', {}, i18n['global-username'] ) + ' ' + mw.html.escape( mw.util.prettifyIP( username ) ) + '</div>' +
					'<div id="global-editcount">' + mw.html.element( 'strong', {}, i18n['global-editcount'] ) + ' ' + mw.html.escape( mw.language.convertNumber( 0 ) ) + '</div>' +
					( data.query.globalblocks.length ? '<div>' + mw.html.element( 'strong', {}, i18n['global-blocked'] ) + ' ' + mw.html.element( 'a', {
						href: mw.util.getUrl( 'Special:GlobalBlockList/' + username ), title: 'Special:GlobalBlockList'
					}, new mw.html.Raw( data.query.globalblocks.map( function (globalblock) {
						if ( new Date(globalblock.expiry) < Date.now() ) return mw.html.element( 's', {}, globalblock.address );
						return mw.html.escape( globalblock.address );
					} ).join(', ') ) ) + '</div>' : '' ) +
				'</fieldset>';
			lookupWikis();
		}).catch(log);
		return;
	}
	
	var queryArgs = {
		action: 'query',
		list: ['users'],
		usprop: [
			'editcount',
			'groups',
			'registration',
			'gender',
			'emailable'
		],
		formatversion: 2
	};
	if ( /^#\d+$/.test(username) ) queryArgs.ususerids = username.slice(1);
	else queryArgs.ususers = username;
	api.get( queryArgs ).then(function(data) {
		const users = data.query.users[0];
		if ( /^#\d+$/.test(username) ) username = '#' + users.userid;
		else username = users.name;
		if (users.missing || users.invalid) {
			widget.setNotices( [] );
			widget.setErrors( [ mw.format(i18n['global-error'], username) ] );
			input.setReadOnly(false);
			button.setDisabled(false);
		} else {
			elements.userinfo.innerHTML =
				'<fieldset>' +
					mw.html.element( 'legend', {}, i18n['global-header'] ) +
					'<div>' + mw.html.element( 'strong', {}, i18n['global-username'] ) + ' ' + mw.html.escape( users.name ) + '</div>' +
					'<div>' + mw.html.element( 'strong', {}, i18n['global-id'] ) + ' ' + mw.html.escape( '#' + users.userid ) + '</div>' +
					'<div>' + mw.html.element( 'strong', {}, i18n['global-editcount'] ) + ' ' + mw.html.escape( mw.language.convertNumber( users.editcount ) ) + '</div>' +
					'<div>' + mw.html.element( 'strong', {}, i18n['global-groups'] ) + ' ' + mw.html.escape( users.groups.filter( function (groups) {
						return globalgroups.includes(groups);
					} ).join(', ') ) + '</div>' +
					'<div>' + mw.html.element( 'strong', {}, i18n['global-registration'] ) + ' ' + mw.html.escape( users.registration ? formatDate(users.registration) : i18n['registration-unknown'] ) + '</div>' +
					//'<div>' + mw.html.element( 'strong', {}, i18n['global-emailable'] ) + ' ' + mw.html.escape( users.emailable ? 'Yes' : 'No' ) + '</div>' +
					'<div>' + mw.html.element( 'strong', {}, i18n['global-gender'] ) + ' ' + mw.html.escape( i18n.gender[users.gender] || users.gender ) + '</div>' +
				'</fieldset>';
			lookupWikis();
		}
	}).catch(log);
}

function init() {
	var $content = $( '#mw-content-text' );
	var mwf = $content.find('#globalTracker')[0];
	if (mwf) return;
	document.getElementById('firstHeading').textContent = i18n.title;
	document.title = i18n.title + ' – Minecraft Wiki';
	$content.empty().addClass('loaded');
	input = new mw.widgets.UserInputWidget( {
		required: true,
		validate: function(inputValue) {
			inputValue = inputValue.trim();
			if ( regex.test(inputValue) && !/[:/]/.test(inputValue) ) return true;
			if ( mw.util.isIPAddress(inputValue, true) ) {
				if ( mw.util.isIPAddress(inputValue, false) ) return true;
				var iprange = inputValue.split('/')[1];
				if ( mw.util.isIPv4Address(inputValue, true) && iprange < 16 ) return false;
				if ( mw.util.isIPv6Address(inputValue, true) && iprange < 32 ) return false;
				return true;
			}
			if ( /^#\d+$/.test(inputValue) ) return true;
			return false;
		},
		placeholder: i18n['main-placeholder']
	} );
	button = new OO.ui.ButtonWidget( {
		label: i18n['main-button'],
		flags: ['primary', 'progressive']
	} );
	widget = new OO.ui.ActionFieldLayout(
		input, button, {
			id: 'globalTracker',
			label: i18n['main-header'],
			align: 'top'
		}
	);
	$content.append(
		widget.$element,
		$('<div>').attr('id', 'tracker-userinfo'),
		$('<div>').attr('id', 'tracker-wikis')
	);
	elements = {
		userinfo: $content.find('#tracker-userinfo')[0],
		wikis: $content.find('#tracker-wikis')[0]
	};
	input.on('enter', function() {
		input.getValidity().done(start);
	});
	button.on('click', function() {
		input.getValidity().done(start);
	});
	const targetParam = mw.util.getParamValue('target');
	if (targetParam) {
		input.setValue(targetParam);
		input.getValidity().done(start);
	}
}

if (config.wgCanonicalSpecialPageName === 'Blankpage' && config.wgTitle.endsWith('/LookupUser')) {
	mw.loader.using( [
		'oojs-ui-widgets',
		'mediawiki.widgets.UserInputWidget',
		'mediawiki.ForeignApi'
	] ).then( init );
}

function createLookupUserLink(portlet) {
	var link = mw.util.getUrl('Special:BlankPage/LookupUser') + (config.wgRelevantUserName ? '?target=' + mw.util.wikiUrlencode(config.wgRelevantUserName) : '');
	if (config.wgCanonicalSpecialPageName === 'Contributions') {
		$('<span>').append(
			$('<a>').addClass('mw-contributions-link-lookupuser').attr('href', link).text(i18n.title)
		).appendTo('.mw-contributions-user-tools .mw-changeslist-links');
	}
	return mw.util.addPortletLink(portlet, link, i18n.title, 't-cats-LookupUser', '', null, 'li:first-of-type');
}

module.exports = createLookupUserLink;