MediaWiki:Gadget-abuseLogRC-core.js: Difference between revisions

From RuneRealm Wiki
Jump to navigation Jump to search
Content added Content deleted
No edit summary
Tag: Reverted
No edit summary
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
"use strict";
/* ====================== 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));

function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return 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) {
var gadgetLoaded = false;
var entryDays = new Set();
var lastUpdate;
var intervalID;
var 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(_ref) {
var _ref2 = _slicedToArray(_ref, 3),
entries = _ref2[0],
users = _ref2[1],
pages = _ref2[2];
lastUpdate = new Date();

// refreshed
if (gadgetLoaded) {
$('.gadget-abuselog-list').replaceWith(buildList(entries, users, pages));
}
// initial load
else {
var $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() {
var $header = $('<div class="gadget-abuselog-header"></div>');
var $left = $('<span class="gadget-abuselog-header-left"></span>');
var $right = buildSettings();
var $title = $('<h4>Abuse log</h4>');
var link = ' (' + buildLink('Special:AbuseLog', {
exists: true,
text: 'all'
}) + ')';
$left.append($title, link);
$header.append($left, $right);
return $header;
}
function buildSettings() {
var $settings = $('<span class="gadget-abuselog-header-right"></span>');
var 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
var 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'
})]
});
var 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) {
var value = getConfig('entries').toString();
var customButton = new OO.ui.ButtonOptionWidget({
data: value,
label: value
});
entriesSelectWidget.addItems(customButton, 0);
entriesSelectWidget.selectItem(customButton);
}

// POPUP BOTTOM HALF: refresh settings
var lastUpdatedLabel = new OO.ui.LabelWidget({
classes: ['gadget-abuselog-settings-last-updated']
});
var autoRefreshCheckbox = new OO.ui.CheckboxInputWidget({
selected: getConfig('autoRefresh')
});
var 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
var 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 () {
var time = new Intl.DateTimeFormat('en-GB', {
hour: 'numeric',
minute: 'numeric',
timeZone: 'UTC'
}).format(lastUpdate);
var day = new Intl.DateTimeFormat('en-GB', {
day: 'numeric',
month: 'long',
year: 'numeric',
timeZone: 'UTC'
}).format(lastUpdate);
var diff = new Date() - lastUpdate;
var h = Math.floor(diff / 1000 / 60 / 60);
var m = Math.floor(diff / 1000 / 60) % 60;
var 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
var hh = h > 0 ? h + 'h ' : '';
var mm = m + 'm';
var ss = s + 's';
var ago = h > 0 || m > 0 ? hh + mm : ss;
lastUpdatedLabel.setLabel(new OO.ui.HtmlSnippet("Last update: <strong class=\"last-update\" title=\"".concat(time, ", ").concat(day, "\">").concat(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) {
var $list = $('<div class="gadget-abuselog-list"></div>');
var pageArr = Object.values(pages);
var _iterator = _createForOfIteratorHelper(entries),
_step;
try {
var _loop = function _loop() {
var entry = _step.value;
var user = users.find(function (user) {
return user.name === entry.user;
});
var page = pageArr.find(function (page) {
return page.title === entry.title;
});
var userPage = pageArr.find(function (page) {
return page.title === "User:".concat(user.name);
});
var talkPage = pageArr.find(function (page) {
return page.title === "User talk:".concat(user.name);
});

// prevent error; User:FeedbackBot redirects to RuneScape:Article feedback
if (user.name === 'FeedbackBot') {
userPage = pageArr.find(function (page) {
return page.title === 'RuneScape:Article feedback';
});
}
var 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));
};
for (_iterator.s(); !(_step = _iterator.n()).done;) {
_loop();
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
return $list;
}
function buildRow(entry, opts) {
// FIRST COLUMN: date and time
var entryDate = new Date(entry.timestamp);
var entryDay = new Intl.DateTimeFormat('en-GB', {
day: '2-digit',
month: 'short',
timeZone: 'UTC'
}).format(entryDate);
var entryTime = new Intl.DateTimeFormat('en-GB', {
hour: 'numeric',
minute: 'numeric',
timeZone: 'UTC'
}).format(entryDate);
var showHideDay = entryDays.has(entryDay) ? 'hide' : '';
var firstColumn = '<span class="gadget-abuselog-col gadget-abuselog-col-1">' + "<span class=\"".concat(showHideDay, "\">").concat(entryDay, "</span> <span>").concat(entryTime, "</span>") + '</span>';
entryDays.add(entryDay);

// SECOND COLUMN: page edited
var 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
var diffLink = entry.revid ? buildLink("Special:Diff/".concat(entry.revid), {
exists: true,
text: 'diff'
}) : 'diff';
var logLink = buildLink("Special:AbuseLog/".concat(entry.id), {
exists: true,
text: 'log'
});
var results = [];

// some filters perform multiple actions on a single edit, eg. <https://oldschool.runescape.wiki/w/Special:AbuseLog/22296>
entry.result.split(',').forEach(function (result) {
results.push("<span class=\"gadget-abuselog-result gadget-abuselog-result-".concat(result, "\">") + "".concat(mw.msg('abusefilter-action-' + result), "</span>"));
});
var thirdColumn = '<span class="gadget-abuselog-col gadget-abuselog-col-3">' + "(".concat(diffLink, " | ").concat(logLink, ") <span class=\"gadget-abuselog-action\">(").concat(results.join(', '), ")</span>") + '</span>';

// FOURTH COLUMN: user details and filter triggered
var filterURL = "Special:AbuseFilter/".concat(entry.filter_id);
if (entry.filter_id.includes('global')) {
filterURL = "meta:Special:AbuseFilter/".concat(entry.filter_id.replace('global-', ''));
}
var filterLink = buildLink(filterURL, {
exists: true,
text: "Filter ".concat(entry.filter_id)
});
var fourthColumn = '<span class="gadget-abuselog-col gadget-abuselog-col-4">' + buildUserLinks(entry.user, opts) + "<span class=\"gadget-abuselog-filter gadget-abuselog-filter-".concat(entry.filter_id, "\">") + "(".concat(filterLink, ": ").concat(entry.filter, ")") + '</span> ' + '</span>';
return '<div class="gadget-abuselog-row">' + firstColumn + secondColumn + thirdColumn + fourthColumn + '</div>';
}
function buildUserLinks(username, opts) {
var userLink;
var toolLinks;

// link text is added with CSS so it can be replaced when readableRC is loaded
var talkLink = "<span>".concat(buildLink("User talk:".concat(username), {
exists: opts.talkPageExists,
text: '',
classes: 'mw-usertoollinks-talk'
}), "</span>");
var contribsLink = "<span>".concat(buildLink("Special:Contributions/".concat(username), {
exists: true,
text: '',
classes: 'mw-usertoollinks-contribs'
}), "</span>");
var logLink = "<span>".concat(buildLink("Special:AbuseLog", {
exists: true,
text: '',
classes: 'mw-usertoollinks-abuselog',
param: 'wpSearchUser',
value: username
}), "</span>");
var blockLink = "<span>".concat(buildLink("Special:Block/".concat(username), {
exists: true,
text: '',
classes: 'mw-usertoollinks-block'
}), "</span>");

// user links vs. anon links
if (opts.isRegistered) {
userLink = "<span>".concat(buildLink("User:".concat(username), {
exists: opts.userPageExists,
text: username,
classes: 'mw-userlink'
}), "</span>");
toolLinks = '<span class="mw-usertoollinks">(' + talkLink + contribsLink + logLink + blockLink + ')</span>';
} else {
userLink = "<span>".concat(buildLink("Special:Contributions/".concat(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) {
var url = mw.util.getUrl(pagename);
if (opts.param) url = mw.util.getUrl(pagename, _defineProperty({}, opts.param, opts.value));
if (!opts.exists) url = mw.util.getUrl(pagename, {
action: 'edit'
});
if (opts.isRedirect) url = mw.util.getUrl(pagename, {
redirect: 'no'
});
var title = opts.exists ? pagename : pagename + ' (page does not exist)';
var redlink = opts.exists ? '' : 'new';
var classes = opts.classes || '';
var link = "<a href=\"".concat(url, "\" title=\"").concat(title, "\" class=\"").concat(redlink, " ").concat(classes, "\">").concat(opts.text, "</a>");
return link;
}
function getUsernames(abuselog) {
var usernames = new Set();
var _iterator2 = _createForOfIteratorHelper(abuselog),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var entry = _step2.value;
usernames.add(entry.user);
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
return _toConsumableArray(usernames);
}
function getPageTitles(abuselog) {
var pages = new Set();
var usernames = getUsernames(abuselog);
var _iterator3 = _createForOfIteratorHelper(abuselog),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var entry = _step3.value;
pages.add(entry.title);
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
var _iterator4 = _createForOfIteratorHelper(usernames),
_step4;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var username = _step4.value;
pages.add('User:' + username);
pages.add('User talk:' + username);
}
} catch (err) {
_iterator4.e(err);
} finally {
_iterator4.f();
}
return _toConsumableArray(pages);
}
function setMessages(messages) {
var _iterator5 = _createForOfIteratorHelper(messages),
_step5;
try {
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
var message = _step5.value;
mw.messages.set(message['name'], message['*']);
}
} catch (err) {
_iterator5.e(err);
} finally {
_iterator5.f();
}
}
function buildError(result) {
var 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() {
var 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) {
var abuselogResult = results.query.abuselog;
var 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) {
var usersResult = results.query.users;
var 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);

Latest revision as of 12:06, 20 October 2024

"use strict";

function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return 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) {
  var gadgetLoaded = false;
  var entryDays = new Set();
  var lastUpdate;
  var intervalID;
  var 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(_ref) {
    var _ref2 = _slicedToArray(_ref, 3),
      entries = _ref2[0],
      users = _ref2[1],
      pages = _ref2[2];
    lastUpdate = new Date();

    // refreshed
    if (gadgetLoaded) {
      $('.gadget-abuselog-list').replaceWith(buildList(entries, users, pages));
    }
    // initial load
    else {
      var $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() {
    var $header = $('<div class="gadget-abuselog-header"></div>');
    var $left = $('<span class="gadget-abuselog-header-left"></span>');
    var $right = buildSettings();
    var $title = $('<h4>Abuse log</h4>');
    var link = ' (' + buildLink('Special:AbuseLog', {
      exists: true,
      text: 'all'
    }) + ')';
    $left.append($title, link);
    $header.append($left, $right);
    return $header;
  }
  function buildSettings() {
    var $settings = $('<span class="gadget-abuselog-header-right"></span>');
    var 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
    var 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'
      })]
    });
    var 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) {
      var value = getConfig('entries').toString();
      var customButton = new OO.ui.ButtonOptionWidget({
        data: value,
        label: value
      });
      entriesSelectWidget.addItems(customButton, 0);
      entriesSelectWidget.selectItem(customButton);
    }

    // POPUP BOTTOM HALF: refresh settings
    var lastUpdatedLabel = new OO.ui.LabelWidget({
      classes: ['gadget-abuselog-settings-last-updated']
    });
    var autoRefreshCheckbox = new OO.ui.CheckboxInputWidget({
      selected: getConfig('autoRefresh')
    });
    var 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
    var 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 () {
      var time = new Intl.DateTimeFormat('en-GB', {
        hour: 'numeric',
        minute: 'numeric',
        timeZone: 'UTC'
      }).format(lastUpdate);
      var day = new Intl.DateTimeFormat('en-GB', {
        day: 'numeric',
        month: 'long',
        year: 'numeric',
        timeZone: 'UTC'
      }).format(lastUpdate);
      var diff = new Date() - lastUpdate;
      var h = Math.floor(diff / 1000 / 60 / 60);
      var m = Math.floor(diff / 1000 / 60) % 60;
      var 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
      var hh = h > 0 ? h + 'h ' : '';
      var mm = m + 'm';
      var ss = s + 's';
      var ago = h > 0 || m > 0 ? hh + mm : ss;
      lastUpdatedLabel.setLabel(new OO.ui.HtmlSnippet("Last update: <strong class=\"last-update\" title=\"".concat(time, ", ").concat(day, "\">").concat(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) {
    var $list = $('<div class="gadget-abuselog-list"></div>');
    var pageArr = Object.values(pages);
    var _iterator = _createForOfIteratorHelper(entries),
      _step;
    try {
      var _loop = function _loop() {
        var entry = _step.value;
        var user = users.find(function (user) {
          return user.name === entry.user;
        });
        var page = pageArr.find(function (page) {
          return page.title === entry.title;
        });
        var userPage = pageArr.find(function (page) {
          return page.title === "User:".concat(user.name);
        });
        var talkPage = pageArr.find(function (page) {
          return page.title === "User talk:".concat(user.name);
        });

        // prevent error; User:FeedbackBot redirects to RuneScape:Article feedback
        if (user.name === 'FeedbackBot') {
          userPage = pageArr.find(function (page) {
            return page.title === 'RuneScape:Article feedback';
          });
        }
        var 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));
      };
      for (_iterator.s(); !(_step = _iterator.n()).done;) {
        _loop();
      }
    } catch (err) {
      _iterator.e(err);
    } finally {
      _iterator.f();
    }
    return $list;
  }
  function buildRow(entry, opts) {
    // FIRST COLUMN: date and time
    var entryDate = new Date(entry.timestamp);
    var entryDay = new Intl.DateTimeFormat('en-GB', {
      day: '2-digit',
      month: 'short',
      timeZone: 'UTC'
    }).format(entryDate);
    var entryTime = new Intl.DateTimeFormat('en-GB', {
      hour: 'numeric',
      minute: 'numeric',
      timeZone: 'UTC'
    }).format(entryDate);
    var showHideDay = entryDays.has(entryDay) ? 'hide' : '';
    var firstColumn = '<span class="gadget-abuselog-col gadget-abuselog-col-1">' + "<span class=\"".concat(showHideDay, "\">").concat(entryDay, "</span> <span>").concat(entryTime, "</span>") + '</span>';
    entryDays.add(entryDay);

    // SECOND COLUMN: page edited
    var 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
    var diffLink = entry.revid ? buildLink("Special:Diff/".concat(entry.revid), {
      exists: true,
      text: 'diff'
    }) : 'diff';
    var logLink = buildLink("Special:AbuseLog/".concat(entry.id), {
      exists: true,
      text: 'log'
    });
    var results = [];

    // some filters perform multiple actions on a single edit, eg. <https://oldschool.runescape.wiki/w/Special:AbuseLog/22296>
    entry.result.split(',').forEach(function (result) {
      results.push("<span class=\"gadget-abuselog-result gadget-abuselog-result-".concat(result, "\">") + "".concat(mw.msg('abusefilter-action-' + result), "</span>"));
    });
    var thirdColumn = '<span class="gadget-abuselog-col gadget-abuselog-col-3">' + "(".concat(diffLink, " | ").concat(logLink, ") <span class=\"gadget-abuselog-action\">(").concat(results.join(', '), ")</span>") + '</span>';

    // FOURTH COLUMN: user details and filter triggered
    var filterURL = "Special:AbuseFilter/".concat(entry.filter_id);
    if (entry.filter_id.includes('global')) {
      filterURL = "meta:Special:AbuseFilter/".concat(entry.filter_id.replace('global-', ''));
    }
    var filterLink = buildLink(filterURL, {
      exists: true,
      text: "Filter ".concat(entry.filter_id)
    });
    var fourthColumn = '<span class="gadget-abuselog-col gadget-abuselog-col-4">' + buildUserLinks(entry.user, opts) + "<span class=\"gadget-abuselog-filter gadget-abuselog-filter-".concat(entry.filter_id, "\">") + "(".concat(filterLink, ": ").concat(entry.filter, ")") + '</span> ' + '</span>';
    return '<div class="gadget-abuselog-row">' + firstColumn + secondColumn + thirdColumn + fourthColumn + '</div>';
  }
  function buildUserLinks(username, opts) {
    var userLink;
    var toolLinks;

    // link text is added with CSS so it can be replaced when readableRC is loaded
    var talkLink = "<span>".concat(buildLink("User talk:".concat(username), {
      exists: opts.talkPageExists,
      text: '',
      classes: 'mw-usertoollinks-talk'
    }), "</span>");
    var contribsLink = "<span>".concat(buildLink("Special:Contributions/".concat(username), {
      exists: true,
      text: '',
      classes: 'mw-usertoollinks-contribs'
    }), "</span>");
    var logLink = "<span>".concat(buildLink("Special:AbuseLog", {
      exists: true,
      text: '',
      classes: 'mw-usertoollinks-abuselog',
      param: 'wpSearchUser',
      value: username
    }), "</span>");
    var blockLink = "<span>".concat(buildLink("Special:Block/".concat(username), {
      exists: true,
      text: '',
      classes: 'mw-usertoollinks-block'
    }), "</span>");

    // user links vs. anon links
    if (opts.isRegistered) {
      userLink = "<span>".concat(buildLink("User:".concat(username), {
        exists: opts.userPageExists,
        text: username,
        classes: 'mw-userlink'
      }), "</span>");
      toolLinks = '<span class="mw-usertoollinks">(' + talkLink + contribsLink + logLink + blockLink + ')</span>';
    } else {
      userLink = "<span>".concat(buildLink("Special:Contributions/".concat(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) {
    var url = mw.util.getUrl(pagename);
    if (opts.param) url = mw.util.getUrl(pagename, _defineProperty({}, opts.param, opts.value));
    if (!opts.exists) url = mw.util.getUrl(pagename, {
      action: 'edit'
    });
    if (opts.isRedirect) url = mw.util.getUrl(pagename, {
      redirect: 'no'
    });
    var title = opts.exists ? pagename : pagename + ' (page does not exist)';
    var redlink = opts.exists ? '' : 'new';
    var classes = opts.classes || '';
    var link = "<a href=\"".concat(url, "\" title=\"").concat(title, "\" class=\"").concat(redlink, " ").concat(classes, "\">").concat(opts.text, "</a>");
    return link;
  }
  function getUsernames(abuselog) {
    var usernames = new Set();
    var _iterator2 = _createForOfIteratorHelper(abuselog),
      _step2;
    try {
      for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
        var entry = _step2.value;
        usernames.add(entry.user);
      }
    } catch (err) {
      _iterator2.e(err);
    } finally {
      _iterator2.f();
    }
    return _toConsumableArray(usernames);
  }
  function getPageTitles(abuselog) {
    var pages = new Set();
    var usernames = getUsernames(abuselog);
    var _iterator3 = _createForOfIteratorHelper(abuselog),
      _step3;
    try {
      for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
        var entry = _step3.value;
        pages.add(entry.title);
      }
    } catch (err) {
      _iterator3.e(err);
    } finally {
      _iterator3.f();
    }
    var _iterator4 = _createForOfIteratorHelper(usernames),
      _step4;
    try {
      for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
        var username = _step4.value;
        pages.add('User:' + username);
        pages.add('User talk:' + username);
      }
    } catch (err) {
      _iterator4.e(err);
    } finally {
      _iterator4.f();
    }
    return _toConsumableArray(pages);
  }
  function setMessages(messages) {
    var _iterator5 = _createForOfIteratorHelper(messages),
      _step5;
    try {
      for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
        var message = _step5.value;
        mw.messages.set(message['name'], message['*']);
      }
    } catch (err) {
      _iterator5.e(err);
    } finally {
      _iterator5.f();
    }
  }
  function buildError(result) {
    var 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() {
    var 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) {
      var abuselogResult = results.query.abuselog;
      var 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) {
        var usersResult = results.query.users;
        var 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);