Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HelpCommand Class #33

Open
Kariton opened this issue Aug 23, 2023 · 1 comment
Open

HelpCommand Class #33

Kariton opened this issue Aug 23, 2023 · 1 comment

Comments

@Kariton
Copy link
Contributor

Kariton commented Aug 23, 2023

Hey,

when creating a command class the examples (and the code) define self.describe().

# optional
def describe(self) -> str:
return None

what is the use of this additional infromation?

i was not able do figure out where this is acutally used.
after that i created a short command myself which might fit right into the examples itself.

from signalbot import Command, Context


class HelpCommand(Command):
    def prefix(self) -> str:
        return "help"

    def describe(self) -> str:
        return "📚 Display a list of available commands or get detailed help for a command."

    def explain(self) -> str:
        return f"{self.prefix()} [COMMAND] - get help message for defined command"

    async def handle(self, c: Context):
        if c.message.text.startswith(self.prefix()):
            # Start typing indicator
            await c.start_typing()

            command_text = c.message.text[len(self.prefix()):].strip()

            if not command_text:
                # Filter available commands based on user access
                available_commands = []
                for cmd, contacts, groups in c.bot.commands:
                    if c.bot._should_react(c.message, contacts, groups):
                        available_commands.append(cmd)

                help_text = "Available commands:\n\n"
                for cmd in available_commands:
                    cmd_prefix = cmd.prefix() if hasattr(cmd, 'prefix') and cmd.prefix() is not None else 'Unknown command prefix'
                    cmd_description = cmd.describe() if hasattr(cmd, 'describe') and cmd.describe() is not None else 'No description available'
                    help_text += f"{cmd_prefix} - {cmd_description}\n"

                help_text += "\nTo get detailed informaition for a given command use: `help [COMMAND]`"

                # Stop typing indicator
                await c.stop_typing()

                await c.send(help_text)
            else:
                for cmd, _, _ in c.bot.commands:
                    if hasattr(cmd, 'prefix') and cmd.prefix() == command_text:
                        # Build man-like help message
                        description = cmd.describe() if hasattr(cmd, 'describe') else 'No description available'
                        usage = f"\nUsage:\n    {cmd.explain()}" if hasattr(cmd, 'explain') else ''

                        # Build the help message with sections
                        help_sections = [section for section in [description, usage] if section]
                        help_message = '\n'.join(help_sections)
                        
                        # Stop typing indicator
                        await c.stop_typing()
                        
                        await c.send(help_message)
                        break
                else:
                    await c.send("Command not found or no detailed help available.")

            return

another command definition:

class ChatGPTCommand(Command):
    def __init__(self, api_key):
        self.api_key = api_key
        self.ongoing_conversations = []

    def prefix(self) -> str:
        return "gpt"

    def describe(self) -> str:
        return "🤖 Engage in a historical conversation with ChatGPT. [GPT 3.5 turbo]"

    def explain(self) -> str:
        return (
            f"{self.prefix()} [MESSAGE] - send message to ChatGPT.\n"
            f"    {self.prefix()} start - start an ongoing ChatGPT conversation; '{self.prefix()}' prefix is no longer nessesary.\n"
            f"    {self.prefix()} end - end an ongoing ChatGPT conversation; '{self.prefix()}' prefix is nessesary again.\n"
            f"    {self.prefix()} status - check if you're in an ongoing ChatGPT conversation.\n"
            f"    {self.prefix()} history - recive ChatGPT conversation history.\n"
            f"    {self.prefix()} clear - clear the ChatGPT conversation history."
        )
[...]

self.prefix() - well, the command itself / message prefix
self.describe()- short describtion of the command
self.explain() - instruction / usage

in signal:

me

help

bot

Available commands:

help - 📚 Display a list of available commands or get detailed help for a command.
ping - 🏓 Ping Command: Listen for a ping.
friday - 🦀 Congratulations sailor, you made it to friday!
reply - 💬 Auto-reply to messages containing 'reply'.
gpt - 🤖 Engage in a historical conversation with ChatGPT. [GPT 3.5 turbo]

To get detailed informaition for a given command use: help [COMMAND]

me

help help

bot (__ to replace 4 spaces)

📚 Display a list of available commands or get detailed help for a command.

Usage:
__help [COMMAND] - get help message for defined command

me (gpt command will be uploaded to github in the future)

help gpt

bot (__ to replace 4 spaces)

🤖 Engage in a historical conversation with ChatGPT. [GPT 3.5 turbo]

Usage:
__gpt [MESSAGE] - send message to ChatGPT.
__gpt start - start an ongoing ChatGPT conversation; 'gpt' prefix is no longer nessesary.
__gpt end - end an ongoing ChatGPT conversation; 'gpt' prefix is nessesary again.
__gpt status - check if you're in an ongoing ChatGPT conversation.
__gpt history - recive ChatGPT conversation history.
__gpt clear - clear the ChatGPT conversation history.

me

help ping

bot

🏓 Ping Command: Listen for a ping.

here my few thoughts:
with the modularity and ability to share snippets to represend rather complex bot actions it might be helpul do define some "standard models".

because those commads are so modular an emedded help command might not be feasable.
but i think everybody should be advises to include the three functions in their command class (prefix, describe, explain)

i know that the name "prefix" does not fit nicely with stuff like:

if "reply" in c.message.text.lower():

but it works.

class ReplyCommand(Command):
    def prefix(self) -> str:
        return "reply"

    def describe(self) -> str:
        return f"💬 Auto-reply to messages containing '{self.prefix()}'."

    async def handle(self, c: Context):
        if self.prefix() in c.message.text.lower():
            await c.reply(
                "i ain't reading all that. i'm happy for u tho or sorry that happened"
            )

furthermore might a globaly configurable "pre_prefix" be useful.
if everybody defines their prefixes alphanumeric a gloabal prefix is added and the command will fit whatever the user wants. /cmd, !cmd, .cmd

renaming prefix to prefix_trigger and an additional cmd_trigger, which does not get the "pre_prefix", might be a good way to handle the different requirements.
and then there is the actual @trigger... which i dont use and havent tested yet.

and last but not least:
with defined prefix warnings can be returned if two commands share the same.

@filipre
Copy link
Owner

filipre commented Aug 24, 2023

Thank you for your thoughts

after that i created a short command myself which might fit right into the examples itself.

That was exactly my use case for my personal bot and I have a very similar command.

# ...
for command in self.bot.commands:
    desc = command.describe()
    if desc is None:
        continue
    response.append(f"• {command.describe()}")
await c.send("\n".join(response))

Actually, I am still debating whether I should have included the method in the first place or not. On the one hand, I like that it encourages people to describe what is happening and that it can be used by other commands (help command, Langchain, ...). On the other hand, there is already __str__ (we are dealing with classes and not functions anyway which is another topic) and I want to keep the API as simple as possible. Telegram and semaphore both use functions. With that in mind, I feel like the prefix functionality is covered by the @triggered decorator already

But! I encourage inheriting Command and defining your own PrefixCommand which can have exactly this functionality. You could even overwrite the .handle method to do the prefix check there. If you prefer this way over the other, I believe it will be also a clean way for your project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants