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:
90
scripts/diagnose-discord-perms.ts
Normal file
90
scripts/diagnose-discord-perms.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user