diff --git a/sorusora/locales/en/commands/help.ftl b/sorusora/locales/en/commands/help.ftl index d3a9907..cea63ae 100644 --- a/sorusora/locales/en/commands/help.ftl +++ b/sorusora/locales/en/commands/help.ftl @@ -3,7 +3,7 @@ help-name = help help-description = Teach you how to use { $help-description-name } # Texts -help-header = To chat with { $help-header-name }, either mention her or reply to a message from her. +help-header = To learn how to chat with { $help-header-name }, use `/chat tutorial`. commands = Commands context-menus = Context Menus context-menus-description = Commands that can be used without typing anything. To use them, diff --git a/sorusora/locales/ko/commands/help.ftl b/sorusora/locales/ko/commands/help.ftl index d4fdf37..e4c9ac1 100644 --- a/sorusora/locales/ko/commands/help.ftl +++ b/sorusora/locales/ko/commands/help.ftl @@ -3,7 +3,7 @@ help-name = 도움말 help-description = { $help-description-name }을(를) 사용하는 방법을 알려줍니다. # Texts -help-header = { $help-header-name }과(와) 대화하려면, 그녀를 언급하거나 그녀가 보낸 메세지에 답장을 보내세요. +help-header = { $help-header-name }와(과) 채팅하는 방법을 배우려면 `/채팅 사용법`을 사용하세요. commands = 명령어 목록 context-menus = 컨텍스트 메뉴 목록 context-menus-description = 타이핑 할 필요 없이 사용할 수 있는 명령어 입니다. 사용하려면 diff --git a/sorusora/locales/zh-TW/commands/help.ftl b/sorusora/locales/zh-TW/commands/help.ftl index 9b80efd..a56ead6 100644 --- a/sorusora/locales/zh-TW/commands/help.ftl +++ b/sorusora/locales/zh-TW/commands/help.ftl @@ -3,7 +3,7 @@ help-name = 幫助 help-description = { $help-description-name } 指令列表 # Texts -help-header = 要與 { $help-header-name } 聊天,請提及她或回覆她的訊息。 +help-header = 想要學習如何與 { $help-header-name } 對話,請使用`/聊天 用法`。 commands = 可用指令 context-menus = 指令選單 context-menus-description = 您可以不用輸入任何指令,只需透過 diff --git a/sorusora/src/commands/__init__.py b/sorusora/src/commands/__init__.py index 1bf01a4..1668368 100644 --- a/sorusora/src/commands/__init__.py +++ b/sorusora/src/commands/__init__.py @@ -4,23 +4,53 @@ Functions: update_locale """ - +import os +from typing import Callable, Coroutine from discord import app_commands, Interaction from mongo.user import get_user, set_user +from utils import Localization +from utils.constants import LOCALES_DIR +from utils.translator import DEFAULT_LANGUAGE def update_locale(): """ - A decorator to update the locale of the user who is using the command + A decorator to update the locale of the user """ async def predicate(interaction: Interaction): user = await get_user(interaction.user.id) user.locale = str(interaction.locale) await set_user(user) - return True return app_commands.check(predicate) + + +def command(*, nsfw: bool = False, **params: str): + """ + A decorator to create a command + + :param nsfw: Whether the command is NSFW + :param params: The parameters for the localization + """ + + args = {} + for key, value in params.items(): + args[key.replace("_", "-")] = value + + loc = Localization(DEFAULT_LANGUAGE, [ + os.path.join("commands", name) for name in os.listdir(LOCALES_DIR / DEFAULT_LANGUAGE.code / "commands") + ]) + + def decorator(func: Callable[..., Coroutine]): + return update_locale()(app_commands.command( + name=loc.format_value(f"{func.__name__.strip('_').replace('_', '-')}-name", args), + description=loc.format_value(f"{func.__name__.strip('_').replace('_', '-')}-description", args), + nsfw=nsfw, + extras=args + ))(func) + + return decorator diff --git a/sorusora/src/commands/about.py b/sorusora/src/commands/about.py index 8a749a1..02151a3 100644 --- a/sorusora/src/commands/about.py +++ b/sorusora/src/commands/about.py @@ -5,13 +5,13 @@ import os import discord -from discord import app_commands, Interaction, ButtonStyle +from discord import Interaction, ButtonStyle from discord.ui import Button -from commands import update_locale +from commands import command from utils import defer_response from utils.constants import BOT_NAME, ABOUT_DIR, BUG_REPORT_URL, GITHUB_URL, INVITE_URL -from utils.translator import Localization, DEFAULT_LANGUAGE, Language, Cache, format_localization +from utils.translator import Localization, DEFAULT_LANGUAGE, Language, Cache resources = [os.path.join("commands", "about.ftl")] default_loc = Localization(DEFAULT_LANGUAGE, resources) @@ -21,7 +21,7 @@ def get_about_dir(language: Language) -> str: """ Get the 'about' directory for a language """ - code = language.code if Localization.has(language.code) else language.trim_territory(language.code) + code = language.code if Localization.has(language.code) else language.trim_territory() return str(os.path.join(ABOUT_DIR, f"{code}.md")) @@ -66,11 +66,7 @@ async def init(self): return self -@format_localization(about_description_name=BOT_NAME) -@app_commands.command(name=default_loc.format_value("about-name"), - description=default_loc.format_value("about-description", - {"about-description-name": BOT_NAME})) -@update_locale() +@command(about_description_name=BOT_NAME) async def about(interaction: Interaction): """Show information about the bot""" diff --git a/sorusora/src/commands/arcaea.py b/sorusora/src/commands/arcaea.py index 3da9dde..55a258f 100644 --- a/sorusora/src/commands/arcaea.py +++ b/sorusora/src/commands/arcaea.py @@ -10,10 +10,10 @@ from discord import app_commands, Interaction, Forbidden, Locale from discord.ext.commands import Bot -from commands import update_locale +from commands import command from utils import templates, ui, defer_response from utils.templates import error, success, info, warning -from utils.translator import Localization, format_localization, DEFAULT_LANGUAGE +from utils.translator import Localization, DEFAULT_LANGUAGE LINK_PLAY_LIFESPAN_MINUTES = 30 LINK_PLAY_LIFESPAN = datetime.timedelta(minutes=LINK_PLAY_LIFESPAN_MINUTES) @@ -183,13 +183,8 @@ def __init__(self, bot: Bot): description=default_loc.format_value("arcaea-description")) self.bot = bot - @format_localization(linkplay_description_duration=LINK_PLAY_LIFESPAN_MINUTES) - @app_commands.command(name=default_loc.format_value("linkplay-name"), - description=default_loc.format_value("linkplay-description", { - "linkplay-description-duration": LINK_PLAY_LIFESPAN_MINUTES - })) + @command(linkplay_description_duration=str(LINK_PLAY_LIFESPAN_MINUTES)) @app_commands.describe(roomcode=default_loc.format_value("linkplay-roomcode-description")) - @update_locale() async def linkplay(self, interaction: Interaction, roomcode: str): """ Create an embed to invite people to your Link Play diff --git a/sorusora/src/commands/channel.py b/sorusora/src/commands/channel.py index 029b111..aecea7a 100644 --- a/sorusora/src/commands/channel.py +++ b/sorusora/src/commands/channel.py @@ -6,12 +6,12 @@ from discord import app_commands, Interaction -from commands import update_locale +from commands import command from commands.translator import TranslatorLanguageSelectView, TranslatorChannelSelect from mongo.channel import set_channel, get_channel from utils import defer_response from utils.templates import success, error -from utils.translator import format_localization, Localization, DEFAULT_LANGUAGE +from utils.translator import Localization, DEFAULT_LANGUAGE from utils.ui import SubmitButton resources = [os.path.join("commands", "channel.ftl")] @@ -32,14 +32,11 @@ def __init__(self, bot): self.bot = bot - @format_localization(translator_this_description_default=str(THIS_DEFAULT)) - @app_commands.command(name=default_loc.format_value("translator-name"), - description=default_loc.format_value("translator-description")) + @command(translator_this_description_default=str(THIS_DEFAULT)) @app_commands.describe(this=default_loc.format_value( "translator-this-description", {"translator-this-description-default": str(THIS_DEFAULT)}) ) - @update_locale() async def translator(self, interaction: Interaction, this: bool = THIS_DEFAULT): """ Set or remove the languages to be translated for channels @@ -73,14 +70,11 @@ async def on_submit(interaction: Interaction): await send(view=language_view, ephemeral=True) - @format_localization(language_this_description_default=THIS_DEFAULT) - @app_commands.command(name=default_loc.format_value("language-name"), - description=default_loc.format_value("language-description")) + @command(language_this_description_default=str(THIS_DEFAULT)) @app_commands.describe(this=default_loc.format_value( "language-this-description", {"language-this-description-default": str(THIS_DEFAULT)}) ) - @update_locale() async def language(self, interaction: Interaction, this: bool = THIS_DEFAULT): """ Set or remove the main language of the channels. diff --git a/sorusora/src/commands/chat.py b/sorusora/src/commands/chat.py index 10fb4d2..358933e 100644 --- a/sorusora/src/commands/chat.py +++ b/sorusora/src/commands/chat.py @@ -22,14 +22,14 @@ import mongo import mongo.chat -from commands import update_locale +from commands import command from commands.help_ import get_help_dir from mongo.chat import get_chat, set_chat from mongo.user import get_user from utils import defer_response from utils.constants import BOT_NAME, DEVELOPER_NAME from utils.templates import success, error -from utils.translator import Language, Localization, DEFAULT_LANGUAGE, format_localization, Cache +from utils.translator import Language, Localization, DEFAULT_LANGUAGE, Cache resources = [os.path.join("commands", "chat.ftl"), Localization.get_resource()] default_loc = Localization(DEFAULT_LANGUAGE, resources) @@ -332,11 +332,7 @@ async def _extend_history(chat: mongo.chat.Chat, messages: Iterable[Message]): ]) await set_chat(chat) - @format_localization(clear_description_name=BOT_NAME) - @app_commands.command(name=default_loc.format_value("clear-name"), - description=default_loc.format_value("clear-description", - {"clear-description-name": BOT_NAME})) - @update_locale() + @command(clear_description_name=BOT_NAME) async def clear(self, interaction: Interaction): """ Clear the chat history between you and this bot @@ -350,10 +346,7 @@ async def clear(self, interaction: Interaction): await set_chat(chat) await send(success(await loc.format_value_or_translate("deleted")), ephemeral=True) - @app_commands.command(name=default_loc.format_value("token-name"), - description=default_loc.format_value("token-description")) - @app_commands.describe(value=default_loc.format_value("token-value-description")) - @update_locale() + @command() async def token(self, interaction: Interaction, value: Optional[str]): """ Set the token for the chat @@ -390,11 +383,7 @@ async def token(self, interaction: Interaction, value: Optional[str]): await send(success(await loc.format_value_or_translate("token-set")), ephemeral=True) - @format_localization(tutorial_description_name=BOT_NAME) - @app_commands.command(name=default_loc.format_value("tutorial-name"), - description=default_loc.format_value("tutorial-description", - {"tutorial-description-name": BOT_NAME})) - @update_locale() + @command(tutorial_description_name=BOT_NAME) async def tutorial(self, interaction: Interaction): """ Get the tutorial for the chat diff --git a/sorusora/src/commands/dashboard.py b/sorusora/src/commands/dashboard.py index 820287b..b51e49b 100644 --- a/sorusora/src/commands/dashboard.py +++ b/sorusora/src/commands/dashboard.py @@ -4,10 +4,10 @@ import os -from discord import app_commands, Interaction, Embed, ChannelType +from discord import Interaction, Embed, ChannelType from discord.ui import View, ChannelSelect -from commands import update_locale +from commands import command from mongo.channel import get_channel from mongo.user import get_user from utils import Localization, defer_response @@ -20,11 +20,7 @@ default_loc = Localization(DEFAULT_LANGUAGE, resources) -@app_commands.command( - name=default_loc.format_value("dashboard-name"), - description=default_loc.format_value("dashboard-description") -) -@update_locale() +@command() async def dashboard(interaction: Interaction): """ Display the dashboard that contains the current configurations and statistics @@ -81,7 +77,7 @@ async def callback(select_interaction: Interaction): inline=False ) .add_field( - name=await loc.format_value_or_translate("language"), + name=await loc.format_value_or_translate("main-language"), value=main_language, inline=False )) diff --git a/sorusora/src/commands/dice.py b/sorusora/src/commands/dice.py index 6f5d66d..346afcd 100644 --- a/sorusora/src/commands/dice.py +++ b/sorusora/src/commands/dice.py @@ -6,18 +6,14 @@ from random import choice import discord -from discord import app_commands -from commands import update_locale +from commands import command from utils.translator import Localization, DEFAULT_LANGUAGE resources = [os.path.join("commands", "dice.ftl")] default_loc = Localization(DEFAULT_LANGUAGE, resources) - -@app_commands.command(name=default_loc.format_value("dice-name"), - description=default_loc.format_value("dice-description")) -@update_locale() +@command() async def dice(interaction: discord.Interaction): """Roll some dice""" diff --git a/sorusora/src/commands/help_.py b/sorusora/src/commands/help_.py index 13f0e9a..752af7a 100644 --- a/sorusora/src/commands/help_.py +++ b/sorusora/src/commands/help_.py @@ -6,11 +6,11 @@ from discord import app_commands, Interaction, AppCommandType from discord.ui import View -from commands import update_locale +from commands import command from commands.movie import Movie from utils import defer_response from utils.constants import BOT_NAME, HELP_DIR -from utils.translator import Cache, format_localization +from utils.translator import Cache from utils.translator import Localization, Language, DEFAULT_LANGUAGE from utils.ui import CommandSelect @@ -48,11 +48,7 @@ async def callback(self, interaction: Interaction): await interaction.response.send_message(Cache.get(Language(str(interaction.locale)), text).text, ephemeral=True) -@format_localization(help_description_name=BOT_NAME, help_header_name=BOT_NAME) -@app_commands.command(name=default_loc.format_value("help-name"), - description=default_loc.format_value("help-description", - {"help-description-name": BOT_NAME})) -@update_locale() +@command(help_description_name=BOT_NAME, help_header_name=BOT_NAME) async def help_(interaction: Interaction): """ Show the help message @@ -72,18 +68,18 @@ async def help_(interaction: Interaction): text += await loc.format_value_or_translate("help-header", {"help-header-name": BOT_NAME}) + "\n" text += f"## {await loc.format_value_or_translate('commands')}\n" - for command in bot.tree.walk_commands(): - if isinstance(command, app_commands.Group) or command.root_parent.__class__ in HIDDEN_COMMANDS: + for cmd in bot.tree.walk_commands(): + if isinstance(cmd, app_commands.Group) or cmd.root_parent.__class__ in HIDDEN_COMMANDS: continue if loc.language == DEFAULT_LANGUAGE: - text += f"* `/{command.qualified_name}`: {command.description}\n" + text += f"* `/{cmd.qualified_name}`: {cmd.description}\n" else: - translated_name = command.qualified_name.split(" ") + translated_name = cmd.qualified_name.split(" ") for i, name in enumerate(translated_name): translated_name[i] = await interaction.translate(name) - text += f"* `/{' '.join(translated_name)}`: {await interaction.translate(command.description)}\n" + text += f"* `/{' '.join(translated_name)}`: {await interaction.translate(cmd.description)}\n" text += (f"## {await loc.format_value_or_translate('context-menus')}\n" f"{await loc.format_value_or_translate('context-menus-description')} ") diff --git a/sorusora/src/commands/movie.py b/sorusora/src/commands/movie.py index fc64a7d..2da9da5 100644 --- a/sorusora/src/commands/movie.py +++ b/sorusora/src/commands/movie.py @@ -15,10 +15,10 @@ from discord.ext.commands import Bot from tqdm import tqdm -from commands import update_locale +from commands import command from utils import templates, constants from utils.constants import ErrorCode -from utils.translator import Localization, DEFAULT_LANGUAGE, format_localization +from utils.translator import Localization, DEFAULT_LANGUAGE DESKTOP_CACHE_PATH = constants.CACHE_DIR / "movie" / "desktop" """ @@ -159,12 +159,10 @@ async def get_frames(name: str, is_on_mobile: bool) -> list[str]: return Movie._cache[path] - @format_localization(play_fps_description_min=FPS_MIN, - play_fps_description_max=FPS_MAX, - play_fps_description_default=FPS_DEFAULT, - play_original_speed_description_default=str(ORIGINAL_SPEED_DEFAULT)) - @app_commands.command(name=default_loc.format_value("play-name"), - description=default_loc.format_value("play-description")) + @command(play_fps_description_min=str(FPS_MIN), + play_fps_description_max=str(FPS_MAX), + play_fps_description_default=str(FPS_DEFAULT), + play_original_speed_description_default=str(ORIGINAL_SPEED_DEFAULT)) @app_commands.describe(title=default_loc.format_value("play-title-description")) @app_commands.describe(fps=default_loc.format_value("play-fps-description", { "play-fps-description-min": FPS_MIN, @@ -179,7 +177,6 @@ async def get_frames(name: str, is_on_mobile: bool) -> list[str]: Choice(name="ULTRA B+K", value="ultra_bk") ]) @app_commands.choices(fps=[Choice(name=str(i), value=i) for i in range(FPS_MIN, FPS_MAX + 1)]) - @update_locale() async def play(self, interaction: Interaction, title: Choice[str], fps: int = FPS_DEFAULT, diff --git a/sorusora/src/commands/ping.py b/sorusora/src/commands/ping.py index 38464f4..de36b77 100644 --- a/sorusora/src/commands/ping.py +++ b/sorusora/src/commands/ping.py @@ -4,22 +4,18 @@ import os import discord -from discord import app_commands -from commands import update_locale +from commands import command from utils import defer_response from utils.constants import BOT_NAME from utils.templates import info -from utils.translator import Localization, DEFAULT_LANGUAGE, format_localization +from utils.translator import Localization, DEFAULT_LANGUAGE resources = [os.path.join("commands", "ping.ftl")] default_loc = Localization(DEFAULT_LANGUAGE, resources) -@format_localization(ping_description_name=BOT_NAME) -@app_commands.command(name=default_loc.format_value("ping-name"), - description=default_loc.format_value("ping-description", {"ping-description-name": BOT_NAME})) -@update_locale() +@command(ping_description_name=BOT_NAME) async def ping(interaction: discord.Interaction): """Ping this bot""" diff --git a/sorusora/src/commands/translator.py b/sorusora/src/commands/translator.py index 529ea44..fa9f557 100644 --- a/sorusora/src/commands/translator.py +++ b/sorusora/src/commands/translator.py @@ -9,13 +9,13 @@ from discord import app_commands, Interaction, Message, Embed, HTTPException, Locale from discord.ext.commands import Bot -from commands import update_locale +from commands import command from mongo.channel import get_channel from mongo.user import get_user, set_user from utils import defer_response, templates from utils.constants import ErrorCode, Limit from utils.templates import success -from utils.translator import Localization, Language, DEFAULT_LANGUAGE, get_translator, format_localization +from utils.translator import Localization, Language, DEFAULT_LANGUAGE, get_translator from utils.ui import LanguageSelectView, ChannelSelect, SubmitButton resources = [os.path.join("commands", "translator.ftl")] @@ -143,14 +143,11 @@ def _split(string: str, count: int): yield string[i: i + count] -@format_localization(translator_all_channels_description_default=str(ALL_CHANNELS_DEFAULT)) -@app_commands.command(name=default_loc.format_value("translator-name"), - description=default_loc.format_value("translator-description")) +@command(translator_all_channels_description_default=str(ALL_CHANNELS_DEFAULT)) @app_commands.describe(all_channels=default_loc.format_value( "translator-all-channels-description", {"translator-all-channels-description-default": str(ALL_CHANNELS_DEFAULT)}) ) -@update_locale() async def translator(interaction: Interaction, all_channels: bool = ALL_CHANNELS_DEFAULT): """ Set or remove the languages to be translated for your messages diff --git a/sorusora/src/context_menus/__init__.py b/sorusora/src/context_menus/__init__.py index e69de29..be0943d 100644 --- a/sorusora/src/context_menus/__init__.py +++ b/sorusora/src/context_menus/__init__.py @@ -0,0 +1,36 @@ +import os +from typing import Callable, Coroutine + +from discord import app_commands + +from commands import update_locale +from utils import Localization +from utils.constants import LOCALES_DIR +from utils.translator import DEFAULT_LANGUAGE + + +def context_menu(*, nsfw: bool = False, **param: str): + """ + A decorator to create a context menu + + :param nsfw: Whether the context menu is NSFW + :param params: The parameters for the localization + """ + + args = {} + for key, value in param.items(): + args[key.replace("_", "-")] = value + + loc = Localization(DEFAULT_LANGUAGE, [ + os.path.join("context_menus", name) + for name in os.listdir(LOCALES_DIR / DEFAULT_LANGUAGE.code / "context_menus") + ]) + + def decorator(func: Callable[..., Coroutine]): + return update_locale()(app_commands.context_menu( + name=loc.format_value(f"{func.__name__.strip('_').replace('_', '-')}-name", args), + nsfw=nsfw, + extras=args + ))(func) + + return decorator diff --git a/sorusora/src/context_menus/translate_message.py b/sorusora/src/context_menus/translate_message.py index 03877b5..63e4673 100644 --- a/sorusora/src/context_menus/translate_message.py +++ b/sorusora/src/context_menus/translate_message.py @@ -4,9 +4,9 @@ import os import discord -from discord import app_commands import utils.translator +from context_menus import context_menu from utils import defer_response from utils.templates import error from utils.translator import Localization, DEFAULT_LANGUAGE, Language @@ -15,7 +15,7 @@ default_loc = Localization(DEFAULT_LANGUAGE, resources) -@app_commands.context_menu(name=default_loc.format_value("translate-message-name")) +@context_menu() async def translate_message(interaction: discord.Interaction, message: discord.Message): """Translate this message into your language""" diff --git a/sorusora/src/utils/translator.py b/sorusora/src/utils/translator.py index a89f322..ff27f42 100644 --- a/sorusora/src/utils/translator.py +++ b/sorusora/src/utils/translator.py @@ -663,25 +663,6 @@ def resources(self) -> list[str]: return self._loc.resource_ids -def format_localization(**kwargs: Any): - """ - Format the localization of the command - - :param kwargs: The arguments for formatting - """ - - def decorator(command: Command | ContextMenu) -> Command | Group | ContextMenu: - if not isinstance(command, (Command, ContextMenu)): - raise TypeError("This decorator must be placed above @app_commands.command or @app_commands.context_menu") - - for key, value in kwargs.items(): - command.extras[key.replace("_", "-")] = value - - return command - - return decorator - - class CommandTranslator(discord.app_commands.Translator): """ Translator for the commands