User:Username/BlockAbuser.js: Difference between revisions

From Test Wiki
Jump to navigation Jump to search
Content deleted Content added
hopefully this makes my life easier
No edit summary
 
(3 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 () {
// Only run on the exact Special:AbuseLog main page
// 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 6: Line 6:


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, $userLink }


const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|:/;
const ipRegex = /^(?:\d{1,3}\.){3}\d{1,3}$|:/;


// Helper: decode username from URL
// 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>)
// Scan all <li> entries (AbuseLog entries)
$content.find('li').each(function () {
$content.find('li').each(function () {
const $li = $(this);
const $li = $(this);


// Find first user link inside this <li>
// Find all user links in this <li>
const $userLink = $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:');
}).first();
});

if ($userLink.length === 0) return; // no user link in this li


if ($userLinks.length === 0) return;
const username = getUsernameFromHref($userLink.attr('href'));
if (!username) return;
if (ipRegex.test(username)) return; // skip IPs


// Find AbuseLog id from any 'details' or 'examine' link inside this <li>
// 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; // break loop
return false; // stop iteration
}
}
});
});

if (!logId) return;
if (!logId) return;


// Store or update userData with most recent log id
// For each user link in this li:
if (!userData[username]) {
$userLinks.each(function () {
const $userLink = $(this);
userData[username] = { latestLogId: logId, extraHits: 0, $li: $li, $userLink: $userLink };
const username = getUsernameFromHref($userLink.attr('href'));
} else {
if (parseInt(logId) > parseInt(userData[username].latestLogId)) {
userData[username].latestLogId = logId;
userData[username].$li = $li;
userData[username].$userLink = $userLink;
}
userData[username].extraHits++;
}
});


// Insert checkboxes once per user, inside their latest abuse log <li> before username link
if (!username) return;
Object.entries(userData).forEach(([username, data]) => {
if (ipRegex.test(username)) return; // skip IP addresses
if (data.$userLink.prev('.blockabuser-checkbox').length) return; // checkbox already exists


// Find the "block" link in this same <li> that applies to this user
const $checkbox = $('<input>', {
// Block links usually have href with "block=" and user name
type: 'checkbox',
// Sometimes encoded spaces appear as + or %20 - check both
class: 'blockabuser-checkbox',
'data-username': username,
const encodedUser1 = encodeURIComponent(username).replace(/%20/g, '+');
const encodedUser2 = encodeURIComponent(username).replace(/%20/g, '%20');
'data-latestlogid': data.latestLogId,
'data-extrahits': data.extraHits
}).css({
marginRight: '6px',
verticalAlign: 'middle',
cursor: 'pointer'
}).attr('title', 'Select this user for block review');


const $blockLink = $li.find('a').filter(function () {
data.$userLink.before($checkbox);
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++;
}
});
});
});


// Add the control button above the log
// Step 2: Delink all but the most recent user link for each user
Object.entries(userData).forEach(([username, data]) => {
const $btn = $('<button>')
data.allUserLinks.forEach(($link) => {
.text('Open selected user AbuseLogs and generate summary')
.css({
if ($link[0] !== data.$userLink[0]) {
$link.replaceWith(document.createTextNode($link.text()));
margin: '1em 0',
padding: '6px 12px',
}
cursor: 'pointer'
});
});
});


// Step 3: Use MediaWiki API to check block status for all users found
$content.prepend($btn);
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();
$btn.on('click', function () {

const checked = $('.blockabuser-checkbox:checked');
api.get({
if (!checked.length) {
action: 'query',
alert('Please select at least one user.');
return;
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
let summaryLines = [];
checked.each(function () {
function addButtonAndSetup(userData) {
const $cb = $(this);
const $btn = $('<button>')
.text('Open all user AbuseLogs, talk deletion, and show summary')
const username = $cb.data('username');
const latestLogId = $cb.data('latestlogid');
.css({
const extraHits = parseInt($cb.data('extrahits'), 10);
margin: '1em 0',
padding: '6px 12px',

// Open AbuseLog filtered by user in a new tab
cursor: 'pointer'
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', {
wpSearchUser: username
});
});
window.open(abuseLogUrl, '_blank');


// Compose summary line
$content.prepend($btn);

let line = `[[Special:AbuseLog/${latestLogId}]]`;
if (extraHits > 0) {
$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);
});


const summaryText =
let summaryLines = [];
usernames.forEach(username => {
'Spambot or spam-only accounts detected. Details:\n' +
summaryLines.join('\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')
);
});
}
});
});

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')
            );
        });
    }
});