User:Polygnotus/Scripts/FilterInactiveOrBlocked.js: Difference between revisions

Content deleted Content added
Created page with '// Wikipedia User Block Checker for common.js // Checks if users are blocked and categorizes them async function checkUserBlocks() { // Get input from user const input = prompt("Enter usernames (one per line):\nSupported formats:\nUser:Username\nUser talk:Username\nUser:Username\nUser talk:Username"); if (!input) { console.log("No input provided"); return; } const users = parseUsers(input); if (use...'
 
No edit summary
 
(16 intermediate revisions by the same user not shown)
Line 1:
// Wikipedia User Block & Activity Checker for common.js
// Checks if users are blocked and categorizesif themthey've been active in the last 12 months
// Features: maxlag support, HTTP error handling, exponential backoff retry (1min, 3min, 5min), deduplication
 
async function checkUserBlocks() {
// GetShow input fromdialog userimmediately
showInputDialog();
const input = prompt("Enter usernames (one per line):\nSupported formats:\n[[User:Username]]\n[[User talk:Username]]\nUser:Username\nUser talk:Username");
}
 
// Helper function to scroll status area to bottom
function scrollStatusToBottom() {
setTimeout(() => {
const statusDiv = $('#status-text');
const statusArea = $('#status-area');
// Try scrolling the status area container instead of the text div
if (statusArea.length && statusArea[0]) {
statusArea[0].scrollTop = statusArea[0].scrollHeight;
}
// Also try the text div as backup
if (statusDiv.length && statusDiv[0]) {
statusDiv[0].scrollTop = statusDiv[0].scrollHeight;
}
}, 10);
}
 
function showInputDialog() {
const inputHtml = `
<div>
<p><strong>Enter usernames (one per line):</strong></p>
<p>Supported formats:</p>
<ul style="margin: 10px 0; padding-left: 20px;">
<li>[[User:Username]]</li>
<li>[[User talk:Username]]</li>
<li>User:Username</li>
<li>User talk:Username</li>
</ul>
<textarea id="user-input" style="width: 100%; height: 200px; font-family: monospace;"
placeholder="Paste your usernames here..."></textarea>
<div id="status-area" style="margin-top: 10px; font-family: monospace; background: #f8f9fa; padding: 10px; border: 1px solid #ddd; height: 170px; overflow-y: auto; display: none;">
<div id="status-text"></div>
</div>
</div>
`;
const dialog = $('<div>').html(inputHtml).dialog({
if (!input) {
console.log("Notitle: input'User provided");Block Checker',
return;width: 600,
height: 650,
}
modal: false,
resizable: true,
buttons: {
'Check Users': function() {
const input = $('#user-input').val().trim();
if (!input) {
alert('Please enter some usernames to check.');
return;
}
processUsers(input, dialog);
},
'Clear': function() {
$('#user-input').val('');
$('#status-area').hide();
$('#status-text').empty();
},
'Close': function() {
$(this).dialog('close');
}
}
});
// Focus the textarea
setTimeout(() => $('#user-input').focus(), 100);
}
 
// Helper function to check if username is a vanished/renamed user
const users = parseUsers(input);
function isVanishedOrRenamed(username) {
const lowerUsername = username.toLowerCase();
return lowerUsername.startsWith('vanished user') || lowerUsername.startsWith('renamed user');
}
 
