chore(scripts): add Discord bot permission diagnostic

One-off Bun script that logs each configured Discord bot in (owner /
reviewer / arbiter), walks every guild it sees, and prints the bot
member's effective permission bits for the capabilities we plan to
expose (ReadMessageHistory, ManageMessages, ManageChannels,
AddReactions, thread perms, etc.). Used to verify "current permissions
support feature X" empirically instead of guessing from intents.
This commit is contained in:
Codex
2026-06-01 10:30:58 +09:00
parent 047f0d3b70
commit 0d87778b78

View File

@@ -0,0 +1,90 @@
#!/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<string, string> {
const envPath = path.join(import.meta.dir, '..', '.env');
if (!fs.existsSync(envPath)) return {};
const out: Record<string, string> = {};
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<void>((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<string, boolean> = {};
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);
}
}