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
 
(One intermediate revision 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}$|:/;
const api = new mw.Api();


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


// 1. 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 46: 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]
blocked: false // to be updated after API call
};
};
} 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 63: Line 83:
});
});


// 2. 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 72: Line 92:
});
});


// 3. Check blocked status via API for all users
// Step 3: Use MediaWiki API to check block status for all users found
const usernames = Object.keys(userData);
const usernames = Object.keys(userData);
if (usernames.length === 0) {
if (usernames.length === 0) {
// No users found, just add the button and return
// No users found - just add the button and stop
addButtonAndSetup(userData);
addButtonAndSetup(userData);
return;
return;
}
}


const api = new mw.Api();
// MediaWiki API query to check if users are blocked

api.get({
api.get({
action: 'query',
action: 'query',
Line 90: Line 111:
data.query.users.forEach(user => {
data.query.users.forEach(user => {
if (user.blockid) {
if (user.blockid) {
const u = user.name;
const uname = user.name;
if (userData[u]) {
if (userData[uname] && userData[uname].$blockLink) {
userData[u].blocked = true;
// 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'
});
}
}
}
}
});
});
}
}

// Now update UI with "(blocked)" labels
Object.entries(userData).forEach(([username, data]) => {
if (data.blocked) {
// Append (blocked) after the user link (or text node)
const $userLink = data.$userLink;
// Check if (blocked) is already there to avoid duplicates
if (!$userLink.next('.blocked-label').length) {
$('<span>')
.text(' (blocked)')
.addClass('blocked-label')
.css({ color: 'red', fontWeight: 'bold', marginLeft: '4px' })
.insertAfter($userLink);
}
}
});

addButtonAndSetup(userData);
addButtonAndSetup(userData);
}).fail(function () {
}).fail(function () {
// If API fails, just add the button without blocked labels
addButtonAndSetup(userData);
addButtonAndSetup(userData);
});
});


// 4. Add control button and handle click
// Step 4: Add the top button, open tabs & show summary on click
function addButtonAndSetup(userData) {
function addButtonAndSetup(userData) {
const $btn = $('<button>')
const $btn = $('<button>')
Line 145: Line 154:
const extraHits = parseInt(data.extraHits, 10);
const extraHits = parseInt(data.extraHits, 10);


// Open filtered AbuseLog tab
// Open AbuseLog filtered for the user
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', {
const abuseLogUrl = mw.util.getUrl('Special:AbuseLog', {
wpSearchUser: username
wpSearchUser: username
Line 151: Line 160:
window.open(abuseLogUrl, '_blank');
window.open(abuseLogUrl, '_blank');


// Open User Talk deletion tab with prefilled reason
// Open User Talk deletion page with prefilled reason
const talkDeleteUrl = mw.util.getUrl('Special:Delete/' + 'User_talk:' + encodeURIComponent(username), {
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.`
reason: `Talk page of an indefinitely blocked user that has little value. The content was: blahblah.`
Line 157: Line 166:
window.open(talkDeleteUrl, '_blank');
window.open(talkDeleteUrl, '_blank');


// Compose summary line
// Build summary line
let line = `[[Special:AbuseLog/${latestLogId}]]`;
let line = `[[Special:AbuseLog/${latestLogId}]]`;
if (extraHits > 0) {
if (extraHits > 0) {
Line 165: Line 174:
});
});


const summaryText =
alert(
'Spambot or spam-only accounts detected. Details:\n' +
'Spambot or spam-only accounts detected. Details:\n' +
summaryLines.join('\n');
summaryLines.join('\n')
);

alert(summaryText);
});
});
}
}

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