Jump to content

User:Veko/common.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Veko (talk | contribs) at 00:02, 9 April 2025 (Installing User:Queen of Hearts/common.js (script-installer)). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
////////// ENHANCED STATUS CHANGER SCRIPT
// Creator: Misza13
// Modified by: Various contributors
// Updated to include additional statuses from the UserStatus template

$.when(
    $.ready,
    mw.loader.using( [ "mediawiki.api" ] )
).then( function () {
    // Create configuration variable if it doesn’t exist
    if (typeof(statusChangerConfig) === 'undefined') {
        statusChangerConfig = {};
    }

    // Expanded status list (Includes additional statuses from your table)
    if (typeof(statusChangerConfig.statusList) === 'undefined') {
        statusChangerConfig.statusList = [
            'online', 'offline', 'sleeping', 'busy', 'away', 'editing', 
            'atwork', 'school', 'eating', 'vandal', 'holiday', 'twinkling',
            'huggling', 'wikibreak', 'working'
        ];
    }

    // Define the status page
    if (typeof(statusChangerConfig.statusPage) === 'undefined') {
        statusChangerConfig.statusPage = 'User:' + mw.config.get('wgUserName') + '/Status';
    }

    function makeListener(newStatus) {
        return function ( evt ) {
            evt.preventDefault();
            var api = new mw.Api({
                ajax: { headers: { 'Api-User-Agent': '[[w:User:Enterprisey/StatusChanger.js]]' } }
            });

            api.postWithEditToken({
                action: 'edit',
                title: statusChangerConfig.statusPage,
                text: newStatus,
                summary: mw.config.get('wgUserName') + " is now " + ((newStatus === "sleep") ? "sleeping" : newStatus) + "."
            }).then(function(){
                // Purge the user page after changing status
                api.post( { action: "purge", titles: 'User:' + mw.config.get('wgUserName') } ).then(function(){
                    mw.notify('Status updated and page purged.');
                });
            });
            return false;
        };
    }

    // Add status changer buttons in a collapsible table
    var statusTable = document.createElement("div");
    statusTable.innerHTML = `
        <div style="text-align: center; margin-top: 10px;">
            <strong>Change Your Status:</strong>
            <table style="margin: auto; border-collapse: collapse;">
                <tr>
                    <td><button class="status-button" data-status="online">Online</button></td>
                    <td><button class="status-button" data-status="offline">Offline</button></td>
                    <td><button class="status-button" data-status="editing">Editing</button></td>
                    <td><button class="status-button" data-status="busy">Busy</button></td>
                </tr>
                <tr>
                    <td><button class="status-button" data-status="away">Away</button></td>
                    <td><button class="status-button" data-status="sleeping">Sleeping</button></td>
                    <td><button class="status-button" data-status="atwork">At Work</button></td>
                    <td><button class="status-button" data-status="school">At School</button></td>
                </tr>
                <tr>
                    <td><button class="status-button" data-status="eating">Eating</button></td>
                    <td><button class="status-button" data-status="vandal">Fighting Vandalism</button></td>
                    <td><button class="status-button" data-status="wikibreak">On a Wikibreak</button></td>
                    <td><button class="status-button" data-status="holiday">On Holiday</button></td>
                </tr>
                <tr>
                    <td><button class="status-button" data-status="huggling">Huggling</button></td>
                    <td><button class="status-button" data-status="twinkling">Twinkling</button></td>
                    <td colspan="2"><button class="status-button" data-status="working">Working</button></td>
                </tr>
            </table>
        </div>
    `;

    // Attach event listeners to buttons
    statusTable.querySelectorAll('.status-button').forEach(button => {
        button.addEventListener('click', makeListener(button.getAttribute('data-status')));
    });

    // Insert the table below the user status box
    var userStatusDiv = document.getElementById("TemplateUserinfo");
    if (userStatusDiv) {
        userStatusDiv.appendChild(statusTable);
    }

    // Add quick-access links to the personal menu (top right)
    for (var i = 0; i < statusChangerConfig.statusList.length; i++) {
        var stat = statusChangerConfig.statusList[i];
        var message = (stat === "sleeping") ? "asleep" : stat;

        mw.util.addPortletLink(
            "p-personal", // Target tab - personal links
            "#",
            stat, // Link text
            "pt-status-" + stat, // ID of new button
            "I'm " + message + "!", // Hover text
            "", // Access key - not needed
            document.getElementById("pt-logout") // Add before logout button
        ).addEventListener('click', makeListener(stat));
    }

    // Add a purge link manually to the status section
    var purgeLink = document.createElement("a");
    purgeLink.href = "https://en.wikipedia.org/w/index.php?title=User:" + mw.config.get('wgUserName') + "&action=purge";
    purgeLink.textContent = "Click here to refresh status";
    purgeLink.style.display = "block";
    purgeLink.style.textAlign = "center";
    purgeLink.style.marginTop = "5px";

    if (userStatusDiv) {
        userStatusDiv.appendChild(purgeLink);
    }
});



