User:Username/BlockAbuser.js: Difference between revisions

From Test Wiki
Jump to navigation Jump to search
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')
}
);
} );
});
}
} );
});

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