TWT233 / khl.py

Python SDK for kaiheila.cn API
MIT License
243 stars 40 forks source link

How can instances of Bot be referenced in other modules in order to use @bot.command or another decorator #212

Closed SiloNeko closed 1 year ago

SiloNeko commented 1 year ago

Besides manually registering commands using the CommandManager like this:


client.register(MessageTypes.TEXT, lambda msg: command.handle(loop, client, msg, {Message: msg, Bot: bot}))

What other methods can register commands in multi-module projects

TWT233 commented 1 year ago

maybe this? https://github.com/TWT233/whosyou/blob/master/bot/cmd/__init__.py#L11-L23

TWT233 commented 1 year ago

or you can use a getter function to wrap the bot referenced, to avoid accessing it directly

e.g.

# A.py

def get_bot() -> Bot :
    ...
# B.py

from A import get_bot

get_bot().sth_you_need()
SiloNeko commented 1 year ago

Thank for your answer. But how can I easily import these modules that reference the bot at the main.py

So, I have an idea from Flask's "BluePrint" designs.

First, we can create a proxy class to wrap event handlers.


# command/registrar.py

class CommandRegistrar:
    _commands: list[Command] = []
    def register(self, name: str = '', ...):
        ...

     @classmethod
    def require(cls, bot: Bot):
        command_manager = bot.command
        for command in cls._commands:
            command_manager.add(command)

Second, We can import the proxy elsewhere and use it to register commands:

# command/help.py

from commands import CommandRegistrar

cmd = CommandRegistrar()

@cmd.register("help", aliases=["?"])
async def on_cmd(msg: Message):
    ...

Then, when the command package is imported, importlib will import those modules(In this example, the commands are all within the 'commands' package. ):

# command/__init__.py

import importlib
import os

from .registrar import CommandRegistrar

package = os.path.dirname(os.path.abspath(__file__))

modules = [module[:-3] for module in os.listdir(package) if
           module.endswith(".py")
           and os.path.isfile(os.path.join(package, module))
           and module != os.path.basename(__file__)]
for module in modules:
    importlib.import_module(f"{os.path.basename(package)}.{module}")

Finally, import the command package at main and use CommandRegister.require to register all commands temporarily stored in the proxy class.

# main.py

from commands import CommandRegistrar

bot = Bot(token=config.account.bot_token)

CommandRegistrar.require(bot)
SiloNeko commented 1 year ago

The above is just an immature idea. I have found that many developers write all their code in main.py (some can even reach thousands of lines). So if these design patterns can be encapsulated in future khl.py, it will be very helpful for the organizational structure of the project.