User:Username/BlockAbuser.js: Difference between revisions

From Test Wiki
Content deleted Content added
Let me just test this out
 
No edit summary
 
(8 intermediate revisions by the same user not shown)
Line 1: Line 1:
mw.loader.using( [ 'mediawiki.api', 'mediawiki.util' ] ).then( 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 api = new mw.Api();
const $content = $('#mw-content-text');
const userData = {}; // username -> { latestLogId, extraHits, $userLink, $blockLink, allLinks }
const $content = $( '#mw-content-text' );


const $btn = $( '<button>' )
const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|:/;
.text( 'Generate spambot block summary' )
.css( {
margin: '10px 0',
padding: '6px 10px',
cursor: 'pointer'
} );


// Helper to decode username from a user link href
$content.prepend( $btn );
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>)
$btn.on( 'click', async function () {
$content.find('li').each(function () {
const $li = $(this);


// Find all user links in this <li>
const params = new URLSearchParams( window.location.search );
const username = params.get( 'wpSearchUser' );
const $userLinks = $li.find('a').filter(function () {
const href = $(this).attr('href');
return href && href.includes('/wiki/User:');
});


if ( !username ) {
if ($userLinks.length === 0) return;
alert( 'Open Special:AbuseLog filtered to a single user first.' );
return;
}


// Find abuse log id for this entry (from any link containing Special:AbuseLog/{id})
try {
// 1. Get user info (edit count)
let logId = null;
$li.find('a').each(function () {
const userInfo = await api.get( {
action: 'query',
const href = $(this).attr('href');
list: 'users',
if (!href) return;
ususers: username,
const match = href.match(/Special:AbuseLog\/(\d+)/);
usprop: 'editcount'
if (match) {
} );
logId = match[1];
return false; // stop iteration
}
});
if (!logId) return;


const user = userInfo.query.users[0];
// For each user link in this li:
$userLinks.each(function () {
const $userLink = $(this);
const username = getUsernameFromHref($userLink.attr('href'));


if ( user.editcount > 0 ) {
if (!username) return;
if (ipRegex.test(username)) return; // skip IP addresses
alert( 'This account has edits. Script only applies to 0-edit accounts.' );

return;
// 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++;
}
}
});
});


// 2. Get AbuseLog entries (last 24h)
// Step 2: Delink all but the most recent user link for each user
Object.entries(userData).forEach(([username, data]) => {
const since = new Date( Date.now() - 86400000 ).toISOString();
data.allUserLinks.forEach(($link) => {
if ($link[0] !== data.$userLink[0]) {
$link.replaceWith(document.createTextNode($link.text()));
}
});
});


const abuseData = await api.get( {
// Step 3: Use MediaWiki API to check block status for all users found
const usernames = Object.keys(userData);
action: 'query',
if (usernames.length === 0) {
list: 'abuselog',
afluser: username,
// No users found - just add the button and stop
afllimit: 'max',
addButtonAndSetup(userData);
aflstart: since
return;
} );
}


const logs = abuseData.query.abuselog;
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);


if ( !logs.length ) {
$btn.on('click', function () {
alert( 'No recent AbuseFilter hits found.' );
const usernames = Object.keys(userData);
if (usernames.length === 0) {
alert('No users found to process.');
return;
return;
}
}


const mostRecent = logs[0];
let summaryLines = [];
const extraHits = logs.length - 1;
usernames.forEach(username => {
const data = userData[username];
const latestLogId = data.latestLogId;
const extraHits = parseInt(data.extraHits, 10);


let summary =
// Open AbuseLog filtered for the user
'Spambot or spam-only account. The following cites the action they committed ' +
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', {
'[[Special:AbuseLog/' + mostRecent.id + ']]';
wpSearchUser: username
});
window.open(abuseLogUrl, '_blank');


if ( extraHits > 0 ) {
// Open User Talk deletion page with prefilled reason
const talkDeleteUrl = mw.util.getUrl('Special:Delete/' + 'User_talk:' + encodeURIComponent(username), {
summary +=
reason: `Talk page of an indefinitely blocked user that has little value. The content was: blahblah.`
' (+[[Special:AbuseLog/' + username + '|' + extraHits + ']])';
}
});
window.open(talkDeleteUrl, '_blank');


// 3. Display output
// Build summary line
let line = `[[Special:AbuseLog/${latestLogId}]]`;
mw.notify(
$( '<pre>' ).text( summary ),
if (extraHits > 0) {
{ autoHide: false }
line += ` (+[[Special:AbuseLog/${username}|${extraHits}]])`;
);
}
summaryLines.push(line);
});


} catch ( e ) {
alert(
'Spambot or spam-only accounts detected. Details:\n' +
console.error( e );
alert( 'Failed to generate summary. See console.' );
summaryLines.join('\n')
}
);
} );
});
}
} );
});