User:Username/BlockAbuser.js: Difference between revisions

From Test Wiki
Jump to navigation Jump to search
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: 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>)
// 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 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, skip


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 details/examine links 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
return false; // stop iteration
}
}
});
});

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


// Track user data, pick 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 {

// Update latestLogId if newer
if (parseInt(logId) > parseInt(userData[username].latestLogId)) {
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++;
}
}
userData[username].extraHits++;
});
}
});
});


// Add checkboxes inside <li>, before username link (only once 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]) => {
if (data.$userLink.prev('.blockabuser-checkbox').length) {
data.allUserLinks.forEach(($link) => {
return; // already has checkbox
if ($link[0] !== data.$userLink[0]) {
$link.replaceWith(document.createTextNode($link.text()));
}
const $checkbox = $('<input>', {
}
type: 'checkbox',
});
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 $btn = $('<button>')
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({
margin: '1em 0',
addButtonAndSetup(userData);
padding: '6px 12px',
return;
}
cursor: 'pointer'
});


$content.prepend($btn);
const api = new mw.Api();


api.get({
$btn.on('click', function () {
action: 'query',
const checked = $('.blockabuser-checkbox:checked');
if (!checked.length) {
list: 'users',
ususers: usernames.join('|'),
alert('Please select at least one user.');
return;
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
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')
            );
        });
    }
});