User:Ingenuity/AbuseFilterContribs.js

(function {

const afcAPI = new mw.Api;

if (mw.util.getUrl.includes("Special:Contributions") && mw.config.values.wgRelevantUserName) { addToContribs(mw.config.values.wgRelevantUserName); }

async function loadAbuseFilterLog(user) { return (await afcAPI.get({ action: "query", list: "abuselog", afllimit: 50, afluser: user })).query.abuselog.filter(e => e.result === "disallow").sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp)); }

function groupAbuseLog(log) { const data = []; outer: for (const item of log) { for (const i of data) { if (i.timestamp === item.timestamp && i.title === item.title) { i.filters.push(item.filter_id); if (item.filter_id) { i.id = item.id; }				continue outer; }		}

data.push({			timestamp: item.timestamp,			filters: [item.filter_id],			id: item.id,			title: item.title		}); }	return data; }

async function addToContribs(user) { const log = groupAbuseLog(await loadAbuseFilterLog(user)); const cList = document.querySelectorAll(".mw-contributions-list"); const children = []; for (const item of cList) { children.push(...item.children); }	const settings = await getSettings; const offset = getOffset(settings); const times = await getTimes(children.map(e => e.attributes["data-mw-revid"].value));

hits: for (const hit of log) { for (let i = children.length - 1; i > -1; i--) { const child = children[i]; const id = Array.prototype.slice.call(child.attributes).filter(a => a.nodeName === "data-mw-revid")[0].value; const ts = times.filter(e => e.id === Number(id))[0].timestamp; if (new Date(hit.timestamp) < new Date(ts)) { const elem = document.createElement("li"); elem.style.background = "rgba(255, 0, 0, 0.13)"; elem.style.borderRadius = "3px"; child.parentElement.insertBefore(elem, child.nextSibling); elem.innerHTML = createText(hit, offset, settings); continue hits; }		}

const noContribsElem = document.querySelector("#mw-content-text > p"); if (!noContribsElem) { const contribsList = document.querySelector(".mw-contributions-list"); const elem = document.createElement("li"); elem.style.background = "rgba(255, 0, 0, 0.13)"; elem.style.borderRadius = "3px"; contribsList.insertBefore(elem, contribsList.children[0]); elem.innerHTML = createText(hit, offset, settings); } else { noContribsElem.remove; const ul = document.createElement("ul"); ul.innerHTML = `${createText(hit, offset, settings)}`; document.querySelector("#mw-content-text").insertBefore(ul, document.querySelector(".mw-contributions-footer")); }	} }

function createText(hit, offset, settings) { const adj = new Date(new Date(hit.timestamp).getTime + offset * 60 * 1000); const details = hit.filters.filter(e => e).length ? `(details | examine)` : "";

return ` ${getTimestampString(settings, adj.getUTCFullYear, adj.getUTCMonth, adj.getUTCDate, adj.getUTCHours, adj.getUTCMinutes, adj.getUTCSeconds)}: triggered ${getLogText(hit.filters)} on ${hit.title} ${details} `; }

function getLogText(filters) { if (filters.length === 1) { return filters[0] ? `filter ${filters[0]}` : "a private edit filter"; }

return `${filters.length} filters (${filters.map(e => { return e ? `${e}` : "private"; }).join(", ")})`; }

async function getTimes(ids) { if (ids.length === 0) { return []; }	const data = []; Object.values((await afcAPI.get({		action: "query",		revids: ids.join("|"),		prop: "revisions",		rvprop: "timestamp|ids"	})).query.pages).forEach(e => {		for (const r of e.revisions) {			data.push({ id: r.revid, timestamp: r.timestamp });		}	});	return data.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); }

async function getSettings { return (await afcAPI.get({ action: "query", meta: "userinfo", uiprop: "options" })).query.userinfo.options; }

function getOffset(settings) { const match = settings.timecorrection.match(/([+-]?\d+)/);

return match ? Number(match[1]) : 0; }

function getTimestampString(settings, year, month, day, hour, minute, second) { const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; switch (settings.date) { case "dmy": case "default": return `${pad(hour)}:${pad(minute)}, ${day} ${monthNames[month]} ${year}`; case "mdy": return `${pad(hour)}:${pad(minute)}, ${monthNames[month]} ${day}, ${year}`; case "ymd": return `${pad(hour)}:${pad(minute)}, ${year} ${monthNames[month]} ${day}`; default: return `${year}-${pad(month + 1)}-${pad(day)}T${pad(hour)}:${pad(minute)}:${pad(second)}`; } }

function pad(num) { return num < 10 ? "0" + num : num; }

});