Benutzer:Funkruf/userstatus.js

aus Wikipedia, der freien Enzyklopädie

Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.

  • Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
  • Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
  • Internet Explorer/Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
  • Opera: Strg+F5
/**
@Description:
* Eng: This script shows on user-pages some information about the user
* Deu: Dieses Script zeigt auf Userseiten einige Informationen über den User an
@Author: Steef389 - 2010-2013 by [[:de:User:Steef389/js/user_status.js]];
@Author: Perhelion - adapted 2016, modified & simplified & fallback language
@Revision: 02:11, 2 February 2018 (UTC)
See also [[:de:Benutzer:Schnark/js/letzteredit]], [[:de:Benutzer:Schnark/js/extratabs]]
* @Required modules: mediawiki.util, mediawiki.api, mediawiki.language, mediawiki.cookie, jquery.spinner
* @TODO: fakeLoader, group since when?, edits/day, Error msg on maxlag instead of disapear
* better IP support
<nowiki>
**/
/* global jQuery, mediaWiki, indexedDB*/
( function ( $, mw ) {
'use strict';
// var us = {};
var project = window.project || mw.config.get( 'wgDBname' );
var msg, i18n = {
	en: {
		noReason: 'reason removed',
		blockCmt: 'no block comment',
		never: 'never',
		not: ' not ',
		block: 'blocked',
		and: '$1 and$2',
		// load : "loading…",
		count: 'Count',
		noedit: 'None (only deleted contributions?)',
		// noav : "not available",
		nodb: '‹not in database›',
		noGrp: 'no extended group',
		dates: [ 'years', 'months', 'days', 'hours', 'minutes', 'seconds' ],
		date: [ 'year', 'month', 'day', 'hour', 'minute', 'second' ],
		nosec: 'less than one ',
		curtimeDiff: ' Locale time difference to server time in s: ',
		thxGn: 'given',
		thxRd: 'received',
		'new': 'new',
		ago: '$1 ago',
		blocklog: 'Block log',
		// Title-lines
		contrib: 'Edits: ',
		usub: 'Subpages⬇',
		thxGvng: 'Thanksgivings: ',
		reviews: 'Active reviews: ',
		regdate: 'Registration: ',
		laedit: 'Last edited: ',
		lala: 'Last log activity',
		fiedit: 'First edit: ',
		blocks: 'Block-status: ',
		loGrp: 'Local user-groups: ',
		glGrp: 'Global user-groups: ',
		blockEnd: 'Block-end: ',
		blocker: 'Blocker: ',
		blockReason: 'Block-reason: '
	},
	de: {
		noReason: 'Begründung entfernt',
		blockCmt: 'kein Sperrkommentar',
		never: 'nie',
		not: ' nicht ',
		block: 'gesperrt',
		and: '$1 und$2',
		// load : "wird geladen…",
		count: 'Anzahl',
		noedit: 'Keiner (nur gelöschte Beiträge?)',
		// noav : "nicht verfügbar",
		nodb: '‹nicht in Datenbank›',
		noGrp: 'keine erweiterte Gruppe',
		date: [ 'Jahr', 'Monat', 'Tag', 'Stunde', 'Minute', 'Sekunde' ],
		dates: [ 'Jahren', 'Monaten', 'Tagen', 'Stunden', 'Minuten', 'Sekunden' ],
		nosec: 'vor weniger als einer ',
		curtimeDiff: ' Lokale Zeit-Differenz zur Server-Zeit in s: ',
		thxGn: 'gegeben',
		thxRd: 'erhalten',
		'new': 'neu',
		ago: 'vor $1',
		blocklog: 'Benutzersperr-Logbuch',
		// Zeilentitel
		thxGvng: 'Danksagungen: ',
		contrib: 'Edits: ', // "Bearbeitungen: ",
		usub: 'Unterseiten⬇',
		reviews: 'Aktive Sichtungen: ',
		regdate: 'Registrierungsdatum: ',
		laedit: 'Letzter Edit: ',
		lala: 'Letzte Log-Aktivität',
		fiedit: 'Erster Edit: ',
		blocks: 'Sperrstatus: ',
		loGrp: 'Lokale Benutzergruppen: ',
		glGrp: 'Globale Benutzergruppen: ',
		blockEnd: 'Sperr-Ende: ',
		blocker: 'Sperrender: ',
		blockReason: 'Sperrbegründung: '
	}
};

var data;
/* = { // JSON cache
timestamp: "",
timediff: "",
editcount: "",
groups: "",
gender: "",
lastedit: "",
firstedit: "",
reviews: "",
glGrp: "" {array}
};*/

var us = mw.libs.userstatus = {
	name: 'Userstatus',
	version: 1.74,
	// Config
	lastEditSeconds: false, // Show seconds of last edit
	styleMissingData: 'color:#999;font-size:90%;', // Styles for missing entries
	styleLoading: 'font-style:italic;',
	styleBlocked: 'color:#c00',
	styleNotBlocked: 'color:#080',
	viewPatrolNumber: false, // Primary for DeWP, supports a direct bot activation.
	lang: mw.config.get( 'wgUserLanguage' ),
	user: mw.config.get( 'wgTitle' ),
	cookie: [],
	thanks: 0,
	patrols: 0,
	actions: [],

	getLocalNames: function ( groups ) {
		if ( groups ) {
			var arr = [];
			for ( var i = 0; i < groups.length; ++i ) {
				var g = groups[ i ];
				var n = us.groupNames[ g ];
				// Link it
				if ( n ) arr.push( '<a href="/wiki/Special:ListGroupRights#' + g + '">' + n + '</a>' );
				// arr.push('<a href="#" onclick="mw.libs.userstatus.goGroupPage(event)" title="' +
				// arr.push(n);
				else arr.push( g );
			}
			// log("GetLocalNames ", arr, groups);
			groups = arr.join( ', ' ).replace( /(.*)\,([^,]*)$/, msg.and ); // String
		}
		return groups;
	},

/* goGroupPage: function (e) {
if (e.target) {
var $e = $(e.target);
$e.injectSpinner("grp");
var title = "grouppage-" + $e.attr('title');
$e.attr('target', '_blank');
$e.off('click').attr('onclick', '');
e.preventDefault();
us.ajaxRequest("&smaxage=86400&meta=allmessages&amenableparser=1&amincludelocal=1&amfrom="+ title +"&amto="+ title +"&amprefix=grouppage-&amlang=" + mw.config.get("wgContentLanguage"),
function (aw) {
$.removeSpinner("grp");
if (!aw || !aw.query) return;
aw = aw.query.allmessages;
if (aw.length)
window.open("/wiki/" + aw[0]["*"]);
}
);
}
return false;
},*/

	writeGroups: function ( groups ) {
		groups = us.getLocalNames( groups );
		if ( !groups ) {
			groups = $( '<span>', {
				style: us.styleMissingData + us.styleLoading,
				text: msg.noGrp
			} );
		}
		us.loading_groups.replaceWith( groups );
	},

	getGroupNames: function ( aw ) {
		if ( !aw || !aw.query ) return;
		aw = aw.query.allmessages;
		var al = aw.length;
		var groups = {};
		while ( al-- ) {
			var an = aw[ al ];
			var name = an.name;
			if ( /^group-/.test( name ) ) {
				name = RegExp.rightContext || name.substring( 6 );
				groups[ name ] = an[ '*' ];
			}
		}
		us.groupNames = groups;
		// console.log("groupNames: ", us.groupNames);
		us.init();
	},

	ajaxRequest: function ( params, on_success, trial ) {
		// log("ajaxRequest", params, on_success, trial);
		var url = us.api;
		if ( !( params instanceof Object ) ) { // Workaround for mw.Api bug
			url += params + '&maxage=2419200&smaxage=2419200';
			params = {};
		} else {
			params.maxlag = 3;
			params.maxage = 2419200;
			params.smaxage = 2419200;
			// params.timeout = 2400;
			if ( trial )
				params.maxlag *= trial;
		// params.timeout *= trial;

		}
		// $.getJSON(url, params, on_success)
		var api = $.ajax( {
			dataType: 'json',
			url: url,
			data: params,
			cache: true,
			success: on_success
		} )
			/* var api = new mw.Api() // Buggy ? Too much timeout errors !
.get(params, { timeout: 2400 * (trial || 1) })*/
			.fail( function ( jqXHR, status ) {
				var note = 'Timeout fail, maybe try again!';
				var error = [ us.name + ': AJAX-Request failed ', note, params, jqXHR, status ];
				if ( trial ) {
					if ( !status.textStatus || ( status.textStatus !== 'timeout' && jqXHR !== 'maxlag' ) )
						note = $( '<span>Fehler bitte <a href="' + mw.util.getUrl( 'User_talk:Perhelion' ) + '">Perhelion</a> melden.<br>Konkrete Error: ' + jqXHR + '</span>' );
					mw.notify( note, {
						title: us.name + ':',
						type: 'error'
					} );
					note = '';
					if ( !( params instanceof Object ) && /list=usercontribs/.test( params ) ) { // Fallback only
						us.actions.push( {
							params: params,
							func: on_success
						} );
						us.us_last_edit_loading.replaceWith( $( '<a>', {
						// "class" : "mw-userlink",
							title: 'Contributions ' + us.user,
							href: '/w/index.php?title=Special:Contributions/' + us.user,
							text: '?',
							click: us.doRequest
						} ) );
						// $.removeSpinner("contribs");
					}
				} else if ( status.textStatus === 'timeout' || jqXHR === 'maxlag' ) { // Try once again
					api.abort();
					mw.log.warn( error );
					return us.ajaxRequest( params, on_success, 2 );
				}
				mw.log.warn( error );
			} );
	},

	getThanks: function ( e ) {
		var params = '&list=logevents&continue=-%7C%7C&leprop=&letype=thanks&ledir=newer&lelimit=max';
		/* {
list : "logevents",
leprop : "",
letype : "thanks",
ledir : "newer",
lelimit : 500
};*/
		if ( e instanceof Object ) {
			if ( e.target ) { // reset
				if ( us.e ) // TODO should be synchron requests allowed?
				{
					return mw.notify( 'Synchron requests are not yet supported.', {
						title: us.name + ':',
						type: 'error'
					} );
				}
				e = e.target;
				us.e = $( e );
				us.e.attr( 'title', us.e.text() );
				us.e.text( '' );
				us.e.injectSpinner( 'thx' );
				us.thanks = 0;
			} else if ( e.lecontinue ) {
				params += '&lecontinue=' + e.lecontinue;
				// params.lecontinue = e.lecontinue;
			}
			if ( us.e.attr( 'title' ) === msg.thxGn )
				params += '&leuser=' + us.user;
			// params.leuser = us.user;
			else // if (us.e === msg.thxrc)
				params += '&letitle=User:' + us.user;
			// params.letitle = "User:" + us.user;
		}
		us.actions.push( {
			params: params,
			func: us.writeThanks
		} );
		us.doRequest();
	},

	writeThanks: function ( uq ) {
		if ( !uq.query || !uq.query.logevents ) return mw.log.warn( uq );
		var e = us.e;
		var user = 'page=';
		var aw = uq.query.logevents;
		us.thanks += aw.length;
		mw.log( us.thanks );
		if ( uq.continue ) return us.getThanks( uq.continue );
		if ( e[ 0 ].title === msg.thxGn ) user += '&user=';
		user += us.user;
		$.removeSpinner( 'thx' );
		e.text( us.thanks );
		e.off( 'click' );
		e[ 0 ].target = '_blank';
		e[ 0 ].href = '/w/index.php?title=Special%3ALog&type=thanks&' + user + '&year=&month=-1&tagfilter=&hide_thanks_log=0';
		delete us.e;
	},

	getUploads: function ( e ) {
		// "&list=usercontribs&uclimit=max&ucuser=Perhelion&ucnamespace=6&ucprop=ids&ucshow=new
		var params = /* {
list: "logevents",
leprop: "ids",
letype: "upload",
ledir: "newer",
leuser: us.user,
lelimit: 500
}*/
'&list=logevents&letype=upload&ledir=newer&lelimit=max&leuser=' + us.user;
		function _start( u ) {
			var text = u.text();
			u.prop( 'title', text );
			u.text( '' );
			u.injectSpinner( 'upload' + text );
			u.data( 'upl', 0 );
			u.data( 'del', 0 );
		}

		if ( e instanceof Object ) {
			if ( e.target ) { // Reset
				if ( us.e ) { // No synchron requests allowed
					return mw.notify( 'Synchron requests are not yet supported.', {
						title: us.name + ':',
						type: 'error'
					} );
				}
				e = e.target;
				us.e = $( e );
				if ( us.e.text() !== msg.new ) {
					us.e2 = us.e.nextAll( 'a' ).eq( 0 );
					if ( us.e2.text() === msg.new )
						_start( us.e2 );
					else delete us.e2;
				}
				_start( us.e );
			} else if ( e.lecontinue ) { params += '&lecontinue=' + e.lecontinue; }
			params += '&leprop=ids';
			if ( us.e.prop( 'title' ) === msg.new ) params += '&leaction=upload/upload';
			// params.leaction = "upload/upload";
			else if ( us.e2 ) params += '|type';
		}
		us.actions.push( {
			params: params,
			func: us.writeUploads
		} );
		us.doRequest();
	},

	writeUploads: function ( uq ) {
		if ( !uq.query || !uq.query.logevents || !us.e ) return mw.log( uq, us.e );
		function _insert( e ) {
			$.removeSpinner( 'upload' + e.prop( 'title' ) );
			e.text( '' );
			e.off( 'click' );
			e.append( e.data( 'del' ),
				$( '<span>', {
					style: us.styleMissingData + us.styleLoading,
					text: ' (+' + ( e.data( 'upl' ) - e.data( 'del' ) ) + ' del.)'
				} ) );
			e[ 0 ].target = '_blank';
			e[ 0 ].href = '/w/index.php?title=Special:Log&type=upload&user=' + us.user + '&subtype=' +
( ( e[ 0 ].title === msg.new ) ? 'upload' : '' );
			// "//commons.wikimedia.org/wiki/Special:ListFiles/" + us.user;
		}
		var e = us.e;
		var e2 = us.e2;
		var aw = uq.query.logevents;
		var alen = aw.length,
			i = 0;
		var del = e.data( 'del' );
		if ( e2 ) {
			var upl = e2.data( 'upl' );
			var del2 = e2.data( 'del' );
			for ( i; i < alen; ++i ) {
				var a = aw[ i ];
				var c = a.pageid ? 1 : 0;
				if ( a.action === 'upload' ) {
					upl++;
					del2 += c;
				}
				del += c;
			}
			e2.data( 'upl', upl );
			e2.data( 'del', del2 );
		} else {
			for ( i; i < alen; ++i )
				if ( aw[ i ].pageid ) del++;

		}
		e.data( 'upl', e.data( 'upl' ) + alen );
		e.data( 'del', del );
		if ( uq.continue ) return us.getUploads( uq.continue );

		_insert( e );
		if ( e2 ) {
			_insert( e2 );
			delete us.e2;
		}
		delete us.e;
	},

/**
*  @Main write function
*
*  @param {json} uq user query, used also without api
*  @return writing the results
*/
	writeCommonInfo: function ( uq ) {
		mw.log( uq );
		var aw = uq.query;
		if ( !aw || !aw.users[ 0 ] || aw.hasOwnProperty( 'missing' ) || aw.users[ 0 ].hasOwnProperty( 'invalid' ) ) return us.status_box.remove();
		aw = aw.users[ 0 ];
		var edits = aw.editcount;
		if ( aw.registration ) data.registration = aw.registration;
		var groups = data.groups || aw.groups;
		var blocked = data.blockreason || aw.blockexpiry;
		var gender = data.gender || aw.gender;
		var uploads = $( '<a>', {
			href: '#',
			title: msg.count,
			text: msg.new,
			style: us.styleLoading,
			click: us.getUploads
		} );
		us.now = us.getDateFromTimestamp( uq.curtimestamp || us.now || mw.now() );

		if ( edits ) { // Write last edit
			if ( data.editcount && data.lastedit && data.editcount === edits ) {
				uq.query.usercontribs = [ {
					timestamp: data.lastedit
				}
				];
				us.writeLastEdit( uq );
			} else {
				// mw.log(["writeLastEdit data", data.lastedit, data.registration || data.firstedit]);
				data.editcount = edits;
				us.loading_last_edit.css( 'display', 'block' );
				us.actions.push( {
					params:
/* {
smaxage: 9000,
maxage: 9000,
requestid: "contribs",
list : "usercontribs",
ucprop: "timestamp",
uclimit : 1,
uccontinue: new Date(us.now).toISOString().replace(/[^\d]/g, '').slice(0,14) + "|2", // workaround for timeout bug!?
ucstart: us.now.toISOString(),
ucend: data.lastedit || (data.registration || data.firstedit)? new Date(data.registration || data.firstedit).toISOString(): undefined, // faster?
ucuser : us.user,
// ucdir: "older"
}*/
'&list=usercontribs&ucuser=' + us.user + '&uccontinue=' + ( new Date( us.now ).toISOString().replace( /[^\d]/g, '' ).slice( 0, 14 ) + '|2' ),
					func: us.writeLastEdit
				} );
			}
			$( '#t-contributions' ).remove(); // we setted a new one
		} else { us.writeLastEdit( {} ); }

		if ( groups ) {
			edits = $( '<a>', {
				title: 'Supercount User Analysis',
				href: '//tools.wmflabs.org/supercount/index.php?user=' + us.user + '&project=' + location.hostname,
				text: edits
			} );
			if ( $.inArray( 'bot', groups ) === -1 ) {
				us.review = $.inArray( 'editor', groups ) !== -1;
				// FIXME: Due bug [[phab:T136493]] patrols are not working correct
				if ( us.review /* || $.inArray("patroller", groups) !== -1 || $.inArray("sysop", groups) !== -1 */ ) {
					if ( typeof data.reviews === 'number' ) { us.writePatrolCount( data.reviews ); }
					// De Powerusers: Benutzer:HRoestBot/Nachsichten
					else if ( typeof data.reviews === 'undefined' && us.viewPatrolNumber && project === 'dewiki' ) {
						us.actions.push( {
							params: {
								prop: 'revisions',
								rvlimit: 1,
								rvprop: 'content',
								titles: 'User:' + us.user + '/Sichterbeiträge'
							},
							func: us.writeBotPatrolCount
						} );
					}
				} else if ( us.viewPatrolNumber ) { us.us_patrolcount_loading.parent().remove(); }
				edits = [
					edits, ' • ',
					$( '<a>', { // XTools
						href: '//xtools.wmflabs.org/ec' + mw.config.get( 'wgServer' ).substr( 1 ) + '/' + us.user,
						title: 'Edit Counter – analysis of user contributions',
						text: 'XTools',
						style: us.styleLoading,
						target: '_blank'
					} ), ' • ',
					$( '<a>', { // MyGallery
						href: '//commons.wikimedia.org/w/index.php?title=Commons:MyGallery/' +
							us.user + '&withJS=MediaWiki:JSONListUploads.js',
						title: 'Commons tool: JSONListUploads.js',
						text: ( project === 'commonswiki' ? '' : 'c:' ) + 'MyGallery',
						style: us.styleLoading,
						target: '_blank'
					} ), ' • ',
					( project !== 'commonswiki' ? $( '<a>', {
						href: '/wiki/Special:ListFiles/' + us.user,
						text: 'Upload',
						title: 'Special:Listfiles',
						style: us.styleLoading
					} ) : 'Upload' ),
					( us.lang === 'de' ? '-' : ' ' ) + msg.count + ': ',
					uploads.clone( 1 ).text( 'total' ), ' / ', uploads
				];
				if ( !$( '#t-subpages' ).length ) // Subpages
				{
					edits.push( $( '<a>', {
						href: mw.util.getUrl( 'Special:Prefixindex/User:' + us.user + '/' ),
						text: ' • ' + msg.usub,
						style: us.styleMissingData + us.styleLoading
					} ) );
				}
			} else if ( us.us_patrolcount_loading ) { us.us_patrolcount_loading.parent().remove(); }
			us.loading_editcount.replaceWith( edits );

			data.groups = $.grep( groups, function ( n ) { // Exclude not needed items
				return $.inArray( n, [ '*', 'user' ] ) === -1; // , "autoconfirmed"
			} );
			us.writeGroups( data.groups );
			$( '#t-userrights' ).remove(); // we setted a new one
		}

		if ( !data.registration ) {
			us.actions.push( {
				params: '&list=logevents&leprop=timestamp|type&letype=newusers&lelimit=1&ledir=newer&leend=2005-12-30T00:00:00Z&leuser=' + us.user,
				func: us.writeRegistration
			} );
			if ( data.firstedit ) {
				uq.query.usercontribs = [ {
					timestamp: data.firstedit
				}
				];
				us.writeFirstEdit( uq );
			} else if ( edits ) { // get first edit before around 22:16, 7 September 2005
				us.actions.push( {
					params: {
						list: 'usercontribs',
						ucuser: us.user,
						uclimit: 1,
						ucend: '2005-09-08T00:00:00Z', // faster?
						ucdir: 'newer', // List oldest first!! Note: ucstart has to be before ucend.
						ucprop: 'timestamp'
					},
					func: us.writeFirstEdit
				} );
			}
		} else { us.writeRegistration( data.registration ); }

		us.loading_blocked.replaceWith( $( '<a>', {
			style: ( ( blocked ) ? us.styleBlocked : us.styleNotBlocked ),
			id: 'us_block_status_span',
			href: '/w/index.php?title=Special:Log/block&page=User:' + us.user,
			title: msg.blocklog,
			text: ( ( blocked ) ? ' ' : msg.not ) + msg.block
		} ) );

		if ( blocked ) {
			data.blockreason = aw.blockreason || ' ';
			us.ul.append( [
				$( '<li>', {
					id: 'us_block_time'
				} ).append( [
					$( '<b>' ).text( '• ' + msg.blockEnd ),
					$.createSpinner( 'us_block_time_loading' )
				] ),
				$( '<li>', {
					id: 'us_block_reason'
				} ).append( [
					$( '<b>' ).text( '• ' + msg.blockReason ),
					$.createSpinner( 'us_block_reason_loading' )
				] ),
				$( '<li>', {
					id: 'us_blocker'
				} ).append( [
					$( '<b>' ).text( '• ' + msg.blocker ),
					$.createSpinner( 'us_blocker_loading' )
				] )
			] );

			us.actions.push( {
				params: {
					list: 'logevents',
					letitle: 'User:' + us.user,
					letype: 'block',
					lelimit: 1
				},
				func: us.writeBlockDetail
			} );
		}

		if ( data.glGrp ) { // TODO  1.31.0-wmf get also local group info
			uq.query.globaluserinfo = {
				groups: ( data.glGrp.length ) ? data.glGrp : null
			};
			us.writeGlobalGroup( uq );
		} else {
			us.actions.push( {
				params: /* {
meta : "globaluserinfo",
guiuser : us.user,
guiprop : "groups"
}*/
'&meta=globaluserinfo&guiprop=groups&guiuser=' + us.user,
				func: us.writeGlobalGroup
			} );
		}

		if ( gender ) {
			data.gender = gender;
			var genderSn = '';
			switch ( gender ) {
				case 'male':
					genderSn = ' \u2642';
					break;
				case 'female':
					genderSn = ' \u2640';
			}
			genderSn = $( '<span>', {
				id: 'ps-gender-' + gender,
				style: 'font-size:80%',
				text: genderSn
			} );
			$( '#firstHeading' ).append( genderSn );
		}
		if ( us.actions.length ) us.doRequest();
	},

	createBox: function () { /* Box erstellen */
		var status_box = $( '<div>', {
			style: 'border-bottom:1px solid #aaa;text-shadow:1px 1px 1px #eff;', // padding:1px
			id: 'us_box'
		} );
		if ( mw.config.get( 'skin' ) === 'vector' ) // Fix size in vector
			status_box.css( 'font-size', '0.8em' );

		// var spanFrag = $("<span>", {style: us.styleLoading, text: msg.load});
		var spanFrag = $.createSpinner();
		us.loading_editcount = spanFrag.clone();
		us.loading_registration = spanFrag.clone();
		us.loading_groups = spanFrag.clone();
		us.loading_blocked = spanFrag.clone();
		us.us_global_group_loading = spanFrag.clone();
		us.us_last_edit_loading = $.createSpinner( 'contribs' );
		us.loading_last_edit = $( '<li>', {
		// id : "us_last_edit",
		// style : "display: none" //if (edits)
		} );
		us.us_global_group = $( '<li>', {
		// id: "us_global_group",
			style: 'display: none'
		} );
		var thx = $( '<a>', {
			href: '#',
			title: msg.count,
			text: msg.thxRd,
			style: us.styleLoading,
			click: us.getThanks
		} );
		var $userrights = $( '#t-userrights a' );
		var $contributions = $( '#t-contributions a' );

		if ( $userrights.length ) { $userrights.attr( 'title', $userrights.text() ); } else {
			$userrights = $( '<a>', {
				href: '/wiki/Special:UserRights/' + us.user
			} );
		}
		if ( $contributions.length ) { $contributions.attr( 'title', $contributions.text() ); } else {
			$contributions = $( '<a>', {
				href: '/wiki/Special:Contributions/' + us.user
			} );
		}
		var ul = $( '<ul>', {
			style: 'list-style: none'
		} ).append( $( '<li>', {
			id: 'us_editcount'
		} ).append( [
			$( '<b>' ).append( $contributions.text( msg.contrib ) ),
			us.loading_editcount ] ) );

		// Sichtungen/Patrols
		us.us_log_count = $( '<span>' );
		if ( us.viewPatrolNumber ) {
			us.us_patrolcount_loading = $( '<a>', {
				style: us.styleLoading,
				href: '#',
				click: us.getPatrolCount,
				text: msg.count
			} );
			us.us_log_count = $( '<span>' ).append( [
				$( '<b>' ).text( msg.reviews ), us.us_patrolcount_loading, ' • '
			] );
		}
		ul.append( us.us_log_count )
			.append( $( '<li>', {
				id: 'us_reg_date'
			} ).append( [
				$( '<b>' ).text( msg.regdate ), us.loading_registration,
				$( '<a>', {
					target: '_blank',
					style: 'float:right;' + us.styleMissingData,
					href: 'https://tools.wmflabs.org/meta/userpages/' + us.user,
					title: 'Find this users pages on all Wikimedia wikis.',
					text: '• User pages'
				} )
			] ) )

		// Lokale Gruppen
			.append( $( '<li>' /* , { id : "us_local_group" }*/ ).append( [
				$( '<b>' ).append( $userrights.text( msg.loGrp ) ),
				us.loading_groups,
				$( '<a>', {
					target: '_blank',
					style: 'float:right;' + us.styleMissingData,
					href: 'https://tools.wmflabs.org/meta/stalktoy/' + us.user,
					title: 'View global details about this user across all Wikimedia wikis.',
					text: '• Stalk toy'
				} )
			] ) )
		// 'Global Groups'
			.append( us.us_global_group.append( [
				$( '<b>' ).append(
					$( '<a>', {
						href: '/w/index.php?title=Special:GlobalUsers&limit=1&username=' + us.user,
						text: msg.glGrp
					} ) ),
				us.us_global_group_loading,
				$( '<a>', {
					target: '_blank',
					style: 'float:right;' + us.styleMissingData,
					href: 'https://tools.wmflabs.org/meta/globalgroups/',
					title: 'A review of extra permissions assigned to global groups on Wikimedia Foundation wikis.',
					text: '• GlobalGroups'
				} )
			] ) )
		// Letzter Edit
			.append( us.loading_last_edit.append( [
				$( '<b>' ).text( msg.laedit ), us.us_last_edit_loading, ' ',
				$( '<a>', {
					target: '_blank',
					style: us.styleMissingData,
					href: '/w/index.php?title=Special:Log/' + us.user + '&hide_thanks_log=0&hide_patrol_log=0&hide_tag_log=0',
					title: msg.lala,
					text: '• LLA'
				} ),
				$( '<a>', {
					target: '_blank',
					style: 'float:right;' + us.styleMissingData,
					href: 'https://tools.wmflabs.org/meta/crossactivity/' + us.user,
					title: 'Measures user\'s latest edit, bureaucrat, or sysop activity on all wikis.',
					text: '• CrossActivity'
				} )
			] ) );

		status_box.append( ul );

		// Block & thx
		us.ul = $( '<ul>', {
			style: 'list-style:none'
		} ).append( $( '<li>', {
				id: 'us_block_status'
			} ).append( [
				$( '<b>' ).text( msg.blocks ),
				us.loading_blocked,
				' • ',
				$( '<b>' ).text( msg.thxGvng ),
				thx, ' / ',
				thx.clone( 1 ).text( msg.thxGn )
			] )
			);

		status_box.append( us.ul );
		$( '#firstHeading' ).after( status_box );
		us.status_box = status_box;
	},

	setCookie: function () {
		var domain = ( mw.config.get( 'wgNoticeProject' ) === 'wikipedia' ) ? 'wikipedia.org' : '';
		var name = us.name + us.user;

		if ( !us.actions[ 0 ] && JSON && data.editcount ) {
			if ( us.cookie.length )
				window.clearTimeout( us.cookie.shift() );

/**
* @param {string} name
* @param {string} key  (for "glGrp": deprecated)
* @param {json} data
*/
			var _saveCookie = function ( key, data ) {
				mw.cookie.set( name, JSON.stringify( data ), {
					prefix: key,
					expires: 600000, // Save 1 week
					domain: domain
				} );
			};

			// Check only once
			var saveData = window.indexedDB ?
/**
* @param {string} name (for "glGrp": deprecated)
* @param {json} JSdata
* @param {integer} version
*/
				function ( name, JSdata, version ) { // _saveIDB
					var store,
						key = ( name || project ) + us.user,
						db,
						request = {};
					data = { // TODO: Maybe extend
						name: key,
						data: JSdata
					};
					request = version ?
						indexedDB.open( us.name, version ) :
						indexedDB.open( us.name );
					// indexedDB.deleteDatabase("Userstatus")

					request.onupgradeneeded = function () {
						db = this.result;
						mw.log( db.version + ' onupgradeDb ' + db.objectStoreNames.contains( key ) );
						if ( !db.objectStoreNames.contains( key ) ) { // New
							store = db.createObjectStore( key, {
								keyPath: 'name'
							} );
							store.createIndex( 'data', 'data', {
								unique: false
							} );
							// Use transaction oncomplete to make sure the objectStore creation is finished before adding data into it.
							store.transaction.oncomplete = function () {
							// Store values in the newly created objectStore.
								mw.log( db.version + ' onupgradeDb.transaction.complete store ' + db.objectStoreNames.contains( key ) );

								store = db.transaction( key, 'readwrite' ).objectStore( key );
								var req = store.get( key );
								req.onsuccess = function () {
									mw.log( 'Success writeDb.transaction close', this.result );
								// db.close();
								};
							};
						} else { mw.log.warn( 'saveDb FAIL' ); }
					};

					request.onerror = function () {
						_saveCookie( name, JSdata );
						mw.log.warn( us.name, 'saved data as cookie. Fail openDb:', this.error.message );
					};

					request.onsuccess = function () {
						db = this.result;
						db.onversionchange = function () {
							mw.log( 'closedDb' );
							db.close();
						// location.reload();
						};
						version = db.version;
						mw.log( version + ' onsuccess ' + db.objectStoreNames.contains( key ) );
						if ( db.objectStoreNames.contains( key ) ) { // Overwrite
							store = db.transaction( key, 'readwrite' ).objectStore( key );
							var req = store.put( data );
							// mw.log("openDb get users:", db.objectStoreNames);
							req.onsuccess = function () {
								if ( this.result ) { // only one
									mw.log( 'replaceDb store DONE:', this.result, req );
								} else {
									saveData( name, JSdata, version + 1 );
								}
							};
							req.onerror = function () {
								mw.log.warn( us.name, key, 'replaceDb store FAIL:', this.error );
							};

						} else {
							saveData( name, JSdata, version + 1 );
						}
					};
				} :
				_saveCookie;

			us.cookie.push( setTimeout( function ( us ) { // prevent double exec
				if ( us.cookie.length === 1 ) {
					data.timestamp = new Date().valueOf();
					if ( !data.registration ) delete data.registration;
					if ( data.gender && data.gender === 'unknown' ) delete data.gender;
					saveData( '', data );
					mw.log( 'Do save ' + us.name + ': ' + JSON.stringify( data ) );
					window.clearTimeout( us.cookie[ 0 ] );
				} else if ( us.cookie.length ) {
					window.clearTimeout( us.cookie.shift() );
					us.setCookie();
				}
			}, 400, us ) );
		}
	},

/**
* API in race condition
* @param {array} us.actions
* @var {json: param, callback} action
*/
	doRequest: function () {
		var action = us.actions.shift();
		if ( action ) us.ajaxRequest( action.params, action.func );
		if ( us.actions.length ) {
			window.setTimeout( function ( thisObj ) {
				thisObj.doRequest();
			}, 100, us );
		}
	},

	getDateDiff: function ( now, date ) {
		var d_str = [];
		var d = {
			years: 0,
			months: 0,
			days: 0,
			hours: 0,
			minutes: 0,
			seconds: 0
		};
		var diffDays = 0;

		if ( now > date ) {
			diffDays = Math.floor( ( now - date ) / 86400000 );

			if ( diffDays > 27 ) {
				// Years and Months
				var year = now.getFullYear();
				d.years = date.getFullYear();
				date.setFullYear( year );
				d.years = year - d.years;
				d.months = date.getMonth();
				var months = now.getMonth();
				if ( date > now ) { // the date month >= now month
					d.years--;
					d.months = 12 - d.months + months;
					date.setFullYear( year - 1 );
				} else { d.months = months - d.months; }

				var days = date.getDate();
				d.days = now.getDate();

				if ( d.days < days ) {
					d.days += ( new Date( new Date( year, months ) - 1000 ).getDate() ) - days;
					d.months--;
					months--;
				} else { d.days -= days; }
				date = new Date( year, months, days, date.getHours(), date.getMinutes(), date.getSeconds() );
			}

			if ( now < date )
				date = new Date( date - 1000 );
			// Diff days and lower
			var s = Math.abs( now - date ) / 1000;
			d.seconds = Math.floor( s % 60 );
			s = s / 60;
			d.minutes = Math.floor( s % 60 );
			s = s / 60;
			d.hours = Math.floor( s % 24 );
			d.days = Math.floor( s / 24 );

			for ( var i = 0, dlen = msg.date.length; i < dlen; ++i ) {
				var t = d[ i18n.en.dates[ i ] ];
				if ( t ) d_str.push( t + ' ' + ( ( t > 1 ) ? msg.dates[ i ] : msg.date[ i ] ) );
			}
		}
		return ( d_str.length ) ?
			d_str.join( ', ' ).replace( /(.*)\,([^,]*)/, msg.and ).replace( /(.*)/, msg.ago ) +
			( ( d.months + d.years ) ? ' = ' + diffDays + ' ' + msg.dates[ 2 ] : '' ) :
			msg.nosec;
	},

	getDateFromTimestamp: function ( t ) {
		if ( !t ) return false;
		t = new Date( t );
		return isNaN( t.valueOf() ) ? false : t;
	},

	writeRegistration: function ( aw ) {
		// mw.log(aw);
		if ( aw ) {
			if ( aw instanceof Object ) {
				if ( !aw || !aw.query ) return;
				aw = aw.query.logevents;
				if ( aw && aw.length && aw[ 0 ].action === 'newusers' ) aw = aw[ 0 ].timestamp;
			}
			if ( !( aw instanceof Object ) ) {
				us.loading_registration.replaceWith( us.formatDate( aw ) + ' ',
					$( '<span>', {
						style: us.styleMissingData,
						text: '(' + us.getDateDiff( us.now, us.getDateFromTimestamp( aw ) ) + ')'
					} ) );
				data.registration = aw;
				return us.setCookie();
			}
		}
		us.loading_registration.replaceWith( $( '<span>', {
			style: us.styleMissingData + us.styleLoading,
			text: msg.nodb
		} ) );
	},

	writeFirstEdit: function ( aw ) {
		var date = aw.query.usercontribs;
		if ( !date.length ) return;
		date = data.firstedit = date[ 0 ].timestamp;
		$( '#us_reg_date' ).append(
			$( '<li>' ).append( [
				$( '<b>', {
					text: '• ' + msg.fiedit
				} ), us.formatDate( date ) + ' ',
				$( '<span>', {
					style: us.styleMissingData,
					text: '(' + us.getDateDiff( us.now, us.getDateFromTimestamp( date ) ) + ')'
				} )
			] ) );
	},

	writeLastEdit: function ( aw ) {
		var uc = aw.query;
		// log(aw);
		if ( !uc || !uc.usercontribs || !uc.usercontribs.length ) {
			return us.us_last_edit_loading.replaceWith( $( '<span>', {
				style: us.styleMissingData,
				text: msg.noedit
			} ) );
		}
		uc = uc.usercontribs;
		var date = data.lastedit = uc[ 0 ].timestamp;
		us.us_last_edit_loading.replaceWith( us.getDateDiff( us.now, us.getDateFromTimestamp( date ) ), ' ',
			$( '<span>', {
				style: us.styleMissingData,
				text: '(' + us.formatDate( date ) + ')'
			} ) );
		data.timediff = new Date() - us.now;
		$( '#us_editcount' ).append(
			$( '<span>', {
				style: 'float:right;' + us.styleMissingData,
				text: msg.curtimeDiff /* + " – " + us.formatDate(now)*/
			} ).append(
				$( '<b>', {
					text: Math.round( data.timediff / 1000 )
				} ) ) );
		us.setCookie();
	},

	writeGlobalGroup: function ( aw ) {
		aw = aw.query.globaluserinfo;
		var groups = data.glGrp = aw.groups;
		if ( groups && groups.length ) {
			us.us_global_group_loading.replaceWith( us.getLocalNames( groups ) );
			us.us_global_group.css( 'display', 'block' );
		}
		// Global lock
		if ( aw.locked ) {
			data.locked = 1;
			$( '#us_block_status_span' ).append( ' [locked]' );
		}
		us.setCookie();
	},

	// /w/api.php?action=query&format=json&list=logevents&letype=block&letitle=User%3AErwin_Lindemann
	writeBlockDetail: function ( aw ) {
		var duration,
			expiry;
		aw = aw.query.logevents[ 0 ];
		if ( aw.params ) {
			duration = aw.params.duration;
			expiry = '';
			if ( /in(de)?finite/.test( duration ) )
				expiry = msg.never;
			else
				expiry = us.formatDate( aw.params.expiry );
		}
		// mw.log("writeBlockDetail:", aw, expiry);
		$( '#us_block_status_span' ).text( msg.block + ' (' + duration + ')' )
			.append( $( '<span>', {
				style: us.styleMissingData,
				text: ' – ' + us.formatDate( aw.timestamp )
			} ) );
		$( '#mw-spinner-us_block_time_loading' ).replaceWith( expiry );
		$( '#mw-spinner-us_block_reason_loading' ).replaceWith( us.parseComment( aw.comment, ( aw.commenthidden ) ) );
		$( '#mw-spinner-us_blocker_loading' ).replaceWith( $( '<a>', {
			'class': 'mw-userlink',
			title: 'User:' + aw.user,
			href: mw.util.getUrl( 'User:' + aw.user ),
			text: aw.user
		} ) );
	},

	writeBotPatrolCount: function ( aw ) {
		aw = aw.query;
		var patrols = '';
		if ( aw ) {
			for ( var key in aw.pages ) {
				if ( key !== '-1' ) {
					patrols += parseInt( aw.pages[ key ].revisions[ 0 ][ '*' ], 10 );
					break;
				}
			}
		}
		if ( patrols ) return us.writePatrolCount( patrols );
	},

	writePatrolCount: function ( aw ) {
		// log(aw);
		if ( aw instanceof Object ) {
			var ql = aw.query.logevents;
			if ( !ql ) return;
			us.patrols += ql.length;
			if ( aw.continue ) return us.getPatrolCount( aw.continue );
			$.removeSpinner( 'pat' );
			aw = us.patrols;
		}
		us.us_patrolcount_loading.replaceWith( $( '<a>', {
			title: 'Review-Log',
			href: '/w/index.php?title=Special:Log&type=' + ( us.review ? 'review&subtype=accept' : 'patrol&subtype=patrol' ) + '&user=' + us.user,
			text: aw
		} ) );
		data.reviews = aw;
		us.setCookie();
	},

	getPatrolCount: function ( e ) {
		// Reviews only on some Wikis like de
		// mw.log(us.review,e)
		var params = /* {
list : "logevents",
ledir : "newer",
leprop : "",
// letype : "patrol",
leaction : "patrol/patrol",
leuser : ,
lelimit : 500
}*/
'&list=logevents&leprop=&ledir=newer&lelimit=max&leuser=' +
us.user + '&leaction=' +
( us.review ? 'review/approve' : 'patrol/patrol' );
		// params.leaction = "review/approve";
		// params.letype = "review";
		if ( e instanceof Object ) {
			if ( e.target ) { // reset
				$( e.target ).injectSpinner( 'pat' );
				us.patrols = 0;
			} else if ( e.lecontinue )
			// params.lecontinue = e.lecontinue;
			{ params += '&lecontinue=' + e.lecontinue; }
		}
		us.actions.push( {
			params: params,
			func: us.writePatrolCount
		} );
		us.doRequest();
	},

	parseComment: function ( text, hidden ) {
		var comment = $( '<span>', {
			'class': 'comment'
		// style : "font-style: normal"
		} );

		if ( typeof ( text ) === 'undefined' ) {
			if ( hidden ) {
				return comment.append( msg.noReason ).css( 'color', '#999' );
			} else {
				mw.notify( $( '<span>Fehler bitte <a href="' + mw.util.getUrl( 'User_talk:Perhelion' ) + '">Perhelion</a> melden:<br><i>Undefined comment at page ' + us.user + '</i></span>' ), {
					title: us.name + ':',
					type: 'error'
				} );
				return comment.append( 'undefined' ).css( 'color', 'red' );
			}
		} else if ( !text ) {
			return comment.append( msg.blockCmt ).css( 'color', '#999' );
		}

		var intLink = /(.*?)\[\[((.*?)\|)?(.*?)\]\](.*)/;
		var suche = text;
		var erg;
		while ( ( erg = intLink.exec( suche ) ) !== null ) {
			erg[ 3 ] = ( erg[ 2 ] ) ?
				$( '<a>', {
					href: mw.util.getUrl( erg[ 3 ] ),
					title: erg[ 3 ]
				} ) :
				$( '<a>', {
					href: mw.util.getUrl( erg[ 4 ] ),
					title: erg[ 4 ]
				} );
			comment.append( [ erg[ 1 ], erg[ 3 ].text( erg[ 4 ] ) ] );
			suche = erg[ 5 ];
		}
		return comment.append( suche );
	},

	formatDate: function ( datum ) {
		if ( !( datum instanceof Object ) ) datum = new Date( datum );
		try {
			datum = datum.toLocaleDateString( us.lang, {
				weekday: 'long',
				year: 'numeric',
				month: 'long',
				day: 'numeric',
				hour: 'numeric',
				minute: 'numeric',
				second: 'numeric'
			} );
		} catch ( e ) {
			if ( e.name === 'RangeError' ) datum = datum.toLocaleString();
		}
		return datum;
	},

	getStoredData: function () {
		var store,
			key = project + us.user,
			db,
			request;

		try {
			request = indexedDB.open( us.name );
			request.onsuccess = function () {
				db = this.result;
				db.onversionchange = function () {
					mw.log( 'closedDb' );
					db.close();
				};
				mw.log( db.version + ' successDb ' + db.objectStoreNames.contains( key ), key );
				if ( db.objectStoreNames.contains( key ) ) {
					store = db.transaction( key, 'readonly' ).objectStore( key );
					store.transaction.oncomplete = function () {
					// mw.log("readDb.transaction.oncomplete runDataStore", data);
						us.runDataStore();
						db.close();
					};
					var req = store.get( key );
					// console.log("openDb get user:", store, req);
					req.onsuccess = function () {
						if ( this.result ) {
							mw.log( 'openDb get user DONE:', this.result );
							data = this.result.data;
						} else { us.getCookie( this.error ); }
					};
					req.onerror = function () {
						us.getCookie( this.error );
					};
				} else {
					us.getCookie( 'new user' );
				}
			};
			request.onerror = function () {
				us.getCookie( this.error );
			};
		} catch ( e ) {
			us.getCookie( 'unknownError' );
		}
	},

	getCookie: function ( err ) {
		mw.log( us.name + ' FAIL open indexedDB store, try get cookie: ' + ( err || '' ) );
		data = mw.cookie.get( us.name + us.user );
		us.runDataStore();
	},

	initI18N: function ( i18n ) {
		var i, chain = mw.language.getFallbackLanguageChain();
		for ( i = chain.length - 1; i >= 0; i-- ) {
			if ( chain[ i ] in i18n )
				msg = i18n[ chain[ i ] ];
		}
	},

	init: function () {
		this.usprop = 'blockinfo|groups|editcount|gender|registration';
		this.self = ( mw.config.get( 'wgUserName' ) === this.user );
		this.initI18N( i18n );

		if ( !us.lastEditSeconds ) msg.date.pop();
		msg.nosec += msg.date.slice( -1 )[ 0 ];

		us.createBox();
		if ( mw.cookie && JSON ) {
			if ( window.indexedDB ) {
				// this.getStoredData("GlGrp");
				us.getStoredData();
			} else {
				// this.getCookie("GlGrp");
				us.getCookie();
			}
		} else { mw.hook( 'resourceloader.loadEnd' ).add( function () { us.run(); } ); }
	},

	runDataStore: function () {
		if ( data ) { // Max 255 Bytes for Cookie
			mw.log( 'data', data, decodeURI( data ), 'glGrp: ', data.glGrp );
			if ( typeof data === 'string' ) {
				try {
					data = JSON.parse( data );
				} catch ( e ) {
					mw.log.warn( e, data );
					data = {};
				}
			}
			var q = {
				query: {}
			};
			q.query.users = [ data ];
			// data.glGrp = data.glGrp || this.glGrpStore;
			if ( data.timestamp ) { us.now = new Date() - data.timediff; } else
			// if (glGrp) q.query.globaluserinfo = { groups : glGrp };
			// mw.log("data parsed", data, "Time: ", data.timestamp, data.timediff, "glGrp: ", data.glGrp, q);
			if ( Math.abs( ( new Date() - data.timestamp - data.timediff ) / 1000 ) < 90 ) { // only if 1.5 min
			// q.curtimestamp = us.now; // Speedup: Maybe only if lastedit is minimum one day old?
			// mw.log(us.name + " RUN fast mode");
				return us.writeCommonInfo( q );
			}
			if ( data.editcount ) this.usprop = 'editcount'; // Minimize API request
		}
		data = data || {};
		// data.glGrp = this.glGrpStore;
		mw.log( 'run' + us.name, data, data.glGrp );
		mw.hook( 'resourceloader.loadEnd' ).add( function () { us.run(); } );
	},

	run: function () {
		if ( us.self ) { // Omit API-request
			data.glGrp = mw.config.get( 'wgGlobalGroups' );
			return us.writeCommonInfo( {
				query: {
					users: [ {
						editcount: mw.config.get( 'wgUserEditCount' ),
						registration: mw.config.get( 'wgUserRegistration' ),
						groups: mw.config.get( 'wgUserGroups' ),
						blockreason: '' // we self should know :P
					}
					]
				}
			} );
		}
		// Start API-request (in race condition)
		us.ajaxRequest( {
			curtimestamp: us.now ? 0 : 1,
			list: 'users',
			ususers: this.user,
			usprop: this.usprop
		}, us.writeCommonInfo );
	}
};

if ( [ 2, 3 ].indexOf( mw.config.get( 'wgNamespaceNumber' ) ) !== -1 && us.user.indexOf( '/' ) === -1 &&
( !mw.config.get( 'wgArticleId' ) || mw.config.get( 'wgAction' ) === 'view' ) ) {
	$( document ).trigger( 'loadWikiScript', us );
	$.when( mw.loader.using( [ 'mediawiki.util', 'mediawiki.api', 'mediawiki.language', 'mediawiki.cookie', 'jquery.spinner' ] ),
		$.ready ).then( function () {
		us.api = '//' + location.hostname + mw.util.wikiScript( 'api' ) + '?action=query&format=json';
		us.ajaxRequest( '&maxage=2419200&smaxage=2419200&meta=allmessages&amenableparser=1&amincludelocal=1&amprefix=group-', us.getGroupNames );
		if ( !mw.libs.viewerInfo ) { // Number of observers
			mw.loader.load( '//meta.wikimedia.org/w/index.php?title=User:Perhelion/viewerInfo.js&action=raw&ctype=text/javascript' );
		}
	} );
}
}( jQuery, mediaWiki ) );
// </nowiki> EOF