User:Username/BlockAbuser.js: Difference between revisions

From Test Wiki
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 => { latestLogId, extraHits, $li, $userLink, allLinks: [] }
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}$|:/;
const api = new mw.Api();


// Helper to decode username from a user link href
function getUsernameFromHref(href) {
function getUsernameFromHref(href) {
if (!href) return null;
const parts = href.split('/wiki/User:');
const match = href.match(/\/wiki\/User:([^?#]+)/);
if (parts.length < 2) return null;
if (!match) return null;
return decodeURIComponent(parts[1]).replace(/_/g, ' ').trim();
return decodeURIComponent(match[1]).replace(/_/g, ' ').trim();
}
}


// 1. Collect user info, group links by username
// 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})
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 () {
$userLinks.each(function () {
const $link = $(this);
const $userLink = $(this);
const username = getUsernameFromHref($link.attr('href'));
const username = getUsernameFromHref($userLink.attr('href'));
if (!username || ipRegex.test(username)) return;


let logId = null;
if (!username) return;
$li.find('a').each(function () {
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') || '';
const match = href.match(/Special:AbuseLog\/(\d+)/);
return href.includes('block=') &&
if (match) {
(href.includes(encodedUser1) || href.includes(encodedUser2));
logId = match[1];
}).first();
return false;
}
});
if (!logId) return;


if (!userData[username]) {
if (!userData[username]) {
Line 46: Line 67:
latestLogId: logId,
latestLogId: logId,
extraHits: 0,
extraHits: 0,
$li: $li,
$userLink: $userLink,
$userLink: $link,
$blockLink: $blockLink.length ? $blockLink : null,
allLinks: [$link],
allUserLinks: [$userLink]
blocked: false // to be updated after API call
};
};
} else {
} else {
userData[username].allLinks.push($link);
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].$li = $li;
userData[username].$userLink = $userLink;
userData[username].$userLink = $link;
if ($blockLink.length) userData[username].$blockLink = $blockLink;
}
}
userData[username].extraHits++;
userData[username].extraHits++;
Line 63: Line 83:
});
});


// 2. Delink all but most recent user link per user
// 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.allLinks.forEach(($link) => {
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. Check blocked status via API for all users
// 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, just add the button and return
// No users found - just add the button and stop
addButtonAndSetup(userData);
addButtonAndSetup(userData);
return;
return;
}
}


const api = new mw.Api();
// 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 u = user.name;
const uname = user.name;
if (userData[u]) {
if (userData[uname] && userData[uname].$blockLink) {
userData[u].blocked = true;
// 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'
});
}
}
}
}
});
});
}
}

// Now update UI with "(blocked)" labels
Object.entries(userData).forEach(([username, data]) => {
if (data.blocked) {
// Append (blocked) after the user link (or text node)
const $userLink = data.$userLink;
// Check if (blocked) is already there to avoid duplicates
if (!$userLink.next('.blocked-label').length) {
$('<span>')
.text(' (blocked)')
.addClass('blocked-label')
.css({ color: 'red', fontWeight: 'bold', marginLeft: '4px' })
.insertAfter($userLink);
}
}
});

addButtonAndSetup(userData);
addButtonAndSetup(userData);
}).fail(function () {
}).fail(function () {
// If API fails, just add the button without blocked labels
addButtonAndSetup(userData);
addButtonAndSetup(userData);
});
});


// 4. Add control button and handle click
// 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 AbuseLog tab
// 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 tab with prefilled reason
// 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');


// Compose summary line
// Build summary line
let line = `[[Special:AbuseLog/${latestLogId}]]`;
let line = `[[Special:AbuseLog/${latestLogId}]]`;
if (extraHits > 0) {
if (extraHits > 0) {
Line 165: Line 174:
});
});


const summaryText =
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);
});
});
}
}