async function processUsers(input, dialog) {
const allUsers = parseUsers(input);
const users = deduplicateUsers(allUsers);
if (users.length === 0) {
console.logalert("'No valid usernames found" in the input.');
return;
}
 
// Show status area and start processing
console.log(`Checking ${users.length} users for blocks...`);
$('#status-area').show();
const statusDiv = $('#status-text');
// Show deduplication info if there were duplicates
const duplicateCount = allUsers.length - users.length;
if (duplicateCount > 0) {
statusDiv.html(`<div>Found ${allUsers.length} usernames, removed ${duplicateCount} duplicates.</div><div>Checking ${users.length} unique users for blocks and activity (last 12 months)...</div>`);
console.log(`Found ${allUsers.length} usernames, removed ${duplicateCount} duplicates.`);
} else {
statusDiv.html(`<div>Checking ${users.length} users for blocks and activity (last 12 months)...</div>`);
}
scrollStatusToBottom();
console.log(`Checking ${users.length} users for blocks and activity...`);
const activeUsers = [];
const blockedUsers = [];
const inactiveUsers = [];
const vanishedUsers = []; // New category for vanished/renamed users
// Disable the Check Users button during processing
for (const userInfo of users) {
const checkButton = dialog.parent().find('.ui-dialog-buttonset button:contains("Check Users")');
console.log(`Checking user: ${userInfo.username} ...`);
checkButton.prop('disabled', true).text('Checking...');
for (let i = 0; i < users.length; i++) {
const userInfo = users[i];
const progress = `[${i + 1}/${users.length}]`;
statusDiv.append(`<div>${progress} Checking ${userInfo.username}...</div>`);
scrollStatusToBottom();
console.log(`${progress} Checking user: ${userInfo.username} ...`);
// Check if this is a vanished or renamed user first
if (isVanishedOrRenamed(userInfo.username)) {
vanishedUsers.push(userInfo.original);
statusDiv.append(`<div style="color: #999;">${progress} ◯ ${userInfo.username} is a vanished/renamed user (skipped)</div>`);
scrollStatusToBottom();
console.log(`◯ ${userInfo.username} is a vanished/renamed user (skipped)`);
// Add base delay between requests
if (i < users.length - 1) {
await sleep(500); // Shorter delay since we're not making API calls
}
continue;
}
try {
// Check if user is blocked first
const isBlocked = await isUserBlocked(userInfo.username);
if (isBlocked) {
blockedUsers.push(userInfo.original);
statusDiv.append(`<div style="color: #d33;">${progress} ✗ ${userInfo.username} is blocked</div>`);
scrollStatusToBottom();
console.log(`✗ ${userInfo.username} is blocked`);
} else {
activeUsers.push(userInfo.original);// If not blocked, check activity
console.log(`✓const ${isActive = await isUserActive(userInfo.username} is active`);
if (isActive) {
activeUsers.push(userInfo.original);
statusDiv.append(`<div style="color: #00af89;">${progress} ✓ ${userInfo.username} is active (not blocked + active in last 12 months)</div>`);
scrollStatusToBottom();
console.log(`✓ ${userInfo.username} is active (not blocked + active in last 12 months)`);
} else {
inactiveUsers.push(userInfo.original);
statusDiv.append(`<div style="color: #fc3;">${progress} ⚠ ${userInfo.username} is not blocked but inactive (no edits in last 12 months)</div>`);
scrollStatusToBottom();
console.log(`⚠ ${userInfo.username} is not blocked but inactive (no edits in last 12 months)`);
}
}
} catch (error) {
console.error(`Failed to check ${userInfo.username} after all retries:`, error);
// Assume active if we can't check after all retries
activeUsers.push(userInfo.original);
statusDiv.append(`<div style="color: #fc3;">${progress} ? ${userInfo.username} - check failed, assuming active</div>`);
scrollStatusToBottom();
console.log(`? ${userInfo.username} - check failed, assuming active`);
}
// Add base delay between requests to avoid hammering the API
awaitif sleep(1000i < users.length - 1); { // 1Don't delay after secondthe betweenlast requestsuser
await sleep(1000);
}
}
// Re-enable button and show completion
checkButton.prop('disabled', false).text('Check Users');
statusDiv.append(`<div style="font-weight: bold; margin-top: 10px;">✓ Completed! ${activeUsers.length} active, ${blockedUsers.length} blocked, ${inactiveUsers.length} inactive, ${vanishedUsers.length} vanished/renamed</div>`);
scrollStatusToBottom();
// Display results
console.log("\n=== RESULTS ===");
console.log(`\nActive/Non-blocked users (not blocked + active in last 12 months) (${activeUsers.length}):`);
activeUsers.forEach(user => console.log(user));
console.log(`\nBlocked/Inactive users (${blockedUsers.length}):`);
blockedUsers.forEach(user => console.log(user));
console.log(`\nInactive users (not blocked but no edits in last 12 months) (${inactiveUsers.length}):`);
// Also display in a more user-friendly format
inactiveUsers.forEach(user => console.log(user));
displayResults(activeUsers, blockedUsers);
console.log(`\nVanished/Renamed users (${vanishedUsers.length}):`);
vanishedUsers.forEach(user => console.log(user));
// Show results in a separate dialog
displayResults(activeUsers, blockedUsers, inactiveUsers, vanishedUsers);
}
 
Line 68 ⟶ 209:
/\[\[User:([^\]]+)\]\]/i, // [[User:Username]]
/\[\[User talk:([^\]]+)\]\]/i, // [[User talk:Username]]
/^User:([^\s\]].+)$/i, // User:Username (full line)
/^User talk:([^\s\]].+)$/i // User talk:Username (full line)
];
Line 89 ⟶ 230:
return users;
}
 
function deduplicateUsers(users) {
const seen = new Set();
const uniqueUsers = [];
for (const user of users) {
// Use lowercase username for comparison to handle case variations
const normalizedUsername = user.username.toLowerCase();
if (!seen.has(normalizedUsername)) {
seen.add(normalizedUsername);
uniqueUsers.push(user);
}
}
return uniqueUsers;
}
 
Line 112 ⟶ 270:
} catch (error) {
console.warn(`Attempt ${attempt + 1} failed for ${username} (block check):`, error);
// Check if this is a maxlag error
Line 126 ⟶ 284:
if (attempt < maxRetries) {
const delay = retryDelays[attempt];
console.log(`Retryable error for ${username} (block check). Waiting ${delay / 1000}s before retry ${attempt + 2}...`);
await sleep(delay);
continue;
} else {
console.error(`Max retries exceeded for ${username} (block check). Final error:`, error);
throw new Error(`Failed after ${maxRetries + 1} attempts: ${error.message}`);
}
} else {
// Non-retryable error, fail immediately
console.error(`Non-retryable error for ${username} (block check):`, error);
throw error;
}
}
}
}
 
