-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cmd): convert language to slash command
- Loading branch information
Showing
4 changed files
with
127 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,147 +1,75 @@ | ||
import { ApplyOptions, RequiresGuildContext, RequiresUserPermissions } from "@sapphire/decorators"; | ||
import { Message, EmbedBuilder } from "discord.js"; | ||
import { send } from "@sapphire/plugin-editable-commands"; | ||
import { ApplyOptions } from "@sapphire/decorators"; | ||
import { EmbedBuilder, InteractionContextType, StringSelectMenuBuilder, ActionRowBuilder } from "discord.js"; | ||
import PayloadColors from "#utils/colors"; | ||
import { inlineCode } from "@discordjs/builders"; | ||
import { LanguageKeys } from "#lib/i18n/all"; | ||
import { Subcommand, type SubcommandMappingArray } from "@sapphire/plugin-subcommands"; | ||
import { Args, CommandOptionsRunTypeEnum } from "@sapphire/framework"; | ||
import { fetchT } from "@sapphire/plugin-i18next"; | ||
import { Subcommand } from "@sapphire/plugin-subcommands"; | ||
import { Command, CommandOptionsRunTypeEnum } from "@sapphire/framework"; | ||
import { fetchT, getLocalizedData } from "@sapphire/plugin-i18next"; | ||
import { PermissionFlagsBits } from "discord-api-types/v10"; | ||
import { guild } from "#root/drizzle/schema"; | ||
import { eq } from "drizzle-orm"; | ||
import { isNullishOrEmpty } from "@sapphire/utilities"; | ||
import { PayloadCommand } from "#lib/structs/commands/PayloadCommand"; | ||
|
||
@ApplyOptions<Subcommand.Options>({ | ||
description: LanguageKeys.Commands.Language.Description, | ||
detailedDescription: LanguageKeys.Commands.Language.DetailedDescription, | ||
runIn: [CommandOptionsRunTypeEnum.GuildText], | ||
}) | ||
export class UserCommand extends Subcommand { | ||
private readonly database = this.container.database; | ||
private readonly t = async (msg: Message) => await fetchT(msg); | ||
|
||
readonly subcommandMappings: SubcommandMappingArray = [ | ||
{ | ||
name: "view", | ||
type: "method", | ||
messageRun: msg => this.view(msg), | ||
default: true, | ||
}, | ||
{ | ||
name: "set", | ||
type: "method", | ||
messageRun: (msg, args) => this.set(msg, args), | ||
}, | ||
{ | ||
name: "update", | ||
type: "method", | ||
messageRun: (msg, args) => this.set(msg, args), | ||
}, | ||
{ | ||
name: "delete", | ||
type: "method", | ||
messageRun: msg => this.delete(msg), | ||
}, | ||
{ | ||
name: "remove", | ||
type: "method", | ||
messageRun: msg => this.delete(msg), | ||
}, | ||
]; | ||
|
||
@RequiresGuildContext() | ||
async view(msg: Message) { | ||
const [g] = await this.database.select({ language: guild.language }).from(guild).where(eq(guild.id, msg.guildId)); | ||
|
||
const t = await this.t(msg); | ||
|
||
const content = t(LanguageKeys.Commands.Language.CurrentLanguage, { | ||
language: inlineCode(g?.language ?? "en-US"), | ||
}); | ||
|
||
return await send(msg, content); | ||
} | ||
|
||
@RequiresGuildContext() | ||
@RequiresUserPermissions([PermissionFlagsBits.Administrator]) | ||
async set(msg: Message, args: Args) { | ||
const [g] = await this.database.select({ language: guild.language }).from(guild).where(eq(guild.id, msg.guildId)); | ||
|
||
const language = await args | ||
.pick("enum", { enum: ["en-US", "es-ES", "fi-FI", "pl-PL", "ru-RU", "de-DE"] }) | ||
.catch(() => null); | ||
|
||
const t = await this.t(msg); | ||
|
||
if (isNullishOrEmpty(language)) { | ||
return await send(msg, t(LanguageKeys.Commands.Language.SetNeedsArgs)); | ||
} | ||
|
||
if (g?.language === language) { | ||
return await send(msg, t(LanguageKeys.Commands.Language.SetSameLanguage)); | ||
} | ||
|
||
await this.database | ||
.update(guild) | ||
.set({ | ||
language, | ||
}) | ||
.where(eq(guild.id, msg.guildId)); | ||
export class UserCommand extends PayloadCommand { | ||
async chatInputRun(interaction: Command.ChatInputCommandInteraction) { | ||
const t = await fetchT(interaction); | ||
|
||
const [g] = await this.database | ||
.select({ language: guild.language }) | ||
.from(guild) | ||
.where(eq(guild.id, interaction.guildId)); | ||
|
||
const languageSelector = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents( | ||
new StringSelectMenuBuilder() | ||
.addOptions( | ||
{ label: t(LanguageKeys.Commands.Language.English), value: "en-US" }, | ||
{ label: t(LanguageKeys.Commands.Language.Spanish), value: "es-ES" }, | ||
{ label: t(LanguageKeys.Commands.Language.German), value: "de" }, | ||
{ label: t(LanguageKeys.Commands.Language.Finnish), value: "fi" }, | ||
{ label: t(LanguageKeys.Commands.Language.French), value: "fr" }, | ||
{ label: t(LanguageKeys.Commands.Language.Russian), value: "ru" }, | ||
{ label: t(LanguageKeys.Commands.Language.Polish), value: "pl" }, | ||
) | ||
.setCustomId("set-guild-language") | ||
.setPlaceholder(t(LanguageKeys.Commands.Language.SelectLanguage)), | ||
); | ||
|
||
const embed = new EmbedBuilder({ | ||
author: { | ||
name: msg.author.tag, | ||
iconURL: msg.author.displayAvatarURL(), | ||
}, | ||
title: t(LanguageKeys.Commands.Language.SetLanguageEmbedTitle, { | ||
user: msg.author.tag, | ||
}), | ||
description: t(LanguageKeys.Commands.Language.SetLanguageEmbedDesc, { | ||
old: inlineCode(g?.language ?? "en-US"), | ||
new: inlineCode(language), | ||
title: t(LanguageKeys.Commands.Language.SelectLanguage), | ||
description: t(LanguageKeys.Commands.Language.CurrentLanguage, { | ||
language: inlineCode(g?.language ?? "en-US"), | ||
}), | ||
timestamp: new Date(), | ||
color: PayloadColors.Admin, | ||
}); | ||
|
||
return await send(msg, { embeds: [embed] }); | ||
} | ||
|
||
@RequiresGuildContext() | ||
@RequiresUserPermissions([PermissionFlagsBits.Administrator]) | ||
async delete(msg: Message) { | ||
const [g] = await this.database.select({ language: guild.language }).from(guild).where(eq(guild.id, msg.guildId)); | ||
|
||
const t = await this.t(msg); | ||
|
||
if (g == null || g.language === "en-US") { | ||
return await send(msg, t(LanguageKeys.Commands.Language.DeleteAlreadyDefault)); | ||
} | ||
|
||
const embed = new EmbedBuilder({ | ||
author: { | ||
name: msg.author.tag, | ||
iconURL: msg.author.displayAvatarURL(), | ||
}, | ||
title: t(LanguageKeys.Commands.Language.SetLanguageEmbedTitle, { | ||
user: msg.author.tag, | ||
}), | ||
description: t(LanguageKeys.Commands.Language.SetLanguageEmbedDesc, { | ||
old: inlineCode(g.language ?? "en-US"), | ||
new: inlineCode("en-US"), | ||
}), | ||
timestamp: new Date(), | ||
color: PayloadColors.Admin, | ||
await interaction.reply({ | ||
embeds: [embed], | ||
components: [languageSelector], | ||
ephemeral: true, | ||
}); | ||
|
||
await this.database | ||
.update(guild) | ||
.set({ | ||
language: "en-US", | ||
}) | ||
.where(eq(guild.id, msg.guildId)); | ||
return; | ||
} | ||
|
||
return await send(msg, { embeds: [embed] }); | ||
public override registerApplicationCommands(registry: Command.Registry) { | ||
const rootNameLocalizations = getLocalizedData(LanguageKeys.Commands.Language.Name); | ||
const rootDescriptionLocalizations = getLocalizedData(this.description); | ||
|
||
registry.registerChatInputCommand(builder => | ||
builder | ||
.setName(this.name) | ||
.setDescription(rootDescriptionLocalizations.localizations["en-US"]) | ||
.setContexts(InteractionContextType.Guild) | ||
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator) | ||
.setDescriptionLocalizations(rootDescriptionLocalizations.localizations) | ||
.setNameLocalizations(rootNameLocalizations.localizations), | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { LanguageKeys } from "#lib/i18n/all/index"; | ||
import { guild } from "#root/drizzle/schema"; | ||
import { InteractionHandler, InteractionHandlerTypes } from "@sapphire/framework"; | ||
import { fetchT } from "@sapphire/plugin-i18next"; | ||
import { isNullOrUndefinedOrEmpty } from "@sapphire/utilities"; | ||
import { inlineCode, type ButtonInteraction, type StringSelectMenuInteraction } from "discord.js"; | ||
|
||
export class ButtonHandler extends InteractionHandler { | ||
public constructor(ctx: InteractionHandler.LoaderContext, options: InteractionHandler.Options) { | ||
super(ctx, { | ||
...options, | ||
interactionHandlerType: InteractionHandlerTypes.SelectMenu, | ||
}); | ||
} | ||
|
||
public override parse(interaction: ButtonInteraction) { | ||
if (interaction.customId !== "set-guild-language") { | ||
return this.none(); | ||
} | ||
|
||
return this.some(); | ||
} | ||
|
||
public async run(interaction: StringSelectMenuInteraction) { | ||
const t = await fetchT(interaction); | ||
const value = interaction.values[0]; | ||
|
||
if (isNullOrUndefinedOrEmpty(value)) { | ||
return; | ||
} | ||
|
||
await this.container.database | ||
.insert(guild) | ||
.values({ | ||
id: interaction.guildId, | ||
language: value, | ||
}) | ||
.onConflictDoUpdate({ | ||
set: { | ||
language: value, | ||
}, | ||
target: guild.id, | ||
}); | ||
|
||
await interaction.reply({ | ||
content: t(LanguageKeys.Commands.Language.SetLanguage, { language: inlineCode(value) }), | ||
ephemeral: true, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,18 @@ | ||
{ | ||
"description": "Manages your language for this server", | ||
"name": "language", | ||
"description": "Manage the language for the server", | ||
"detailedDescription": { | ||
"usages": [ | ||
"", | ||
"set <new language>", | ||
"update <new language>", | ||
"delete", | ||
"reset", | ||
"view" | ||
], | ||
"details": "Manage your server's language. You may reset it at anytime using the `delete` or `reset` arguments." | ||
"usages": [], | ||
"details": "Manage your server's language" | ||
}, | ||
"currentLanguage": "The current language is: {{ language }}", | ||
"setNeedsArgs": "Please specify a new language!", | ||
"setSameLanguage": "Your new language is the same as the old language!", | ||
"setLanguageEmbedTitle": "Language updated by {{ user }}", | ||
"setLanguageEmbedDesc": "Language updated from {{ old }} to {{ new }}", | ||
"deleteAlreadyDefault": "Your language is already the default!" | ||
"selectLanguage": "Select a language", | ||
"setLanguage": "New language set: {{ language }}", | ||
"english": "English", | ||
"spanish": "Spanish", | ||
"german": "German", | ||
"finnish": "Finnish", | ||
"french": "French", | ||
"polish": "Polish", | ||
"russian": "Russian" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,17 @@ | ||
import { FT, T } from "#lib/types/index"; | ||
|
||
export const Name = T<string>("commands/language:name"); | ||
export const Description = T<string>("commands/language:description"); | ||
export const DetailedDescription = T<string>("commands/language:detailedDescription"); | ||
export const CurrentLanguage = FT<{ language: string }, string>("commands/language:currentLanguage"); | ||
export const SetNeedsArgs = T<string>("commands/language:setNeedsArgs"); | ||
export const SetSameLanguage = T<string>("commands/language:setSamelanguage"); | ||
export const SetLanguageEmbedTitle = FT<{ user: string }, string>("commands/language:setLanguageEmbedTitle"); | ||
export const SetLanguageEmbedDesc = FT<{ old: string; new: string }, string>("commands/language:setLanguageEmbedDesc"); | ||
export const DeleteAlreadyDefault = T<string>("commands/language:deleteAlreadyDefault"); | ||
|
||
export const SelectLanguage = T<string>("commands/language:selectLanguage"); | ||
export const SetLanguage = FT<string, { language: string }>("commands/language:setLanguage"); | ||
export const CurrentLanguage = FT<string, { language: string }>("commands/language:currentLanguage"); | ||
|
||
export const English = T<string>("commands/language:english"); | ||
export const Spanish = T<string>("commands/language:spanish"); | ||
export const German = T<string>("commands/language:german"); | ||
export const Finnish = T<string>("commands/language:finnish"); | ||
export const French = T<string>("commands/language:french"); | ||
export const Polish = T<string>("commands/language:polish"); | ||
export const Russian = T<string>("commands/language:russian"); |