From 0be7c73d4ad3167b70a03a1e8d2ba1e8303c0b28 Mon Sep 17 00:00:00 2001 From: MARCROCK22 Date: Fri, 28 Jun 2024 05:58:30 +0000 Subject: [PATCH] feat: check for locales --- src/client/base.ts | 66 +++++++++++++++++++++++++---------------- src/commands/handler.ts | 49 +++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/src/client/base.ts b/src/client/base.ts index 4f3aa5c..b3e0bf4 100644 --- a/src/client/base.ts +++ b/src/client/base.ts @@ -284,13 +284,29 @@ export class BaseClient { throw new Error('Function not implemented'); } - private shouldUploadCommands(cachePath: string) { - return this.commands!.shouldUpload(cachePath).then(async should => { - if (should) await promises.writeFile(cachePath, JSON.stringify(this.commands!.values.map(x => x.toJSON()))); + private shouldUploadCommands(cachePath: string, guildId?: string) { + return this.commands!.shouldUpload(cachePath, guildId).then(should => { + this.logger.debug( + should + ? `[${guildId ?? 'global'}] Change(s) detected, uploading commands` + : `[${guildId ?? 'global'}] commands seems to be up to date`, + ); return should; }); } + private syncCachePath(cachePath: string) { + this.logger.debug('Syncing commands cache'); + return promises.writeFile( + cachePath, + JSON.stringify( + this.commands!.values.filter(cmd => !('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash).map(x => + x.toJSON(), + ), + ), + ); + } + async uploadCommands({ applicationId, cachePath }: { applicationId?: string; cachePath?: string } = {}) { applicationId ??= await this.getRC().then(x => x.applicationId ?? this.applicationId); BaseClient.assertString(applicationId, 'applicationId is not a string'); @@ -298,18 +314,12 @@ export class BaseClient { const commands = this.commands!.values; const filter = filterSplit(commands, command => !command.guildId); - if (!cachePath || (cachePath && (await this.shouldUploadCommands(cachePath)))) - await this.proxy - .applications(applicationId) - .commands.put({ - body: filter.expect - .filter(cmd => !('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash) - .map(x => x.toJSON()), - }) - .catch(async e => { - if (cachePath) await promises.unlink(cachePath); - throw e; - }); + if (!cachePath || (await this.shouldUploadCommands(cachePath))) + await this.proxy.applications(applicationId).commands.put({ + body: filter.expect + .filter(cmd => !('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash) + .map(x => x.toJSON()), + }); const guilds = new Set(); @@ -319,16 +329,22 @@ export class BaseClient { } } - for (const guild of guilds) { - await this.proxy - .applications(applicationId) - .guilds(guild) - .commands.put({ - body: filter.never - .filter(cmd => cmd.guildId?.includes(guild) && (!('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash)) - .map(x => x.toJSON()), - }); + for (const guildId of guilds) { + if (!cachePath || (await this.shouldUploadCommands(cachePath, guildId))) { + await this.proxy + .applications(applicationId) + .guilds(guildId) + .commands.put({ + body: filter.never + .filter( + cmd => cmd.guildId?.includes(guildId) && (!('ignore' in cmd) || cmd.ignore !== IgnoreCommand.Slash), + ) + .map(x => x.toJSON()), + }); + } } + + if (cachePath) await this.syncCachePath(cachePath); } async loadCommands(dir?: string) { @@ -363,7 +379,7 @@ export class BaseClient { T extends InternalRuntimeConfigHTTP | InternalRuntimeConfig = InternalRuntimeConfigHTTP | InternalRuntimeConfig, >() { const seyfertConfig = (BaseClient._seyfertConfig || - (await this.options.getRC?.()) || + (await this.options?.getRC?.()) || (await Promise.any( ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].map(ext => magicImport(join(process.cwd(), `seyfert.config${ext}`)).then(x => x.default ?? x), diff --git a/src/commands/handler.ts b/src/commands/handler.ts index 08ef53f..68ac2fa 100644 --- a/src/commands/handler.ts +++ b/src/commands/handler.ts @@ -8,6 +8,7 @@ import { type APIApplicationCommandSubcommandOption, type APIApplicationCommandSubcommandGroupOption, type APIApplicationCommandChannelOption, + type LocalizationMap, } from 'discord-api-types/v10'; import { basename, dirname } from 'node:path'; import type { Logger, MakeRequired, NulleableCoalising, OmitInsert } from '../common'; @@ -48,6 +49,24 @@ export class CommandHandler extends BaseHandler { } } + protected shouldUploadLocales(locales?: LocalizationMap | null, cachedLocales?: LocalizationMap | null) { + if (!locales && !cachedLocales) return false; + if (!locales && cachedLocales) return true; + if (locales && !cachedLocales) return true; + if (locales && cachedLocales) { + const localesEntries = Object.entries(locales); + const cachedLocalesEntries = Object.entries(cachedLocales); + if (localesEntries.length !== cachedLocalesEntries.length) return true; + + for (const [key, value] of localesEntries) { + const cached = cachedLocalesEntries.find(x => x[0] === key); + if (!cached) return true; + if (value !== cached[1]) return true; + } + } + return false; + } + protected shouldUploadOption(option: APIApplicationCommandOption, cached: APIApplicationCommandOption) { if (option.description !== cached.description) return true; if (option.type !== cached.type) return true; @@ -55,6 +74,9 @@ export class CommandHandler extends BaseHandler { if (option.name !== cached.name) return true; //TODO: locales + if (this.shouldUploadLocales(option.name_localizations, cached.name_localizations)) return true; + if (this.shouldUploadLocales(option.description_localizations, cached.description_localizations)) return true; + switch (option.type) { case ApplicationCommandOptionType.String: return ( @@ -108,24 +130,34 @@ export class CommandHandler extends BaseHandler { return false; } - async shouldUpload(file: string) { + async shouldUpload(file: string, guildId?: string) { + const values = this.values.filter(x => { + if (!guildId) return !x.guildId; + return x.guildId?.includes(guildId); + }); if ( !(await promises.access(file).then( () => true, () => false, )) ) { - await promises.writeFile(file, JSON.stringify(this.values.map(x => x.toJSON()))); + await promises.writeFile(file, JSON.stringify(values.map(x => x.toJSON()))); return true; } - const cachedCommands: (ReturnType | ReturnType)[] = JSON.parse( - (await promises.readFile(file)).toString(), - ); + const cachedCommands = ( + JSON.parse((await promises.readFile(file)).toString()) as ( + | ReturnType + | ReturnType + )[] + ).filter(x => { + if (!guildId) return !x.guild_id; + return x.guild_id?.includes(guildId); + }); - if (cachedCommands.length !== this.values.length) return true; + if (cachedCommands.length !== values.length) return true; - for (const command of this.values.map(x => x.toJSON())) { + for (const command of values.map(x => x.toJSON())) { const cached = cachedCommands.find(x => { if (x.name !== command.name) return false; if (command.guild_id) return command.guild_id.every(id => x.guild_id?.includes(id)); @@ -140,7 +172,8 @@ export class CommandHandler extends BaseHandler { if (!!('options' in cached) !== !!('options' in command)) return true; if (!!cached.contexts !== !!command.contexts) return true; if (!!cached.integration_types !== !!command.integration_types) return true; - //TODO: locales + if (this.shouldUploadLocales(command.name_localizations, cached.name_localizations)) return true; + if (this.shouldUploadLocales(command.description_localizations, cached.description_localizations)) return true; if ('contexts' in command && 'contexts' in cached) { if (command.contexts.length !== cached.contexts.length) return true;