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.
//<pre><nowiki>

var errorText = '';
var password = '';

function ProjectWatchlist(project, cat)
{
    this.request = sajax_init_object();
    this.request.parentObj = this;
    this.page = 'Wikipedia:' + project + '/watchlist';
    this.category = 'Category:' + cat;
    
    this.text = '{{Project watchlist|' + project + '}}\n';
    this.fetchPages();
}

/* fetches a json encoded list of category members */
ProjectWatchlist.prototype.fetchPages = function(continueid) 
{
    if (!continueid) {
        this.query = {
            'cmtitle': this.category,
            'action': 'query',
            'format': 'json',
            'list': 'categorymembers',
            'cmlimit': 500,
        };
    } else 
        this.query.cmcontinue = continueid;
    query = createQuery('api.php', this.query);
    this.request.open('GET', query, false);
    this.request.send(null);       

    if (this.request.status == 200) 
        this.processPages();
    else
        error('Failed to find ' + this.category);
}

/* Iterates through the list of category members striping off 'Talk' or '_talk' if applicable */
ProjectWatchlist.prototype.processPages = function() 
{
    var response = eval('(' + this.request.responseText + ')');
    var pages = response.query.categorymembers;

    for (i = 0; i < pages.length; ++i) {
       var page = pages[i].title;
       switch (pages[i].ns) {
           case 1:
               page = page.substring(5);
               break;

           case 3:
           case 5:
           case 7:
           case 9:
           case 11:
           case 13:
           case 101:
           case 103:
           case 105:
               page.replace('_talk', '');
               break;
           
           case 15:
               page.replace('_talk', '');
               // fall through
 
           case 14:
               page = ":" + page;
               break;
       }

       this.text += '*[[' + page + ']]\n'; 
    }

    if (response.categorymembers) 
        this.fetchPages(response.categorymembers.cmcontinue);
    else
        updatePage(this.page, this.text, 'Update by [[User:Joshurtreebot|Joshurtreebot]]');
}

/* Updates a page specified by title with text */
function updatePage(title, text, summary)
{   
    var request = sajax_init_object();

    var query = {
        'action': 'login',
        'lgname': 'joshurbot',
        'lgpassword': password
    };

    request.open('POST', createQuery('api.php', query), false);
    request.send(null);

    query = {
        'prop': 'info',
        'action': 'query',
        'titles': title,
        'format': 'json',
        'intoken': 'edit'
    };

    request.open('GET',  createQuery('api.php', query), false);
    request.send(null);

    if (request.status == 200) {
        var response = eval( '(' + request.responseText + ')' );
        var pageinfo = Iterator(response.query.pages).next()[1]
        var edittoken = pageinfo.edittoken;
        var postdata = {
            'wpTextbox1': encodeURIComponent(text),
            'wpSummary': summary,
            'wpEditToken': encodeURIComponent(edittoken),
            'wpStarttime': stripTimestamp(pageinfo.touched),
            'wpEdittime': toTimestamp(new Date())
        };

        var query = {
            'action': 'submit',
            'title': title,
            //'section': 1
        };

        request.open('POST', createQuery('index.php', query), false);
        request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        request.send(createQuery(null, postdata)); 
        
        if (request.status != 200)
            error('Failed to edit ' + title);

    } else 
        error('Failed to edit ' + title + ' (token error)');
/* commenting out pending update of api.php
        var request = sajax_init_object();
        request.query = {
        'action': 'edit',
        'eptitle': wgPageName
        };

        var postData = {
            'action': 'edit',
            'epedittoken': edittoken,
            'epsummary': 'Update by createwatchlist.js',
            'eptext': this.text
        };
        request.open('POST', createQuery(request.query), true);
            
        request.onload = function() {
            window.location.reload();
        };
        var post = createQuery(null, postData);
        this.request.send(createQuery(null, postData));
    };

    this.request.send(null);*/
}

function error(text) 
{
    errorText += text + '\n';
}

function createQuery(prefix, parameters)
{
    var retText =  prefix ? wgScriptPath + '/' + prefix + '?' : '';
    for (x in parameters) {
        if (parameters[x] != null)
            retText += x + '=' + parameters[x] + '&';
    }
    return retText.substring(0, retText.length - 1);
}

function toTimestamp(date) 
{
    var timestamp = String(date.getUTCFullYear()) + String(date.getUTCMonth() + 1) + String(date.getUTCDate());
    timestamp += String(date.getUTCHours()) + String(date.getUTCMinutes()) + String(date.getUTCSeconds());
    return timestamp;
}

/* Strips a timestamp returned by api.php for use as a timestamp for index.php */
function stripTimestamp(timestamp) 
{
    timestamp = timestamp.replace('T', '');
    timestamp = timestamp.replace('Z', '');
    timestamp = timestamp.replace(/:/g, '');
    timestamp = timestamp.replace(/-/g, '');
    return timestamp;
}
        
function updateWatchlists() 
{
    password = prompt('Password needed', '');        
    var request = sajax_init_object();
    var query = { 
        'title': 'User:joshurBot/projects', 
        'action': 'raw', 
        'ctype': 'text/javascript', 
        'maxage': 0 
    };
    request.open('GET', createQuery('index.php', query), false);
    request.send(null);

   
    if (request.status == 200) {
        var value = eval('(' + request.responseText + ')');   

        for (var i = 0; i < value.projects.length; ++i) {
            new ProjectWatchlist(value.projects[i].project, value.projects[i].category);
        }
 
        if (errorText != '')
             alert(errorText);
    }
}

function checkLastUpdate() 
{
    var request = sajax_init_object();
    var query = {
        'title': 'User:Joshurtree/lastupdate',
        'action': 'raw'
    };

    request.open('GET', createQuery('index.php', query), false);
    request.send(null);

    if (request.status == 200) {
        var response = request.responseText;
        var updateTime = new Date();
        updateTime.setFullYear(Number(response.substr(0, 4)));
        updateTime.setMonth(Number(response.substr(4, 2)) - 1);
        updateTime.setDate(Number(response.substr(6, 2)) + 1);
        updateTime.setHours(Number(response.substr(8, 2)));
        updateTime.setMinutes(Number(response.substr(10, 2)));
        updateTime.setSeconds(Number(response.substr(12, 2)));
        
        if (updateTime < new Date()) {
            updatePage('User:Joshurtree/lastupdate', '{{subst:CURRENTTIMESTAMP}}', '');
            return true;
        }   
    }
 
    return false;
}

addOnloadHook(function() {
    if (checkLastUpdate()) {
        updateWatchlists();
    }
    mw.util.addPortletLink('p-tb', 'javascript:updateWatchlists()', 'Update watchlists');
});
//</nowiki></pre>