175 lines
5.3 KiB
JavaScript
175 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Bulk lookup Discord user information
|
|
*
|
|
* Usage:
|
|
* node scripts/bulk-lookup-users.js <input_file> <output_file>
|
|
*
|
|
* Input file: Text file with one user ID per line
|
|
* Output file: JSON file with user lookup results
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { Client, GatewayIntentBits } = require('discord.js');
|
|
|
|
// Load environment variables from repo root
|
|
const envPath = path.join(__dirname, '../../.env');
|
|
console.log(`Loading .env from: ${envPath}`);
|
|
const result = require('dotenv').config({ path: envPath });
|
|
if (result.error) {
|
|
console.error(`Error loading .env: ${result.error.message}`);
|
|
// Try broccolini-bot/.env as fallback
|
|
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
|
}
|
|
|
|
const TOKEN = process.env.DISCORD_BOT_TOKEN || process.env.DISCORD_TOKEN;
|
|
const GUILD_ID = process.env.GUILD_ID || process.env.SERVER_ID;
|
|
|
|
if (!TOKEN) {
|
|
console.error('Error: DISCORD_BOT_TOKEN must be set in .env');
|
|
console.error('Available env vars:', Object.keys(process.env).filter(k => k.includes('DISCORD')));
|
|
process.exit(1);
|
|
}
|
|
|
|
// Parse command line args
|
|
const args = process.argv.slice(2);
|
|
if (args.length < 2) {
|
|
console.error('Usage: node scripts/bulk-lookup-users.js <input_file> <output_file>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const inputFile = args[0];
|
|
const outputFile = args[1];
|
|
|
|
// Read user IDs from input file
|
|
const userIds = fs.readFileSync(inputFile, 'utf-8')
|
|
.split('\n')
|
|
.map(line => line.trim())
|
|
.filter(line => line.length > 0);
|
|
|
|
console.log(`Loaded ${userIds.length} user IDs from ${inputFile}`);
|
|
|
|
const client = new Client({
|
|
intents: [
|
|
GatewayIntentBits.Guilds,
|
|
GatewayIntentBits.GuildMembers
|
|
]
|
|
});
|
|
|
|
const results = {};
|
|
let processed = 0;
|
|
let errors = 0;
|
|
|
|
async function lookupUser(userId) {
|
|
try {
|
|
// Add timeout to prevent hanging
|
|
const timeoutPromise = new Promise((_, reject) =>
|
|
setTimeout(() => reject(new Error('Lookup timeout')), 10000)
|
|
);
|
|
|
|
const fetchPromise = client.users.fetch(userId);
|
|
const user = await Promise.race([fetchPromise, timeoutPromise]);
|
|
|
|
return {
|
|
success: true,
|
|
id: user.id,
|
|
username: user.username,
|
|
globalName: user.globalName || user.username,
|
|
tag: user.tag,
|
|
bot: user.bot,
|
|
avatar: user.displayAvatarURL()
|
|
};
|
|
} catch (error) {
|
|
// Handle errors (not found, timeout, rate limit)
|
|
if (error.message.includes('429')) {
|
|
console.log(` ⚠️ Rate limit hit for user ${userId}, will retry`);
|
|
}
|
|
return {
|
|
success: false,
|
|
id: userId,
|
|
error: error.message,
|
|
username: null,
|
|
globalName: null,
|
|
tag: null,
|
|
bot: false
|
|
};
|
|
}
|
|
}
|
|
|
|
async function processUsers() {
|
|
console.log('\nStarting bulk lookup...');
|
|
console.log('This will take a few minutes for 2,428 users\n');
|
|
|
|
const startTime = Date.now();
|
|
|
|
// Process in batches to avoid rate limits
|
|
const BATCH_SIZE = 3; // Very small batches to avoid rate limits
|
|
const DELAY_MS = 2000; // 2 seconds between batches
|
|
|
|
for (let i = 0; i < userIds.length; i += BATCH_SIZE) {
|
|
const batch = userIds.slice(i, i + BATCH_SIZE);
|
|
|
|
// Lookup batch in parallel
|
|
const promises = batch.map(userId => lookupUser(userId));
|
|
const batchResults = await Promise.all(promises);
|
|
|
|
// Store results
|
|
batchResults.forEach(result => {
|
|
if (!result.success) {
|
|
errors++;
|
|
}
|
|
results[result.id] = result;
|
|
processed++;
|
|
});
|
|
|
|
// Log every batch for debugging
|
|
if (processed <= 50) {
|
|
console.log(` Batch complete: ${processed} users processed`);
|
|
}
|
|
|
|
// Progress update every 100 users
|
|
if (processed % 100 === 0 || processed === userIds.length) {
|
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
const rate = (processed / elapsed).toFixed(1);
|
|
const remaining = ((userIds.length - processed) / rate).toFixed(0);
|
|
console.log(`Progress: ${processed}/${userIds.length} (${errors} errors) - ${elapsed}s elapsed, ~${remaining}s remaining`);
|
|
}
|
|
|
|
// Wait before next batch to avoid rate limits
|
|
if (i + BATCH_SIZE < userIds.length) {
|
|
await new Promise(resolve => setTimeout(resolve, DELAY_MS));
|
|
}
|
|
}
|
|
|
|
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
console.log(`\n✅ Completed in ${totalTime}s`);
|
|
console.log(` Successful: ${processed - errors}`);
|
|
console.log(` Errors: ${errors}`);
|
|
|
|
// Save results
|
|
const output = {
|
|
timestamp: new Date().toISOString(),
|
|
total_users: userIds.length,
|
|
successful: processed - errors,
|
|
errors: errors,
|
|
users: results
|
|
};
|
|
|
|
fs.writeFileSync(outputFile, JSON.stringify(output, null, 2));
|
|
console.log(`\n💾 Saved results to ${outputFile}`);
|
|
|
|
process.exit(0);
|
|
}
|
|
|
|
client.once('ready', () => {
|
|
console.log(`✅ Bot logged in as ${client.user.tag}`);
|
|
processUsers();
|
|
});
|
|
|
|
client.on('error', (error) => {
|
|
console.error('Discord client error:', error);
|
|
});
|
|
|
|
client.login(TOKEN);
|