User:MastCell/api.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:MastCell/api. |
// This is a local copy of http://en.wiktionary.org/wiki/User:Conrad.Irwin/Api.js
//API documentation is at http://www.mediawiki.org/wiki/Api
//JsMwApi documentation is at http://en.wiktionary.org/wiki/User_talk:Conrad.Irwin/Api.js
//Basic usage for the impatient: JsMwApi()({action:'query',prop:'meta'},function(res){alert(res)});
/* A Javascript wrapper to the MediaWiki API
*
* @param {String} url The url of api.php
* (default: wgScriptPath + '/api.php')
* @param {"local"|"remote"} "local" causes AJAX POST requests, and "remote" Javascript callback style request.
* (default: if url starts with http:// or https:// then "remote" else "local")
*/
function JsMwApi (api_url, request_type) {
if (!api_url)
{
if (typeof(wgEnableAPI) === 'undefined' || wgEnableAPI == false)
throw "Local API is not usable.";
api_url = wgScriptPath + "/api.php";
}
if (!request_type)
{
if (api_url.indexOf('http://') == 0 || api_url.indexOf('https://') == 0)
request_type = "remote";
else
request_type = "local";
}
/* The function returned by JsMwApi()
*
* @param{Parameters} query An object to be encoded, a string to use directly, or nested arrays of the above
* @param{Function(res)} callback The function that will be called with the parsed result from the API.
*/
function call_api (query, callback)
{
if(!query || !callback)
throw "Insufficient parameters for API call";
query = serialise_query(query);
if(request_type == "remote")
request_remote(api_url, query, callback, call_api.on_error || default_on_error);
else
request_local(api_url, query, callback, call_api.on_error || default_on_error);
}
/* The default error handler, can be overwritten by setting call_api.on_error
*
* @param {XmlHttpRequest|null} The request object used for this call, or null for a remote request
* @param {Function(res)} The callback that would have been called on success.
* @param {res} The parsed result from the API (or null).
*/
var default_on_error = JsMwApi.prototype.on_error || function (xhr, callback, res)
{
if (typeof(console) != 'undefined')
console.log([xhr, res]);
callback(null);
}
/* Try to get a new XmlHttpRequest
*
* @return {XmlHttpRequest}
* @throws "Could not create an XmlHttpRequest"
*/
function get_xhr ()
{
try{
return new XMLHttpRequest();
}catch(e){ try {
return new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){ try {
return new ActiveXObject("Microsoft.XMLHTTP");
}catch(e){
throw "Could not create an XmlHttpRequest";
}}}
}
/* Make an AJAX request to a local api.php
*
* @param {String} the URI of api.php
* @param {String} the (URIencoded) query
* @param {Function(res)} the callback
* @throws "Could not create an XmlHttpRequest"
*/
function request_local (url, query, callback, on_error)
{
var xhr = get_xhr();
xhr.open('POST', url + '?format=json', true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(query);
xhr.onreadystatechange = function ()
{
if (xhr.readyState == 4)
{
var res;
if (xhr.status != 200)
res = {error: {
code: '_badresponse',
info: xhr.status + " " + xhr.statusText
}};
else
{
try
{
res = JSON.parse("("+xhr.responseText+")");
}
catch(e)
{
res = {error: {
code: '_badresult',
info: "The server returned an incorrectly formatted response"
}};
}
}
if (!res || res.error || res.warnings)
on_error(xhr, callback, res);
else
callback(res);
}
}
}
/* Make a callback request to a remote api.php. Restricted as per api.php documentation.
*
* @param {Url} The URI of api.php
* @param {String} A (URIencoded) request string.
* @param {Function(res)} The callback
*/
function request_remote (url, query, callback, on_error)
{
if(! window.__JsMwApi__counter)
window.__JsMwApi__counter = 0;
var cbname = '__JsMwApi__callback' + window.__JsMwApi__counter++;
window[cbname] = function (res)
{
if (res.error || res.warnings)
on_error(null, callback, res);
else
callback(res);
}
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', url + '?format=json&callback=window.' + cbname + '&' + query);
document.getElementsByTagName('head')[0].appendChild(script);
}
/* Convert an input object into a URI-encoded query string.
*
* @param {Parameters} Either:
* A String (returned unchanged)
* An Object (each key value pair is encoded and then joined with &s)
* A boolean true causes the key to inserted with no value
* A boolean false causes the key to be missed out completely
* An array as a key is joined with "|" before encoding
* An Array (each item is recursively encoded and the result joined together)
* Note that parameters set in later parts of the array will obscure parameters
* with the same name set from earlier parts.
*
* @return {String} A string that can be fed to the api over HTTP
*/
function serialise_query (obj)
{
var amp = "";
var out = "";
if (String(obj) === obj)
{
out = obj;
}
else if (obj instanceof Array)
{
for (var i=0; i < obj.length; i++)
{
out += amp + serialise_query(obj[i]);
amp = (out == '' || out.charAt(out.length-1) == '&') ? '' : '&';
}
}
else if (obj instanceof Object)
{
for (var k in obj)
{
if (obj[k] === true)
out += amp + encodeURIComponent(k) + '=1';
else if (obj[k] === false)
continue;
else if (obj[k] instanceof Array)
out += amp + encodeURIComponent(k) + '=' + encodeURIComponent(obj[k].join('|'));
else if (obj[k] instanceof Object)
throw "API parameters may not be objects";
else
out += amp + encodeURIComponent(k) + '=' + encodeURIComponent(obj[k]);
amp = '&';
}
}
else if (typeof(obj) !== 'undefined' && obj !== null)
{
throw "An API query can only be a string or an object";
}
return out;
}
// Make JSON.parse work
var JSON = (typeof JSON == 'undefined' ? new Object() : JSON);
if (typeof JSON.parse != 'function')
JSON.parse = function (json) { return eval('(' + json + ')'); };
// Allow .prototype. extensions
if (JsMwApi.prototype)
{
for (var i in JsMwApi.prototype)
{
call_api[i] = JsMwApi.prototype[i];
}
}
return call_api;
}
JsMwApi.prototype.page = function (title) {
/* The function returned by JsMwApi().page("foo")
*
* Overrides the title= and titles= parameters of the query given to it.
*
* @param{Parameters}
* @param{Function(res)}
*/
function call_with_page (params, callback)
{
call_with_page.api([params, {title: title, titles: title}], callback);
}
/* Access to this pages JsMwApi() object, can be used to set on_error */
call_with_page.api = this;
/* A function to initiate the editing cycle.
*
* @param {Parameters} (optional)
* @param {Function(text, save_function, res)}
* text: The current text of the page,
* page: A function to save the result of this edit
* res: The raw API result
*/
call_with_page.edit = function (params, edit_function)
{
if (typeof(params) == 'function')
{
edit_function = params;
params = null;
}
params = [params, {
action: "query",
prop: ["info", "revisions"],
intoken: "edit",
rvprop: ["content", "timestamp"]
}];
call_with_page(params, function (res)
{
if (!res || !res.query || !res.query.pages)
return edit_function(null);
// Get the first (and only) page from res.query.pages
for (var pageid in res.query.pages) break;
var page = res.query.pages[pageid];
var text = page.revisions ? page.revisions[0]['*'] : '';
/* Save the given text to the page, will only work if the edit function has previously been called.
*
* @param {String} ntext The new text to save, if it is the same as the old text, or blank, this method will not save
* @param {Parameters} Extra parameters for the edit {summary: "blah", minor: true}
* @param {Function(res)} A callback once save has completed
*/
function save_function (ntext, params, post_save)
{
if (typeof(params) == 'function')
{
post_save = params;
params = null;
}
params = [params, {
action: "edit",
text: ntext,
token: page.edittoken,
starttimestamp: page.starttimestamp,
basetimestamp: (page.revisions ? page.revisions[0].timestamp : false)
}];
call_with_page(params, post_save);
}
// Give control back to the outside world
edit_function(text, save_function, res);
});
}
/* A thin wrapper around the API's parse function. Set's the pst flag to
* make subst: work, and returns all parse information
*
* @param {String} params The text to parse, if this is omitted or null,
* then the page itself will be used
* @param {Function(text, res)} callback
*/
call_with_page.parse = function (to_parse, callback)
{
if (typeof to_parse == "function")
{
callback = to_parse;
to_parse = null;
}
var params = (to_parse == null ? {page: title} : {title: title, text: to_parse});
call_with_page.api([{action: "parse", pst: true}, params], function (res)
{
if (!res || !res.parse || !res.parse.text)
callback(null, res);
else
callback(res.parse.text['*'], res);
})
}
/* A thin wrapper around .parse that forces the text to be parsed without
* any <p> tags that might otherwise get added.
* @param {String} params The text to parse
* NOTE This function is only useful for tiny fragments, anything ill-formed or with block-level elements is likely to fail.
* @param {Function(text, res)} callback called with output and original parse query result
*/
call_with_page.parseFragment = function (to_parse, callback)
{
call_with_page.parse("<div>\n" + to_parse + "</div>", function (parsed, res)
{
callback(parsed ? parsed.replace(/^<div>\n?/,'').replace(/(\s*\n)?<\/div>\n*(<!--[^>]*-->\s*)?$/,'') : parsed, res);
})
}
return call_with_page;
}