User:Ingenuity/VandalismScanner.js

(function {	const usersFetched = {};	let lastFetched = "2000-01-01T00:00:00Z";	const api = new mw.Api;

async function scanLogs { const abuseHits = (await api.get({ "action": "query", "list": "abuselog", "afllimit": 100, "aflend": lastFetched, "format": "json" })).query.abuselog; const abuseDisallows = abuseHits.filter(e => e.result === "disallow"); const userAbuseLog = {}; const totalHits = {}; abuseDisallows.forEach(e => {			if (!userAbuseLog[e.user]) {				userAbuseLog[e.user] = [];			}			userAbuseLog[e.user].push(e.timestamp);		});

for (const user in userAbuseLog) { let lastAbuseHit = "2000-01-01T00:00:00Z"; for (const hit of userAbuseLog[user]) { if (Math.abs(new Date(hit).getTime - new Date(lastAbuseHit).getTime) > 10000) { totalHits[user] = (totalHits[user] || 0) + 1; }				lastAbuseHit = hit; }		}

const tags = ["mw-rollback", "mw-undo"]; const reverts = []; for (const tag of tags) { const edits = (await api.get({ "action": "query", "list": "recentchanges", "rctag": tag, "rcprop": "comment", "rclimit": 50, "rcend": lastFetched, "format": "json" })).query.recentchanges; reverts.push(...edits); }

const rollbackUsers = reverts.map(edit => {			const match = edit.comment.match(/special:contributions\/([^\]\|]+)/i);			return match ? match[1] : null;		}).filter(e => e);

for (const user in totalHits) { createUser(usersFetched, user); usersFetched[user].abuseHits += totalHits[user]; usersFetched[user].lastHit = new Date.toISOString; }

for (const user of rollbackUsers) { createUser(usersFetched, user); usersFetched[user].rollbacks++; usersFetched[user].lastHit = new Date.toISOString; }

lastFetched = new Date.toISOString;

for (let user in usersFetched) { if (new Date.getTime - new Date(user.lastHit).getTime > 3600000) { delete usersFetched[user]; }		}

renderUsers;

window.setTimeout(scanLogs, 30000); }

function createUser(dict, username) { if (!dict[username]) { dict[username] = { "abuseHits": 0, "rollbacks": 0, "lastHit": new Date.toISOString, "hideUntil": "2000-01-01T00:00:00Z" };		}	}

function renderUsers { const container = document.getElementById("mw-content-text"); container.innerHTML = "";

const userArray = Object.keys(usersFetched).map(user => {			return {				"user": user,				"abuseHits": usersFetched[user].abuseHits,				"rollbacks": usersFetched[user].rollbacks,				"lastHit": usersFetched[user].lastHit,				"hideUntil": usersFetched[user].hideUntil			};		}).sort((a, b) => {			return new Date(b.lastHit) - new Date(a.lastHit);		});

userArray.forEach(user => {			if (user.abuseHits + user.rollbacks >= 3) {				const minsAgo = Math.floor((new Date.getTime - new Date(user.lastHit).getTime) / 60000);				container.innerHTML += `						${user.user} 						 Abuse hits: ${user.abuseHits} &bull;						 Rollbacks: ${user.rollbacks} &bull;						 Last hit: ${minsAgo} minutes ago 				`;			}		}) }

if (mw.config.get("wgPageName") === "Special:BlankPage/VandalismScanner") { document.getElementById("firstHeading").textContent = "Vandalism Scanner"; scanLogs; } else { mw.util.addPortletLink(			'p-navigation',			mw.util.getUrl('Special:BlankPage/VandalismScanner'),			'vscan',			'pt-vscan',			'vscan',			null,			'#pt-preferences'		); } });