User:Username/BlockAbuser.js: Difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
No edit summary |
No edit summary |
||
| (One intermediate revision by the same user not shown) | |||
| Line 1: | Line 1: | ||
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery'], function () { |
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery'], function () { |
||
// Run only on the exact Special:AbuseLog page (no subpages) |
|||
if (mw.config.get('wgPageName') !== 'Special:AbuseLog') { |
if (mw.config.get('wgPageName') !== 'Special:AbuseLog') { |
||
return; |
return; |
||
| Line 5: | Line 6: | ||
const $content = $('#mw-content-text'); |
const $content = $('#mw-content-text'); |
||
const userData = {}; // username |
const userData = {}; // username -> { latestLogId, extraHits, $userLink, $blockLink, allLinks } |
||
const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|:/; |
const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|:/; |
||
| ⚫ | |||
// Helper to decode username from a user link href |
|||
function getUsernameFromHref(href) { |
function getUsernameFromHref(href) { |
||
| ⚫ | |||
const |
const match = href.match(/\/wiki\/User:([^?#]+)/); |
||
| ⚫ | |||
| ⚫ | |||
return decodeURIComponent( |
return decodeURIComponent(match[1]).replace(/_/g, ' ').trim(); |
||
} |
} |
||
// 1 |
// Step 1: Collect user info from each abuse log entry (<li>) |
||
$content.find('li').each(function () { |
$content.find('li').each(function () { |
||
const $li = $(this); |
const $li = $(this); |
||
// Find all user links in this <li> |
|||
const $userLinks = $li.find('a').filter(function () { |
const $userLinks = $li.find('a').filter(function () { |
||
const href = $(this).attr('href') |
const href = $(this).attr('href'); |
||
return href.includes('/wiki/User:'); |
return href && href.includes('/wiki/User:'); |
||
}); |
}); |
||
if ($userLinks.length === 0) return; |
if ($userLinks.length === 0) return; |
||
// Find abuse log id for this entry (from any link containing Special:AbuseLog/{id}) |
|||
| ⚫ | |||
$li.find('a').each(function () { |
|||
| ⚫ | |||
| ⚫ | |||
const match = href.match(/Special:AbuseLog\/(\d+)/); |
|||
| ⚫ | |||
| ⚫ | |||
return false; // stop iteration |
|||
| ⚫ | |||
| ⚫ | |||
if (!logId) return; |
|||
// For each user link in this li: |
|||
$userLinks.each(function () { |
$userLinks.each(function () { |
||
const $ |
const $userLink = $(this); |
||
const username = getUsernameFromHref($ |
const username = getUsernameFromHref($userLink.attr('href')); |
||
if (!username || ipRegex.test(username)) return; |
|||
if (!username) return; |
|||
if (ipRegex.test(username)) return; // skip IP addresses |
|||
// Find the "block" link in this same <li> that applies to this user |
|||
// Block links usually have href with "block=" and user name |
|||
// Sometimes encoded spaces appear as + or %20 - check both |
|||
const encodedUser1 = encodeURIComponent(username).replace(/%20/g, '+'); |
|||
const encodedUser2 = encodeURIComponent(username).replace(/%20/g, '%20'); |
|||
const $blockLink = $li.find('a').filter(function () { |
|||
const href = $(this).attr('href') || ''; |
const href = $(this).attr('href') || ''; |
||
return href.includes('block=') && |
|||
(href.includes(encodedUser1) || href.includes(encodedUser2)); |
|||
}).first(); |
|||
| ⚫ | |||
} |
|||
| ⚫ | |||
| ⚫ | |||
if (!userData[username]) { |
if (!userData[username]) { |
||
| Line 46: | Line 67: | ||
latestLogId: logId, |
latestLogId: logId, |
||
extraHits: 0, |
extraHits: 0, |
||
$ |
$userLink: $userLink, |
||
$ |
$blockLink: $blockLink.length ? $blockLink : null, |
||
allUserLinks: [$userLink] |
|||
blocked: false // to be updated after API call |
|||
}; |
}; |
||
} else { |
} else { |
||
userData[username]. |
userData[username].allUserLinks.push($userLink); |
||
if (parseInt(logId) > parseInt(userData[username].latestLogId)) { |
if (parseInt(logId, 10) > parseInt(userData[username].latestLogId, 10)) { |
||
userData[username].latestLogId = logId; |
userData[username].latestLogId = logId; |
||
userData[username].$ |
userData[username].$userLink = $userLink; |
||
userData[username].$ |
if ($blockLink.length) userData[username].$blockLink = $blockLink; |
||
} |
} |
||
userData[username].extraHits++; |
userData[username].extraHits++; |
||
| Line 63: | Line 83: | ||
}); |
}); |
||
// 2 |
// Step 2: Delink all but the most recent user link for each user |
||
Object.entries(userData).forEach(([username, data]) => { |
Object.entries(userData).forEach(([username, data]) => { |
||
data. |
data.allUserLinks.forEach(($link) => { |
||
if ($link[0] !== data.$userLink[0]) { |
if ($link[0] !== data.$userLink[0]) { |
||
$link.replaceWith(document.createTextNode($link.text())); |
$link.replaceWith(document.createTextNode($link.text())); |
||
| Line 72: | Line 92: | ||
}); |
}); |
||
// 3 |
// Step 3: Use MediaWiki API to check block status for all users found |
||
const usernames = Object.keys(userData); |
const usernames = Object.keys(userData); |
||
if (usernames.length === 0) { |
if (usernames.length === 0) { |
||
// No users found |
// No users found - just add the button and stop |
||
addButtonAndSetup(userData); |
addButtonAndSetup(userData); |
||
return; |
return; |
||
} |
} |
||
| ⚫ | |||
// MediaWiki API query to check if users are blocked |
|||
api.get({ |
api.get({ |
||
action: 'query', |
action: 'query', |
||
| Line 90: | Line 111: | ||
data.query.users.forEach(user => { |
data.query.users.forEach(user => { |
||
if (user.blockid) { |
if (user.blockid) { |
||
const |
const uname = user.name; |
||
if (userData[ |
if (userData[uname] && userData[uname].$blockLink) { |
||
// Underline and color the existing block link to indicate the user is blocked |
|||
userData[uname].$blockLink.css({ |
|||
| ⚫ | |||
| ⚫ | |||
| ⚫ | |||
'cursor': 'pointer' |
|||
| ⚫ | |||
} |
} |
||
} |
} |
||
}); |
}); |
||
} |
} |
||
// Now update UI with "(blocked)" labels |
|||
Object.entries(userData).forEach(([username, data]) => { |
|||
| ⚫ | |||
// Append (blocked) after the user link (or text node) |
|||
| ⚫ | |||
// Check if (blocked) is already there to avoid duplicates |
|||
if (!$userLink.next('.blocked-label').length) { |
|||
| ⚫ | |||
| ⚫ | |||
| ⚫ | |||
.css({ color: 'red', fontWeight: 'bold', marginLeft: '4px' }) |
|||
| ⚫ | |||
} |
|||
| ⚫ | |||
| ⚫ | |||
addButtonAndSetup(userData); |
addButtonAndSetup(userData); |
||
}).fail(function () { |
}).fail(function () { |
||
// If API fails, just add the button without blocked labels |
|||
addButtonAndSetup(userData); |
addButtonAndSetup(userData); |
||
}); |
}); |
||
// 4 |
// Step 4: Add the top button, open tabs & show summary on click |
||
function addButtonAndSetup(userData) { |
function addButtonAndSetup(userData) { |
||
const $btn = $('<button>') |
const $btn = $('<button>') |
||
| Line 145: | Line 154: | ||
const extraHits = parseInt(data.extraHits, 10); |
const extraHits = parseInt(data.extraHits, 10); |
||
// Open filtered |
// Open AbuseLog filtered for the user |
||
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', { |
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', { |
||
wpSearchUser: username |
wpSearchUser: username |
||
| Line 151: | Line 160: | ||
window.open(abuseLogUrl, '_blank'); |
window.open(abuseLogUrl, '_blank'); |
||
// Open User Talk deletion |
// Open User Talk deletion page with prefilled reason |
||
const talkDeleteUrl = mw.util.getUrl('Special:Delete/' + 'User_talk:' + encodeURIComponent(username), { |
const talkDeleteUrl = mw.util.getUrl('Special:Delete/' + 'User_talk:' + encodeURIComponent(username), { |
||
reason: `Talk page of an indefinitely blocked user that has little value. The content was: blahblah.` |
reason: `Talk page of an indefinitely blocked user that has little value. The content was: blahblah.` |
||
| Line 157: | Line 166: | ||
window.open(talkDeleteUrl, '_blank'); |
window.open(talkDeleteUrl, '_blank'); |
||
// |
// Build summary line |
||
let line = `[[Special:AbuseLog/${latestLogId}]]`; |
let line = `[[Special:AbuseLog/${latestLogId}]]`; |
||
if (extraHits > 0) { |
if (extraHits > 0) { |
||
| Line 165: | Line 174: | ||
}); |
}); |
||
alert( |
|||
'Spambot or spam-only accounts detected. Details:\n' + |
'Spambot or spam-only accounts detected. Details:\n' + |
||
summaryLines.join('\n') |
summaryLines.join('\n') |
||
| ⚫ | |||
alert(summaryText); |
|||
}); |
}); |
||
} |
} |
||
Latest revision as of 02:37, 21 January 2026
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'jquery'], function () {
// Run only on the exact Special:AbuseLog page (no subpages)
if (mw.config.get('wgPageName') !== 'Special:AbuseLog') {
return;
}
const $content = $('#mw-content-text');
const userData = {}; // username -> { latestLogId, extraHits, $userLink, $blockLink, allLinks }
const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|:/;
// Helper to decode username from a user link href
function getUsernameFromHref(href) {
if (!href) return null;
const match = href.match(/\/wiki\/User:([^?#]+)/);
if (!match) return null;
return decodeURIComponent(match[1]).replace(/_/g, ' ').trim();
}
// Step 1: Collect user info from each abuse log entry (<li>)
$content.find('li').each(function () {
const $li = $(this);
// Find all user links in this <li>
const $userLinks = $li.find('a').filter(function () {
const href = $(this).attr('href');
return href && href.includes('/wiki/User:');
});
if ($userLinks.length === 0) return;
// Find abuse log id for this entry (from any link containing Special:AbuseLog/{id})
let logId = null;
$li.find('a').each(function () {
const href = $(this).attr('href');
if (!href) return;
const match = href.match(/Special:AbuseLog\/(\d+)/);
if (match) {
logId = match[1];
return false; // stop iteration
}
});
if (!logId) return;
// For each user link in this li:
$userLinks.each(function () {
const $userLink = $(this);
const username = getUsernameFromHref($userLink.attr('href'));
if (!username) return;
if (ipRegex.test(username)) return; // skip IP addresses
// Find the "block" link in this same <li> that applies to this user
// Block links usually have href with "block=" and user name
// Sometimes encoded spaces appear as + or %20 - check both
const encodedUser1 = encodeURIComponent(username).replace(/%20/g, '+');
const encodedUser2 = encodeURIComponent(username).replace(/%20/g, '%20');
const $blockLink = $li.find('a').filter(function () {
const href = $(this).attr('href') || '';
return href.includes('block=') &&
(href.includes(encodedUser1) || href.includes(encodedUser2));
}).first();
if (!userData[username]) {
userData[username] = {
latestLogId: logId,
extraHits: 0,
$userLink: $userLink,
$blockLink: $blockLink.length ? $blockLink : null,
allUserLinks: [$userLink]
};
} else {
userData[username].allUserLinks.push($userLink);
if (parseInt(logId, 10) > parseInt(userData[username].latestLogId, 10)) {
userData[username].latestLogId = logId;
userData[username].$userLink = $userLink;
if ($blockLink.length) userData[username].$blockLink = $blockLink;
}
userData[username].extraHits++;
}
});
});
// Step 2: Delink all but the most recent user link for each user
Object.entries(userData).forEach(([username, data]) => {
data.allUserLinks.forEach(($link) => {
if ($link[0] !== data.$userLink[0]) {
$link.replaceWith(document.createTextNode($link.text()));
}
});
});
// Step 3: Use MediaWiki API to check block status for all users found
const usernames = Object.keys(userData);
if (usernames.length === 0) {
// No users found - just add the button and stop
addButtonAndSetup(userData);
return;
}
const api = new mw.Api();
api.get({
action: 'query',
list: 'users',
ususers: usernames.join('|'),
usprop: 'blockinfo'
}).done(function (data) {
if (data && data.query && data.query.users) {
data.query.users.forEach(user => {
if (user.blockid) {
const uname = user.name;
if (userData[uname] && userData[uname].$blockLink) {
// Underline and color the existing block link to indicate the user is blocked
userData[uname].$blockLink.css({
'text-decoration': 'underline',
'font-weight': 'bold',
'color': '#c00',
'cursor': 'pointer'
});
}
}
});
}
addButtonAndSetup(userData);
}).fail(function () {
addButtonAndSetup(userData);
});
// Step 4: Add the top button, open tabs & show summary on click
function addButtonAndSetup(userData) {
const $btn = $('<button>')
.text('Open all user AbuseLogs, talk deletion, and show summary')
.css({
margin: '1em 0',
padding: '6px 12px',
cursor: 'pointer'
});
$content.prepend($btn);
$btn.on('click', function () {
const usernames = Object.keys(userData);
if (usernames.length === 0) {
alert('No users found to process.');
return;
}
let summaryLines = [];
usernames.forEach(username => {
const data = userData[username];
const latestLogId = data.latestLogId;
const extraHits = parseInt(data.extraHits, 10);
// Open AbuseLog filtered for the user
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', {
wpSearchUser: username
});
window.open(abuseLogUrl, '_blank');
// Open User Talk deletion page with prefilled reason
const talkDeleteUrl = mw.util.getUrl('Special:Delete/' + 'User_talk:' + encodeURIComponent(username), {
reason: `Talk page of an indefinitely blocked user that has little value. The content was: blahblah.`
});
window.open(talkDeleteUrl, '_blank');
// Build summary line
let line = `[[Special:AbuseLog/${latestLogId}]]`;
if (extraHits > 0) {
line += ` (+[[Special:AbuseLog/${username}|${extraHits}]])`;
}
summaryLines.push(line);
});
alert(
'Spambot or spam-only accounts detected. Details:\n' +
summaryLines.join('\n')
);
});
}
});