User:Username/BlockAbuser.js: Difference between revisions
From Test Wiki
Content deleted Content added
No edit summary |
No edit summary |
||
| (4 intermediate revisions 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('wgCanonicalSpecialPageName') !== 'AbuseLog') { |
|||
if (mw.config.get('wgPageName') !== 'Special:AbuseLog') { |
|||
return; |
return; |
||
} |
} |
||
const $content = $('#mw-content-text'); |
const $content = $('#mw-content-text'); |
||
const userData = {}; // username -> { latestLogId, extraHits, $userLink, $blockLink, allLinks } |
|||
const seenUsers = new Set(); |
|||
const userData = {}; // username => { latestLogId, extraHits, $li } |
|||
const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|:/; |
const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|:/; |
||
// Helper |
// 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(); |
|||
} |
} |
||
// Step 1: Collect user info from each abuse log entry (<li>) |
|||
// Find all <li> entries inside mw-content-text (the AbuseLog entries) |
|||
$content.find('li').each(function () { |
$content.find('li').each(function () { |
||
const $li = $(this); |
const $li = $(this); |
||
// Find user |
// Find all user links in this <li> |
||
const $ |
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 ($userLink.length === 0) return; // no user link, skip |
|||
if ($userLinks.length === 0) return; |
|||
const username = getUsernameFromHref($userLink.attr('href')); |
|||
if (!username) return; |
|||
if (ipRegex.test(username)) return; // skip IPs |
|||
// Find |
// Find abuse log id for this entry (from any link containing Special:AbuseLog/{id}) |
||
let logId = null; |
let logId = null; |
||
$li.find('a').each(function () { |
$li.find('a').each(function () { |
||
const href = $(this).attr('href') |
const href = $(this).attr('href'); |
||
if (!href) return; |
|||
const match = href.match(/Special:AbuseLog\/(\d+)/); |
const match = href.match(/Special:AbuseLog\/(\d+)/); |
||
if (match) { |
if (match) { |
||
logId = match[1]; |
logId = match[1]; |
||
return false; // |
return false; // stop iteration |
||
} |
} |
||
}); |
}); |
||
if (!logId) return; |
if (!logId) return; |
||
// |
// For each user link in this li: |
||
$userLinks.each(function () { |
|||
const $userLink = $(this); |
|||
userData[username] = { latestLogId: logId, extraHits: 0, $li: $li, $userLink: $userLink }; |
|||
const username = getUsernameFromHref($userLink.attr('href')); |
|||
} else { |
|||
// Update latestLogId if newer |
|||
if ( |
if (!username) return; |
||
if (ipRegex.test(username)) return; // skip IP addresses |
|||
userData[username].latestLogId = logId; |
|||
userData[username].$li = $li; |
|||
// Find the "block" link in this same <li> that applies to this user |
|||
userData[username].$userLink = $userLink; |
|||
// 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]) => { |
Object.entries(userData).forEach(([username, data]) => { |
||
data.allUserLinks.forEach(($link) => { |
|||
if ($link[0] !== data.$userLink[0]) { |
|||
$link.replaceWith(document.createTextNode($link.text())); |
|||
} |
|||
} |
|||
}); |
|||
class: 'blockabuser-checkbox', |
|||
'data-username': username, |
|||
'data-latestlogid': data.latestLogId, |
|||
'data-extrahits': data.extraHits |
|||
}).css({ |
|||
marginRight: '6px', |
|||
verticalAlign: 'middle', |
|||
cursor: 'pointer' |
|||
}).attr('title', 'Select this user for block review'); |
|||
data.$userLink.before($checkbox); |
|||
}); |
}); |
||
// Step 3: Use MediaWiki API to check block status for all users found |
|||
// Button at top (same as before) |
|||
const |
const usernames = Object.keys(userData); |
||
if (usernames.length === 0) { |
|||
.text('Open selected user AbuseLogs and generate summary') |
|||
// No users found - just add the button and stop |
|||
.css({ |
|||
addButtonAndSetup(userData); |
|||
return; |
|||
} |
|||
cursor: 'pointer' |
|||
}); |
|||
const api = new mw.Api(); |
|||
api.get({ |
|||
$btn.on('click', function () { |
|||
action: 'query', |
|||
const checked = $('.blockabuser-checkbox:checked'); |
|||
list: 'users', |
|||
ususers: usernames.join('|'), |
|||
alert('Please select at least one user.'); |
|||
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 |
|||
let summaryLines = []; |
|||
function addButtonAndSetup(userData) { |
|||
const $btn = $('<button>') |
|||
.text('Open all user AbuseLogs, talk deletion, and show summary') |
|||
const username = $cb.data('username'); |
|||
.css({ |
|||
margin: '1em 0', |
|||
padding: '6px 12px', |
|||
cursor: 'pointer' |
|||
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', { |
|||
wpSearchUser: username |
|||
}); |
}); |
||
window.open(abuseLogUrl, '_blank'); |
|||
$content.prepend($btn); |
|||
let line = `[[Special:AbuseLog/${latestLogId}]]`; |
|||
$btn.on('click', function () { |
|||
const usernames = Object.keys(userData); |
|||
line += ` (+[[Special:AbuseLog/${username}|${extraHits}]])`; |
|||
if (usernames.length === 0) { |
|||
alert('No users found to process.'); |
|||
return; |
|||
} |
} |
||
summaryLines.push(line); |
|||
}); |
|||
let summaryLines = []; |
|||
usernames.forEach(username => { |
|||
'Spambot or spam-only accounts detected. Details:\n' + |
|||
const data = userData[username]; |
|||
const latestLogId = data.latestLogId; |
|||
const extraHits = parseInt(data.extraHits, 10); |
|||
// Open AbuseLog filtered for the user |
|||
alert(summaryText); |
|||
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') |
|||
); |
|||
}); |
|||
} |
|||
}); |
}); |
||