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
 
(2 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('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}$|:/;


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


// 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 45: 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]
};
};
} 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 61: Line 83:
});
});


// 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 70: Line 92:
});
});


// Step 3: Use MediaWiki API to check block status for all users found
// Add control button above log
const $btn = $('<button>')
const usernames = Object.keys(userData);
if (usernames.length === 0) {
.text('Open all user AbuseLogs, talk deletion, and show 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 usernames = Object.keys(userData);
if (usernames.length === 0) {
list: 'users',
alert('No users found to process.');
ususers: usernames.join('|'),
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 = [];
function addButtonAndSetup(userData) {
usernames.forEach(username => {
const data = userData[username];
const $btn = $('<button>')
.text('Open all user AbuseLogs, talk deletion, and show summary')
const latestLogId = data.latestLogId;
const extraHits = parseInt(data.extraHits, 10);
.css({
margin: '1em 0',

// Open filtered AbuseLog tab
padding: '6px 12px',
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', {
cursor: 'pointer'
wpSearchUser: username
});
});
window.open(abuseLogUrl, '_blank');


$content.prepend($btn);
// Open User Talk deletion tab 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');


$btn.on('click', function () {
// Compose summary line
let line = `[[Special:AbuseLog/${latestLogId}]]`;
const usernames = Object.keys(userData);
if (extraHits > 0) {
if (usernames.length === 0) {
line += ` (+[[Special:AbuseLog/${username}|${extraHits}]])`;
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')
            );
        });
    }
});