# 会话控制 > 大于等于 v3.4.36 为什么需要会话控制?考虑一个 成语接龙 插件,某个/群用户需要和机器人进行多次对话,而不是一次性的指令。这时候就需要会话控制。 ```txt 用户: /成语接龙 机器人: 请发送一个成语 用户: 一马当先 机器人: 先见之明 用户: 明察秋毫 ... ``` AstrBot 提供了开箱即用的会话控制功能: 导入: ```py import astrbot.api.message_components as Comp from astrbot.core.utils.session_waiter import ( session_waiter, SessionController, ) ``` handler 内的代码可以如下: ```python from astrbot.api.event import filter, AstrMessageEvent @filter.command("成语接龙") async def handle_empty_mention(self, event: AstrMessageEvent): """成语接龙具体实现""" try: yield event.plain_result("请发送一个成语~") # 具体的会话控制器使用方法 @session_waiter(timeout=60, record_history_chains=False) # 注册一个会话控制器,设置超时时间为 60 秒,不记录历史消息链 async def empty_mention_waiter(controller: SessionController, event: AstrMessageEvent): idiom = event.message_str # 用户发来的成语,假设是 "一马当先" if idiom == "退出": # 假设用户想主动退出成语接龙,输入了 "退出" await event.send(event.plain_result("已退出成语接龙~")) controller.stop() # 停止会话控制器,会立即结束。 return if len(idiom) != 4: # 假设用户输入的不是4字成语 await event.send(event.plain_result("成语必须是四个字的呢~")) # 发送回复,不能使用 yield return # 退出当前方法,不执行后续逻辑,但此会话并未中断,后续的用户输入仍然会进入当前会话 # ... message_result = event.make_result() message_result.chain = [Comp.Plain("先见之明")] # import astrbot.api.message_components as Comp await event.send(message_result) # 发送回复,不能使用 yield controller.keep(timeout=60, reset_timeout=True) # 重置超时时间为 60s,如果不重置,则会继续之前的超时时间计时。 # controller.stop() # 停止会话控制器,会立即结束。 # 如果记录了历史消息链,可以通过 controller.get_history_chains() 获取历史消息链 try: await empty_mention_waiter(event) except TimeoutError as _: # 当超时后,会话控制器会抛出 TimeoutError yield event.plain_result("你超时了!") except Exception as e: yield event.plain_result("发生错误,请联系管理员: " + str(e)) finally: event.stop_event() except Exception as e: logger.error("handle_empty_mention error: " + str(e)) ``` 当激活会话控制器后,该发送人之后发送的消息会首先经过上面你定义的 `empty_mention_waiter` 函数处理,直到会话控制器被停止或者超时。 ## SessionController 用于开发者控制这个会话是否应该结束,并且可以拿到历史消息链。 - keep(): 保持这个会话 - timeout (float): 必填。会话超时时间。 - reset_timeout (bool): 设置为 True 时, 代表重置超时时间, timeout 必须 > 0, 如果 <= 0 则立即结束会话。设置为 False 时, 代表继续维持原来的超时时间, 新 timeout = 原来剩余的 timeout + timeout (可以 < 0) - stop(): 结束这个会话 - get_history_chains() -> List[List[Comp.BaseMessageComponent]]: 获取历史消息链 ## 自定义会话 ID 算子 默认情况下,AstrBot 会话控制器会将基于 `sender_id` (发送人的 ID)作为识别不同会话的标识,如果想将一整个群作为一个会话,则需要自定义会话 ID 算子。 ```py import astrbot.api.message_components as Comp from astrbot.core.utils.session_waiter import ( session_waiter, SessionFilter, SessionController, ) # 沿用上面的 handler # ... class CustomFilter(SessionFilter): def filter(self, event: AstrMessageEvent) -> str: return event.get_group_id() if event.get_group_id() else event.unified_msg_origin await empty_mention_waiter(event, session_filter=CustomFilter()) # 这里传入 session_filter # ... ``` 这样之后,当群内一个用户发送消息后,会话控制器会将这个群作为一个会话,群内其他用户发送的消息也会被认为是同一个会话。 甚至,可以使用这个特性来让群内组队!