#!/usr/bin/env bun /** * One-off diagnostic. Logs in with each configured Discord bot token, walks * every guild it sees, and prints the bot member's effective permission bits. * * Output is meant to verify capability claims like "we have Read Message * History" / "we have Manage Channels" empirically rather than guessing from * intents. */ import { Client, GatewayIntentBits, PermissionsBitField } from 'discord.js'; import fs from 'fs'; import path from 'path'; function loadEnv(): Record { const envPath = path.join(import.meta.dir, '..', '.env'); if (!fs.existsSync(envPath)) return {}; const out: Record = {}; for (const line of fs.readFileSync(envPath, 'utf-8').split('\n')) { const m = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/); if (m) out[m[1]] = m[2].replace(/^["']|["']$/g, ''); } return out; } const env = loadEnv(); const TOKENS: Array<[string, string]> = [ ['owner', env.DISCORD_OWNER_BOT_TOKEN ?? ''], ['reviewer', env.DISCORD_REVIEWER_BOT_TOKEN ?? ''], ['arbiter', env.DISCORD_ARBITER_BOT_TOKEN ?? ''], ].filter(([, t]) => t.length > 0) as Array<[string, string]>; // Subset of permissions we actually care about for the capability matrix. const RELEVANT = [ 'ViewChannel', 'SendMessages', 'EmbedLinks', 'AttachFiles', 'ReadMessageHistory', 'AddReactions', 'ManageMessages', 'ManageChannels', 'ManageThreads', 'CreatePublicThreads', 'CreatePrivateThreads', 'SendMessagesInThreads', 'MentionEveryone', 'UseExternalEmojis', ] as const; async function diagnose(role: string, token: string) { const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, GatewayIntentBits.DirectMessages, ], }); await new Promise((resolve, reject) => { client.once('ready', () => resolve()); client.once('error', reject); client.login(token).catch(reject); }); console.log( `\n=== role=${role} bot=${client.user?.tag} (id=${client.user?.id}) ===`, ); const guilds = await client.guilds.fetch(); for (const [gid, partial] of guilds) { const guild = await partial.fetch(); const me = await guild.members.fetchMe(); const perms = me.permissions; const has: Record = {}; for (const name of RELEVANT) { has[name] = perms.has( PermissionsBitField.Flags[ name as keyof typeof PermissionsBitField.Flags ], ); } console.log(` guild=${guild.name} (id=${gid})`); console.log( ` admin=${perms.has(PermissionsBitField.Flags.Administrator)}`, ); for (const name of RELEVANT) console.log(` ${name}=${has[name]}`); } client.destroy(); } for (const [role, token] of TOKENS) { try { await diagnose(role, token); } catch (err) { console.log(`\n=== role=${role} FAILED ===`); console.log(err); } }