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;

}

})();