S. Wasta
S. Wasta

Reputation: 638

Telethon (bot), how to work with commands, buttons and anwers

I want to implement something like BotFather. It sends buttons and you select your option, and if the option need a answer you must answer with the correct answer.

For example if I want to add/edit commands I press "Edit Command" and command must be in format command - description if format is not valid it will reply a error. How does it knows my message is a answer to "Edit commands" and not a answer to "Edit About" (for example, because "Edit about" doesn't have this format)? I know if I want to listen action in button, have to use @client.on(events.CallbackQuery(pattern=...)) but this won't 'wait' for user answer for specific callback, does it? I tried but didn't work.


# client pre-configuration...


@client.on(events.NewMessage(pattern=r"\/new"))
async def handler(event):
    keyboard = [
        [
            Button.inline("New Command", "edit_command"),
            Button.inline("Anything", "any"),
        ],
    ]
    await client.send_message(event.sender_id, "Hey, There!", buttons=keyboard)



@client.on(events.CallbackQuery(pattern="edit_command"))
async def call_handler(event):
    await client.send_message(event.sender_id, "Please send command in format:\ncommand - description")

Do I have to implement event.NewMessage(), Validate if message have "X" format if previous message is 'Please send command in format:\ncommand - description'? Otherwise ignore message?

Example idea:

handler all messages from user, or with pattern ^[a-z- ]+$, and read previous message....

@client.on(events.NewMessage())
async def handler(event):
    if "send command in format" in previous_message or "Command format is invalid" in previous_message:
        if ensure_command_message_format(event.raw_text):
            # Hey! Nice!....
        else:
            await client.send_message(event.sender_id, "Command format is invalid, please try again.")
    if anything_else in previous_message:
        if ensure_anything_else_format(event.raw_text):
            # etc...

Is this the best way to do it?

Upvotes: 0

Views: 2076

Answers (1)

Mubarak Salley
Mubarak Salley

Reputation: 35

Yes, what you are doing is not wrong, but it's not the best way, and when I say it's not the best way, I mean it in terms of maintainability, if you were to come back some months later to update your code, you will probably struggle, but you are doing fine, for systems like this, there is a pattern called 'STATE DESIGN PATTERN', I mention it here just in case you want to read further, make sure to understand the concept of it, so basically, copying some parts of the state design pattern, I will suggest you get a STATE variable, which will store a state out of a list of states, your list of states could look like this '["edit_name", "edit_description", "edit_menu", "main_menu", "start"]' so what each state means and what each state is named is up to you to define, for me "edit_name" state means that we are currently trying to edit the name of something so I should be expecting some text, "main_menu" state means we have response from main menu, so every state system has a start state, for me, my start state is "start", so my code will kinda look like this :

#DEFINE YOUR STATES AND SET THE START STATE
STATE = "start"

@client.on(events.NewMessage()):
if STATE == "start":
    #send main menu stuff to user
    #Change state to main menu
    new_STATE = "main_menu"
if STATE == "main_menu":
    #here i am only interested in responded suited for main therefore
    if response in state_responses[STATE]:
        #This is a valid main menu response, maybe the response was "EDIT" 
        #so we need to switch to edit state now

        #DO YOUR EDIT CODE HERE, probably sending edit options, Name or Description

        #Edit State Change
        new_STATE = "edit_menu"

if STATE == "edit_menu":
    #ok so we are in an editing mood, and we know what we are expecting, in response
    #either name or description
    if response in state_responses[STATE]:
        if "response_is_name":
             #ok name has been chosen so next input we are expecting pure 
             #text so we will put state to "edit_name"
             new_STATE = "edit_name"
        #<.... am gonna skip some codes and jump to edit_name state ....>

if STATE == "edit_name":
    #well then whatever input we get, we will use it to edit the name, 
    #maybe the name of the bot or something, make sure to validate the name
    #people can create some weird names so create some name validation
    set_name(response)

    #OK so now that we are done changing the name where do we want to do 
    #Maybe we will send a "name change successful" message and go back to the
    #main menu, ok so we will send our message 
    send.message("name change successful")

    #Now which state sends the main menu to the user ? ... if you remember it was
    #the start state, so we will set the state to start, to start the entire loop
    new_STATE = "start"


#SET STATE = new_STATE
STATE = new_STATE



THE ABOVE IS JUST AN EXAMPLE:

Yeah so its the same if-else you have, but its more readable, and feels organized, there is a way to implement this without if-else, but with classes instead but I will only suggest you do that if your state meets are starting to exceed a huge number like 20 or so, so long story short, you have the right idea, but you lack structure, I highly suggest you watch videos on state design patterns, note that design patterns are tools for solving problems, hence depending on your problem you can modify the design pattern to suit your needs, normally in state design pattern videos, they will teach you the "class" way of implementing it, but for small projects the "if-else" way is fine

Upvotes: 0

Related Questions