diff --git a/adapters/discord/src/bot.ts b/adapters/discord/src/bot.ts index 091ef395..1005b7da 100644 --- a/adapters/discord/src/bot.ts +++ b/adapters/discord/src/bot.ts @@ -200,7 +200,7 @@ export class DiscordBot extends Bot { async updateCommands(commands: Universal.Command[]) { this.commands = commands const local = Object.fromEntries(commands.map(cmd => [cmd.name, cmd] as const)) - const remote = Object.fromEntries((await this.internal.getGlobalApplicationCommands(this.selfId)) + 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)) @@ -216,7 +216,7 @@ export class DiscordBot extends Bot { if (!remote[key]) { logger.debug('create command: %s', local[key].name) await this.internal.createGlobalApplicationCommand(this.selfId, data) - } else if (shapeEqual(data, remote[key])) { + } else if (!shapeEqual(data, remote[key])) { logger.debug('edit command: %s', local[key].name) await this.internal.editGlobalApplicationCommand(this.selfId, remote[key].id, data) } @@ -226,9 +226,16 @@ export class DiscordBot extends Bot { function shapeEqual(a: any, b: any, strict = false) { if (a === b) return true - if (!strict && isNullable(a) && isNullable(b)) return true + if (strict && isNullable(a) && isNullable(b)) return true + if (!strict && !a && !b) return true + // ^ a.required = false, b.required = undefined + 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 (!a || !b) return false // check array diff --git a/adapters/discord/src/types/command.ts b/adapters/discord/src/types/command.ts index dc2264eb..52fedc56 100644 --- a/adapters/discord/src/types/command.ts +++ b/adapters/discord/src/types/command.ts @@ -140,6 +140,11 @@ export namespace ApplicationCommand { } export namespace Params { + /** https://discord.com/developers/docs/interactions/application-commands#get-global-application-commands-query-string-params */ + export interface Get { + /** Whether to include full localization dictionaries (name_localizations and description_localizations) in the returned objects, instead of the name_localized and description_localized fields. Default false. */ + with_localizations: boolean + } /** https://discord.com/developers/docs/interactions/application-commands#create-global-application-command-json-params */ export interface Create { /** 1-32 character name */ @@ -210,7 +215,7 @@ declare module './internal' { * Fetch all of the global commands for your application. Returns an array of application command objects. * @see https://discord.com/developers/docs/interactions/application-commands#get-global-application-commands */ - getGlobalApplicationCommands(application_id: snowflake): Promise + getGlobalApplicationCommands(application_id: snowflake, param?: ApplicationCommand.Params.Get): Promise /** * Creating a command with the same name as an existing command for your application will overwrite the old command. * @see https://discord.com/developers/docs/interactions/application-commands#create-global-application-command diff --git a/adapters/discord/src/utils.ts b/adapters/discord/src/utils.ts index 0e2621ca..34951d58 100644 --- a/adapters/discord/src/utils.ts +++ b/adapters/discord/src/utils.ts @@ -163,7 +163,8 @@ function setupReaction(session: Partial, data: ReactionEvent) { export async function adaptSession(bot: DiscordBot, input: Discord.Gateway.Payload) { const session = bot.session({}, input) if (input.t === 'MESSAGE_CREATE') { - if (input.d.webhook_id) { + 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) { // koishi's webhook @@ -172,7 +173,6 @@ export async function adaptSession(bot: DiscordBot, input: Discord.Gateway.Paylo } session.type = 'message' await decodeMessage(bot, input.d, session) - setupMessageGuildId(session, input.d.guild_id) // dc 情况特殊 可能有 embeds 但是没有消息主体 // if (!session.content) return } else if (input.t === 'MESSAGE_UPDATE') { @@ -230,7 +230,7 @@ export async function adaptSession(bot: DiscordBot, input: Discord.Gateway.Paylo session.subtype = input.d.guild_id ? 'group' : 'private' session.channelId = input.d.channel_id session.guildId = input.d.guild_id - session.userId = input.d.member.user.id + session.userId = session.isDirect ? input.d.user.id : input.d.member.user.id session.messageId = input.d.id session.content = '' session.data.argv = decodeArgv(data, command) @@ -297,7 +297,8 @@ export function encodeCommandOptions(cmd: Universal.Command): Discord.Applicatio description: arg.description[''] || arg.name, description_localizations: pick(arg.description, Discord.Locale), type: types[arg.type] ?? types.text, - required: arg.required ?? false, + // required: arg.required ?? false, + required: false, }) } for (const option of cmd.options) { @@ -306,7 +307,8 @@ export function encodeCommandOptions(cmd: Universal.Command): Discord.Applicatio description: option.description[''] || option.name, description_localizations: pick(option.description, Discord.Locale), type: types[option.type] ?? types.text, - required: option.required ?? false, + // required: option.required ?? false, + required: false, min_value: option.type === 'posint' ? 1 : undefined, }) }