diff --git a/src/commands.ts b/src/commands.ts index 66ad04c..8cf2277 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1,6 +1,6 @@ import { Config } from "./config"; import { logger } from "./logger"; -import { fetchDiscord } from "./discord/core"; +import * as Discord from "./discord"; const COMMANDS = [ { @@ -23,18 +23,55 @@ const COMMANDS = [ }, ]; -export const registerCommand = async () => { - logger.info( - { command: COMMANDS, guildId: Config.DEFAULT_GUILD_ID }, - "Registering commands to discord server", +const normalizeCommand = (cmd: any) => ({ + name: cmd.name, + description: cmd.description, + type: cmd.type, + options: (cmd.options || []) + .map((o: any) => ({ + name: o.name, + description: o.description, + type: o.type, + required: o.required || false, + })) + .sort((a: any, b: any) => a.name.localeCompare(b.name)), +}); + +const commandsAreEqual = (existingCommands: any[], newCommands: any[]) => { + if (existingCommands.length !== newCommands.length) return false; + + const sortedExisting = [...existingCommands].sort((a, b) => + a.name.localeCompare(b.name), ); - for (const commmand of COMMANDS) { - const response = await fetchDiscord( - `/applications/${Config.APP_ID}/guilds/${Config.DEFAULT_GUILD_ID}/commands`, - { - method: "POST", - body: JSON.stringify(commmand), - }, - ); + const sortedNew = [...newCommands].sort((a, b) => + a.name.localeCompare(b.name), + ); + + for (let i = 0; i < sortedNew.length; i++) { + if ( + JSON.stringify(normalizeCommand(sortedExisting[i])) !== + JSON.stringify(normalizeCommand(sortedNew[i])) + ) { + return false; + } + } + return true; +}; + +export const registerCommand = async () => { + try { + logger.info("Checking for command updates..."); + const existingCommands = await Discord.getGuildCommands(); + + if (commandsAreEqual(existingCommands, COMMANDS)) { + logger.info("Commands are already up to date."); + return; + } + + logger.info("Commands are different, registering..."); + await Discord.bulkOverwriteGuildCommands(COMMANDS); + logger.info("Commands registered successfully."); + } catch (error) { + logger.error(error, "Failed to register commands"); } }; diff --git a/src/discord/application.ts b/src/discord/application.ts new file mode 100644 index 0000000..88605ed --- /dev/null +++ b/src/discord/application.ts @@ -0,0 +1,58 @@ +import { fetchDiscord } from "./core"; +import { Config } from "../config"; + +export interface Application { + id: string; + name: string; + icon: string | null; + description: string; + bot_public: boolean; + bot_require_code_grant: boolean; + team: any | null; +} + +export const getCurrentApplication = async (): Promise => { + const res = await fetchDiscord("/applications/@me"); + if (!res.ok) { + throw new Error(`Failed to get current application: ${res.status}`); + } + return res.json(); +}; + +export const editCurrentApplication = async (data: any): Promise => { + const res = await fetchDiscord("/applications/@me", { + method: "PATCH", + body: JSON.stringify(data), + }); + if (!res.ok) { + throw new Error(`Failed to edit current application: ${res.status}`); + } + return res.json(); +}; + +export const getGuildCommands = async () => { + const res = await fetchDiscord( + `/applications/${Config.APP_ID}/guilds/${Config.DEFAULT_GUILD_ID}/commands`, + ); + if (!res.ok) { + throw new Error(`Failed to get guild commands: ${res.status}`); + } + return res.json(); +}; + +export const bulkOverwriteGuildCommands = async (commands: any[]) => { + const res = await fetchDiscord( + `/applications/${Config.APP_ID}/guilds/${Config.DEFAULT_GUILD_ID}/commands`, + { + method: "PUT", + body: JSON.stringify(commands), + }, + ); + if (!res.ok) { + const error = await res.text(); + throw new Error( + `Failed to bulk overwrite guild commands: ${res.status} ${error}`, + ); + } + return res.json(); +}; diff --git a/src/discord/index.ts b/src/discord/index.ts index a879e85..1552da4 100644 --- a/src/discord/index.ts +++ b/src/discord/index.ts @@ -1,3 +1,4 @@ +export * from "./application"; export * from "./audit-log"; export * from "./core"; export * from "./guild"; diff --git a/src/index.ts b/src/index.ts index eda9e00..f7c64e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,12 +3,17 @@ import { Config } from "./config"; import { logger } from "./logger"; import * as nacl from "tweetnacl"; import * as Discord from "./discord"; +import { registerCommand } from "./commands"; logger.info( { port: Config.PORT, guildId: Config.DEFAULT_GUILD_ID }, "Discord police bot is starting", ); +registerCommand().catch((err) => + logger.error(err, "Failed to register commands"), +); + const snowflakeToMs = (id: string) => Number((BigInt(id) >> BigInt(22)) + BigInt(1420070400000));