NOTE: I have posted about this in Template talk:COVID-19 cases in Asia#Automated script that can update all countries stats from virusncov.com

Below is a script that I intend to run on Template:COVID-19 cases in Asia, it scrapes virusncov.com and replaces the wikitext in the template page with updated statistics.

Currently the code is rather unclean as I just whipped this script while I was bored in class but I intend to (hopefully) clean it up soon.

The script is really easy to run, once set up correctly it's almost as simple as clicking a button.

Ambitious list of sources to cover

Yeah, yeah... there isn't a whole lot, I know. I'll add more later


How to run edit

  1. Show your bookmarks bar (Ctrl+⇧ Shift+B)
  2. Click on create a page, enter the name as 'run covidstats' (or whatever you like), and for the url, first enter javascript:, and then copy the code below and paste it after the colon
  3. Save that bookmark, click it while on any Wikipedia page and it should work :^)
    Once you click the button, give it a second or two and it will take you take you to the 'show changes' page with the changes made.
var region = "Asia";
var title = `Template:COVID-19_cases_in_${region}`;

if(!window.jQuery) {
    alert("jQuery is required to run this script!");
    throw Error("jQuery is missing");
}

function getDOM(url) {
    var xmlhttp = new XMLHttpRequest();
    xmlhttp.open("GET", url, false);
    xmlhttp.send();
    parser = new DOMParser();
    return parser.parseFromString(xmlhttp.responseText, "text/html");
}

function createWithTBody(b){
	let obj = {};
	Array.from(b.childNodes).filter(e => e.nodeName == "TR").forEach(e => {
        let countryTarget;
        let nodes = e.cells[1].childNodes;
		if(nodes && (countryTarget = Array.from(nodes).find(ee => ee.className == "country-detail"))){
			obj[countryTarget.textContent] = {
				"totalCases": e.cells[2].innerText,
				"activeCases": e.cells[6].innerText,
				"totalDeaths": e.cells[4].innerText,
			  "totalRecoveries": e.cells[7].innerText
			}
		}
	});
	return obj;
}

var doc = getDOM("https://virusncov.com/");

var cases = createWithTBody(doc.querySelector("#table_stas tbody"));

var reg = /\|\-\n[^\n]+?\|([\w ]+)\]\]\n\|([\d, ]+)\n\|([\d, ]+)\n\|([\d, ]+)\n\|([\d, ]+)/gm; /* See https://regex101.com/r/rH3OCD/1 */

function transl(w){ /* wikitext -> virusncov name mismatch handling */
	if(w == "South Korea") return "S. Korea";
	if(w == "Macau") return "Macao";
    if(w == "United Arab Emirates") return "UAE";
	return w;
}

function parseNum(num) {
    if(num == "" || !num) return 0;
    return parseInt(num.replace(/,/g, "").trim());
}

function comma(n) {
    return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

function anyChange(wiki, ncov) {
    return (parseNum(wiki.totCases) != parseNum(ncov.totalCases))
        || (parseNum(wiki.actCases) != parseNum(ncov.activeCases))
        || (parseNum(wiki.totDeaths) != parseNum(ncov.totalDeaths))
        || (parseNum(wiki.totRecovs) != parseNum(ncov.totalRecoveries));
}

var updated = [];

/* c = cumulative */
var cTotalCases = 0;
var cActiveCases = 0;
var cTotalDeaths = 0;
var cTotalRecoveries = 0;

function update(tc, ac, td, tr) {
    cTotalCases += parseNum(tc);
    cActiveCases += parseNum(ac);
    cTotalDeaths += parseNum(td);
    cTotalRecoveries += parseNum(tr);
}

function pad(n){
    return n.toString().padStart(2, '0');
}

function UTCtime() {
    const date = new Date();
    const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    return `${date.getUTCDate()} ${months[date.getUTCMonth()]} ${date.getUTCFullYear()}, ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} UTC`;
}

function replacer(match, country, totCases, actCases, totDeaths, totRecovs, offset, str) {
	if(!match || match == "") return "";
	country = transl(country);
    if(!(country in cases)) throw Error(`cannot resolve country: ${country}`);
    if(parseNum(cases[country].totalCases) < parseNum(totCases)) {
        /* console.log(`Skpping ${country} because it is more up to date`) */
        return match;
    }
    const cc = cases[country];
    if(anyChange({totCases, actCases, totDeaths, totRecovs}, cases[country])) {
        updated.push(country);
    return `${match.split("\n")[0]}\n${match.split("\n")[1]}\n|${cc.totalCases || 0}\n|${cc.activeCases || 0}\n|${cc.totalDeaths || 0}\n|${cc.totalRecoveries|| 0}`;
    }else{
        return match;
    }
}

function summer(match, country, totCases, actCases, totDeaths, totRecovs, offset, str) {
	if(!match || match == "") return "";
    update(totCases, actCases, totDeaths, totRecovs);
    return match;
}

var cumulReg = /\|'''Total'''\n.+?'([\d,]+)'''\s*?\n.+?'([\d,]+)'''\s*?\n.+?'([\d,]+)'''\s*?\n.+?'([\d,]+)'''/gm; /* See https://regex101.com/r/Y316l5/1 */

function goToShowChangesScreen(title, wikicode, editSummary) {
	let titleEncoded = encodeURIComponent(title.replace(/ /g, "_")); /* must incl namespace */
	let wgServer = mw.config.get('wgServer');
	let wgScriptPath = mw.config.get('wgScriptPath');
	let baseURL = wgServer + wgScriptPath + '/';
	$(`<form action="${baseURL}index.php?title=${titleEncoded}&action=submit" method="POST"/>`)
		.append($('<input type="hidden" name="wpTextbox1">').val(wikicode))
		.append($('<input type="hidden" name="wpSummary">').val(editSummary))
		.append($('<input type="hidden" name="mode">').val('preview'))
		.append($('<input type="hidden" name="wpDiff">').val('Show changes'))
		.append($('<input type="hidden" name="wpUltimateParam">').val('1'))
		.appendTo($(document.body))
		.submit();
}

function getWikitext(title){
    const apiEndpoint = "https://en.wikipedia.org/w/api.php";
    const params = `action=query&prop=revisions&rvprop=content&format=json&titles=${title}&rvslots=main`;
    return fetch(apiEndpoint + "?" + params + "&origin=*")
        .then(res => res.json())
        .then(res => res.query.pages[Object.keys(res.query.pages)[0]].revisions[0].slots.main['*']);
}

getWikitext(title).then(wikit => {
    modified = wikit.replace(reg, replacer);
    modified = modified.replace(reg, summer);
    modified = modified.replace(cumulReg, (a) => {
        if(!a || a == "") return a;
        return `|'''Total'''\n|'''${comma(cTotalCases)}'''\n|'''${comma(cActiveCases)}'''\n|'''${comma(cTotalDeaths)}'''\n|'''${comma(cTotalRecoveries)}'''`
    });

    if(!updated.length) {
        alert("No changes to be made. Already up to date!");
        return;
    }

    const summary = `Updated (${updated.join(", ")}) with virusncov.com data as of ${UTCtime()} ([[User:Satricious/covidstats|covidstats]])`;
    goToShowChangesScreen(title, modified, summary)
})