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)
接下来修改你的三个全局处理器,加上状态判断:
- 对于
/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}')
- 专门写一个处理器来处理「等待输入歌手名」状态下的消息,并且把它的优先级设高一点(确保先触发):
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}')
- 最后修改那个「其他消息」的处理器,让它只在用户没有处于等待状态时才触发:
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




