mirror of
https://github.com/discordeno/discordeno.git
synced 2026-05-21 10:50:09 +00:00
Add localization
The "command versioning" system works the same say as before, but instead of a command version the code updates the commands every time they change based on the SHA1 of the commands
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# General Configurations
|
||||
#
|
||||
|
||||
# For Prisma use, it should be a postgres connection url
|
||||
# Used by Prisma, it should be a postgres connection string
|
||||
# Template: postgres://[username]:[password]@[host]:[port]/[db]
|
||||
# Replate the [...] from the template with your values
|
||||
# TEMPLATE-SETUP: Add a postgres connection string
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@discordeno/bot": "19.0.0-next.ad7e74c",
|
||||
"@fastify/multipart": "^8.3.0",
|
||||
"@influxdata/influxdb-client": "^1.33.2",
|
||||
"@prisma/client": "^5.15.0",
|
||||
"amqplib": "^0.10.4",
|
||||
"fastify": "^4.27.0"
|
||||
},
|
||||
@@ -30,6 +31,7 @@
|
||||
"@swc/core": "^1.5.25",
|
||||
"@types/amqplib": "^0.10.5",
|
||||
"@types/node": "^20.14.2",
|
||||
"prisma": "^5.15.0",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Guild" (
|
||||
"guildId" BIGINT NOT NULL,
|
||||
"language" TEXT NOT NULL DEFAULT 'english',
|
||||
"commands" TEXT,
|
||||
|
||||
CONSTRAINT "Guild_pkey" PRIMARY KEY ("guildId")
|
||||
);
|
||||
3
examples/bigbot-19/prisma/migrations/migration_lock.toml
Normal file
3
examples/bigbot-19/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
21
examples/bigbot-19/prisma/schema.prisma
Normal file
21
examples/bigbot-19/prisma/schema.prisma
Normal file
@@ -0,0 +1,21 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Guild {
|
||||
// The guild id
|
||||
guildId BigInt @id
|
||||
// The language for translations in this server
|
||||
language String @default("english")
|
||||
// Hash of the command objects that have been deployed
|
||||
// Used to check when to deploy the commands to a guild
|
||||
commands String?
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Collection, createBot, type Bot } from '@discordeno/bot'
|
||||
import { Collection, LogDepth, createBot, type Bot, type logger } from '@discordeno/bot'
|
||||
import assert from 'node:assert'
|
||||
import { DISCORD_TOKEN, GATEWAY_INTENTS, REST_AUTHORIZATION, REST_URL } from '../config.js'
|
||||
import type { Command } from './commands.js'
|
||||
@@ -29,6 +29,7 @@ props.interaction.id = true
|
||||
props.interaction.data = true
|
||||
props.interaction.type = true
|
||||
props.interaction.token = true
|
||||
props.interaction.guildId = true
|
||||
|
||||
props.message.id = true
|
||||
|
||||
@@ -36,6 +37,9 @@ props.message.id = true
|
||||
function createCustomBot<TBot extends Bot = Bot>(rawBot: TBot): CustomBot<TBot> {
|
||||
const bot = rawBot as CustomBot<TBot>
|
||||
|
||||
// We need to set the log depth for the default discordeno logger or else only the first param will be logged
|
||||
;(bot.logger as typeof logger).setDepth(LogDepth.Full)
|
||||
|
||||
bot.commands = new Collection()
|
||||
|
||||
return bot
|
||||
|
||||
@@ -1,21 +1,46 @@
|
||||
import type {
|
||||
ApplicationCommandOptionTypes,
|
||||
ApplicationCommandTypes,
|
||||
Attachment,
|
||||
CamelizedDiscordApplicationCommandOption,
|
||||
ChannelTypes,
|
||||
CreateApplicationCommand,
|
||||
CreateSlashApplicationCommand,
|
||||
Interaction,
|
||||
Member,
|
||||
Role,
|
||||
User,
|
||||
} from '@discordeno/bot'
|
||||
import { bot } from './bot.js'
|
||||
import type { DefaultLocale, TranslationKey } from './languages/languages.js'
|
||||
import { translate } from './languages/translate.js'
|
||||
|
||||
export default function createCommand<const TOptions extends CommandOptions>(command: Command<TOptions>): void {
|
||||
bot.commands.set(command.name, command as Command)
|
||||
bot.commands.set(translate('english', command.name), command as Command)
|
||||
}
|
||||
|
||||
export type Command<TOptions extends CommandOptions = CommandOptions> = CreateApplicationCommand & {
|
||||
export type Command<TOptions extends CommandOptions = CommandOptions> =
|
||||
| SlashApplicationCommand<TOptions>
|
||||
| (Omit<SlashApplicationCommand<TOptions>, 'options' | 'description' | 'descriptionLocalizations'> & {
|
||||
/** The type of the command */
|
||||
type: ApplicationCommandTypes.Message | ApplicationCommandTypes.User
|
||||
})
|
||||
|
||||
// This is needed to properly support ApplicationCommandTypes.Message or ApplicationCommandTypes.User commands
|
||||
type SlashApplicationCommand<TOptions extends CommandOptions> = CreateSlashApplicationCommand & {
|
||||
/**
|
||||
* @remarks
|
||||
* The value should be set to the translation key for the name of this command
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
name: TranslationKey
|
||||
/**
|
||||
* @remarks
|
||||
* The value should be set to the translation key for the name of this command
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
description: TranslationKey
|
||||
/** @inheritdoc */
|
||||
options?: TOptions
|
||||
/**
|
||||
@@ -34,7 +59,23 @@ export type GetCommandOptions<T extends CommandOptions> = T extends CommandOptio
|
||||
? { [Prop in keyof BuildOptions<T> as Prop]: BuildOptions<T>[Prop] }
|
||||
: never
|
||||
|
||||
export type CommandOption = CamelizedDiscordApplicationCommandOption
|
||||
export type CommandOption = CamelizedDiscordApplicationCommandOption & {
|
||||
/**
|
||||
* @remarks
|
||||
* The value should be set to the translation key for the name of this command
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
name: TranslationKey
|
||||
/**
|
||||
* @remarks
|
||||
* The value should be set to the translation key for the name of this command
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
description: TranslationKey
|
||||
}
|
||||
|
||||
export type CommandOptions = CommandOption[]
|
||||
|
||||
// Option parsing
|
||||
@@ -73,7 +114,7 @@ interface TypeToResolvedMap {
|
||||
type ConvertTypeToResolved<T extends ApplicationCommandOptionTypes> = T extends keyof TypeToResolvedMap ? TypeToResolvedMap[T] : ResolvedValues
|
||||
|
||||
type SubCommandApplicationCommand = ApplicationCommandOptionTypes.SubCommand | ApplicationCommandOptionTypes.SubCommandGroup
|
||||
type GetOptionName<T> = T extends { name: string } ? T['name'] : never
|
||||
type GetOptionName<T> = T extends { name: TranslationKey } ? (DefaultLocale[T['name']] extends string ? DefaultLocale[T['name']] : never) : never
|
||||
type GetOptionValue<T> = T extends { type: ApplicationCommandOptionTypes; required?: boolean }
|
||||
? T extends { type: SubCommandApplicationCommand; options?: CommandOptions }
|
||||
? BuildOptions<T['options']>
|
||||
|
||||
47
examples/bigbot-19/src/bot/commands/language.ts
Normal file
47
examples/bigbot-19/src/bot/commands/language.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ApplicationCommandOptionTypes, DiscordInteractionContextType } from '@discordeno/bot'
|
||||
import assert from 'node:assert'
|
||||
import createCommand from '../commands.js'
|
||||
import type { LanguageNames } from '../languages/languages.js'
|
||||
import languages from '../languages/languages.js'
|
||||
import { languageCache, translate } from '../languages/translate.js'
|
||||
import prisma from '../prisma.js'
|
||||
import { updateCommands } from '../utils/updateCommands.js'
|
||||
|
||||
createCommand({
|
||||
name: 'languageCommandName',
|
||||
description: 'languageCommandDescription',
|
||||
// Allow this command only in Guilds, it will not appear in DMs
|
||||
contexts: [DiscordInteractionContextType.Guild],
|
||||
// By default, allow only someone with MANAGE_GUILD to run this command
|
||||
defaultMemberPermissions: ['MANAGE_GUILD'],
|
||||
options: [
|
||||
{
|
||||
name: 'languageCommandOptionName',
|
||||
description: 'languageCommandDescription',
|
||||
type: ApplicationCommandOptionTypes.String,
|
||||
choices: Object.keys(languages).map((key) => ({ name: key, value: key })),
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
async run(interaction, options) {
|
||||
assert(interaction.guildId, '/language - The guildId is missing in the interaction')
|
||||
|
||||
await interaction.defer(true)
|
||||
const language = options.language as LanguageNames
|
||||
|
||||
// Update the db
|
||||
await prisma.guild.upsert({
|
||||
where: { guildId: interaction.guildId },
|
||||
create: { language, guildId: interaction.guildId },
|
||||
update: { language },
|
||||
})
|
||||
|
||||
// Update the cache
|
||||
languageCache.set(interaction.guildId, language)
|
||||
// Update the commands for this guild so they get the new translation
|
||||
await updateCommands(interaction.guildId)
|
||||
|
||||
// Let the user know its been updated.
|
||||
await interaction.respond(translate(interaction.guildId, 'languageCommandUpdated', language))
|
||||
},
|
||||
})
|
||||
@@ -1,16 +1,17 @@
|
||||
import { snowflakeToTimestamp } from '@discordeno/bot'
|
||||
import { bot } from '../bot.js'
|
||||
import createCommand from '../commands.js'
|
||||
import { translate } from '../languages/translate.js'
|
||||
|
||||
createCommand({
|
||||
name: 'ping',
|
||||
description: 'The ping command',
|
||||
name: 'pingCommandName',
|
||||
description: 'pingCommandDescription',
|
||||
async run(interaction) {
|
||||
await interaction.respond(`🏓 Ping?`)
|
||||
await interaction.respond(translate(interaction.guildId, 'pingCommandInitialResponse'))
|
||||
const response = await bot.helpers.getOriginalInteractionResponse(interaction.token)
|
||||
|
||||
const ping = snowflakeToTimestamp(response.id) - snowflakeToTimestamp(interaction.id)
|
||||
|
||||
await interaction.edit(`🏓 Pong! Gateway Latency: TBD, Roundtrip Latency: ${ping}ms. I am online and responsive! 🕙`)
|
||||
await interaction.edit(translate(interaction.guildId, 'pingCommandResponseWithLatencies', 0, ping))
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InteractionTypes, commandOptionsParser } from '@discordeno/bot'
|
||||
import { bot } from '../../bot.js'
|
||||
import { loadLocale } from '../../languages/translate.js'
|
||||
|
||||
bot.events.interactionCreate = async (interaction) => {
|
||||
const isCommandOrAutocomplete =
|
||||
@@ -7,6 +8,10 @@ bot.events.interactionCreate = async (interaction) => {
|
||||
|
||||
if (!interaction.data || !isCommandOrAutocomplete) return
|
||||
|
||||
if (interaction.guildId) {
|
||||
await loadLocale(interaction.guildId)
|
||||
}
|
||||
|
||||
const command = bot.commands.get(interaction.data.name)
|
||||
|
||||
if (!command) {
|
||||
@@ -15,6 +20,7 @@ bot.events.interactionCreate = async (interaction) => {
|
||||
}
|
||||
|
||||
// TODO: log the command was triggered
|
||||
// TODO: handle autocomplete
|
||||
|
||||
const options = commandOptionsParser(interaction)
|
||||
|
||||
|
||||
34
examples/bigbot-19/src/bot/events/raw.ts
Normal file
34
examples/bigbot-19/src/bot/events/raw.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { hasProperty, type DiscordGatewayPayload } from '@discordeno/bot'
|
||||
import { bot } from '../bot.js'
|
||||
import { updateCommands, usesLatestCommands } from '../utils/updateCommands.js'
|
||||
|
||||
bot.events.raw = async (payload) => {
|
||||
// Check if the guild needs a command update
|
||||
const guildId = attemptToGetGuildId(payload)
|
||||
|
||||
if (guildId && !(await usesLatestCommands(guildId))) {
|
||||
await updateCommands(guildId)
|
||||
}
|
||||
}
|
||||
|
||||
function attemptToGetGuildId(payload: DiscordGatewayPayload): bigint | undefined {
|
||||
const data = payload.d
|
||||
|
||||
if (payload.t === 'GUILD_CREATE' || payload.t === 'GUILD_UPDATE') {
|
||||
// Attempt to find the guild_id
|
||||
|
||||
if (typeof data !== 'object' || !data || !hasProperty(data, 'id') || typeof data.id !== 'string') return
|
||||
|
||||
return BigInt(data.id)
|
||||
}
|
||||
|
||||
// Attempt to find the guild_id in another object
|
||||
if (typeof data !== 'object' || !data || !hasProperty(data, 'guild_id') || typeof data.guild_id !== 'string') return
|
||||
|
||||
// The bigint constructor throws an error if you pass in something that isn't a number
|
||||
const isNumber = Number.isInteger(Number.parseInt(data.guild_id))
|
||||
|
||||
if (!isNumber) return undefined
|
||||
|
||||
return BigInt(data.guild_id)
|
||||
}
|
||||
@@ -13,7 +13,9 @@ import {
|
||||
import { bot } from './bot.js'
|
||||
import { buildFastifyApp } from './fastify.js'
|
||||
import importDirectory from './utils/loader.js'
|
||||
import { updateCommands } from './utils/updateCommands.js'
|
||||
|
||||
// Initialize the prisma client
|
||||
import './prisma.js'
|
||||
|
||||
assert(EVENT_HANDLER_AUTHORIZATION, 'The EVENT_HANDLER_AUTHORIZATION environment variable is missing')
|
||||
assert(EVENT_HANDLER_HOST, 'The EVENT_HANDLER_HOST environment variable is missing')
|
||||
@@ -26,8 +28,6 @@ assert(!Number.isNaN(portNumber), 'The EVENT_HANDLER_PORT environment variable s
|
||||
await importDirectory('./dist/bot/commands')
|
||||
await importDirectory('./dist/bot/events')
|
||||
|
||||
await updateCommands()
|
||||
|
||||
if (MESSAGEQUEUE_ENABLE) {
|
||||
await connectToRabbitMQ()
|
||||
}
|
||||
|
||||
27
examples/bigbot-19/src/bot/languages/english.ts
Normal file
27
examples/bigbot-19/src/bot/languages/english.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { LanguageLocale } from './languages.js'
|
||||
|
||||
export default {
|
||||
//
|
||||
// slash command handler
|
||||
//
|
||||
executeCommandNotFound: '❌ Something went wrong. I was not able to find this command.',
|
||||
executeCommandError: '❌ Something went wrong. The command execution has thrown an error.',
|
||||
|
||||
//
|
||||
// /language command
|
||||
//
|
||||
languageCommandName: 'language',
|
||||
languageCommandDescription: '⚙️ Change the bot language.',
|
||||
languageCommandOptionName: 'language',
|
||||
languageCommandOptionDescription: 'What language would you like to set?',
|
||||
languageCommandUpdated: (language: string) => `The language has been updated to ${language}`,
|
||||
|
||||
//
|
||||
// /ping command
|
||||
//
|
||||
pingCommandName: 'ping',
|
||||
pingCommandDescription: '🏓 Check whether the bot is online and responsive.',
|
||||
pingCommandInitialResponse: '🏓 Pong! I am online and responsive! 🕙',
|
||||
pingCommandResponseWithLatencies: (shardLatency, restLatency) =>
|
||||
`🏓 Pong! Gateway Latency: ${shardLatency}ms, Roundtrip Latency: ${restLatency}ms. I am online and responsive! 🕙`,
|
||||
} as const satisfies LanguageLocale
|
||||
41
examples/bigbot-19/src/bot/languages/languages.ts
Normal file
41
examples/bigbot-19/src/bot/languages/languages.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import english from './english.js'
|
||||
// TEMPLATE-SETUP: Import other locales if you have them
|
||||
|
||||
const languages: Record<LanguageNames, LanguageLocale> = {
|
||||
english,
|
||||
}
|
||||
|
||||
export default languages
|
||||
|
||||
// TEMPLATE-SETUP: when adding a new locale, you should add them to this type with an OR
|
||||
export type LanguageNames = 'english'
|
||||
|
||||
// TEMPLATE-SETUP: when adding new translation keys, you should add them to ensure all the locales have a translation
|
||||
// When the translation does not need any parameters you can type it as `string`, if there is the need for parameters you can type is a function that returns a string
|
||||
export interface LanguageLocale {
|
||||
//
|
||||
// slash command handler
|
||||
//
|
||||
executeCommandNotFound: string
|
||||
executeCommandError: string
|
||||
|
||||
//
|
||||
// /language command
|
||||
//
|
||||
languageCommandName: string
|
||||
languageCommandDescription: string
|
||||
languageCommandOptionName: string
|
||||
languageCommandOptionDescription: string
|
||||
languageCommandUpdated: (language: LanguageNames) => string
|
||||
|
||||
//
|
||||
// /ping command
|
||||
//
|
||||
pingCommandName: string
|
||||
pingCommandDescription: string
|
||||
pingCommandInitialResponse: string
|
||||
pingCommandResponseWithLatencies: (shardLatency: number, restLatency: number) => string
|
||||
}
|
||||
|
||||
export type TranslationKey = keyof LanguageLocale
|
||||
export type DefaultLocale = typeof english
|
||||
53
examples/bigbot-19/src/bot/languages/translate.ts
Normal file
53
examples/bigbot-19/src/bot/languages/translate.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Collection } from '@discordeno/bot'
|
||||
import prisma from '../prisma.js'
|
||||
import languages, { type LanguageLocale, type LanguageNames, type TranslationKey } from './languages.js'
|
||||
|
||||
export const languageCache = new Collection<bigint, LanguageNames>()
|
||||
|
||||
export function translate<TKey extends TranslationKey>(
|
||||
guildIdOrLanguage: bigint | LanguageNames | undefined,
|
||||
key: TKey,
|
||||
...params: GetTranslationArguments<TKey>
|
||||
): string {
|
||||
const locale = getLocale(guildIdOrLanguage ?? 'english')
|
||||
const translation = locale[key]
|
||||
|
||||
if (typeof translation === 'function') {
|
||||
// This type cast is needed to avoid TS doing stuff with the statically typed functions union
|
||||
const translationFunction = translation as (...args: unknown[]) => string
|
||||
|
||||
return translationFunction(...params)
|
||||
}
|
||||
|
||||
return translation
|
||||
}
|
||||
|
||||
export function getLocale(guildIdOrLanguage: bigint | LanguageNames): LanguageLocale {
|
||||
return languages[getLanguage(guildIdOrLanguage)]
|
||||
}
|
||||
|
||||
export function getLanguage(guildIdOrLanguage: bigint | LanguageNames): LanguageNames {
|
||||
const language =
|
||||
typeof guildIdOrLanguage === 'string'
|
||||
? // guildIdOrLanguage is actually a language, so we can return it as is
|
||||
guildIdOrLanguage
|
||||
: // guildIdOrLanguage is a guildId, so we need to get from the cache what is the language for that server
|
||||
languageCache.get(guildIdOrLanguage) ?? 'english'
|
||||
|
||||
return language
|
||||
}
|
||||
|
||||
export async function loadLocale(guildId: bigint): Promise<void> {
|
||||
if (languageCache.has(guildId)) return
|
||||
|
||||
const dbLanguage = await prisma.guild.findFirst({
|
||||
where: { guildId },
|
||||
})
|
||||
|
||||
const language = (dbLanguage?.language ?? 'english') as LanguageNames
|
||||
|
||||
// set the cache for the next time
|
||||
languageCache.set(guildId, language)
|
||||
}
|
||||
|
||||
type GetTranslationArguments<TKey extends TranslationKey> = LanguageLocale[TKey] extends (...args: infer U) => unknown ? U : []
|
||||
3
examples/bigbot-19/src/bot/prisma.ts
Normal file
3
examples/bigbot-19/src/bot/prisma.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
export default new PrismaClient()
|
||||
@@ -1,19 +1,112 @@
|
||||
import assert from 'node:assert'
|
||||
import { type CamelizedDiscordApplicationCommandOption, type CreateApplicationCommand, type CreateSlashApplicationCommand } from '@discordeno/bot'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { DEVELOPMENT, DEV_SERVER_ID } from '../../config.js'
|
||||
import { bot } from '../bot.js'
|
||||
import type { Command } from '../commands.js'
|
||||
import type { TranslationKey } from '../languages/languages.js'
|
||||
import { loadLocale, translate } from '../languages/translate.js'
|
||||
import prisma from '../prisma.js'
|
||||
|
||||
export async function updateCommands(): Promise<void> {
|
||||
bot.logger.info('Updating commands')
|
||||
const commandCache = new Map<CreateApplicationCommand, CreateApplicationCommand>()
|
||||
|
||||
const userCommands = bot.commands.filter((x) => !x.devOnly).array()
|
||||
await bot.helpers.upsertGlobalApplicationCommands(userCommands)
|
||||
// TODO: add some comments in this file, it is currently quite hard to understand what is going on
|
||||
|
||||
if (DEVELOPMENT) {
|
||||
assert(DEV_SERVER_ID, 'The DEV_SERVER_ID environment is missing')
|
||||
export async function updateCommands(guildId: bigint): Promise<void> {
|
||||
bot.logger.info(`Updating commands for guildId ${guildId}`)
|
||||
|
||||
bot.logger.info('Updating developer commands')
|
||||
await loadLocale(guildId)
|
||||
|
||||
const devCommands = bot.commands.filter((x) => x.devOnly ?? false).array()
|
||||
await bot.helpers.upsertGuildApplicationCommands(DEV_SERVER_ID, devCommands)
|
||||
const userCommands = bot.commands
|
||||
.filter((x) => (guildId === BigInt(DEV_SERVER_ID ?? -1n) && DEVELOPMENT ? true : !x.devOnly))
|
||||
.array()
|
||||
.map((x) => translateCommands(guildId, x))
|
||||
|
||||
await bot.helpers.upsertGuildApplicationCommands(guildId, userCommands)
|
||||
|
||||
bot.logger.info(`Saving the command hash for guildId ${guildId}`)
|
||||
await prisma.guild.upsert({
|
||||
where: { guildId },
|
||||
create: { guildId, commands: currentCommandHash },
|
||||
update: { commands: currentCommandHash },
|
||||
})
|
||||
}
|
||||
|
||||
let currentCommandHash: string
|
||||
|
||||
const guildCommandHashes = new Map<bigint, string>()
|
||||
|
||||
export async function usesLatestCommands(guildId: bigint): Promise<boolean> {
|
||||
const current = await getCurrentCommandHash(guildId)
|
||||
|
||||
return current === currentCommandHash
|
||||
}
|
||||
|
||||
export async function getCurrentCommandHash(guildId: bigint): Promise<string | null> {
|
||||
if (!currentCommandHash) {
|
||||
const serializedCommands = JSON.stringify(bot.commands.array())
|
||||
currentCommandHash = createHash('sha1').update(serializedCommands).digest('hex')
|
||||
}
|
||||
|
||||
const cached = guildCommandHashes.get(guildId)
|
||||
|
||||
if (cached) return cached
|
||||
|
||||
const commandVersion = await prisma.guild.findFirst({ where: { guildId } })
|
||||
|
||||
if (commandVersion?.commands) guildCommandHashes.set(guildId, commandVersion.commands)
|
||||
|
||||
return commandVersion?.commands ?? null
|
||||
}
|
||||
|
||||
function translateCommands(guildId: bigint, command: Command): CreateApplicationCommand {
|
||||
const cached = commandCache.get(command)
|
||||
|
||||
if (cached) return cached
|
||||
|
||||
// we don't want to modify the original command, so we transform it and copying it in the process
|
||||
const appCommand = transformCommand(command)
|
||||
|
||||
appCommand.name = translate(guildId, appCommand.name as TranslationKey)
|
||||
if ('description' in appCommand) {
|
||||
appCommand.description = translate(guildId, appCommand.description as TranslationKey)
|
||||
|
||||
if (appCommand.options) translateOptions(guildId, appCommand.options)
|
||||
}
|
||||
|
||||
commandCache.set(command, appCommand)
|
||||
|
||||
return appCommand
|
||||
}
|
||||
|
||||
function translateOptions(guildId: bigint, options: CamelizedDiscordApplicationCommandOption[]): void {
|
||||
for (const option of options) {
|
||||
option.name = translate(guildId, option.name as TranslationKey)
|
||||
option.description = translate(guildId, option.description as TranslationKey)
|
||||
|
||||
if (option.options) translateOptions(guildId, option.options)
|
||||
}
|
||||
}
|
||||
|
||||
// Transform our custom Command object to the discordeno CreateApplicationCommand to avoid issues
|
||||
function transformCommand(command: Command): CreateApplicationCommand {
|
||||
const discordPayload = {} as CreateApplicationCommand
|
||||
|
||||
if (command.name) discordPayload.name = command.name
|
||||
if (command.defaultMemberPermissions) discordPayload.defaultMemberPermissions = command.defaultMemberPermissions
|
||||
if (command.dmPermission) discordPayload.dmPermission = command.dmPermission
|
||||
if (command.contexts) discordPayload.contexts = command.contexts
|
||||
if (command.integrationTypes) discordPayload.integrationTypes = command.integrationTypes
|
||||
if (command.nameLocalizations) discordPayload.nameLocalizations = command.nameLocalizations
|
||||
if (command.nsfw) discordPayload.nsfw = command.nsfw
|
||||
if (command.type) discordPayload.type = command.type
|
||||
|
||||
if ('description' in command) {
|
||||
const payload = discordPayload as CreateSlashApplicationCommand
|
||||
|
||||
if (command.description) payload.description = command.description
|
||||
if (command.descriptionLocalizations) payload.descriptionLocalizations = command.descriptionLocalizations
|
||||
if (command.options) payload.options = command.options
|
||||
}
|
||||
|
||||
return discordPayload
|
||||
}
|
||||
|
||||
@@ -102,6 +102,11 @@ function createShard(shardId: number): DiscordenoShard {
|
||||
return await promise
|
||||
}
|
||||
|
||||
// We do not want to camelize the packet, so we need to override the function as the default behavior is to camelize
|
||||
shard.forwardToBot = (packet) => {
|
||||
shard.events.message?.(shard, packet)
|
||||
}
|
||||
|
||||
shard.events.message = async (shard, payload) => {
|
||||
const body = JSON.stringify({ payload, shardId: shard.id })
|
||||
|
||||
|
||||
@@ -224,6 +224,64 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/client@npm:^5.15.0":
|
||||
version: 5.15.0
|
||||
resolution: "@prisma/client@npm:5.15.0"
|
||||
peerDependencies:
|
||||
prisma: "*"
|
||||
peerDependenciesMeta:
|
||||
prisma:
|
||||
optional: true
|
||||
checksum: 6948885425a62fef5a90f039761acbd1f61c9713e8e49f30892244926ad402b3489c0b527d5d6f6469ed1057848dd6d052eec51c99d279d4f956fd021279330f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/debug@npm:5.15.0":
|
||||
version: 5.15.0
|
||||
resolution: "@prisma/debug@npm:5.15.0"
|
||||
checksum: a0464ae8ba938ad611cf00101f8725977e822f48ad8d1ca5cd17b98e586264b8936db79b0b3521d9859651802f73da92508856aea363c9767fa418d749af72b7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/engines-version@npm:5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022":
|
||||
version: 5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022
|
||||
resolution: "@prisma/engines-version@npm:5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022"
|
||||
checksum: 5e4e29c701134e395fb06ddb94471356fa0cee3abc237726443643bf6462e8781d2f8de536f2b5ccdb6c00d9a9a6fb4f9d68e0a8ee744648ebd1924c359e865c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/engines@npm:5.15.0":
|
||||
version: 5.15.0
|
||||
resolution: "@prisma/engines@npm:5.15.0"
|
||||
dependencies:
|
||||
"@prisma/debug": "npm:5.15.0"
|
||||
"@prisma/engines-version": "npm:5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022"
|
||||
"@prisma/fetch-engine": "npm:5.15.0"
|
||||
"@prisma/get-platform": "npm:5.15.0"
|
||||
checksum: bbe3c942793602fc3cfbd9a5b772cdb5cece2b2515a41fe49e503b7c21c76c0215d4c3861d3db29677b5e1bce0baf98e226c9c3239946f3909b8cc72ac422da3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/fetch-engine@npm:5.15.0":
|
||||
version: 5.15.0
|
||||
resolution: "@prisma/fetch-engine@npm:5.15.0"
|
||||
dependencies:
|
||||
"@prisma/debug": "npm:5.15.0"
|
||||
"@prisma/engines-version": "npm:5.15.0-29.12e25d8d06f6ea5a0252864dd9a03b1bb51f3022"
|
||||
"@prisma/get-platform": "npm:5.15.0"
|
||||
checksum: fd3696095cd4e780806655cfd23332235eda51231840467d90d663937583ed5ac2950e4aa193f1586fd19d62028eb52d4ddacff9b0a247f76eb701621e43098e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/get-platform@npm:5.15.0":
|
||||
version: 5.15.0
|
||||
resolution: "@prisma/get-platform@npm:5.15.0"
|
||||
dependencies:
|
||||
"@prisma/debug": "npm:5.15.0"
|
||||
checksum: b969d502aea4a9bb3cae2f751e8c9729f161ebe0442dd44c4a2fd68e0a940111ee133e96e402a7ca9156a231d1e197bdd4966823f8fa610e623a6bae7bb55c8d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@sindresorhus/is@npm:^4.0.0":
|
||||
version: 4.6.0
|
||||
resolution: "@sindresorhus/is@npm:4.6.0"
|
||||
@@ -829,12 +887,14 @@ __metadata:
|
||||
"@discordeno/bot": "npm:19.0.0-next.ad7e74c"
|
||||
"@fastify/multipart": "npm:^8.3.0"
|
||||
"@influxdata/influxdb-client": "npm:^1.33.2"
|
||||
"@prisma/client": "npm:^5.15.0"
|
||||
"@swc/cli": "npm:^0.3.12"
|
||||
"@swc/core": "npm:^1.5.25"
|
||||
"@types/amqplib": "npm:^0.10.5"
|
||||
"@types/node": "npm:^20.14.2"
|
||||
amqplib: "npm:^0.10.4"
|
||||
fastify: "npm:^4.27.0"
|
||||
prisma: "npm:^5.15.0"
|
||||
typescript: "npm:^5.4.5"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -1984,6 +2044,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prisma@npm:^5.15.0":
|
||||
version: 5.15.0
|
||||
resolution: "prisma@npm:5.15.0"
|
||||
dependencies:
|
||||
"@prisma/engines": "npm:5.15.0"
|
||||
bin:
|
||||
prisma: build/index.js
|
||||
checksum: 8baa30db5edcc244adf4c0a6af36f4b5b3e5fd6b15db75bea6ddc636d2ebdbae9c3c2824a7a6dab6eec9630646e2ea9a5140e5806c86963bad6484560dc27b5d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"proc-log@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "proc-log@npm:3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user