Difference between revisions of "User:Moussekateer/vector.js"

From Team Fortress Wiki
Jump to: navigation, search
m
m (can no longer hotlink javascript from github)
Line 1: Line 1:
importScriptURI('https://raw.github.com/Moussekateer/TFWiki-scripts/master/spambot_killer.js', 'text/javascript');
+
//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;
 +
}
  
importScriptURI('https://raw.github.com/Moussekateer/tfwiki_stats_link/master/tfwiki_stats_link.js', 'text/javascript');
+
//// START SPAMBOT KILLER ///
 +
 
 +
// create JsMwApi object for wiki queries
 +
var nearbyApi = JsMwApi("/w/api.php");
 +
 
 +
function deletePage(title){
 +
    // query to get deletetoken
 +
    nearbyApi({action: "query", prop: "info", intoken: 'delete', titles: title}, function (res){
 +
        console.log('LOG: deletetokenres=' + res);
 +
        for (var key in res.query.pages){
 +
            var deletetoken = res.query.pages[key].deletetoken;
 +
            // use deletetoken in POST request to delete page
 +
            nearbyApi({action: "delete", title: title, reason: 'Spam', token: deletetoken}, function (res){
 +
                console.log('LOG: deleted=' + title);
 +
            });
 +
        }
 +
    });
 +
}
 +
 
 +
function killContribs(contribs){
 +
    console.log('LOG: usercontribs=' + contribs);
 +
    for(var edit in contribs){
 +
        // only delete contribution if new page
 +
        if ('new' in contribs[edit]){
 +
            console.log('LOG: usercontribtodelete=' + contribs[edit]);
 +
            deletePage(contribs[edit].title);
 +
        }
 +
    }
 +
}
 +
 
 +
function blockUser(user){
 +
    // query to get blocktoken
 +
    nearbyApi({action: "query", prop: "info", intoken: 'block', titles: 'User:' + user}, function (res){
 +
        console.log('LOG: blocktokenres=' + res);
 +
        for (key in res.query.pages){
 +
            // use blocktoken in POST request to block user
 +
            var blocktoken = res.query.pages[key].blocktoken;
 +
            console.log('LOG: blocktoken=' + blocktoken);
 +
            nearbyApi({action: "block", user: user, expiry: 'never', nocreate: '', autoblock: '', reason: 'Spamming links to external sites', token: blocktoken}, function (res){
 +
                console.log('LOG: blocked=' + user);
 +
            });
 +
        }
 +
    });
 +
}
 +
 
 +
function keel(user){
 +
    // query user details
 +
    nearbyApi({action: "query", list: "users", ususers: user, usprop: "registration"}, function (res){
 +
        nearbyApi({action: "query", list: "usercontribs", ucuser: user}, function (res){
 +
            if (res.query.usercontribs.length > 5){
 +
                alert('User:' + user + ' has more than 5 edits');
 +
            }
 +
            else {
 +
                // hit it doc
 +
                blockUser(user);
 +
                killContribs(res.query.usercontribs);
 +
                // vas gud
 +
                alert('User:' + user + ' has been terminated. Good day');
 +
            }
 +
        });
 +
    });
 +
}
 +
 
 +
function pootSecretSauce(){
 +
    // insert blockdelete link in new page revision details
 +
    $('.mw-usertoollinks a:last-child').after(" | <a href=# id='blockdelete'>blockdelete</a>")
 +
    $("a#blockdelete").click(function(){
 +
        // grab username from revision details
 +
        var user = $('.diff-ntitle .mw-userlink').text();
 +
        console.log('LOG: User=' + user);
 +
 
 +
        keel(user);
 +
    });
 +
}
 +
addOnloadHook(pootSecretSauce);
 +
/// END SPAMBOT KILLER ///

Revision as of 01:31, 7 August 2014

//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;
}

//// START SPAMBOT KILLER ///

// create JsMwApi object for wiki queries
var nearbyApi = JsMwApi("/w/api.php");

function deletePage(title){
    // query to get deletetoken
    nearbyApi({action: "query", prop: "info", intoken: 'delete', titles: title}, function (res){
        console.log('LOG: deletetokenres=' + res);
        for (var key in res.query.pages){
            var deletetoken = res.query.pages[key].deletetoken;
            // use deletetoken in POST request to delete page
            nearbyApi({action: "delete", title: title, reason: 'Spam', token: deletetoken}, function (res){ 
                console.log('LOG: deleted=' + title);
            });
        }
    });
}

function killContribs(contribs){
    console.log('LOG: usercontribs=' + contribs);
    for(var edit in contribs){
        // only delete contribution if new page
        if ('new' in contribs[edit]){
            console.log('LOG: usercontribtodelete=' + contribs[edit]);
            deletePage(contribs[edit].title);
        }
    }
}

function blockUser(user){
    // query to get blocktoken
    nearbyApi({action: "query", prop: "info", intoken: 'block', titles: 'User:' + user}, function (res){
        console.log('LOG: blocktokenres=' + res);
        for (key in res.query.pages){
            // use blocktoken in POST request to block user
            var blocktoken = res.query.pages[key].blocktoken;
            console.log('LOG: blocktoken=' + blocktoken);
            nearbyApi({action: "block", user: user, expiry: 'never', nocreate: '', autoblock: '', reason: 'Spamming links to external sites', token: blocktoken}, function (res){ 
                console.log('LOG: blocked=' + user);
            });
        }
    });
}

function keel(user){
    // query user details
    nearbyApi({action: "query", list: "users", ususers: user, usprop: "registration"}, function (res){
        nearbyApi({action: "query", list: "usercontribs", ucuser: user}, function (res){
            if (res.query.usercontribs.length > 5){
                alert('User:' + user + ' has more than 5 edits');
            }
            else {
                // hit it doc
                blockUser(user);
                killContribs(res.query.usercontribs);
                // vas gud
                alert('User:' + user + ' has been terminated. Good day');
            }
        });
    });
}

function pootSecretSauce(){
    // insert blockdelete link in new page revision details
    $('.mw-usertoollinks a:last-child').after(" | <a href=# id='blockdelete'>blockdelete</a>")
    $("a#blockdelete").click(function(){
        // grab username from revision details
        var user = $('.diff-ntitle .mw-userlink').text();
        console.log('LOG: User=' + user);

        keel(user);
    });
}
addOnloadHook(pootSecretSauce);
/// END SPAMBOT KILLER ///