Jump to content

User:Ingenuity/VandalismScanner.js

From Wikipedia, the free encyclopedia
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.
(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 += `
					<div>
						<a href="/wiki/Special:Contributions/${user.user}" target="_blank">${user.user}</a><br>
						<span>Abuse hits: ${user.abuseHits}</span>&nbsp;&bull;
						<span>Rollbacks: ${user.rollbacks}</span>&nbsp;&bull;
						<span>Last hit: ${minsAgo} minutes ago</span>
					</div>
				`;
			}
		})
	}

	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'
		);
	}
})();