//[[Category:Wikipedia scripts|statusChanger]]
importScript('User:Lupin/recent2.js'); // Backlink: [[User:Lupin/recent2.js]]
importScript('User:Ingenuity/AntiVandal.js'); // Backlink: [[User:Ingenuity/AntiVandal.js]]
importScript('User:Awesome Aasim/rcpatrol.js'); // Backlink: [[User:Awesome Aasim/rcpatrol.js]]
mw.loader.load( '/w/index.php?title=User:Evad37/rater.js&action=raw&ctype=text/javascript' ); // Backlink: [[User:Evad37/rater.js]]
(function($, mw){
	mw.loader.using(['mediawiki.api']).done(function () {
		var defaultOptions = {
			parameters: {
				format: 'json'
			},
			ajax: {
				timeout: 30 * 1000, // 30 seconds
				dataType: 'json',
				type: 'GET'
			},
			score: {
				batchSize: 50,
				maxWorkers: 4
			}
		};
	
		/**
		 * Constructor to create a pool of workers to request a set of model-scores
		 * from the ORES api.  This object will allow scoring jobs to be
		 * transparently started within a worker pool.  The worker pool will then
		 * manage all requests in order to comply with the options provided
		 * (e.g. batchSize and maxWorkers).
		 *
		 *     var ores = require('ext.ores.api');
		 *     aqPool = ores.Pool([ "articlequality" ], {batchSize: 50, maxWorkers: 3})
		 *
		 *     aqPool.score(214412)
		 *       .done(function(scoreDoc){...})
		 *     aqPool.score(214413)
		 *       .done(function(scoreDoc){...})
		 *
		 * @constructor
		 * @param {Object} [oresApi] An OresApi object to use for querying
		 * @param {Array} [models] A list of models to query for in each request
		 * @param {Object} [options] A set of options for querying.  See defaultOptions.score above
		 */
		var OresScoreBatcherPool = function(oresApi, models, options) {
			this.oresApi = oresApi;
			this.models = models;
			this.batchSize = options.batchSize || defaultOptions.score.batchSize;
			this.maxWorkers = options.maxWorkers || defaultOptions.score.maxWorkers;
			this.liveWorkers = 0;
			this.taskQueue = [];
			this.tasksQueued = $.Deferred()
				.progress(this.ensureWorkers.bind(this));
		};
		OresScoreBatcherPool.prototype = {
			/**
			 * Add a score job to the queue and return a promise for the result.
			 *
			 * @param {number} [revId] A revision to score with the given model
			 */
			score: function(revId) {
				var resultDfd = $.Deferred(),
					task = {revId: revId, resultDfd: resultDfd};
	
				this.taskQueue.push(task);
				this.tasksQueued.notify();
				return resultDfd.promise();
			},
			/**
			 * Process a batch and recurse until the taskQueue is empty
			 */
			processTaskBatches: function(){
				// Get a batch to process
				batch = this.taskQueue.splice(0, this.batchSize);
	
				// If there's stuff to process, start a new score batch processing job and
				// recurse when it finishes.
				if (batch.length) {
					this.scoreBatch(batch)
						.fail(function(){
							for (var i = 0; i < batch.length; i++) {
								batch[i].resultDfd.error.apply(null, arguments);
							}
						})
						.always(this.processTaskBatches.bind(this));
				} else {
					// shut down worker and decrement the worker count
					this.liveWorkers -= 1;
					console.debug("Shutting down worker");
				}
			},
			/**
			 * Generate a set of scores for a batch.  Results will be sent to specific
			 * deferred result objects.
			 */
			scoreBatch: function(batch){
				var batchDfd = $.Deferred(),
					revIds = [];
	
				for (var i = 0; i < batch.length; i++) {
					revIds.push(batch[i].revId);
				}
	
				this.oresApi.get({revids: revIds, models: this.models})
					.done(function(responseDoc){
						for (var i = 0; i < batch.length; i++) {
							var scoreDoc = responseDoc[this.oresApi.options.dbname].scores[batch[i].revId];
							batch[i].resultDfd.resolve(scoreDoc);
						}
					}.bind(this))
					.fail(function(){
						for (var i = 0; i < batch.length; i++) {
							batch[i].resultDfd.error.apply(null, arguments);
						}
					})
					.always(function(){batchDfd.resolve()});
	
				return batchDfd.promise();
			},
			/**
			 * Ensure that workers are running.  This method is used when a task is
			 * added to the queue to make sure that the workers are started/restarted
			 * if necessary.  Note that a 50ms delay is implemented for starting a
			 * worker process to ensure that tasks are given enough time to enqueue
			 * before batch processing starts.
			 */
			ensureWorkers: function(){
				while (this.liveWorkers < this.maxWorkers) {
					console.debug("Starting up worker");
					setTimeout(
						function(){this.processTaskBatches()}.bind(this),
						50);
					this.liveWorkers += 1;
				}
			}
		};
	
		/**
		 * Constructor to create an object to interact with the API of an ORES server.
		 * OresApi objects represent the API of a particular ORES server.
		 *
		 *     var ores = require('ext.ores.api');
		 *     ores.get( {
		 *         revids: [1234, 1235],
		 *         models: [ 'damaging', 'articlequality' ] // same effect as 'damaging|articlequality'
		 *     } ).done( function ( data ) {
		 *         console.log( data );
		 *     } );
		 *
		 * @constructor
		 * @param {Object} [options] See #defaultOptions documentation above.
		 */
		var OresApi = function ( options ) {
	
			options.parameters = $.extend( {}, defaultOptions.parameters, options.parameters );
			options.ajax = $.extend( {}, defaultOptions.ajax, options.ajax );
			options.score = $.extend( {}, defaultOptions.score, options.score );
	
			if ( options.ajax.url ) {
				options.ajax.url = String( options.ajax.url );
			} else {
				options.ajax.url = options.host + '/v3/scores/' + options.dbname;
			}
	
			this.options = options;
			this.requests = [];
		};
	
		OresApi.prototype = {
			/**
			 * Abort all unfinished requests issued by this Api object.
			 *
			 * @method
			 */
			abort: function () {
				this.requests.forEach( function ( request ) {
					if ( request ) {
						request.abort();
					}
				} );
			},
	
			/**
			 * Massage parameters from the nice format we accept into a format suitable for the API.
			 *
			 * @private
			 * @param {Object} parameters (modified in-place)
			 */
			preprocessParameters: function ( parameters ) {
				var key;
				for ( key in parameters ) {
					if ( Array.isArray( parameters[ key ] ) ) {
						parameters[ key ] = parameters[ key ].join( '|' );
					} else if ( parameters[ key ] === false || parameters[ key ] === undefined ) {
						// Boolean values are only false when not given at all
						delete parameters[ key ];
					}
				}
			},
	
			/**
			 * Perform an API call.
			 *
			 * @param {Object} parameters
			 * @return {jQuery.Promise} Done: API response data and the jqXHR object.
			 *  Fail: Error code
			 */
			get: function ( parameters ) {
				var requestIndex,
					api = this,
					apiDeferred = $.Deferred(),
					xhr,
					ajaxOptions;
	
				parameters = $.extend( {}, this.options.parameters, parameters );
				ajaxOptions = $.extend( {}, this.options.ajax );
	
				this.preprocessParameters( parameters );
	
				ajaxOptions.data = $.param( parameters );
	
				xhr = $.ajax( ajaxOptions )
					.done( function ( result, textStatus, jqXHR ) {
						var code;
						if ( result.error ) {
							code = result.error.code === undefined ? 'unknown' : result.error.code;
							apiDeferred.reject( code, result, jqXHR );
						} else {
							apiDeferred.resolve( result, jqXHR );
						}
					} );
	
				requestIndex = this.requests.length;
				this.requests.push( xhr );
				xhr.always( function () {
					api.requests[ requestIndex ] = null;
				} );
				return apiDeferred.promise( { abort: xhr.abort } ).fail( function ( code, details ) {
					if ( !( code === 'http' && details && details.textStatus === 'abort' ) ) {
						mw.log( 'OresApi error: ', code, details );
					}
				} );
			},
			/**
			 * Create a new OresScoreBatcherPool using this api session.
			 *
			 * @param {Array} [models] A list of models to query for in each request
			 * @param {Object} [options] A set of options for querying.  See defaultOptions.score above
			 */
			pool: function(models, options){
				return new OresScoreBatcherPool(this, models, options);
			}
		};
	
		var ArticleQuality = function(options){
			this.weights = options.weights;
			this.names = options.names;
			this.assessment_system = options.assessment_system;
			this.modelName = options.modelName || "articlequality";
			this.weightedSumPlaceholder = options.weightedSumPlaceholder
	
			this.mwApi = new mw.Api();
			this.oresHost = options.ores_host
			this.oresDbname = options.dbname
			this.oresApi = new OresApi({host: options.ores_host, dbname: options.dbname});
			this.aqPool = this.oresApi.pool(this.modelName, {batchSize: 10});
		};
		ArticleQuality.prototype = {
			computeWeightedSum: function(score){
				var clsProba = score.probability;
				var weightedSum = 0;
				for (var cls in clsProba) {
					if (clsProba.hasOwnProperty(cls)) {
						var proba = clsProba[cls];
						weightedSum += proba * this.weights[cls];
					}
				}
				return weightedSum;
			},
			computeWeightedProportion: function(score){
				var weightedSum = this.computeWeightedSum(score);
				return weightedSum / Math.max.apply(null, Object.values(this.weights));
			},
			extractPrediction: function(score){
				return score.prediction;
			},
			parseText: function(text){
				var dfd = jQuery.Deferred();
				this.mwApi.get({action: "parse", text: text, contentmodel: "wikitext", formatversion: 2, prop: "text", disablelimitreport: true})
					.done(function(data){dfd.resolve($(data.parse.text).find('p').html())})
					.fail(function(error){dfd.reject(error)});
				return dfd.promise();
			},
			getCurrentRevId: function(title){
				var dfd = jQuery.Deferred();
				this.mwApi.get({action: 'query', prop: 'revisions', titles: title, rvprop: 'ids', formatversion: 2})
					.done(function(data){
						if (data.query.pages[0].missing) {
							dfd.reject('Missing page: ' + data.query.pages[0].title);
						} else {
							dfd.resolve(data.query.pages[0].revisions[0].revid);
						}
					})
					.fail(function(error){dfd.reject(error)});
				return dfd.promise();
			},
			oresScore: function(revId){
				var dfd = $.Deferred();
				this.aqPool.score(revId)
					.done(function(scoreDoc){dfd.resolve(scoreDoc[this.modelName].score, revId)}.bind(this))
					.fail(function(){dfd.error.apply(null, arguments)});
				return dfd.promise();
			},
			getAndRenderScoreHeader: function(){
				var revId = mw.config.get('wgRevisionId');
				this.oresScore(revId)
					.done(this.renderScoreHeader.bind(this))
					.fail(function(error){console.error(error)});
			},
			renderScoreHeader: function(score, revId){
				var rawText = this.formatScoreHeader(score, revId);
				var v2022 = document.body.classList.contains( 'skin-vector-2022' );
				var qualityBlock = $('<div>').addClass("article_quality");
				if ( v2022 ) {
					qualityBlock.addClass( 'mw-indicator' );
					$('.mw-indicators').prepend(qualityBlock);
				} else {
					$('#bodyContent').prepend(qualityBlock);
				}
				this.parseText(rawText)
					.done(function(html){qualityBlock.html(html)})
					.fail(function(error){console.error(error)});
			},
			formatScoreHeader: function(score, revId){
				var prediction = this.extractPrediction(score);
				if(!this.weightedSumPlaceholder){
					var weightedSum = this.computeWeightedSum(score);
					var roundedWeightedSum = Math.round(weightedSum*100)/100;
					var localizedWeightedSum = roundedWeightedSum.toLocaleString(
						mw.config.get("wgULSAcceptLanguageList", window.navigator.languages || [])[0]);
				}else{
					var localizedWeightedSum = this.weightedSumPlaceholder;
				}
				var anchoredWeightedSum = '[' + this.oresHost + "/v3/scores/" + 
					this.oresDbname + "/" + revId + "/articlequality " + 
					localizedWeightedSum + "]"
				
				return this.assessment_system + ": " +
					this.names[prediction] + " (" +
					anchoredWeightedSum + ")";
			},
			renderScoreLink: function(score, span){
				var prediction = this.extractPrediction(score);
				this.parseText(this.names[prediction])
					.done(function(html){span.prepend(html)})
					.fail(function(error){console.error(error)});
			},
			getAndRenderScoreLink: function(revId, span){
				this.oresScore(revId)
					.done(function(score){this.renderScoreLink(score, span)}.bind(this))
					.fail(function(error){console.error(error)});
			},
			addScoresToArticleLinks: function(){
				$("span.ores-wp10-prediction, span.ores-quality-prediction").each(function(i, element){
					var span = $(element);
					var anchor = span.find('a');
					var pageTitle = anchor.attr('title');
					this.getCurrentRevId(pageTitle)
						.done(function(revId){this.getAndRenderScoreLink(revId, span)}.bind(this))
				    	.fail(function(error){console.error(error)});
				}.bind(this));
			},
			renderHistoryScore: function(li, score){
				var level = Math.round(this.computeWeightedSum(score));
				var weightedProportion = this.computeWeightedProportion(score);
				var qualityPredictionNode = $("<div>").addClass("qualityprediction")
					.addClass("level_" + level)
					.append($("<div>").addClass("bar").css("width", Math.round(weightedProportion*100) + "%").append("&nbsp;"))
					.attr('title', this.formatScoreHeader(score));
				li.prepend(qualityPredictionNode);
			},
			getAndRenderHistoryScore: function(li){
				var revId = li.attr('data-mw-revid');
				this.oresScore(revId)
					.done(function(score){this.renderHistoryScore(li, score)}.bind(this))
					.fail(function(error){console.error(error)});
			},
			getAndRenderHistoryScores: function(){
				var revisionNodes = $('#pagehistory li');
				revisionNodes.each(function(i, element){this.getAndRenderHistoryScore($(element))}.bind(this));
			}
		}
		window.ArticleQuality = ArticleQuality;
	})
})(jQuery, mediaWiki)
importScript('User:Queen of Hearts/common.js'); // Backlink: [[User:Queen of Hearts/common.js]]