async function isUserActive(username) {
const maxRetries = 3;
const retryDelays = [60000, 180000, 300000]; // 1min, 3min, 5min in milliseconds
const cutoffDate = new Date();
cutoffDate.setMonth(cutoffDate.getMonth() - 12); // 12 months ago
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const api = new mw.Api();
const response = await api.get({
action: 'query',
format: 'json',
list: 'usercontribs',
ucuser: username,
uclimit: 1,
maxlag: 5 // Wait if server lag is more than 5 seconds
});
const usercontribs = response.query.usercontribs;
if (usercontribs && usercontribs.length > 0) {
const lastContrib = usercontribs[0];
const timestamp = lastContrib.timestamp;
// Parse timestamp (format: 2024-01-15T10:30:45Z)
const lastActivity = new Date(timestamp);
return lastActivity > cutoffDate;
}
return false; // No contributions found
} catch (error) {
console.warn(`Attempt ${attempt + 1} failed for ${username} (activity check):`, error);
// Check if this is a maxlag error
if (error.code === 'maxlag') {
const lagTime = error.lag || 5;
console.log(`Server lag detected (${lagTime}s). Waiting before retry...`);
await sleep((lagTime + 1) * 1000); // Wait lag time + 1 second
continue;
}
// Check for HTTP error codes that warrant retry
if (isRetryableError(error)) {
if (attempt < maxRetries) {
const delay = retryDelays[attempt];
console.log(`Retryable error for ${username} (activity check). Waiting ${delay / 1000}s before retry ${attempt + 2}...`);
await sleep(delay);
continue;
} else {
console.error(`Max retries exceeded for ${username} (activity check). Final error:`, error);
throw new Error(`Failed after ${maxRetries + 1} attempts: ${error.message}`);
}
} else {
// Non-retryable error, fail immediately
console.error(`Non-retryable error for ${username} (activity check):`, error);
throw error;
}
Line 175 ⟶ 397:
}
 
function displayResults(activeUsers, blockedUsers, inactiveUsers, vanishedUsers) {
// Create a results dialog
const resultsHtml = `
<div style="max-height: 400px500px; overflow-y: auto;">
<h3>Active/Non-blocked Users (not blocked + active in last 12 months) (${activeUsers.length})</h3>
<textarea readonly style="width: 100%; height: 150px100px; font-family: monospace; margin-bottom: 10px;">
${activeUsers.join('\n')}</textarea>
</textarea>
<h3>Blocked/Inactive Users (${blockedUsers.length})</h3>
<textarea readonly style="width: 100%; height: 150px100px; font-family: monospace; margin-bottom: 10px;">
${blockedUsers.join('\n')}</textarea>
</textarea>
<h3>⚠ Inactive Users (not blocked but no edits in last 12 months) (${inactiveUsers.length})</h3>
<textarea readonly style="width: 100%; height: 100px; font-family: monospace; margin-bottom: 10px;">
${inactiveUsers.join('\n')}</textarea>
<h3>◯ Vanished/Renamed Users (${vanishedUsers.length})</h3>
<textarea readonly style="width: 100%; height: 100px; font-family: monospace;">
${vanishedUsers.join('\n')}</textarea>
</div>
`;
// Create and show results dialog (non-modal)
const dialog = $('<div>').html(resultsHtml).dialog({
title: 'User Block & Activity Check Results',
width: 600700,
height: 500650,
modal: truefalse,
resizable: true,
buttons: {
'Copy Active Users': function() {
navigator.clipboard.writeText(activeUsers.join('\n')).then(() => {
mw.notify('Active users copied to clipboard!', { type: 'success' });
}).catch(() => {
mw.notify('Failed to copy to clipboard', { type: 'error' });
});
},
'Copy Blocked Users': function() {
navigator.clipboard.writeText(blockedUsers.join('\n')).then(() => {
mw.notify('Blocked users copied to clipboard!', { type: 'success' });
}).catch(() => {
mw.notify('Failed to copy to clipboard', { type: 'error' });
});
},
'Copy Inactive Users': function() {
navigator.clipboard.writeText(inactiveUsers.join('\n')).then(() => {
mw.notify('Inactive users copied to clipboard!', { type: 'success' });
}).catch(() => {
mw.notify('Failed to copy to clipboard', { type: 'error' });
});
},
'Copy Vanished/Renamed Users': function() {
navigator.clipboard.writeText(vanishedUsers.join('\n')).then(() => {
mw.notify('Vanished/Renamed users copied to clipboard!', { type: 'success' });
}).catch(() => {
mw.notify('Failed to copy to clipboard', { type: 'error' });
});
},
'Close': function() {
$(this).dialog('close');
Line 213 ⟶ 470:
portletId,
'#',
'Check User Blocks & Activity',
't-check-blocks',
'Check if users are blocked and active (last 12 months)'
);