#!/usr/bin/env node /** * Bulk lookup Discord user information * * Usage: * node scripts/bulk-lookup-users.js * * 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 '); 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);