User:PleaseStand/userinfo-dev.js: Difference between revisions

Content deleted Content added
m -MW 1.19 fix
Undid revision 1225074654 by PleaseStand (talk) - "named" group was removed in git:mediawiki/core/+/93e8350d295203ac
 
(41 intermediate revisions by the same user not shown)
Line 1:
// based on http://en.wikipedia.org/wiki/User:Fran RogersFran_Rogers/dimorphism.js
// and on http://en.wikipedia.org/wiki/User:Splarka/sysopdectector.js
 
( function ( mw, $ ) {
/*global window, wgNamespaceNumber, wgTitle, wgFormattedNamespaces
wgArticlePath, wgScriptPath, addOnloadHook, importScriptURI, sajax_init_object*/
 
var h = mw.html,
// for non-Firefox browsers
api,
if(!Array.prototype.map) {
ns = mw.config.get( 'wgNamespaceNumber' ),
Array.prototype.map = function(fun /*, thisp*/) {
title = mw.config.get( 'wgTitle' );
var len = this.length >>> 0;
if (typeof fun != "function") {
throw new TypeError();
}
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in this) {
res[i] = fun.call(thisp, this[i], i, this);
}
}
return res;
};
}
 
// Bail out unless on a user or user talk page, and not a subpage.
if ( ns !== 2 && ns !== 3 || title.indexOf( '/' ) >= 0 ) {
return;
}
 
// TODO: add additional languages
function UserinfoJsParseDate(utcDate) {
mw.messages.set( {
// The ISO 8601 date format used by MediaWiki
'ps-userinfo-gendersymbol-male': '♂',
var s = /^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/.exec(utcDate);
'ps-userinfo-gendersymbol-female': '♀',
if(s === null) {
'ps-userinfo-gendersymbol-unknown': '',
return null;
'ps-userinfo-itemseparator': '$1\u00a0| $2',
}
'ps-userinfo-groupseparator': '$1, $2',
var d = new Date();
d.setUTCFullYear(s[1], s[2] - 1, s[3]);
d.setUTCHours(s[4], s[5], s[6]);
return d;
}
 
'ps-userinfo-seconds': '$1 {{PLURAL:$1|second|seconds}}',
function UserinfoJsValidateIp(ip) {
'ps-userinfo-minutes': '$1 {{PLURAL:$1|minute|minutes}}',
var ipSplit = ip.split(".");
'ps-userinfo-hours': '$1 {{PLURAL:$1|hour|hours}}',
if(ipSplit.length != 4) {
'ps-userinfo-days': '$1 {{PLURAL:$1|day|days}}',
return false;
'ps-userinfo-weeks': '$1 {{PLURAL:$1|week|weeks}}',
}
'ps-userinfo-months': '$1 {{PLURAL:$1|month|months}}',
if(ipSplit.map(function(s){return parseInt(s,10);}).join(".") != ip) {
'ps-userinfo-years': '$1 {{PLURAL:$1|year|years}}',
return false;
'ps-userinfo-years-months': '$1 {{PLURAL:$1|year|years}} $2 {{PLURAL:$2|month|months}}',
}
return true;
}
 
'ps-userinfo-blocked': 'BLOCKED',
function UserinfoJsFormatQty(qty, singular, plural) {
'ps-userinfo-editcount': '$1 {{PLURAL:$1|edit|edits}}',
return String(qty).replace(/\d{1,3}(?=(\d{3})+(?!\d))/g, "$&,") + "\u00a0" + (qty == 1 ? singular : plural);
'ps-userinfo-invalid': 'invalid username',
}
'ps-userinfo-ipv4': 'IPv4 address',
'ps-userinfo-ipv6': 'IPv6 address',
'ps-userinfo-lastedited': 'last edited [$2 $1 ago]',
'ps-userinfo-missing': 'username not registered',
'ps-userinfo-registration': '$1 old',
'ps-userinfo-user': '{{GENDER:$1|registered user}}'
} );
 
