You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Telethon中如何通过点击Inline按钮触发专属的events.NewMessage事件处理器?

Telethon中如何通过点击Inline按钮触发专属的events.NewMessage事件处理器?

嗨,我明白你现在的困扰——点击按钮后想让后续的消息只触发那个专门的处理器,结果被之前的全局处理器截胡了对吧?这是因为Telethon的NewMessage处理器默认是全局生效的,而且你之前的第三个处理器(匹配非/start/delete的消息)会先匹配到普通消息,导致嵌套的处理器根本没机会干活。

下面给你两种靠谱的解决思路,都是Telethon开发里常用的方案:


方案一:用用户状态跟踪(最常用,易维护)

核心思路是给每个用户维护一个状态,比如点击按钮后标记用户处于「等待输入歌手名」的状态,然后在各个处理器里先判断状态,再决定是否处理消息。

首先,定义一个全局字典来存用户状态:

# 全局变量,记录用户当前状态,key是用户ID,value是状态标识
user_states = {}

然后修改按钮回调,给用户设置状态:

@client.on(events.CallbackQuery(data=b'send_name_button'))
async def handler_send_name_button(event):
    user_id = event.sender_id
    # 标记用户当前处于等待输入歌手名的状态
    user_states[user_id] = "awaiting_artist"
    
    msg = await event.respond(f'Type the name of a musician. After that the bot will send the list of tracks of the chosen musician...' )
    functions.append_msg_id(msg_ids, event.id)
    functions.append_msg_id(msg_ids, msg.id)

接下来修改你的三个全局处理器,加上状态判断:

  1. 对于/start/delete,处理完可以重置状态:
try:
    @client.on(events.NewMessage(pattern='/start'))
    async def respond_start(event):
        user_id = event.sender_id
        # 重置用户状态
        if user_id in user_states:
            del user_states[user_id]
            
        user = await event.get_sender()
        msg = await event.respond(
              f'Hello, {user.first_name} ☘️!\nThis bot will send you a chosen song (mp3 file) from a songs list of an artist you chose...', 
              buttons=[ 
                  Button.inline('Click and send a music artist name...', b'send_name_button')
              ]
        )
        append_msg_id(msg_ids, msg.id)
    except Exception as ex:
        print(f'Exception: respond_start: {ex}')

try:
    @client.on(events.NewMessage(pattern='/delete'))
    async def delete_dialog(event):
        user_id = event.sender_id
        # 重置用户状态
        if user_id in user_states:
            del user_states[user_id]
            
        append_msg_id(msg_ids, event.id)
        await client.delete_messages(event.chat_id, msg_ids)
        clean_msg_ids(msg_ids)                  
except Exception as ex:
    print(f'Exception: delete_messages: {ex}')
  1. 专门写一个处理器来处理「等待输入歌手名」状态下的消息,并且把它的优先级设高一点(确保先触发):
try:
    # 设置priority=10,比默认的0高,确保先被处理
    @client.on(events.NewMessage(priority=10))
    async def handler_awaiting_artist(event):
        user_id = event.sender_id
        # 只有用户处于等待状态时才处理
        if user_states.get(user_id) != "awaiting_artist":
            return  # 不满足条件就跳过
        
        # 这里写你处理歌手名的逻辑
        artist_name = event.raw_text
        await event.respond(f"收到歌手名:{artist_name},我这就去查歌单!")
        
        # 处理完后重置状态
        del user_states[user_id]
        
        # 别忘了把消息ID加入列表(如果需要的话)
        append_msg_id(msg_ids, event.id)
except Exception as ex:
    print(f'Exception: handler_awaiting_artist: {ex}')
  1. 最后修改那个「其他消息」的处理器,让它只在用户没有处于等待状态时才触发:
try:
    @client.on(events.NewMessage(pattern=r'^((?!\/start|\/delete).)*$'))
    async def respond_else(event):
        user_id = event.sender_id
        # 如果用户正在等待输入歌手名,就跳过这个处理器
        if user_states.get(user_id) == "awaiting_artist":
            return
            
        append_msg_id(msg_ids, event.id)
        msg = await event.respond('Try /start...')                   
        append_msg_id(msg_ids, msg.id)
except Exception as ex:
    print(f'Exception: respond_else: {ex}')

方案二:临时添加一次性处理器(适合简单场景)

如果你只想让这个处理器触发一次,处理完就失效,可以用Telethon的add_event_handler方法手动添加处理器,然后在处理完消息后移除它。

修改按钮回调:

@client.on(events.CallbackQuery(data=b'send_name_button'))
async def handler_send_name_button(event):
    user_id = event.sender_id
    chat_id = event.chat_id
    
    msg = await event.respond(f'Type the name of a musician. After that the bot will send the list of tracks of the chosen musician...' )
    functions.append_msg_id(msg_ids, event.id)
    functions.append_msg_id(msg_ids, msg.id)
    
    # 定义临时处理器
    async def temp_artist_handler(new_event):
        # 确保是同一个用户、同一个聊天的消息
        if new_event.sender_id != user_id or new_event.chat_id != chat_id:
            return
        
        # 处理歌手名逻辑
        artist_name = new_event.raw_text
        await new_event.respond(f"收到歌手名:{artist_name}!")
        
        # 处理完后移除这个临时处理器
        client.remove_event_handler(temp_artist_handler)
        
        # 记录消息ID
        append_msg_id(msg_ids, new_event.id)
    
    # 添加临时处理器,设置高优先级
    client.add_event_handler(temp_artist_handler, events.NewMessage(priority=10))

这种方式的好处是不用维护全局状态,处理器是一次性的,处理完就自动失效,适合简单的单次交互场景。


为什么你的原代码不行?

你在按钮回调里嵌套定义@client.on(events.NewMessage),这个处理器会被全局注册,而且优先级和其他处理器一样,所以会和第三个处理器(匹配非命令的消息)竞争,而第三个处理器的模式会先匹配到普通消息,导致你的嵌套处理器根本没机会执行。而且这个嵌套处理器会一直存在,除非你手动移除,后面所有用户的消息都会触发它,这显然不是你想要的。

备注:内容来源于stack exchange,提问作者Nemicus Satani

火山引擎 最新活动