27 lines
1.8 KiB
TypeScript
27 lines
1.8 KiB
TypeScript
export type CommandDefinition = { name: string; description: string; type?: number; contexts: number[]; integration_types: number[]; options?: unknown[] };
|
|
type Transport = (url: string, init?: RequestInit) => Promise<Response>;
|
|
/** Upsert only Minabot's commands. Never bulk-overwrite unrelated application commands. */
|
|
export async function registerCommands(applicationId: string, token: string, definitions: CommandDefinition[], request: Transport = fetch) {
|
|
if (!/^\d{17,20}$/.test(applicationId) || !token.trim()) throw new Error('Set DISCORD_CLIENT_ID and DISCORD_BOT_TOKEN.');
|
|
const names = new Set<string>();
|
|
for (const command of definitions) {
|
|
if (names.has(command.name)) throw new Error(`Duplicate command: ${command.name}`);
|
|
names.add(command.name);
|
|
if (!command.contexts.includes(0) || !command.contexts.includes(1)) throw new Error(`${command.name} must support servers and bot DMs.`);
|
|
}
|
|
const registered: string[] = [];
|
|
for (const command of definitions) {
|
|
const response = await request(`https://discord.com/api/v10/applications/${applicationId}/commands`, {
|
|
method: 'POST', headers: { Authorization: `Bot ${token.trim().replace(/^Bot\s+/i, '')}`, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(command), redirect: 'error', signal: AbortSignal.timeout(15000),
|
|
});
|
|
if (!response.ok) {
|
|
// Provider payloads may contain sensitive data; report status only.
|
|
const retry = response.status === 429 ? ` Retry after ${response.headers.get('Retry-After') ?? 'a few'} seconds.` : '';
|
|
throw new Error(`Registration of /${command.name} failed (HTTP ${response.status}).${retry} Already registered: ${registered.join(', ') || 'none'}. Rerunning safely upserts commands.`);
|
|
}
|
|
registered.push(command.name);
|
|
}
|
|
return registered;
|
|
}
|