/**
function UserinfoJsFormatDateRel(old) {
* Get the user's information by performing an AJAX request and processing the response.
// The code below requires the computer's clock to be set correctly.
* @param {string} userName The user to retrieve information for
var age = new Date().getTime() - old.getTime();
* @return {jQuery.Deferred}
var ageNumber, ageRemainder, ageWords;
*/
if(age < 60000) {
function getInfo( userName ) {
// less than one minute old
return api.get( {
ageNumber = Math.floor(age / 1000);
action: 'query',
ageWords = UserinfoJsFormatQty(ageNumber, "second", "seconds");
list: 'users|usercontribs',
} else if(age < 3600000) {
maxage: 300,
// less than one hour old
uclimit: 1,
ageNumber = Math.floor(age / 60000);
ucprop: 'timestamp',
ageWords = UserinfoJsFormatQty(ageNumber, "minute", "minutes");
ucuser: userName,
} else if(age < 86400000) {
usprop: 'blockinfo|editcount|gender|registration|groups',
// less than one day old
ususers: userName
ageNumber = Math.floor(age / 3600000);
} ).then( function ( data ) {
ageWords = UserinfoJsFormatQty(ageNumber, "hour", "hours");
var query = data.query, user = query.users[0], contribs = query.usercontribs;
ageRemainder = Math.floor((age - ageNumber * 3600000) / 60000);
} else if(age < 604800000) {
// less than one week old
ageNumber = Math.floor(age / 86400000);
ageWords = UserinfoJsFormatQty(ageNumber, "day", "days");
} else if(age < 2592000000) {
// less than one month old
ageNumber = Math.floor(age / 604800000);
ageWords = UserinfoJsFormatQty(ageNumber, "week", "weeks");
} else if(age < 31536000000) {
// less than one year old
ageNumber = Math.floor(age / 2592000000);
ageWords = UserinfoJsFormatQty(ageNumber, "month", "months");
} else {
// one year or older
ageNumber = Math.floor(age / 31536000000);
ageWords = UserinfoJsFormatQty(ageNumber, "year", "years");
ageRemainder =
Math.floor((age - ageNumber * 31536000000) / 2592000000);
if(ageRemainder) {
ageWords += " " +
UserinfoJsFormatQty(ageRemainder, "month", "months");
}
}
return ageWords;
}
 
return {
// If on a user or user talk page, and not a subpage...
name: userName,
if((wgNamespaceNumber == 2 || wgNamespaceNumber == 3) && !(/\//.test(wgTitle))) {
isInvalid: 'invalid' in user,
// add a hook to...
isIPv4: mw.util.isIPv4Address( userName ),
addOnloadHook(function(){
isIPv6: mw.util.isIPv6Address( userName ),
// Request the user's information from the API.
isMissing: 'missing' in user,
// Note that this is allowed to be up to 5 minutes old.
groups: user.groups || [],
editCount: 'editcount' in user ? user.editcount : null,
// But if the user is an IP, do not look up. --PS 2010-05-16
registration: user.registration ? new Date( user.registration ) : null,
// Commented out to allow "last edit" lookups --PS 2010-06-27
isBlocked: 'blockexpiry' in user,
/* if(UserinfoJsValidateIp(wgTitle)) {
gender: user.gender || 'unknown',
// Code copied from bottom of callback function --PS 2010-05-16
lastEdited: ( contribs && contribs[0] ) ? new Date( contribs[0].timestamp ) : null
var fh = window.document.getElementById("firstHeading");
};
var stDiv = window.document.createElement("div");
} );
stDiv.innerHTML = "An anonymous user";
}
fh.parentNode.insertBefore(stDiv, fh.nextSibling);
return;
} */
var et = encodeURIComponent(wgTitle);
var x = sajax_init_object();
x.open("GET", wgScriptPath + "/api.php?format=json&maxage=300&action=query&list=users|usercontribs&usprop=blockinfo|editcount|gender|registration|groups&uclimit=1&ucprop=timestamp&ususers=" + et + "&ucuser=" + et , true);
x.onreadystatechange = function() {
if(x.readyState != 4 || x.status != 200) {
return;
}
// When response arrives extract the information we need.
var query = eval("(" + x.responseText + ")");
if(!query.query) { return; } // Suggested by Gary King to avoid JS errors --PS 2010-08-25
query = query.query;
var user, invalid, missing, groups, editcount, registration, blocked, gender, lastEdited;
try {
user = query.users[0];
invalid = typeof user.invalid != "undefined";
missing = typeof user.missing != "undefined";
groups = (typeof user.groups == "object") ? user.groups : [];
editcount = (typeof user.editcount == "number") ? user.editcount : null;
registration = (typeof user.registration == "string") ?
UserinfoJsParseDate(user.registration) : null;
blocked = typeof user.blockedby != "undefined";
gender = (typeof user.gender == "string") ? user.gender : null;
lastEdited = (typeof query.usercontribs[0] == "object") &&
(typeof query.usercontribs[0].timestamp == "string") ?
UserinfoJsParseDate(query.usercontribs[0].timestamp) : null;
} catch(e) {
return; // Not much to do if the server is returning an error (e.g. if the username is malformed).
}
 
/**
// Format the information for on-screen display
* Display the user's information on screen.
* @param info Information about the user
var statusText = "";
*/
var ipUser = false;
function displayInfo( info ) {
var userPrefix = mw.config.get( 'wgFormattedNamespaces' )[2] + ':',
items = [],
groups = [],
text = '';
 
if ( info.isBlocked ) {
// User status
items.push( h.element( 'a', { href:
if(blocked) {
mw.util.wikiScript( 'index' ) + '?' + $.param( {
statusText += "<a href=\"" + wgScriptPath +
title: 'Special:Log',
"/index.php?title=Special:Log&amp;page=" +
page: userPrefix + info.name
encodeURIComponent(wgFormattedNamespaces[2] + ":" + user.name) +
} )
"&amp;type=block\">blocked</a> ";
}, new h.Raw( h.element( 'strong', {}, mw.msg( 'ps-userinfo-blocked' ) ) ) ) );
}
}
if (missing) {
statusText += "username not registered";
} else if (invalid) {
statusText += (ipUser = UserinfoJsValidateIp(user.name)) ? "anonymous user" : "invalid username";
} else {
// User is registered and may be in a privileged group. Below we have a list of user groups.
// Only need the ones different from the software's name (or ones to exclude), though.
var friendlyGroupNames = {
// Exclude implicit user group information provided by MW 1.17 --PS 2010-02-17
'*': false,
'user': false,
'autoconfirmed': false,
sysop: "administrator",
accountcreator: "account creator",
"import": "importer",
transwiki: "transwiki importer",
"ipblock-exempt": "IP block exemption",
oversight: "oversighter",
confirmed: "confirmed user",
abusefilter: "edit filter manager",
autoreviewer: "autopatrolled user" // Group has been renamed --PS 2010-07-06
};
var friendlyGroups = [];
for(var i = 0; i < groups.length; ++i) {
var s = groups[i];
if(friendlyGroupNames.hasOwnProperty(s)) {
if(friendlyGroupNames[s]) {
friendlyGroups.push(friendlyGroupNames[s]);
}
} else {
friendlyGroups.push(s);
}
}
switch(friendlyGroups.length) {
case 0:
// User not in a privileged group
// Changed to "registered user" by request of [[User:Svanslyck]]
// --PS 2010-05-16
// statusText += "user";
if(blocked) {
statusText += "user";
} else {
statusText += "registered user";
}
break;
case 1:
statusText += friendlyGroups[0];
break;
case 2:
statusText += friendlyGroups[0] + " and " + friendlyGroups[1];
break;
default:
statusText += friendlyGroups.slice(0, -1).join(", ") +
", and " + friendlyGroups[friendlyGroups.length - 1];
break;
}
}
// Registration date
if(registration) {
statusText += ", " + UserinfoJsFormatDateRel(registration) + " old";
}
// Edit count
if(editcount !== null) {
statusText += ", with " +
"<a href=\"http://toolserver.org/~tparis/count/index.php?name=" +
encodeURIComponent(user.name) +
"&amp;lang=en&amp;wiki=wikipedia\">" +
UserinfoJsFormatQty(editcount, "edit", "edits") + "</a>";
}
// Prefix status text with correct article
if("AEIOaeio".indexOf(statusText.charAt(0)) >= 0) {
statusText = "An " + statusText;
} else {
statusText = "A " + statusText;
}
 
if ( info.isMissing ) {
// Add full stop to status text
items.push( mw.message( 'ps-userinfo-missing' ).escaped() );
statusText += ".";
} else if ( info.isIPv4 ) {
items.push( mw.message( 'ps-userinfo-ipv4' ).escaped() );
} else if ( info.isIPv6 ) {
items.push( mw.message( 'ps-userinfo-ipv6' ).escaped() );
} else if ( info.isInvalid ) {
items.push( mw.message( 'ps-userinfo-invalid' ).escaped() );
} else {
$.each( info.groups, function ( i, v ) {
if ( $.inArray( v, ['*', 'user', 'autoconfirmed'] ) < 0 ) {
groups.push( mw.message( 'group-' + v + '-member', info.gender ).escaped() );
}
} );
if ( groups.length ) {
items.push( groups.join( mw.message( 'ps-userinfo-groupseparator', '', '' ).escaped() ) );
} else {
items.push( mw.message( 'ps-userinfo-user', info.gender ).escaped() );
}
 
if ( info.registration ) {
// Last edited --PS 2010-06-27
items.push( mw.message(
// Added link to contributions page --PS 2010-07-03
'ps-userinfo-registration',
if(lastEdited) {
buildDurationMessage( info.registration, new Date() ).text(),
statusText += " Last edited <a href=\"" + wgArticlePath.replace("$1", "Special:Contributions/" + encodeURIComponent(user.name)) + "\">" + UserinfoJsFormatDateRel(lastEdited) + " ago</a>.";
info.gender
}
).escaped() );
}
 
if ( info.editCount !== null ) {
// Show the correct gender symbol
items.push( h.element( 'a', { href:
var fh = window.document.getElementById("firstHeading") ||
'https://xtools.wmcloud.org/ec/' +
window.document.getElementById("section-0");
encodeURIComponent( ___location.hostname ) + '/' +
// Add classes for blocked, registered, and anonymous users
encodeURIComponent( info.name )
var newClasses = [];
}, mw.message( 'ps-userinfo-editcount', info.editCount ).text() ) );
if(blocked) {
}
newClasses.push("ps-blocked");
}
}
if(ipUser) {
newClasses.push("ps-anonymous");
} else if(invalid) {
newClasses.push("ps-invalid");
} else {
newClasses.push("ps-registered");
}
fh.className += (fh.className.length ? " " : "") + groups.map(function(s) {
return "ps-group-" + s;
}).concat(newClasses).join(" ");
var genderSpan = window.document.createElement("span");
genderSpan.id = "ps-gender-" + (gender || "unknown");
genderSpan.style.paddingLeft = "0.25em";
genderSpan.style.fontFamily = '"Lucida Grande", "Lucida Sans Unicode", "sans-serif"';
genderSpan.style.fontSize = "75%";
var genderSymbol;
switch(gender) {
case "male": genderSymbol = "\u2642"; break;
case "female": genderSymbol = "\u2640"; break;
default: genderSymbol = ""; break;
}
genderSpan.appendChild(window.document.createTextNode(genderSymbol));
fh.appendChild(genderSpan);
 
if ( info.lastEdited ) {
// Now show the other information. Non-standard? Yes, but it gets the job done.
items.push( mw.message(
// Add a period after the tagline when doing so. --PS 2010-07-03
'ps-userinfo-lastedited',
buildDurationMessage( info.lastEdited, new Date() ).escaped(),
$( '<a>' ).prop( 'href', mw.util.getUrl( 'Special:Contributions/' + info.name ) ),
mw.html.escape( info.name )
).parse() );
}
 
// Add the gender symbol after the user's name
var ss = window.document.getElementById("siteSub");
$( '<span>' )
if(!ss) {
.prop( 'id', 'ps-gender-' + info.gender )
ss = window.document.createElement("div");
.css( {
ss.id = "siteSub";
paddingLeft: '0.25em',
ss.innerHTML = "From Wikipedia, the free encyclopedia";
fontFamily: '"Lucida Grande", "Lucida Sans Unicode", sans-serif',
var bc = window.document.getElementById("bodyContent");
fontSize: '75%'
bc.insertBefore(ss, bc.firstChild);
} )
}
.text( mw.msg( 'ps-userinfo-gendersymbol-' + info.gender ) )
ss.innerHTML = '<span id="ps-userinfo">' + statusText + '</span> ' + ss.innerHTML + '.';
.appendTo( '#firstHeading' );
};
 
x.send(null);
// Replace the subtitle with the rest of the info
});
$( '#siteSub' ).html( items.join( mw.message( 'ps-userinfo-itemseparator', '', '' ).escaped() ) );
}
}
 
/**
* Load messages corresponding to MediaWiki user groups into mw.messages.
* @return {jQuery.Deferred}
*/
function loadGroupMemberMessages() {
return api.get( {
action: 'query',
amfilter: '-member',
amlang: mw.config.get( 'wgUserLanguage' ),
amprefix: 'group-',
maxage: 86400,
meta: 'allmessages'
} ).then( function ( data ) {
$.each( data.query.allmessages, function ( i, message ) {
mw.messages.set( message.name, message['*'] );
} );
} );
}
 
/**
* Convert a duration to a message object.
* @param {Date} start The duration's start
* @param {Date} end The duration's end
* @return {mediaWiki.Message}
*/
function buildDurationMessage( start, end ) {
var age = ( end - start ) / 1000; // start with seconds
if ( age < 60 ) {
return mw.message( 'ps-userinfo-seconds', Math.floor( age ) );
}
 
age /= 60; // convert to minutes
if ( age < 60 ) {
return mw.message( 'ps-userinfo-minutes', Math.floor( age ) );
}
 
age /= 60; // convert to hours
if ( age < 24 ) {
return mw.message( 'ps-userinfo-hours', Math.floor( age ) );
}
 
age /= 24; // convert to days
if ( age < 7 ) {
return mw.message( 'ps-userinfo-days', Math.floor( age ) );
}
 
age /= 7; // convert to weeks
if ( age < 4.3481 ) {
return mw.message( 'ps-userinfo-weeks', Math.floor( age ) );
}
 
age /= 4.3481; // convert to months
if ( age < 12 ) {
return mw.message( 'ps-userinfo-months', Math.floor( age ) );
}
 
// convert to years and months
var years = Math.floor( age / 12 ), months = Math.floor( age % 12 );
if ( months ) {
return mw.message( 'ps-userinfo-years-months', years, months );
}
return mw.message( 'ps-userinfo-years', years );
}
 
mw.loader.using( [
'mediawiki.api',
'mediawiki.jqueryMsg',
'mediawiki.language',
'mediawiki.util'
], function () {
api = new mw.Api();
loadGroupMemberMessages().done( function () {
getInfo( title ).done( displayInfo );
} );
} );
} )( mediaWiki, jQuery );