User:R'n'B/wrappi.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
![]() | Documentation for this user script can be added at User:R'n'B/wrappi. |
/* wrappi.js : Javascript wrapper for MediaWiki API *
* Copyright (c) 2011 [[en:User:R'n'B]]
*
* This wrapper is intended as a user script for pages loaded from a MediaWiki
* wiki; requires MediaWiki 1.17 and jQuery 1.4.2 (included with MediaWiki)
*/
(function ($) {
var lagpattern = new RegExp("Waiting for [\\d.]+: (\\d+) seconds? lagged");
// reserve a private namespace.
if (!mw.RnB) {
mw.RnB = {};
}
// shim if mw.log is not active and console is
if ( ! mw.config.get("debug") && console !== undefined) {
mw.log = function() {
console.log(arguments);
};
}
/* Wiki: an object providing methods used to submit requests
* to a wiki's API.
* options: an Object containing values for any of the following settings --
* options.url: URL of a remote wiki to access; default is to access the
* local wiki on which this script is used
*/
mw.RnB.Wiki = function (options) {
var self = this,
lock = false,
lastwriteaction = 0,
queryqueue = [],
handlerequest,
handlefailure,
handleresponse,
process;
this.settings = $.extend({}, this.defaults, options);
if (this.settings.url.charAt(0) === "/") {
//local api
if ( ! mw.config.exists("wgEnableAPI") ||
! mw.config.get("wgEnableAPI")) {
throw "API is not enabled on this wiki.";
}
}
/* handlerequest: submit the request
*/
handlerequest = function (query, onsuccess) {
// TODO: hook for user-supplied pre-processing?
// TODO: retry count?
// TODO: hook for user-supplied error handler?
$.ajaxSetup({
error: function (xhr, textStatus, errorThrown) {
handlefailure.call(self, query, textStatus, errorThrown, onsuccess);
}
});
// submit query with inserted callback for post-processing
$.post(this.settings.url, query, function (data) {
handleresponse.call(self, data, query, onsuccess);
});
};
handlefailure = function (query, status, errorThrown, onsuccess) {
var answer = confirm(
"Ajax query failed; status '" + status
+ "'; error code '" + errorThrown +"'. Retry?");
if (answer) {
handlerequest(query, onsuccess);
} else {
// give up
lock = false;
if (queryqueue.length > 0) {
process.call(self);
}
}
};
/* handleresponse: check response for errors,
* dispatch callback, trigger processing of next request, and
* TODO: handle retry on server errors
*/
handleresponse = function (response, query, callback) {
var module, warningtext, lag, pause, i;
// TODO: hook for user-supplied post-processing?
if (response.error) {
if (response.error.code === "maxlag") {
lag = parseInt(lagpattern.exec(response.error.info)[1], 10);
// pause half of lag, but >= 5 and <= 60
pause = Math.min(Math.max(5, lag/2), 60);
mw.log("Pausing " + pause + " sec. due to database lag: "
+ response.code.info);
setTimeout(function() {
handlerequest.call(self, query, callback);
}, 1000 * pause);
// keep the lock on so that no other requests can run
// during the lag-induced pause
return;
} else {
lock = false;
alert("API error " + response.error.code +
": '" + response.error.info + "'.");
}
} else {
lock = false;
if (response.warnings) {
for (module in response.warnings)
if (response.warnings.hasOwnProperty(module)) {
warningtext = response.warnings[module].split("\n");
for (i=0; i<warningtext.length; i++) {
mw.log("API warning in " + module + " module: "
+ warningtext[i]);
}
}
}
callback.call(self, response, query);
}
if (queryqueue.length > 0) {
process.call(self);
}
};
/* process: check the queryqueue for pending requests and submit
the next one if possible
*/
process = function() {
var item, nextitem, now, query, writequeue = [];
if (queryqueue.length === 0
// nothing there to process, so forget it
|| lock) {
// there's another instance running, so forget it
return;
}
lock = true;
item = queryqueue.shift();
query = item[0];
now = (new Date()).getTime();
if (now - lastwriteaction < this.settings.write_interval) {
// write throttle time hasn't expired yet
while ($.inArray(query.action, this.writeactions) !== -1) {
// query is a write action, so throttle it
writequeue.push(item);
if (queryqueue.length === 0) {
// this was last query on the queue, so we have to wait
queryqueue = writequeue;
setTimeout(lastwriteaction
+ this.settings.write_interval - now,
process);
return;
}
// try the next one in line
nextitem = queryqueue.shift();
query = item[0];
}
queryqueue = writequeue.concat(queryqueue);
}
handlerequest.call(self, query, item[1]);
lock = false;
};
/* request: higher-level method that formats parameters, and puts the
request on the queue, to prevent overlapping requests to server.
*/
this.request = function(query, onsuccess) {
if (query.action === undefined) {
mw.log(query);
throw "Invalid query: no 'action' parameter.";
}
query.format = 'json';
if (this.settings.maxlag !== 0) {
query.maxlag = this.settings.maxlag;
}
query = mw.RnB.Wiki.serialize(query);
queryqueue.push([query, onsuccess]);
if (queryqueue.length === 1) {
// this is the first item on queue, so start processor
process.call(self);
}
};
};
mw.RnB.Wiki.prototype.defaults = {
url: mw.config.get("wgScriptPath") + "/api.php",
maxlag: 0, // time in seconds
write_interval: 10000 // time in microseconds
};
/* actions that require write privileges */
mw.RnB.Wiki.prototype.writeactions = [
'review', 'emailcapture', 'articlefeedback', 'stabilize', 'purge',
'rollback', 'delete', 'undelete', 'protect', 'block', 'unblock',
'move', 'edit', 'upload', 'emailuser', 'watch', 'patrol', 'import',
'userrights'
];
/* serialize: convert any arrays or numbers in param values to strings
* returns converted query object
*/
mw.RnB.Wiki.serialize = function (query) {
var key,
params = {};
for (key in query) {
if (query.hasOwnProperty(key)) {
if ($.isArray(query[key])) {
// serialize any array values
params[key] = query[key].join("|");
} else {
// stringify all other values
params[key] = query[key].toString();
}
}
}
return params;
};
} (jQuery));