MediaWiki:Gadget-abuseLogRC-core.js

This is an old revision of this page, as edited by Alex (talk | contribs) at 17:12, 17 October 2024. The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

After saving, you may need to bypass your browser's cache to see the changes. For further information, see Wikipedia:Bypass your cache.

  • In most Windows and Linux browsers: Hold down Ctrl and press F5.
  • In Safari: Hold down ⇧ Shift and click the Reload button.
  • In Chrome and Firefox for Mac: Hold down both ⌘ Cmd+⇧ Shift and press R.
/*  ======================	      AbuseLogRC	======================	Shows certain Special:AbuseLog entries at the top of Special:RecentChanges	for better vandalism detection. Use of this gadget requires the user right	to view private filters ("abusefilter-log-private").	Keep this in sync with [[rsw:MediaWiki:Gadget-abuseLogRC-core.js]].	Inspired by Suppa chuppa's original script at [[User:Suppa chuppa/abuselog.js]]	@author Iiii_I_I_I*/;(function ($, mw) {	let gadgetLoaded = false;	let entryDays = new Set();	let lastUpdate;	let intervalID;	let filters = '2|3|5|6|7|12|14|19|21|global-4';	// default config	if (getConfig('autoRefresh') === null) setConfig('autoRefresh', false);	if (getConfig('interval') === null) setConfig('interval', 30);	if (getConfig('entries') === null) setConfig('entries', 5);	function getConfig(key) {		return JSON.parse(localStorage.getItem('gadget-abuseLogRC-' + key));	}	function setConfig(key, value) {		localStorage.setItem('gadget-abuseLogRC-' + key, value);	}	function refreshData() {		$('.gadget-abuselog-list').addClass('loading'); // class is cleared when new list replaces old		entryDays.clear();		getData();	}	function toggleAutoRefresh(isToggledOn, refreshButton) {		if (isToggledOn) {			intervalID = setInterval(refreshData, getConfig('interval') * 1000);		} else {			clearInterval(intervalID);		}		// hide manual refresh button when autoRefresh is on, show when it's off		refreshButton.toggle(!isToggledOn);		// update cookie		setConfig('autoRefresh', isToggledOn);	}	function buildGadget([entries, users, pages]) {		lastUpdate = new Date();		// refreshed		if (gadgetLoaded) {			$('.gadget-abuselog-list').replaceWith(buildList(entries, users, pages));		}		// initial load		else {			let $container = $('<div class="gadget-abuselog"></div>');			$container.append(buildHeader(), buildList(entries, users, pages));			$('.mw-changeslist').before($container);			gadgetLoaded = true;			// change user tool link text if readableRC is on			mw.hook('ext.gadget.readableRC').add(function () {				$('.gadget-abuselog').addClass('match-gadget-rc');			});		}		mw.hook('ext.gadget.abuseLogRC').fire();	}	function buildHeader() {		let $header = $('<div class="gadget-abuselog-header"></div>');		let $left = $('<span class="gadget-abuselog-header-left"></span>');		let $right = buildSettings();		let $title = $('<h4>Abuse log</h4>');		let link = ' (' + buildLink('Special:AbuseLog', {exists: true, text: 'all'}) + ')';		$left.append($title, link);		$header.append($left, $right);		return $header;	}	function buildSettings() {		let $settings = $('<span class="gadget-abuselog-header-right"></span>');		let refreshButton = new OO.ui.ButtonWidget({			framed: false,			icon: 'reload',			flags: ['progressive'],			label: 'Refresh log',			invisibleLabel: true,			title: 'Refresh abuse log entries',			classes: ['gadget-abuselog-manual-refresh']		});		// hide when autoRefresh is on, show when it's off		refreshButton.toggle(!getConfig('autoRefresh'));		refreshButton.on('click', function (e) {			refreshButton.setDisabled(true); // disabled state cleared by hook below			refreshData();		});		// use RecentChanges' "View new changes" button as another way to refresh		$('.mw-rcfilters-ui-filterWrapperWidget-showNewChanges a').on('click', function (e) {			refreshData();		});		// POPUP TOP HALF: number of log entries to show		let entriesSelectWidget = new OO.ui.ButtonSelectWidget({			items: [				new OO.ui.ButtonOptionWidget({ data: '3', label: '3' }),				new OO.ui.ButtonOptionWidget({ data: '5', label: '5' }),				new OO.ui.ButtonOptionWidget({ data: '10', label: '10' }),				new OO.ui.ButtonOptionWidget({ data: '20', label: '20' }),				new OO.ui.ButtonOptionWidget({ data: '50', label: '50' })			]		});		let entriesFieldset = new OO.ui.FieldsetLayout({			label: 'Entries to show',			classes: ['gadget-abuselog-settings-entries'],			items: [entriesSelectWidget]		});		entriesSelectWidget.selectItemByData(getConfig('entries').toString())		entriesSelectWidget.on('choose', function (button, selected) {			setConfig('entries', button.data);			refreshData();		});		// if user changes # entries to a custom value in localStorage, insert new button at the start		if (entriesSelectWidget.findSelectedItem() === null) {			let value = getConfig('entries').toString();			let customButton = new OO.ui.ButtonOptionWidget({ data: value, label: value });			entriesSelectWidget.addItems(customButton, 0);			entriesSelectWidget.selectItem(customButton);		}		// POPUP BOTTOM HALF: refresh settings		let lastUpdatedLabel = new OO.ui.LabelWidget({			classes: ['gadget-abuselog-settings-last-updated']		});		let autoRefreshCheckbox = new OO.ui.CheckboxInputWidget({			selected: getConfig('autoRefresh')		});		let refreshFieldset = new OO.ui.FieldsetLayout({			label: 'Refresh',			classes: ['gadget-abuselog-settings-refresh'],			items: [				lastUpdatedLabel,				new OO.ui.FieldLayout(autoRefreshCheckbox, {					classes: ['gadget-abuselog-settings-auto-refresh'],					label: 'Auto-refresh log entries every ' + getConfig('interval') + ' seconds'				})			]		});		// initial load		toggleAutoRefresh(getConfig('autoRefresh'), refreshButton);		// when clicked		autoRefreshCheckbox.on('change', function (isSelected, indeterminate) {			toggleAutoRefresh(isSelected, refreshButton);		});		// POPUP MENU		let menuButton = new OO.ui.PopupButtonWidget({			icon: 'menu',			framed: false,			label: 'Abuse log settings',			invisibleLabel: true,			classes: ['gadget-abuselog-settings'],			popup: {				head: false, anchor: false, padded: true, autoFlip: false, align: 'backwards',				$content: $('<div>').append(					entriesFieldset.$element,					refreshFieldset.$element				)			}		});		// to do when settings popup is opened		menuButton.on('click', function () {			let time = new Intl.DateTimeFormat('en-GB', {hour: 'numeric', minute: 'numeric', timeZone: 'UTC'}).format(lastUpdate);			let day = new Intl.DateTimeFormat('en-GB', {day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC'}).format(lastUpdate);			let diff = new Date() - lastUpdate;			let h = Math.floor(diff / 1000 / 60 / 60);			let m = Math.floor(diff / 1000 / 60) % 60;			let s = Math.floor(diff / 1000) % 60;			// if over one minute since last update, hide seconds unit;			// if under one hour since last update, hide hour unit			let hh = (h > 0) ? h + 'h ' : '';			let mm = m + 'm';			let ss = s + 's';			let ago = (h > 0 || m > 0) ? hh + mm : ss;			lastUpdatedLabel.setLabel(				new OO.ui.HtmlSnippet(`Last update: <strong class="last-update" title="${time}, ${day}">${ago} ago</strong>.`)			);		});		// to do on each gadget refresh		mw.hook('ext.gadget.abuseLogRC').add(function () {			refreshButton.setDisabled(false);		});		$settings.append(			refreshButton.$element,			menuButton.$element		);		return $settings;	}	function buildList(entries, users, pages) {		let $list = $('<div class="gadget-abuselog-list"></div>');		let pageArr = Object.values(pages);		for (let entry of entries) {			let user = users.find(user => user.name === entry.user);			let page = pageArr.find(page => page.title === entry.title);			let userPage = pageArr.find(page => page.title === `User:${user.name}`);			let talkPage = pageArr.find(page => page.title === `User talk:${user.name}`);			// prevent error; User:FeedbackBot redirects to RuneScape:Article feedback			if (user.name === 'FeedbackBot') {				userPage = pageArr.find(page => page.title === 'RuneScape:Article feedback');			}			let opts = {				isRegistered: Object.hasOwn(user, 'userid'),				isRedirect: page === undefined, // API response separates redirects from pages object				pageExists: page === undefined || Object.hasOwn(page, 'pageid'),				userPageExists: Object.hasOwn(userPage, 'pageid'),				talkPageExists: Object.hasOwn(talkPage, 'pageid')			};			$list.append(buildRow(entry, opts));		}		return $list;	}	function buildRow(entry, opts) {		// FIRST COLUMN: date and time		let entryDate = new Date(entry.timestamp);		let entryDay = new Intl.DateTimeFormat('en-GB', {day: '2-digit', month: 'short', timeZone: 'UTC'}).format(entryDate);		let entryTime = new Intl.DateTimeFormat('en-GB', {hour: 'numeric', minute: 'numeric', timeZone: 'UTC'}).format(entryDate);		let showHideDay = (entryDays.has(entryDay)) ? 'hide' : '';		let firstColumn =			'<span class="gadget-abuselog-col gadget-abuselog-col-1">' +				`<span class="${showHideDay}">${entryDay}</span> <span>${entryTime}</span>` +			'</span>';		entryDays.add(entryDay);		// SECOND COLUMN: page edited		let secondColumn =			'<span class="gadget-abuselog-col gadget-abuselog-col-2">' +				buildLink(entry.title, {exists: opts.pageExists, text: entry.title, isRedirect: opts.isRedirect}) +			'</span>';		// THIRD COLUMN: diff, details, and action taken		let diffLink = (entry.revid) ? buildLink(`Special:Diff/${entry.revid}`, {exists: true, text: 'diff'}) : 'diff';		let logLink = buildLink(`Special:AbuseLog/${entry.id}`, {exists: true, text: 'log'});		let results = [];		// some filters perform multiple actions on a single edit, eg. <https://oldschool.runescape.wiki/w/Special:AbuseLog/22296>		entry.result.split(',').forEach(result => {			results.push(				`<span class="gadget-abuselog-result gadget-abuselog-result-${result}">` +				`${mw.msg('abusefilter-action-' + result)}</span>`			);		});		let thirdColumn =			'<span class="gadget-abuselog-col gadget-abuselog-col-3">' +				`(${diffLink} | ${logLink}) <span class="gadget-abuselog-action">(${results.join(', ')})</span>` +			'</span>';		// FOURTH COLUMN: user details and filter triggered		let filterURL = `Special:AbuseFilter/${entry.filter_id}`;		if (entry.filter_id.includes('global')) {			filterURL = `meta:Special:AbuseFilter/${entry.filter_id.replace('global-', '')}`;		}		let filterLink = buildLink(filterURL, {exists: true, text: `Filter ${entry.filter_id}`});		let fourthColumn =			'<span class="gadget-abuselog-col gadget-abuselog-col-4">' +				buildUserLinks(entry.user, opts) +				`<span class="gadget-abuselog-filter gadget-abuselog-filter-${entry.filter_id}">` +					`(${filterLink}: ${entry.filter})` +				'</span> ' +			'</span>';		return '<div class="gadget-abuselog-row">' + firstColumn + secondColumn + thirdColumn + fourthColumn + '</div>';	}	function buildUserLinks(username, opts) {		let userLink;		let toolLinks;		// link text is added with CSS so it can be replaced when readableRC is loaded		let talkLink = `<span>${buildLink(`User talk:${username}`, {			exists: opts.talkPageExists,			text: '',			classes: 'mw-usertoollinks-talk'		})}</span>`;		let contribsLink = `<span>${buildLink(`Special:Contributions/${username}`, {			exists: true,			text: '',			classes: 'mw-usertoollinks-contribs'		})}</span>`;		let logLink = `<span>${buildLink(`Special:AbuseLog`, {			exists: true,			text: '',			classes: 'mw-usertoollinks-abuselog',			param: 'wpSearchUser',			value: username		})}</span>`;		let blockLink = `<span>${buildLink(`Special:Block/${username}`, {			exists: true,			text: '',			classes: 'mw-usertoollinks-block'		})}</span>`;		// user links vs. anon links		if (opts.isRegistered) {			userLink = `<span>${buildLink(`User:${username}`, {				exists: opts.userPageExists,				text: username,				classes: 'mw-userlink'			})}</span>`;			toolLinks = '<span class="mw-usertoollinks">(' + talkLink + contribsLink + logLink + blockLink + ')</span>';		} else {			userLink = `<span>${buildLink(`Special:Contributions/${username}`, {				exists: true,				text: username,				classes: 'mw-userlink mw-anonuserlink'			})}</span>`;			toolLinks = '<span class="mw-usertoollinks">(' + talkLink + logLink + blockLink + ')</span>';		}		return userLink + ' ' + toolLinks + ' ';	}	function buildLink(pagename, opts) {		let url = mw.util.getUrl(pagename);		if (opts.param) url = mw.util.getUrl(pagename, {[opts.param]: opts.value});		if (!opts.exists) url = mw.util.getUrl(pagename, {action: 'edit'});		if (opts.isRedirect) url = mw.util.getUrl(pagename, {redirect: 'no'});		let title = (opts.exists) ? pagename : pagename + ' (page does not exist)';		let redlink = (opts.exists) ? '' : 'new';		let classes = opts.classes || '';		let link = `<a href="${url}" title="${title}" class="${redlink} ${classes}">${opts.text}</a>`;		return link;	}	function getUsernames(abuselog) {		let usernames = new Set();		for (let entry of abuselog) {			usernames.add(entry.user);		}		return [...usernames];	}	function getPageTitles(abuselog) {		let pages = new Set();		let usernames = getUsernames(abuselog);		for (let entry of abuselog) {			pages.add(entry.title);		}		for (let username of usernames) {			pages.add('User:' + username);			pages.add('User talk:' + username);		}		return [...pages];	}	function setMessages(messages) {		for (let message of messages) {			mw.messages.set(message['name'], message['*']);		}	}	function buildError(result) {		let warning = new OO.ui.MessageWidget({			type: 'notice',			classes: ['gadget-abuselog-error'],			label: new OO.ui.HtmlSnippet('<strong>AbuseLogRC encountered an API error:</strong><br>' + result.error.info)		});		$('.mw-changeslist').before(warning.$element);	}	function getData() {		let api = new mw.Api();		// this api call gets:		// - abuselog entries for commonly triggered vandalism filters		// - mw messages for abusefilter results (tag, warn, disallow, etc.)		api.get({			list: 'abuselog',			afllimit: getConfig('entries'),			aflprop: 'ids|user|title|action|result|timestamp|revid|filter',			aflfilter: filters,			meta: 'allmessages',			amprefix: 'abusefilter-action-'		})		.then(function (results) {			let abuselogResult = results.query.abuselog;			let messagesResult = results.query.allmessages;			if (!gadgetLoaded) setMessages(messagesResult);			// this api call gets:			// - page info for target pages in abuselog			// - user info for users listed in abuselog			// - page info for those users' userpages and talk pages			return api.get({				list: 'users',				ususers: getUsernames(abuselogResult),				titles: getPageTitles(abuselogResult),				redirects: true			})			.then(				// success				function (results) {					let usersResult = results.query.users;					let pagesResult = results.query.pages;					return [abuselogResult, usersResult, pagesResult];				},				// fail				function (code, result) {					buildError(result);				}			);		})		.then(			// success			function (results) {				buildGadget(results);			},			// fail			function (code, result) {				buildError(result);			}		);	}	getData();}(jQuery, mediaWiki));