strip: remove data-forensics scripts from parent IB-Discord-Bot project
Kept: backup-env.js, test-mongodb.js (wired to npm run test-mongodb).
This commit is contained in:
@@ -1,193 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Bulk lookup Discord user information - IMPROVED VERSION
|
|
||||||
*
|
|
||||||
* Features:
|
|
||||||
* - Saves progress incrementally (every 100 users)
|
|
||||||
* - Can resume from where it left off
|
|
||||||
* - Better error handling
|
|
||||||
* - Uses guild member cache when possible
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node scripts/bulk-lookup-users-v2.js <input_file> <output_file>
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
// Load environment variables
|
|
||||||
const envPath = path.join(__dirname, '../../.env');
|
|
||||||
const result = require('dotenv').config({ path: envPath });
|
|
||||||
|
|
||||||
const TOKEN = process.env.DISCORD_BOT_TOKEN || process.env.DISCORD_TOKEN;
|
|
||||||
|
|
||||||
if (!TOKEN) {
|
|
||||||
console.error('Error: DISCORD_BOT_TOKEN must be set in .env');
|
|
||||||
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-v2.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}`);
|
|
||||||
|
|
||||||
// Load existing results if any (for resume capability)
|
|
||||||
let results = {};
|
|
||||||
let processed = 0;
|
|
||||||
let errors = 0;
|
|
||||||
|
|
||||||
if (fs.existsSync(outputFile)) {
|
|
||||||
try {
|
|
||||||
const existing = JSON.parse(fs.readFileSync(outputFile, 'utf-8'));
|
|
||||||
results = existing.users || {};
|
|
||||||
processed = Object.keys(results).length;
|
|
||||||
errors = existing.errors || 0;
|
|
||||||
console.log(`📂 Found existing results: ${processed} users already processed`);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`⚠️ Could not load existing results, starting fresh`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [
|
|
||||||
GatewayIntentBits.Guilds,
|
|
||||||
GatewayIntentBits.GuildMembers
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
async function lookupUser(userId) {
|
|
||||||
// Skip if already processed
|
|
||||||
if (results[userId]) {
|
|
||||||
return results[userId];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await client.users.fetch(userId);
|
|
||||||
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) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
id: userId,
|
|
||||||
error: error.message,
|
|
||||||
username: null,
|
|
||||||
globalName: null,
|
|
||||||
tag: null,
|
|
||||||
bot: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveResults() {
|
|
||||||
const output = {
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
total_users: userIds.length,
|
|
||||||
processed: processed,
|
|
||||||
successful: processed - errors,
|
|
||||||
errors: errors,
|
|
||||||
users: results
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(outputFile, JSON.stringify(output, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processUsers() {
|
|
||||||
console.log('\n🚀 Starting bulk lookup...');
|
|
||||||
console.log(` Progress will be saved every 100 users\n`);
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
const startProcessed = processed;
|
|
||||||
|
|
||||||
// Filter out already processed users
|
|
||||||
const toProcess = userIds.filter(id => !results[id]);
|
|
||||||
console.log(` ${toProcess.length} users remaining to process\n`);
|
|
||||||
|
|
||||||
// Process one at a time (safer and can still be reasonably fast)
|
|
||||||
for (let i = 0; i < toProcess.length; i++) {
|
|
||||||
const userId = toProcess[i];
|
|
||||||
|
|
||||||
const result = await lookupUser(userId);
|
|
||||||
results[result.id] = result;
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
errors++;
|
|
||||||
}
|
|
||||||
processed++;
|
|
||||||
|
|
||||||
// Save every 100 users
|
|
||||||
if (processed % 100 === 0) {
|
|
||||||
saveResults();
|
|
||||||
const elapsed = ((Date.now() - startTime) / 1000);
|
|
||||||
const rate = (processed - startProcessed) / elapsed;
|
|
||||||
const remaining = (toProcess.length - i - 1) / rate;
|
|
||||||
console.log(`💾 Progress: ${processed}/${userIds.length} (${errors} errors) - saved checkpoint - ~${remaining.toFixed(0)}s remaining`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slower delay to avoid rate limits (500ms = 2 requests/second - more reliable)
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final save
|
|
||||||
saveResults();
|
|
||||||
|
|
||||||
const totalTime = ((Date.now() - startTime) / 1000);
|
|
||||||
|
|
||||||
console.log(`\n${'='.repeat(70)}`);
|
|
||||||
console.log(`✅ Lookup Complete!`);
|
|
||||||
console.log(`${'='.repeat(70)}`);
|
|
||||||
console.log(` Total time: ${totalTime.toFixed(1)}s`);
|
|
||||||
console.log(` Total processed: ${processed}/${userIds.length}`);
|
|
||||||
console.log(` Successful: ${processed - errors} (${((processed - errors)/userIds.length*100).toFixed(1)}%)`);
|
|
||||||
console.log(` Errors: ${errors}`);
|
|
||||||
console.log(` Rate: ${((processed - startProcessed)/totalTime).toFixed(1)} users/second`);
|
|
||||||
console.log(`\n💾 Saved to: ${outputFile}\n`);
|
|
||||||
|
|
||||||
// Sample successful results
|
|
||||||
const sample = Object.values(results).filter(r => r.success).slice(0, 5);
|
|
||||||
if (sample.length > 0) {
|
|
||||||
console.log('📋 Sample results:');
|
|
||||||
sample.forEach(u => console.log(` ${u.username} (${u.id})`));
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.once('ready', () => {
|
|
||||||
console.log(`✅ Bot logged in as ${client.user.tag}\n`);
|
|
||||||
processUsers();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('error', (error) => {
|
|
||||||
console.error('❌ Discord client error:', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle graceful shutdown
|
|
||||||
process.on('SIGINT', () => {
|
|
||||||
console.log('\n\n⚠️ Interrupted! Saving progress...');
|
|
||||||
saveResults();
|
|
||||||
console.log('✅ Progress saved. You can resume by running the same command again.\n');
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(TOKEN);
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
#!/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);
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Export transcript channel messages with embed "Users in transcript" to JSONL.
|
|
||||||
* Each line: { message_id, created, ticket_name, ticket_owner_id, users: [{ id, count }], total }
|
|
||||||
* Usage: node scripts/export-transcript-embeds.js <channelId> [maxMessages] [outputPath]
|
|
||||||
* If outputPath is omitted, writes to stdout (redirect: node ... > transcript_embeds.jsonl).
|
|
||||||
* If outputPath is given, writes JSONL to that file (avoids dotenv/logs mixing with JSON).
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const channelId = process.argv[2];
|
|
||||||
const maxMessages = parseInt(process.argv[3], 10) || 10000;
|
|
||||||
const outputPath = process.argv[4];
|
|
||||||
const PAGE = 100;
|
|
||||||
|
|
||||||
// Parse "Users in transcript" value: "5 - <@123> - name#0\n 4 - <@456> - ..."
|
|
||||||
function parseUsersInTranscript(value) {
|
|
||||||
const users = [];
|
|
||||||
let total = 0;
|
|
||||||
const lines = (value || '').split(/\n/).map((s) => s.trim()).filter(Boolean);
|
|
||||||
for (const line of lines) {
|
|
||||||
const match = line.match(/^(\d+)\s+-\s+<@!?(\d+)>/);
|
|
||||||
if (match) {
|
|
||||||
const count = parseInt(match[1], 10);
|
|
||||||
users.push({ id: match[2], count });
|
|
||||||
total += count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { users, total };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TOKEN || !channelId) {
|
|
||||||
console.error('Usage: node scripts/export-transcript-embeds.js <channelId> [maxMessages]');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
|
|
||||||
});
|
|
||||||
|
|
||||||
client.once('ready', async () => {
|
|
||||||
try {
|
|
||||||
const channel = await client.channels.fetch(channelId).catch(() => null);
|
|
||||||
if (!channel) {
|
|
||||||
console.error('Channel not found or bot cannot access it.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
if (outputPath) {
|
|
||||||
fs.writeFileSync(outputPath, '');
|
|
||||||
}
|
|
||||||
let totalScanned = 0;
|
|
||||||
let before = undefined;
|
|
||||||
while (totalScanned < maxMessages) {
|
|
||||||
const limit = Math.min(PAGE, maxMessages - totalScanned);
|
|
||||||
const options = before ? { limit, before } : { limit };
|
|
||||||
const messages = await channel.messages.fetch(options);
|
|
||||||
if (messages.size === 0) break;
|
|
||||||
totalScanned += messages.size;
|
|
||||||
for (const [, m] of messages.sort((a, b) => b.createdTimestamp - a.createdTimestamp)) {
|
|
||||||
if (!m.embeds?.length) continue;
|
|
||||||
for (const emb of m.embeds) {
|
|
||||||
const usersField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('users in transcript'));
|
|
||||||
if (!usersField?.value) continue;
|
|
||||||
const ticketNameField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('ticket name'));
|
|
||||||
const ticketName = ticketNameField?.value?.trim() || '';
|
|
||||||
const ownerField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('ticket owner'));
|
|
||||||
const ownerMatch = ownerField?.value?.match(/<@!?(\d+)>/);
|
|
||||||
const ticket_owner_id = ownerMatch ? ownerMatch[1] : null;
|
|
||||||
const { users, total } = parseUsersInTranscript(usersField.value);
|
|
||||||
if (users.length === 0 && !ticket_owner_id) continue;
|
|
||||||
const out = {
|
|
||||||
message_id: m.id,
|
|
||||||
created: m.createdAt.toISOString(),
|
|
||||||
ticket_name: ticketName,
|
|
||||||
ticket_owner_id: ticket_owner_id || undefined,
|
|
||||||
users,
|
|
||||||
total,
|
|
||||||
};
|
|
||||||
const line = JSON.stringify(out) + '\n';
|
|
||||||
if (outputPath) {
|
|
||||||
fs.appendFileSync(outputPath, line);
|
|
||||||
} else {
|
|
||||||
process.stdout.write(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const oldestMsg = messages.reduce((a, m) => (m.createdTimestamp < (a?.createdTimestamp ?? Infinity) ? m : a), null);
|
|
||||||
before = oldestMsg?.id;
|
|
||||||
if (messages.size < PAGE) break;
|
|
||||||
}
|
|
||||||
process.stderr.write('Scanned ' + totalScanned + ' messages\n');
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e.message || e);
|
|
||||||
} finally {
|
|
||||||
client.destroy();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(TOKEN).catch((e) => {
|
|
||||||
console.error('Login failed:', e.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Fetch recent messages from a Discord channel.
|
|
||||||
* Usage: node scripts/fetch-channel-messages.js <channelId> [limit]
|
|
||||||
* Default limit: 10
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const channelId = process.argv[2];
|
|
||||||
const limit = Math.min(parseInt(process.argv[3], 10) || 10, 100);
|
|
||||||
|
|
||||||
if (!TOKEN || !channelId) {
|
|
||||||
console.error('Usage: node scripts/fetch-channel-messages.js <channelId> [limit]');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
|
|
||||||
});
|
|
||||||
|
|
||||||
client.once('ready', async () => {
|
|
||||||
try {
|
|
||||||
const channel = await client.channels.fetch(channelId).catch(() => null);
|
|
||||||
if (!channel) {
|
|
||||||
console.log('Channel not found or bot cannot access it.');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
const messages = await channel.messages.fetch({ limit });
|
|
||||||
console.log('Channel:', channel.name, '(' + channel.id + ')');
|
|
||||||
console.log('Messages fetched:', messages.size, '(requested', limit + ')');
|
|
||||||
if (messages.size === 0) {
|
|
||||||
console.log('No messages visible (empty channel or no Read Message History permission).');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
for (const [, m] of messages.sort((a, b) => b.createdTimestamp - a.createdTimestamp)) {
|
|
||||||
const preview = (m.content || '(embed/attachment only)').slice(0, 80);
|
|
||||||
console.log('---');
|
|
||||||
console.log('ID:', m.id, '| Author:', m.author.tag, '|', m.createdAt.toISOString());
|
|
||||||
console.log(preview + (m.content && m.content.length > 80 ? '...' : ''));
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e.message || e);
|
|
||||||
} finally {
|
|
||||||
client.destroy();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(TOKEN).catch((e) => {
|
|
||||||
console.error('Login failed:', e.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Fetch a Discord channel by ID and print its name and type.
|
|
||||||
* Usage: node scripts/fetch-channel.js <channelId>
|
|
||||||
* Example: node scripts/fetch-channel.js 1335424071227281520
|
|
||||||
*
|
|
||||||
* Uses DISCORD_BOT_TOKEN or MEMBER_BOT_TOKEN from .env (broccolini-bot or parent).
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const channelId = process.argv[2];
|
|
||||||
|
|
||||||
if (!TOKEN) {
|
|
||||||
console.error('❌ No bot token (DISCORD_BOT_TOKEN or MEMBER_BOT_TOKEN)');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
if (!channelId) {
|
|
||||||
console.error('Usage: node scripts/fetch-channel.js <channelId>');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
|
||||||
|
|
||||||
client.once('ready', async () => {
|
|
||||||
try {
|
|
||||||
const channel = await client.channels.fetch(channelId).catch((err) => null);
|
|
||||||
if (!channel) {
|
|
||||||
console.log('Channel not found or bot cannot access it.');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
console.log('Channel ID:', channel.id);
|
|
||||||
console.log('Name:', channel.name);
|
|
||||||
console.log('Type:', channel.type);
|
|
||||||
if (channel.guild) console.log('Guild:', channel.guild.name, `(${channel.guild.id})`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e.message || e);
|
|
||||||
} finally {
|
|
||||||
client.destroy();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(TOKEN).catch((e) => {
|
|
||||||
console.error('Login failed:', e.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Fetch a Discord message by channel ID and message ID.
|
|
||||||
* Usage: node scripts/fetch-message.js <channelId> <messageId>
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const [channelId, messageId] = process.argv.slice(2);
|
|
||||||
|
|
||||||
if (!TOKEN || !channelId || !messageId) {
|
|
||||||
console.error('Usage: node scripts/fetch-message.js <channelId> <messageId>');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
|
|
||||||
});
|
|
||||||
|
|
||||||
client.once('ready', async () => {
|
|
||||||
try {
|
|
||||||
const channel = await client.channels.fetch(channelId).catch(() => null);
|
|
||||||
if (!channel) {
|
|
||||||
console.log('Channel not found or bot cannot access it.');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
const message = await channel.messages.fetch(messageId).catch((err) => null);
|
|
||||||
if (!message) {
|
|
||||||
console.log('Message not found (wrong channel, deleted, or no access).');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
console.log('Channel:', channel.name, '(' + channel.id + ')');
|
|
||||||
console.log('Message ID:', message.id);
|
|
||||||
console.log('Author:', message.author.tag, '(' + message.author.id + ')');
|
|
||||||
console.log('Created:', message.createdAt ? message.createdAt.toISOString() : message.createdTimestamp);
|
|
||||||
console.log('Content:', message.content || '(empty or embed only)');
|
|
||||||
if (message.embeds && message.embeds.length) {
|
|
||||||
message.embeds.forEach((emb, i) => {
|
|
||||||
console.log('\n--- Embed', i + 1, '---');
|
|
||||||
if (emb.title) console.log('Title:', emb.title);
|
|
||||||
if (emb.description) console.log('Description:', emb.description);
|
|
||||||
if (emb.url) console.log('URL:', emb.url);
|
|
||||||
if (emb.fields && emb.fields.length) {
|
|
||||||
emb.fields.forEach((f) => console.log('Field:', f.name, '\n', f.value));
|
|
||||||
}
|
|
||||||
if (emb.footer?.text) console.log('Footer:', emb.footer.text);
|
|
||||||
// Ticket name for display (e.g. "indifferentketchup🍅" from "indifferentketchup🍅-claimed-7235")
|
|
||||||
const ticketNameField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('ticket name'));
|
|
||||||
if (ticketNameField?.value) {
|
|
||||||
const full = ticketNameField.value.trim();
|
|
||||||
const short = full.replace(/-claimed-\d+$/, '').trim();
|
|
||||||
console.log('Ticket (short):', short || full);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e.message || e);
|
|
||||||
} finally {
|
|
||||||
client.destroy();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(TOKEN).catch((e) => {
|
|
||||||
console.error('Login failed:', e.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Find transcript channel messages whose embed "Users in transcript" lists a given member ID.
|
|
||||||
* Usage: node scripts/find-transcript-by-member.js <channelId> <memberId> [maxMessages]
|
|
||||||
* Example: node scripts/find-transcript-by-member.js 1335424071227281520 219276746153787392 500
|
|
||||||
* Fetches in pages of 100; maxMessages limits total (e.g. 500 = 5 pages). Default 100.
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const channelId = process.argv[2];
|
|
||||||
const memberId = process.argv[3];
|
|
||||||
const maxMessages = parseInt(process.argv[4], 10) || 100;
|
|
||||||
const PAGE = 100;
|
|
||||||
|
|
||||||
if (!TOKEN || !channelId || !memberId) {
|
|
||||||
console.error('Usage: node scripts/find-transcript-by-member.js <channelId> <memberId> [maxMessages]');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
|
|
||||||
});
|
|
||||||
|
|
||||||
client.once('ready', async () => {
|
|
||||||
try {
|
|
||||||
const channel = await client.channels.fetch(channelId).catch(() => null);
|
|
||||||
if (!channel) {
|
|
||||||
console.log('Channel not found or bot cannot access it.');
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
console.log('Channel:', channel.name, '(' + channel.id + ')');
|
|
||||||
console.log('Looking for member ID', memberId, 'in embed "Users in transcript"');
|
|
||||||
const memberRef = `<@${memberId}>`;
|
|
||||||
let totalScanned = 0;
|
|
||||||
let found = 0;
|
|
||||||
let before = undefined;
|
|
||||||
while (totalScanned < maxMessages) {
|
|
||||||
const limit = Math.min(PAGE, maxMessages - totalScanned);
|
|
||||||
const options = before ? { limit, before } : { limit };
|
|
||||||
const messages = await channel.messages.fetch(options);
|
|
||||||
if (messages.size === 0) break;
|
|
||||||
totalScanned += messages.size;
|
|
||||||
for (const [, m] of messages.sort((a, b) => b.createdTimestamp - a.createdTimestamp)) {
|
|
||||||
if (!m.embeds?.length) continue;
|
|
||||||
for (const emb of m.embeds) {
|
|
||||||
const usersField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('users in transcript'));
|
|
||||||
if (!usersField?.value || !usersField.value.includes(memberRef)) continue;
|
|
||||||
const ticketNameField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('ticket name'));
|
|
||||||
const ticketName = ticketNameField?.value?.trim() || '(no Ticket Name field)';
|
|
||||||
console.log('\n--- Match ---');
|
|
||||||
console.log('Message ID:', m.id);
|
|
||||||
console.log('Created:', m.createdAt.toISOString());
|
|
||||||
console.log('Ticket Name:', ticketName);
|
|
||||||
console.log('Users in transcript:\n' + usersField.value);
|
|
||||||
found++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const oldestMsg = messages.reduce((a, m) => (m.createdTimestamp < (a?.createdTimestamp ?? Infinity) ? m : a), null);
|
|
||||||
before = oldestMsg?.id;
|
|
||||||
if (messages.size < PAGE) break;
|
|
||||||
}
|
|
||||||
console.log('\nTotal messages scanned:', totalScanned);
|
|
||||||
console.log('Total messages matching member', memberId, ':', found);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e.message || e);
|
|
||||||
} finally {
|
|
||||||
client.destroy();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(TOKEN).catch((e) => {
|
|
||||||
console.error('Login failed:', e.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Find transcript messages whose embed "Ticket Owner" is a given user ID.
|
|
||||||
* Usage: node scripts/find-transcript-by-owner.js <channelId> <ownerId> [totalMessages] [maxMessages]
|
|
||||||
* If totalMessages is given, only show messages where "Users in transcript" sum equals that.
|
|
||||||
* Example: node scripts/find-transcript-by-owner.js 1335424071227281520 241129484483297280 5 10000
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const channelId = process.argv[2];
|
|
||||||
const ownerId = process.argv[3];
|
|
||||||
const totalMessages = parseInt(process.argv[4], 10) || null;
|
|
||||||
const maxMessages = parseInt(process.argv[5], 10) || 10000;
|
|
||||||
const PAGE = 100;
|
|
||||||
|
|
||||||
function parseUsersTotal(value) {
|
|
||||||
let total = 0;
|
|
||||||
(value || '').split(/\n/).forEach((line) => {
|
|
||||||
const m = line.trim().match(/^(\d+)\s+-\s+<@!?\d+>/);
|
|
||||||
if (m) total += parseInt(m[1], 10);
|
|
||||||
});
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TOKEN || !channelId || !ownerId) {
|
|
||||||
console.error('Usage: node scripts/find-transcript-by-owner.js <channelId> <ownerId> [totalMessages] [maxMessages]');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ownerRef = `<@${ownerId}>`;
|
|
||||||
const client = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
|
|
||||||
});
|
|
||||||
|
|
||||||
client.once('ready', async () => {
|
|
||||||
try {
|
|
||||||
const channel = await client.channels.fetch(channelId).catch(() => null);
|
|
||||||
if (!channel) {
|
|
||||||
console.error('Channel not found or bot cannot access it.');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
console.error('Channel:', channel.name, '(' + channel.id + ')');
|
|
||||||
console.error('Looking for Ticket Owner', ownerId, totalMessages != null ? 'and total=' + totalMessages : '');
|
|
||||||
let totalScanned = 0;
|
|
||||||
let before = undefined;
|
|
||||||
let found = 0;
|
|
||||||
while (totalScanned < maxMessages) {
|
|
||||||
const limit = Math.min(PAGE, maxMessages - totalScanned);
|
|
||||||
const options = before ? { limit, before } : { limit };
|
|
||||||
const messages = await channel.messages.fetch(options);
|
|
||||||
if (messages.size === 0) break;
|
|
||||||
totalScanned += messages.size;
|
|
||||||
for (const [, m] of messages.sort((a, b) => b.createdTimestamp - a.createdTimestamp)) {
|
|
||||||
if (!m.embeds?.length) continue;
|
|
||||||
for (const emb of m.embeds) {
|
|
||||||
const ownerField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('ticket owner'));
|
|
||||||
if (!ownerField?.value || !ownerField.value.includes(ownerRef)) continue;
|
|
||||||
const usersField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('users in transcript'));
|
|
||||||
const total = usersField?.value ? parseUsersTotal(usersField.value) : 0;
|
|
||||||
if (totalMessages != null && total !== totalMessages) continue;
|
|
||||||
const ticketNameField = emb.fields?.find((f) => f.name && f.name.toLowerCase().includes('ticket name'));
|
|
||||||
const ticketName = ticketNameField?.value?.trim() || '';
|
|
||||||
console.log('Message ID:', m.id);
|
|
||||||
console.log('Created:', m.createdAt.toISOString());
|
|
||||||
console.log('Ticket Name:', ticketName);
|
|
||||||
console.log('Total messages:', total);
|
|
||||||
console.log('---');
|
|
||||||
found++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const oldestMsg = messages.reduce((a, msg) => (msg.createdTimestamp < (a?.createdTimestamp ?? Infinity) ? msg : a), null);
|
|
||||||
before = oldestMsg?.id;
|
|
||||||
if (messages.size < PAGE) break;
|
|
||||||
}
|
|
||||||
console.error('Scanned', totalScanned, 'messages, matches:', found);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e.message || e);
|
|
||||||
} finally {
|
|
||||||
client.destroy();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(TOKEN).catch((e) => {
|
|
||||||
console.error('Login failed:', e.message);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* Look up a Discord user by ID. Uses repo root .env for token so it works without broccolini-bot config.
|
|
||||||
* Usage: node scripts/lookup-user.js [user_id]
|
|
||||||
* Run from broccolini-bot/ (or use full path to script).
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const token = (process.env.DISCORD_BOT_TOKEN || process.env.DISCORD_TOKEN || '').trim();
|
|
||||||
if (!token) {
|
|
||||||
console.error('Set DISCORD_BOT_TOKEN or DISCORD_TOKEN in repo root .env (/IB-Discord-Bot/.env)');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
const userId = process.argv[2] || '140081819986034688';
|
|
||||||
|
|
||||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
|
||||||
|
|
||||||
client.once('ready', async () => {
|
|
||||||
try {
|
|
||||||
const user = await client.users.fetch(userId);
|
|
||||||
console.log('User:', {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
globalName: user.globalName ?? user.username,
|
|
||||||
tag: user.tag,
|
|
||||||
bot: user.bot
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Lookup failed:', err.message);
|
|
||||||
if (err.code === 10013) console.error('Unknown user, or bot does not share a server with this user.');
|
|
||||||
} finally {
|
|
||||||
client.destroy();
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
client.login(token);
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* User lookup using a dedicated minimal-permissions bot
|
|
||||||
*
|
|
||||||
* This bot:
|
|
||||||
* - Has NO server permissions
|
|
||||||
* - Only needs to be in the server
|
|
||||||
* - Uses separate token from main bot
|
|
||||||
* - Won't affect your main bot's rate limits
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* LOOKUP_BOT_TOKEN=your_token node scripts/lookup-with-dedicated-bot.js <input_file> <output_file>
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
// Load environment
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
// Use dedicated bot token OR fall back to main bot
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.LOOKUP_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
|
|
||||||
if (!TOKEN) {
|
|
||||||
console.error('❌ Error: No bot token found');
|
|
||||||
console.error(' Set MEMBER_BOT_TOKEN in .env or use DISCORD_BOT_TOKEN');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
if (args.length < 2) {
|
|
||||||
console.error('Usage: node scripts/lookup-with-dedicated-bot.js <input_file> <output_file>');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputFile = args[0];
|
|
||||||
const outputFile = args[1];
|
|
||||||
|
|
||||||
// Read user IDs
|
|
||||||
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`);
|
|
||||||
|
|
||||||
// Load existing results
|
|
||||||
let results = {};
|
|
||||||
let processed = 0;
|
|
||||||
let errors = 0;
|
|
||||||
|
|
||||||
if (fs.existsSync(outputFile)) {
|
|
||||||
try {
|
|
||||||
const existing = JSON.parse(fs.readFileSync(outputFile, 'utf-8'));
|
|
||||||
results = existing.users || {};
|
|
||||||
processed = Object.keys(results).length;
|
|
||||||
errors = existing.errors || 0;
|
|
||||||
console.log(`📂 Found existing: ${processed} users`);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`⚠️ Starting fresh`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create bot with MINIMAL intents
|
|
||||||
const client = new Client({
|
|
||||||
intents: [
|
|
||||||
GatewayIntentBits.Guilds // Only need this to stay in server
|
|
||||||
// NO other intents needed!
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
async function lookupUser(userId) {
|
|
||||||
if (results[userId]) return results[userId];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const user = await client.users.fetch(userId);
|
|
||||||
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) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
id: userId,
|
|
||||||
error: error.message,
|
|
||||||
username: null,
|
|
||||||
globalName: null,
|
|
||||||
tag: null,
|
|
||||||
bot: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveResults() {
|
|
||||||
const output = {
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
total_users: userIds.length,
|
|
||||||
processed: processed,
|
|
||||||
successful: processed - errors,
|
|
||||||
errors: errors,
|
|
||||||
bot_type: (process.env.MEMBER_BOT_TOKEN || process.env.LOOKUP_BOT_TOKEN) ? 'dedicated' : 'main',
|
|
||||||
users: results
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(outputFile, JSON.stringify(output, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processUsers() {
|
|
||||||
console.log('\n🚀 Starting lookups...');
|
|
||||||
const isDedicated = !!(process.env.MEMBER_BOT_TOKEN || process.env.LOOKUP_BOT_TOKEN);
|
|
||||||
console.log(` Bot type: ${isDedicated ? '✅ Dedicated lookup bot' : '⚠️ Main bot'}`);
|
|
||||||
console.log(` Rate: SLOW (1 user/second for safety)`);
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
const toProcess = userIds.filter(id => !results[id]);
|
|
||||||
console.log(` ${toProcess.length} users remaining\n`);
|
|
||||||
|
|
||||||
for (let i = 0; i < toProcess.length; i++) {
|
|
||||||
const userId = toProcess[i];
|
|
||||||
|
|
||||||
const result = await lookupUser(userId);
|
|
||||||
results[result.id] = result;
|
|
||||||
|
|
||||||
if (!result.success) errors++;
|
|
||||||
processed++;
|
|
||||||
|
|
||||||
// Save every 10 users for frequent updates
|
|
||||||
if (processed % 10 === 0) {
|
|
||||||
saveResults();
|
|
||||||
const elapsed = (Date.now() - startTime) / 1000;
|
|
||||||
const rate = (processed - (userIds.length - toProcess.length)) / elapsed;
|
|
||||||
const remaining = (toProcess.length - i - 1) / rate;
|
|
||||||
console.log(`💾 ${processed}/${userIds.length} (${errors} errors) - saved - ~${remaining.toFixed(0)}s left`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Very slow to avoid rate limits (1/second)
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
saveResults();
|
|
||||||
|
|
||||||
const totalTime = (Date.now() - startTime) / 1000;
|
|
||||||
console.log(`\n${'='.repeat(60)}`);
|
|
||||||
console.log(`✅ Complete!`);
|
|
||||||
console.log(`${'='.repeat(60)}`);
|
|
||||||
console.log(` Time: ${totalTime.toFixed(1)}s`);
|
|
||||||
console.log(` Processed: ${processed}/${userIds.length}`);
|
|
||||||
console.log(` Successful: ${processed - errors}`);
|
|
||||||
console.log(` Errors: ${errors}`);
|
|
||||||
console.log(`\n💾 Saved to: ${outputFile}\n`);
|
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.once('ready', () => {
|
|
||||||
const isDedicated = !!(process.env.MEMBER_BOT_TOKEN || process.env.LOOKUP_BOT_TOKEN);
|
|
||||||
const botType = isDedicated ? 'DEDICATED LOOKUP BOT' : 'Main Bot';
|
|
||||||
console.log(`✅ Logged in as ${client.user.tag}`);
|
|
||||||
console.log(` Type: ${botType}`);
|
|
||||||
console.log();
|
|
||||||
processUsers();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('error', (error) => {
|
|
||||||
console.error('❌ Error:', error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
|
||||||
console.log('\n\n⚠️ Interrupted! Saving...');
|
|
||||||
saveResults();
|
|
||||||
console.log('✅ Saved. Resume by running same command.\n');
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('🔌 Connecting to Discord...');
|
|
||||||
client.login(TOKEN);
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Discord user lookup WITH ROLES
|
|
||||||
*
|
|
||||||
* Fetches:
|
|
||||||
* - User info (username, display name, avatar)
|
|
||||||
* - Guild member info (roles, join date, server nickname)
|
|
||||||
* - All Palpocalypse server roles
|
|
||||||
*
|
|
||||||
* Requires: Server Members Intent enabled in Discord Developer Portal
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const GUILD_ID = '798321161082896395'; // Indifferent Broccoli server
|
|
||||||
|
|
||||||
if (!TOKEN) {
|
|
||||||
console.error('❌ Error: No bot token found');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = process.argv.slice(2);
|
|
||||||
if (args.length < 2) {
|
|
||||||
console.error('Usage: node scripts/lookup-with-roles.js <input_file> <output_file>');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputFile = args[0];
|
|
||||||
const outputFile = args[1];
|
|
||||||
|
|
||||||
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`);
|
|
||||||
|
|
||||||
let results = {};
|
|
||||||
let processed = 0;
|
|
||||||
let errors = 0;
|
|
||||||
|
|
||||||
if (fs.existsSync(outputFile)) {
|
|
||||||
try {
|
|
||||||
const existing = JSON.parse(fs.readFileSync(outputFile, 'utf-8'));
|
|
||||||
results = existing.users || {};
|
|
||||||
processed = Object.keys(results).length;
|
|
||||||
errors = existing.errors || 0;
|
|
||||||
console.log(`📂 Found existing: ${processed} users`);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`⚠️ Starting fresh`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [
|
|
||||||
GatewayIntentBits.Guilds,
|
|
||||||
GatewayIntentBits.GuildMembers // Required for roles!
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
let guild = null;
|
|
||||||
|
|
||||||
async function lookupUserWithRoles(userId) {
|
|
||||||
if (results[userId]) return results[userId];
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Fetch basic user info
|
|
||||||
const user = await client.users.fetch(userId);
|
|
||||||
|
|
||||||
// Try to fetch guild member (for roles)
|
|
||||||
let roles = [];
|
|
||||||
let serverNickname = null;
|
|
||||||
let joinedAt = null;
|
|
||||||
let isInServer = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const member = await guild.members.fetch(userId);
|
|
||||||
isInServer = true;
|
|
||||||
serverNickname = member.nickname;
|
|
||||||
joinedAt = member.joinedAt ? member.joinedAt.toISOString() : null;
|
|
||||||
|
|
||||||
// Get all roles except @everyone
|
|
||||||
roles = member.roles.cache
|
|
||||||
.filter(role => role.name !== '@everyone')
|
|
||||||
.map(role => ({
|
|
||||||
id: role.id,
|
|
||||||
name: role.name,
|
|
||||||
color: role.hexColor,
|
|
||||||
position: role.position
|
|
||||||
}))
|
|
||||||
.sort((a, b) => b.position - a.position); // Highest role first
|
|
||||||
|
|
||||||
} catch (memberError) {
|
|
||||||
// User exists but not in this server
|
|
||||||
isInServer = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
globalName: user.globalName || user.username,
|
|
||||||
tag: user.tag,
|
|
||||||
bot: user.bot,
|
|
||||||
avatar: user.displayAvatarURL(),
|
|
||||||
// Server-specific data
|
|
||||||
server_nickname: serverNickname,
|
|
||||||
joined_at: joinedAt,
|
|
||||||
in_server: isInServer,
|
|
||||||
roles: roles,
|
|
||||||
role_names: roles.map(r => r.name),
|
|
||||||
highest_role: roles[0]?.name || null
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
id: userId,
|
|
||||||
error: error.message,
|
|
||||||
username: null,
|
|
||||||
globalName: null,
|
|
||||||
roles: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveResults() {
|
|
||||||
const output = {
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
total_users: userIds.length,
|
|
||||||
processed: processed,
|
|
||||||
successful: processed - errors,
|
|
||||||
errors: errors,
|
|
||||||
guild_id: GUILD_ID,
|
|
||||||
includes_roles: true,
|
|
||||||
users: results
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(outputFile, JSON.stringify(output, null, 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processUsers() {
|
|
||||||
console.log('\n🎭 Starting lookups WITH ROLES...');
|
|
||||||
console.log(` Guild ID: ${GUILD_ID}`);
|
|
||||||
console.log(` Rate: 1 user/second\n`);
|
|
||||||
|
|
||||||
// Fetch guild
|
|
||||||
guild = await client.guilds.fetch(GUILD_ID);
|
|
||||||
console.log(`✅ Connected to: ${guild.name}\n`);
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
const toProcess = userIds.filter(id => !results[id]);
|
|
||||||
console.log(` ${toProcess.length} users remaining\n`);
|
|
||||||
|
|
||||||
for (let i = 0; i < toProcess.length; i++) {
|
|
||||||
const userId = toProcess[i];
|
|
||||||
|
|
||||||
const result = await lookupUserWithRoles(userId);
|
|
||||||
results[result.id] = result;
|
|
||||||
|
|
||||||
if (!result.success) errors++;
|
|
||||||
processed++;
|
|
||||||
|
|
||||||
// Save every 10 users
|
|
||||||
if (processed % 10 === 0) {
|
|
||||||
saveResults();
|
|
||||||
const elapsed = (Date.now() - startTime) / 1000;
|
|
||||||
const rate = (processed - (userIds.length - toProcess.length)) / elapsed;
|
|
||||||
const remaining = (toProcess.length - i - 1) / rate;
|
|
||||||
|
|
||||||
// Show sample with roles
|
|
||||||
if (result.success && result.roles.length > 0) {
|
|
||||||
const rolePreview = result.role_names.slice(0, 2).join(', ');
|
|
||||||
console.log(`💾 ${processed}/${userIds.length} - ${result.globalName} [${rolePreview}] - ~${remaining.toFixed(0)}s left`);
|
|
||||||
} else {
|
|
||||||
console.log(`💾 ${processed}/${userIds.length} (${errors} errors) - ~${remaining.toFixed(0)}s left`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
saveResults();
|
|
||||||
|
|
||||||
const totalTime = (Date.now() - startTime) / 1000;
|
|
||||||
|
|
||||||
// Stats
|
|
||||||
const usersWithRoles = Object.values(results).filter(u => u.success && u.roles.length > 0).length;
|
|
||||||
const allRoleNames = new Set();
|
|
||||||
Object.values(results).forEach(u => {
|
|
||||||
if (u.success) {
|
|
||||||
u.role_names?.forEach(r => allRoleNames.add(r));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`\n${'='.repeat(70)}`);
|
|
||||||
console.log(`✅ Complete with Roles!`);
|
|
||||||
console.log(`${'='.repeat(70)}`);
|
|
||||||
console.log(` Time: ${totalTime.toFixed(1)}s`);
|
|
||||||
console.log(` Processed: ${processed}/${userIds.length}`);
|
|
||||||
console.log(` Successful: ${processed - errors}`);
|
|
||||||
console.log(` Users with roles: ${usersWithRoles}`);
|
|
||||||
console.log(` Unique roles found: ${allRoleNames.size}`);
|
|
||||||
console.log(`\n💾 Saved to: ${outputFile}\n`);
|
|
||||||
|
|
||||||
// Show some roles
|
|
||||||
if (allRoleNames.size > 0) {
|
|
||||||
console.log('📋 Sample roles found:');
|
|
||||||
Array.from(allRoleNames).slice(0, 10).forEach(r => console.log(` • ${r}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.once('ready', () => {
|
|
||||||
console.log(`✅ Logged in as ${client.user.tag}\n`);
|
|
||||||
processUsers();
|
|
||||||
});
|
|
||||||
|
|
||||||
client.on('error', (error) => {
|
|
||||||
console.error('❌ Error:', error.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
|
||||||
console.log('\n\n⚠️ Interrupted! Saving...');
|
|
||||||
saveResults();
|
|
||||||
console.log('✅ Saved. Resume by running same command.\n');
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('🔌 Connecting to Discord...');
|
|
||||||
client.login(TOKEN);
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
/**
|
|
||||||
* Map batch tickets (TICKET: guild_channelId_suffix) to transcript channel messages.
|
|
||||||
*
|
|
||||||
* Connection:
|
|
||||||
* - Batch line: TICKET: 798321161082896395_1423340928588054621_indiffe → channelId = 1423340928588054621.
|
|
||||||
* - Transcript channel (🖥️│transcripts): each message is an embed with "Ticket Name: indifferentketchup🍅-claimed-7235".
|
|
||||||
* - Embed does NOT include channel ID, so we match by (1) ticket name (when known) or (2) time: transcript posted when ticket closes.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* node scripts/map-batch-to-transcript.js list [limit] -- fetch transcript messages, output CSV (messageId, created, ticket_name)
|
|
||||||
* node scripts/map-batch-to-transcript.js find <channelId> -- find transcript message(s) likely for this ticket (by time window)
|
|
||||||
*
|
|
||||||
* Known mapping (from embed): 1423340928588054621 ↔ message 1423400708769579120 (Ticket: indifferentketchup🍅-claimed-7235).
|
|
||||||
*/
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
|
||||||
const { Client, GatewayIntentBits } = require('discord.js');
|
|
||||||
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../.env') });
|
|
||||||
require('dotenv').config({ path: path.join(__dirname, '../../.env') });
|
|
||||||
|
|
||||||
const TOKEN = process.env.MEMBER_BOT_TOKEN || process.env.DISCORD_BOT_TOKEN;
|
|
||||||
const TRANSCRIPT_CHANNEL_ID = '1335424071227281520';
|
|
||||||
const METRICS_CSV = path.join(__dirname, '../../Discord Ticket Transcripts/transcript_metrics_per_ticket.csv');
|
|
||||||
|
|
||||||
function getTicketNameFromEmbed(emb) {
|
|
||||||
const f = emb.fields?.find((x) => x.name && x.name.toLowerCase().includes('ticket name'));
|
|
||||||
return f ? f.value.trim() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchTranscriptMessages(client, limit = 100) {
|
|
||||||
const channel = await client.channels.fetch(TRANSCRIPT_CHANNEL_ID).catch(() => null);
|
|
||||||
if (!channel) return [];
|
|
||||||
const cap = Math.min(limit, 100); // Discord API max 100 per request
|
|
||||||
const messages = await channel.messages.fetch({ limit: cap });
|
|
||||||
const out = [];
|
|
||||||
for (const [, m] of messages) {
|
|
||||||
const emb = m.embeds?.[0];
|
|
||||||
const ticketName = emb ? getTicketNameFromEmbed(emb) : null;
|
|
||||||
out.push({
|
|
||||||
messageId: m.id,
|
|
||||||
created: m.createdAt ? m.createdAt.toISOString() : m.createdTimestamp,
|
|
||||||
createdTs: m.createdTimestamp,
|
|
||||||
ticketName: ticketName || '',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
out.sort((a, b) => b.createdTs - a.createdTs);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMetricsCsv() {
|
|
||||||
if (!fs.existsSync(METRICS_CSV)) return [];
|
|
||||||
const text = fs.readFileSync(METRICS_CSV, 'utf8');
|
|
||||||
const lines = text.split(/\r?\n/).filter((l) => l.trim());
|
|
||||||
const header = lines[0].split(',');
|
|
||||||
const ticketIdIdx = header.indexOf('ticket_id');
|
|
||||||
const lastTsIdx = header.indexOf('last_message_ts');
|
|
||||||
if (ticketIdIdx === -1 || lastTsIdx === -1) return [];
|
|
||||||
const rows = [];
|
|
||||||
for (let i = 1; i < lines.length; i++) {
|
|
||||||
const parts = lines[i].split(',');
|
|
||||||
const ticketId = parts[ticketIdIdx];
|
|
||||||
const lastTs = parseInt(parts[lastTsIdx], 10);
|
|
||||||
if (!ticketId || !ticketId.includes('_')) continue;
|
|
||||||
const channelId = ticketId.split('_')[1];
|
|
||||||
if (channelId && !isNaN(lastTs)) rows.push({ ticketId, channelId, last_message_ts: lastTs });
|
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const cmd = process.argv[2];
|
|
||||||
const arg = process.argv[3];
|
|
||||||
|
|
||||||
if (!TOKEN) {
|
|
||||||
console.error('No bot token');
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages],
|
|
||||||
});
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
client.once('ready', resolve);
|
|
||||||
client.login(TOKEN).catch(reject);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (cmd === 'list') {
|
|
||||||
const limit = Math.min(parseInt(arg, 10) || 100, 100);
|
|
||||||
const list = await fetchTranscriptMessages(client, limit);
|
|
||||||
console.log('transcript_message_id,created_iso,ticket_name');
|
|
||||||
list.forEach((r) => console.log([r.messageId, r.created, r.ticketName].map((c) => `"${String(c).replace(/"/g, '""')}"`).join(',')));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmd === 'find' && arg) {
|
|
||||||
const channelId = arg.trim();
|
|
||||||
const metrics = loadMetricsCsv();
|
|
||||||
const row = metrics.find((r) => r.channelId === channelId);
|
|
||||||
const closeTs = row ? row.last_message_ts : null;
|
|
||||||
const list = await fetchTranscriptMessages(client, 100);
|
|
||||||
const windowMs = 2 * 60 * 60 * 1000; // ±2 hours
|
|
||||||
const candidates = closeTs
|
|
||||||
? list.filter((r) => Math.abs(r.createdTs - closeTs) <= windowMs)
|
|
||||||
: list.slice(0, 20);
|
|
||||||
console.log('Batch ticket channelId:', channelId);
|
|
||||||
if (row) console.log('Ticket close time (last_message_ts):', closeTs, new Date(closeTs).toISOString());
|
|
||||||
console.log('Transcript channel messages (candidates by time or recent):');
|
|
||||||
candidates.forEach((r) => {
|
|
||||||
const delta = closeTs != null ? (r.createdTs - closeTs) / 60000 : null;
|
|
||||||
console.log(' ', r.messageId, r.created, r.ticketName || '(no name)', delta != null ? `delta ${delta.toFixed(0)} min` : '');
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Usage: node scripts/map-batch-to-transcript.js list [limit]');
|
|
||||||
console.log(' node scripts/map-batch-to-transcript.js find <channelId>');
|
|
||||||
} finally {
|
|
||||||
client.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main().catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user