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 { function JsMwApi (api_url, request_type) {    if (!api_url)     {        if (typeof(true) === 'undefined' || true == 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' ? {} : 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 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(" \n" + to_parse + " ", function (parsed, res)        {            callback(parsed ? parsed.replace(/^ \n?/,).replace(/(\s*\n)?<\/div>\n*(\s*)?$/,) : parsed, res);       }); };

return call_with_page; };

mw.log.deprecate(window, 'JsMwApi', JsMwApi, "use mw.Api from the mediawiki.api module instead; see ResourceLoader/Default_modules"); });