How to handle NewMessage only in Telethon conversation

3.4k views Asked by At

I have two handlers in my bot's code:

  1. my_conversation - catches '/start' message and starts new conversation, where wait message from user
  2. digits - catches messages by pattern - only digits
    import asyncio
    import logging
    import re
    
    from telethon import TelegramClient
    from telethon.events import StopPropagation, NewMessage

    me = TelegramClient('bot', 'API_ID_BOT', 'API_HASH_BOT').start(bot_token='BOT_TOKEN')
    
    
    async def my_conversation(event):
        async with me.conversation(event.sender_id) as conv:
            await conv.send_message('I\'m waiting for message')
            response = conv.get_response()
            response = await response
            await conv.send_message(f'conversation: {response.text}')
        raise StopPropagation
    
    
    async def digits(event):
        await me.send_message(event.sender_id, f'catches digits: {event.text}')
        raise StopPropagation
    
    
    async def main():
        me.add_event_handler(my_conversation, NewMessage(incoming=True, pattern=r'^\/start$'))
        me.add_event_handler(digits, NewMessage(incoming=True, pattern=re.compile(r'[0-9]+')))
        await me.run_until_disconnected()
    
    
    if __name__ == '__main__':
        logging.basicConfig(level=logging.INFO)
        loop = asyncio.get_event_loop()
        loop.run_until_complete(main())

What I expect:

  1. I send '/start'
  2. Bot start conversation and replyes "I'm waiting for message"
  3. I send "123"
  4. Bot send message "conversation: 123" because the conversation has started. Other handlers must ignore message because the conversation has started.

What I have got:

  1. Bot send message "catches digits: 123"
  2. Bot send message "conversation: 123"

So Bot also catched message by handler outside the conversation, so unexpected. What I must change in script to make it work properly?

1

There are 1 answers

1
Lonami On BEST ANSWER

I plan to remove the conversation method in future versions of the library because, as you can see, one runs into big limitations very quickly because mixing the callbacks-based approach of events with the imperative style of a conversation is difficult. I recommend you use a FSM-style conversation instead. You should be able to adapt that answer's code to "wait for digits" easily:

from enum import Enum, auto

class State(Enum):
    WAIT_DIGITS = auto()

conversation_state = {}

@client.on(events.NewMessage)
async def handler(event):
    who = event.sender_id
    state = conversation_state.get(who)
    
    if state is None:
        await event.respond('Please send digits!')
        conversation_state[who] = State.WAIT_DIGITS

    elif state == State.WAIT_DIGITS:
        if event.text.isdigit():
            digits = event.text
            await event.respond(f'Thanks for your digits! {digits}')
            del conversation_state[who]
        else:
            await event.respond('Please only send digits, not letters')

You can easily add more abstractions to this (build your own decorators for other functions depending on the state, or just separate the code of each state to another function, using their return values as the next state, etc.).