diff --git a/adapters/discord/src/bot.ts b/adapters/discord/src/bot.ts index d92e91ac..7c4a63f3 100644 --- a/adapters/discord/src/bot.ts +++ b/adapters/discord/src/bot.ts @@ -204,39 +204,37 @@ export class DiscordBot extends Bot { const remote = Object.fromEntries((await this.internal.getGlobalApplicationCommands(this.selfId, { with_localizations: true })) .filter(cmd => cmd.type === Discord.ApplicationCommand.Type.CHAT_INPUT) .map(cmd => [cmd.name, cmd] as const)) - + const updates: any[] = [] for (const key in { ...local, ...remote }) { if (!local[key]) { logger.debug('delete command %s', key) await this.internal.deleteGlobalApplicationCommand(this.selfId, remote[key].id) continue } - const data = Discord.encodeCommand(local[key]) logger.debug(data, remote[key]) if (!remote[key]) { logger.debug('create command: %s', local[key].name) - await this.internal.createGlobalApplicationCommand(this.selfId, data) + updates.push(data) } else if (!shapeEqual(data, remote[key])) { logger.debug('edit command: %s', local[key].name) - await this.internal.editGlobalApplicationCommand(this.selfId, remote[key].id, data) + updates.push(data) } } + if (updates.length) { + await this.internal.bulkOverwriteGlobalApplicationCommands(this.selfId, updates) + } } } -function shapeEqual(a: any, b: any, strict = false) { +function shapeEqual(a: any, b: any) { if (a === b) return true - if (strict && isNullable(a) && isNullable(b)) return true - if (!strict && !a && !b) return true - // ^ a.required = false, b.required = undefined + if (isNullable(a) && isNullable(b)) return true if (typeof a !== typeof b) return false if (typeof a !== 'object') return false - if ((typeof a === 'object' && Object.values(a).every(v => !v) && !b) - || (typeof b === 'object' && Object.values(b).every(v => !v) && !a)) return true - // ^ one is object with undefined values, other is undefined (*_localizations) - // a = { foo: undefined }, b = undefined + if (Object.values(a).every(isNullable) && isNullable(b)) return true + // ^ a = { foo: undefined }, b = null if (!a || !b) return false // check array @@ -248,7 +246,7 @@ function shapeEqual(a: any, b: any, strict = false) { } // check object - return Object.keys(a).every(key => shapeEqual(a[key], b[key], strict)) + return Object.keys(a).every(key => shapeEqual(a[key], b[key])) } export namespace DiscordBot { diff --git a/adapters/discord/src/types/command.ts b/adapters/discord/src/types/command.ts index 52fedc56..03becddd 100644 --- a/adapters/discord/src/types/command.ts +++ b/adapters/discord/src/types/command.ts @@ -225,7 +225,7 @@ declare module './internal' { * Takes a list of application commands, overwriting the existing global command list for this application. Updates will be available in all guilds after 1 hour. Returns 200 and a list of application command objects. Commands that do not already exist will count toward daily application command create limits. * @see https://discord.com/developers/docs/interactions/application-commands#bulk-overwrite-global-application-commands */ - bulkOverwriteGlobalApplicationCommands(application_id: snowflake): Promise + bulkOverwriteGlobalApplicationCommands(application_id: snowflake, data: ApplicationCommand.Params.Create[]): Promise /** * Fetch a global command for your application. Returns an application command object. * @see https://discord.com/developers/docs/interactions/application-commands#get-global-application-command diff --git a/adapters/discord/src/utils.ts b/adapters/discord/src/utils.ts index 34951d58..0526fb06 100644 --- a/adapters/discord/src/utils.ts +++ b/adapters/discord/src/utils.ts @@ -165,11 +165,12 @@ export async function adaptSession(bot: DiscordBot, input: Discord.Gateway.Paylo if (input.t === 'MESSAGE_CREATE') { setupMessageGuildId(session, input.d.guild_id) if (input.d.webhook_id && !session.isDirect) { - const webhook = await bot.ensureWebhook(input.d.channel_id) - if (webhook.id === input.d.webhook_id) { + try { + // 403 Missing Permissions + const webhook = await bot.ensureWebhook(input.d.channel_id) // koishi's webhook - return - } + if (webhook.id === input.d.webhook_id) return + } catch (e) {} } session.type = 'message' await decodeMessage(bot, input.d, session) @@ -291,6 +292,7 @@ export function encodeCommandOptions(cmd: Universal.Command): Discord.Applicatio description_localizations: pick(cmd.description, Discord.Locale), }))) } else { + // `getGlobalApplicationCommands()` does not return `required` property. for (const arg of cmd.arguments) { result.push({ name: arg.name.toLowerCase().replace(/[^a-z0-9]/g, ''), @@ -298,7 +300,6 @@ export function encodeCommandOptions(cmd: Universal.Command): Discord.Applicatio description_localizations: pick(arg.description, Discord.Locale), type: types[arg.type] ?? types.text, // required: arg.required ?? false, - required: false, }) } for (const option of cmd.options) { @@ -308,7 +309,6 @@ export function encodeCommandOptions(cmd: Universal.Command): Discord.Applicatio description_localizations: pick(option.description, Discord.Locale), type: types[option.type] ?? types.text, // required: option.required ?? false, - required: false, min_value: option.type === 'posint' ? 1 : undefined, }) }