ekonda / kutana

The library for developing systems for messengers and social networks
MIT License
72 stars 17 forks source link

Более гибкая система команд #70

Closed nukdokplex closed 1 year ago

nukdokplex commented 1 year ago

Я беру во внимание Red (Discord bot). В нем используется более гибкая система команд, которая позволяет очень легко и просто создавать подкоманды (subcommands) в плагинах с помощью декораторов. Также было бы удобно передавать аргументы, которые пользователь передает вместе с командой в *args.

Ожидаемое поведение

Просто посмотрите, как легко и просто в Red создаются команды:

    @commands.group(alias=["nf"])
    async def nickforcer(self, ctx:commands.Context):
        """Nickname Forcer related commands"""

        pass

    @nickforcer.command()
    @commands.admin()
    async def set(self, ctx: commands.Context, user: discord.Member, nick: commands.UserInputOptional[str]):
        """Sets the user to Nickname Forcer"""
        await self.config.user(user).nickname.set(nick)
        current_users = await self.config.guild(ctx.guild).users_to_force()
        if user.id not in current_users:
            current_users.append(user.id)
            await self.config.guild(ctx.guild).users_to_force.set(current_users)
        await ctx.send(_("This user now will be forced with this nickname: **{nickname}**").format(nickname=nick))
        try:
            await user.edit(nick=nick)
        except discord.errors.Forbidden:
            await ctx.send(_("So I've tried to do this, but i have no permission to change nicknames of other users, "
                             "add it to me, please."))

    @nickforcer.command()
    @commands.admin()
    async def unset(self, ctx: commands.context, user: discord.Member):
        """Unsets the user to Nickname Forcer"""
        current_users = await self.config.guild(ctx.guild).users_to_force()
        if user.id not in current_users:
            await ctx.send(_("Nothing to unset..."))
        else:
            current_users.remove(user.id)
            await ctx.send(_("User {mention} has been removed from forcing!").format(mention=user.mention))

Как вы можете заметить, самая первая функция - назовём её "мастер-командой", с которой начинается вся магия. Использовав ее как декоратор, вторая функция становится после нее и получается так, что чтобы вызвать эту команду нужно набрать /nickforcer set [аргументы]. Ведь просто же? И что самое главное логично - вторая команда как-бы наследует мастер-команду.

Также обратите внимание, что у функций после ctx объявлены еще несколько параметров. Red передает в них, аргументы, который спарсил из полученной команды. Разработчику плагинов при этом больше не нужно заниматься парсингом body, достаточно доверить все фреймворку, просто объявив аргументы разного типа. Также, если им задать значение по умолчанию None, можно сделать их необязательными. Было бы здорово позаимствовать такой мощный парсер команд у Red и добавить в Kutana, не так ли?

И ещё один ключевой фактор, который здесь присутствует - это docstring. У каждой команды есть свой комментарий, который находится там не просто так. Это справка по команде, которую использует встроенный плагин help в Red. Также, эти комментарии, как и все строки внутри функции перевода _("string") замечательно извлекается с помощью форка gettext от Red, который называется redgettext (есть в PyPI). Хотелось бы еще, чтобы Kutana отошла от использования yml в i18n и использовала специальные форматы, такие как po и mo. Все системы перевода заточены именно под эти форматы файлов.

Текущее поведение

На данный момент Kutana очень скудно обрабатывает команды. Да, конечно сейчас можно сделать подкоманды вот так:

@plugin.on_commands(['settings set'])
async def __(message, context):
    ...

Но делать их с помощью декораторов гораздо приятнее и красивее. Ну и, кстати говоря, Kutana не умеет парсить аргументы, приходится самостоятельно парсить их из body, что не очень удобно и выглядит несколько кустарно.

Также сейчас Kutana использует yml для i18n, что неудобно для перевода плагинов, так как все системы перевода используют форматы файлов po и mo.

Возможные решения

Red - открытый фреймворк для построения ботов для Discord. Вооружившись его открытым исходным кодом, из него можно позаимствовать различные идеи и наработки, с помощью которых можно сделать Kutana лучшим фреймворком для создания мультиплатформенных ботов.

Контекст

Контекст не требуется. Всё было сказано выше. Если у вас есть какие-то вопросы, прошу связаться со мной: ВКонтакте, Telegram

michaelkryukov commented 1 year ago

На сколько мне известно, на платформах, поддерживаемых кутаной, плоские иерархии команд – поэтому я не вижу смысла вводить группы, кроме как для создания "декоратора" для команд. И при этом можно просто использовать декоратор – тогда будет меньше магии вокруг обработчиков и за счёт этого будет легче работать с кодовой базой. Возможно ещё для описательных плагинов это можно использовать, но тут стоит отметить, что kutana стремиться быть библиотекой, а не фреймворком – поэтому такие структурные решения я предпочитаю оставлять на разработчика. Текущий функционал достаточно приятный и красивый, поэтому вряд ли в ближайшее время эта возможность будет добавлена, если не будет более веских аргументов. Никто не мешает вам создать свой модуль, который будет группировать команды и возвращать мета-информацию о группах, в принципе)

Касательно разбора аргументов – идея интересная, надо над ней подумать поосновательнее. Скорее всего, заведу отдельный issue, а тот в этом навалено много разных фичей.

Вообще текущая система переводов была экспериментальной, и у меня в планах от неё отказаться, опять же, оставив решения о выборе систем/реализаций переводов на разработчика. Так что в этом направлении продвижений не будет