deploy #1
@@ -4,6 +4,10 @@ NODE_ENV=production
|
||||
BOT_TOKEN=
|
||||
BOT_CLIENT_ID=
|
||||
BOT_CLIENT_SECRET=
|
||||
# Optional: register slash commands only on this guild (instant sync for dev).
|
||||
# Leave empty in production so commands are global. When set, global commands
|
||||
# are cleared; when empty, leftover guild-scoped commands are cleared on startup
|
||||
# so Discord does not show every command twice.
|
||||
BOT_GUILD_ID=
|
||||
DATABASE_URL=postgresql://nexumi:nexumi@postgres:5432/nexumi
|
||||
REDIS_URL=redis://redis:6379
|
||||
|
||||
@@ -69,38 +69,82 @@ const commands: SlashCommand[] = [
|
||||
const map = new Map(commands.map((c) => [c.data.name, c]));
|
||||
const contextMenuMap = new Map(utilityContextMenus.map((c) => [c.data.name, c]));
|
||||
|
||||
function buildCommandBody(): object[] {
|
||||
const seen = new Set<string>();
|
||||
const body: object[] = [];
|
||||
for (const command of [...commands, ...utilityContextMenus]) {
|
||||
const json = command.data.toJSON() as { name: string; type?: number };
|
||||
const key = `${json.type ?? 1}:${json.name}`;
|
||||
if (seen.has(key)) {
|
||||
logger.warn({ name: json.name, type: json.type }, 'Skipping duplicate command definition');
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
body.push(json);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guild-scoped leftovers + global commands both appear in Discord's picker.
|
||||
* Always clear the unused scope so each command shows once.
|
||||
*/
|
||||
async function clearGuildCommandLeftovers(rest: REST, clientId: string): Promise<number> {
|
||||
const guilds = (await rest.get(Routes.userGuilds())) as Array<{ id: string }>;
|
||||
let cleared = 0;
|
||||
for (const guild of guilds) {
|
||||
const existing = (await rest.get(
|
||||
Routes.applicationGuildCommands(clientId, guild.id)
|
||||
)) as unknown[];
|
||||
if (existing.length === 0) {
|
||||
continue;
|
||||
}
|
||||
await rest.put(Routes.applicationGuildCommands(clientId, guild.id), { body: [] });
|
||||
cleared += 1;
|
||||
}
|
||||
return cleared;
|
||||
}
|
||||
|
||||
export async function registerCommands() {
|
||||
const rest = new REST({ version: '10' }).setToken(env.BOT_TOKEN);
|
||||
const body = [
|
||||
...commands.map((c) => c.data.toJSON()),
|
||||
...utilityContextMenus.map((c) => c.data.toJSON())
|
||||
];
|
||||
const body = buildCommandBody();
|
||||
|
||||
// Guild commands sync instantly; global can take up to ~1h.
|
||||
if (env.BOT_GUILD_ID) {
|
||||
await rest.put(Routes.applicationGuildCommands(env.BOT_CLIENT_ID, env.BOT_GUILD_ID), {
|
||||
body
|
||||
});
|
||||
// Remove global copies so the guild does not show every command twice.
|
||||
await rest.put(Routes.applicationCommands(env.BOT_CLIENT_ID), { body: [] });
|
||||
logger.info(
|
||||
{
|
||||
scope: 'guild',
|
||||
guildId: env.BOT_GUILD_ID,
|
||||
count: commands.length,
|
||||
contextMenus: utilityContextMenus.length
|
||||
count: body.length,
|
||||
clearedGlobal: true
|
||||
},
|
||||
'Registered guild application commands'
|
||||
'Registered guild application commands and cleared global commands'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await rest.put(Routes.applicationCommands(env.BOT_CLIENT_ID), { body });
|
||||
|
||||
// Wipe leftover guild-scoped registrations from earlier BOT_GUILD_ID / shard runs.
|
||||
let clearedGuilds = 0;
|
||||
try {
|
||||
clearedGuilds = await clearGuildCommandLeftovers(rest, env.BOT_CLIENT_ID);
|
||||
} catch (error) {
|
||||
logger.warn({ error }, 'Failed to clear leftover guild application commands');
|
||||
}
|
||||
|
||||
logger.info(
|
||||
{
|
||||
scope: 'global',
|
||||
count: commands.length,
|
||||
contextMenus: utilityContextMenus.length
|
||||
count: body.length,
|
||||
clearedGuilds
|
||||
},
|
||||
'Registered global application commands'
|
||||
'Registered global application commands and cleared guild-scoped leftovers'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user