from types import ModuleType from datetime import datetime from contextvars import ContextVar from collections import defaultdict from contextlib import AsyncExitStack from typing import ( TYPE_CHECKING, Any, Dict, List, Type, Union, TypeVar, Callable, NoReturn, Optional, ) from nonebot.log import logger from nonebot.dependencies import Dependent from nonebot.consts import ( ARG_KEY, RECEIVE_KEY, REJECT_TARGET, LAST_RECEIVE_KEY, REJECT_CACHE_TARGET, ) from nonebot.typing import ( Any, T_State, T_Handler, T_TypeUpdater, T_DependencyCache, T_PermissionUpdater, ) from nonebot.exception import ( TypeMisMatch, PausedException, StopPropagation, SkippedException, FinishedException, RejectedException, ) from .rule import Rule from .permission import USER, Permission from .adapter import Bot, Event, Message, MessageSegment, MessageTemplate from .params import ( Depends, ArgParam, BotParam, EventParam, StateParam, DependParam, DefaultParam, MatcherParam, ) if TYPE_CHECKING: from nonebot.plugin import Plugin T = TypeVar("T") matchers: Dict[int, List[Type["Matcher"]]] = defaultdict(list) """用于存储当前所有的事件响应器""" current_bot: ContextVar[Bot] = ContextVar("current_bot") current_event: ContextVar[Event] = ContextVar("current_event") current_matcher: ContextVar["Matcher"] = ContextVar("current_matcher") current_handler: ContextVar[Dependent] = ContextVar("current_handler") class MatcherMeta(type): if TYPE_CHECKING: module: Optional[str] plugin_name: Optional[str] module_name: Optional[str] module_prefix: Optional[str] type: str rule: Rule permission: Permission handlers: List[T_Handler] priority: int block: bool temp: bool expire_time: Optional[datetime] def __repr__(self) -> str: return ( f"" ) def __str__(self) -> str: return repr(self) class Matcher(metaclass=MatcherMeta): """事件响应器类""" plugin: Optional["Plugin"] = None """事件响应器所在插件""" module: Optional[ModuleType] = None """事件响应器所在插件模块""" plugin_name: Optional[str] = None """事件响应器所在插件名""" module_name: Optional[str] = None """事件响应器所在点分割插件模块路径""" type: str = "" """事件响应器类型""" rule: Rule = Rule() """事件响应器匹配规则""" permission: Permission = Permission() """事件响应器触发权限""" handlers: List[Dependent[Any]] = [] """事件响应器拥有的事件处理函数列表""" priority: int = 1 """事件响应器优先级""" block: bool = False """事件响应器是否阻止事件传播""" temp: bool = False """事件响应器是否为临时""" expire_time: Optional[datetime] = None """事件响应器过期时间点""" _default_state: T_State = {} """事件响应器默认状态""" _default_type_updater: Optional[Dependent[str]] = None """事件响应器类型更新函数""" _default_permission_updater: Optional[Dependent[Permission]] = None """事件响应器权限更新函数""" HANDLER_PARAM_TYPES = [ DependParam, BotParam, EventParam, StateParam, ArgParam, MatcherParam, DefaultParam, ] def __init__(self): self.handlers = self.handlers.copy() self.state = self._default_state.copy() def __repr__(self) -> str: return ( f"" ) def __str__(self) -> str: return repr(self) @classmethod def new( cls, type_: str = "", rule: Optional[Rule] = None, permission: Optional[Permission] = None, handlers: Optional[List[Union[T_Handler, Dependent[Any]]]] = None, temp: bool = False, priority: int = 1, block: bool = False, *, plugin: Optional["Plugin"] = None, module: Optional[ModuleType] = None, expire_time: Optional[datetime] = None, default_state: Optional[T_State] = None, default_type_updater: Optional[Union[T_TypeUpdater, Dependent[str]]] = None, default_permission_updater: Optional[ Union[T_PermissionUpdater, Dependent[Permission]] ] = None, ) -> Type["Matcher"]: """ 创建一个新的事件响应器,并存储至 `matchers <#matchers>`_ 参数: type_: 事件响应器类型,与 `event.get_type()` 一致时触发,空字符串表示任意 rule: 匹配规则 permission: 权限 handlers: 事件处理函数列表 temp: 是否为临时事件响应器,即触发一次后删除 priority: 响应优先级 block: 是否阻止事件向更低优先级的响应器传播 plugin: 事件响应器所在插件 module: 事件响应器所在模块 default_state: 默认状态 `state` expire_time: 事件响应器最终有效时间点,过时即被删除 返回: Type[Matcher]: 新的事件响应器类 """ NewMatcher = type( "Matcher", (Matcher,), { "plugin": plugin, "module": module, "plugin_name": plugin and plugin.name, "module_name": module and module.__name__, "type": type_, "rule": rule or Rule(), "permission": permission or Permission(), "handlers": [ handler if isinstance(handler, Dependent) else Dependent[Any].parse( call=handler, allow_types=cls.HANDLER_PARAM_TYPES ) for handler in handlers ] if handlers else [], "temp": temp, "expire_time": expire_time, "priority": priority, "block": block, "_default_state": default_state or {}, "_default_type_updater": ( default_type_updater if isinstance(default_type_updater, Dependent) else default_type_updater and Dependent[str].parse( call=default_type_updater, allow_types=cls.HANDLER_PARAM_TYPES ) ), "_default_permission_updater": ( default_permission_updater if isinstance(default_permission_updater, Dependent) else default_permission_updater and Dependent[Permission].parse( call=default_permission_updater, allow_types=cls.HANDLER_PARAM_TYPES, ) ), }, ) logger.trace(f"Define new matcher {NewMatcher}") matchers[priority].append(NewMatcher) return NewMatcher @classmethod async def check_perm( cls, bot: Bot, event: Event, stack: Optional[AsyncExitStack] = None, dependency_cache: Optional[T_DependencyCache] = None, ) -> bool: """检查是否满足触发权限 参数: bot: Bot 对象 event: 上报事件 stack: 异步上下文栈 dependency_cache: 依赖缓存 返回: 是否满足权限 """ event_type = event.get_type() return event_type == (cls.type or event_type) and await cls.permission( bot, event, stack, dependency_cache ) @classmethod async def check_rule( cls, bot: Bot, event: Event, state: T_State, stack: Optional[AsyncExitStack] = None, dependency_cache: Optional[T_DependencyCache] = None, ) -> bool: """检查是否满足匹配规则 参数: bot: Bot 对象 event: 上报事件 state: 当前状态 stack: 异步上下文栈 dependency_cache: 依赖缓存 返回: 是否满足匹配规则 """ event_type = event.get_type() return event_type == (cls.type or event_type) and await cls.rule( bot, event, state, stack, dependency_cache ) @classmethod def type_updater(cls, func: T_TypeUpdater) -> T_TypeUpdater: """装饰一个函数来更改当前事件响应器的默认响应事件类型更新函数 参数: func: 响应事件类型更新函数 """ cls._default_type_updater = Dependent[str].parse( call=func, allow_types=cls.HANDLER_PARAM_TYPES ) return func @classmethod def permission_updater(cls, func: T_PermissionUpdater) -> T_PermissionUpdater: """装饰一个函数来更改当前事件响应器的默认会话权限更新函数 参数: func: 会话权限更新函数 """ cls._default_permission_updater = Dependent[Permission].parse( call=func, allow_types=cls.HANDLER_PARAM_TYPES ) return func @classmethod def append_handler( cls, handler: T_Handler, parameterless: Optional[List[Any]] = None ) -> Dependent[Any]: handler_ = Dependent[Any].parse( call=handler, parameterless=parameterless, allow_types=cls.HANDLER_PARAM_TYPES, ) cls.handlers.append(handler_) return handler_ @classmethod def handle( cls, parameterless: Optional[List[Any]] = None ) -> Callable[[T_Handler], T_Handler]: """装饰一个函数来向事件响应器直接添加一个处理函数 参数: parameterless: 非参数类型依赖列表 """ def _decorator(func: T_Handler) -> T_Handler: cls.append_handler(func, parameterless=parameterless) return func return _decorator @classmethod def receive( cls, id: str = "", parameterless: Optional[List[Any]] = None ) -> Callable[[T_Handler], T_Handler]: """装饰一个函数来指示 NoneBot 在接收用户新的一条消息后继续运行该函数 参数: id: 消息 ID parameterless: 非参数类型依赖列表 """ async def _receive(event: Event, matcher: "Matcher") -> Union[None, NoReturn]: matcher.set_target(RECEIVE_KEY.format(id=id)) if matcher.get_target() == RECEIVE_KEY.format(id=id): matcher.set_receive(id, event) return if matcher.get_receive(id, ...) is not ...: return await matcher.reject() _parameterless = [Depends(_receive), *(parameterless or [])] def _decorator(func: T_Handler) -> T_Handler: if cls.handlers and cls.handlers[-1].call is func: func_handler = cls.handlers[-1] for depend in reversed(_parameterless): func_handler.prepend_parameterless(depend) else: cls.append_handler(func, parameterless=_parameterless) return func return _decorator @classmethod def got( cls, key: str, prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, parameterless: Optional[List[Any]] = None, ) -> Callable[[T_Handler], T_Handler]: """装饰一个函数来指示 NoneBot 获取一个参数 `key` 当要获取的 `key` 不存在时接收用户新的一条消息再运行该函数,如果 `key` 已存在则直接继续运行 参数: key: 参数名 prompt: 在参数不存在时向用户发送的消息 parameterless: 非参数类型依赖列表 """ async def _key_getter(event: Event, matcher: "Matcher"): matcher.set_target(ARG_KEY.format(key=key)) if matcher.get_target() == ARG_KEY.format(key=key): matcher.set_arg(key, event.get_message()) return if matcher.get_arg(key, ...) is not ...: return await matcher.reject(prompt) _parameterless = [ Depends(_key_getter), *(parameterless or []), ] def _decorator(func: T_Handler) -> T_Handler: if cls.handlers and cls.handlers[-1].call is func: func_handler = cls.handlers[-1] for depend in reversed(_parameterless): func_handler.prepend_parameterless(depend) else: cls.append_handler(func, parameterless=_parameterless) return func return _decorator @classmethod async def send( cls, message: Union[str, Message, MessageSegment, MessageTemplate], **kwargs: Any, ) -> Any: """发送一条消息给当前交互用户 参数: message: 消息内容 kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api """ bot = current_bot.get() event = current_event.get() state = current_matcher.get().state if isinstance(message, MessageTemplate): _message = message.format(**state) else: _message = message return await bot.send(event=event, message=_message, **kwargs) @classmethod async def finish( cls, message: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, **kwargs, ) -> NoReturn: """发送一条消息给当前交互用户并结束当前事件响应器 参数: message: 消息内容 kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api """ if message is not None: await cls.send(message, **kwargs) raise FinishedException @classmethod async def pause( cls, prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, **kwargs, ) -> NoReturn: """发送一条消息给当前交互用户并暂停事件响应器,在接收用户新的一条消息后继续下一个处理函数 参数: prompt: 消息内容 kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api """ if prompt is not None: await cls.send(prompt, **kwargs) raise PausedException @classmethod async def reject( cls, prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, **kwargs, ) -> NoReturn: """最近使用 `got` / `receive` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数 参数: prompt: 消息内容 kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api """ if prompt is not None: await cls.send(prompt, **kwargs) raise RejectedException @classmethod async def reject_arg( cls, key: str, prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, **kwargs, ) -> NoReturn: """最近使用 `got` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一条消息后从头开始执行当前处理函数 参数: key: 参数名 prompt: 消息内容 kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api """ matcher = current_matcher.get() matcher.set_target(ARG_KEY.format(key=key)) if prompt is not None: await cls.send(prompt, **kwargs) raise RejectedException @classmethod async def reject_receive( cls, id: str = "", prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None, **kwargs, ) -> NoReturn: """最近使用 `receive` 接收的消息不符合预期, 发送一条消息给当前交互用户并将当前事件处理流程中断在当前位置,在接收用户新的一个事件后从头开始执行当前处理函数 参数: id: 消息 id prompt: 消息内容 kwargs: {ref}`nonebot.adapters.Bot.send` 的参数,请参考对应 adapter 的 bot 对象 api """ matcher = current_matcher.get() matcher.set_target(RECEIVE_KEY.format(id=id)) if prompt is not None: await cls.send(prompt, **kwargs) raise RejectedException @classmethod def skip(cls) -> NoReturn: """跳过当前事件处理函数,继续下一个处理函数 通常在事件处理函数的依赖中使用。 """ raise SkippedException def get_receive(self, id: str, default: T = None) -> Union[Event, T]: """获取一个 `receive` 事件 如果没有找到对应的事件,返回 `default` 值 """ return self.state.get(RECEIVE_KEY.format(id=id), default) def set_receive(self, id: str, event: Event) -> None: """设置一个 `receive` 事件""" self.state[RECEIVE_KEY.format(id=id)] = event self.state[LAST_RECEIVE_KEY] = event def get_last_receive(self, default: T = None) -> Union[Event, T]: """获取最近一次 `receive` 事件 如果没有事件,返回 `default` 值 """ return self.state.get(LAST_RECEIVE_KEY, default) def get_arg(self, key: str, default: T = None) -> Union[Message, T]: """获取一个 `got` 消息 如果没有找到对应的消息,返回 `default` 值 """ return self.state.get(ARG_KEY.format(key=key), default) def set_arg(self, key: str, message: Message) -> None: """设置一个 `got` 消息""" self.state[ARG_KEY.format(key=key)] = message def set_target(self, target: str, cache: bool = True) -> None: if cache: self.state[REJECT_CACHE_TARGET] = target else: self.state[REJECT_TARGET] = target def get_target(self, default: T = None) -> Union[str, T]: return self.state.get(REJECT_TARGET, default) def stop_propagation(self): """阻止事件传播""" self.block = True async def update_type(self, bot: Bot, event: Event) -> str: updater = self.__class__._default_type_updater if not updater: return "message" return await updater(bot=bot, event=event, state=self.state, matcher=self) async def update_permission(self, bot: Bot, event: Event) -> Permission: updater = self.__class__._default_permission_updater if not updater: return USER(event.get_session_id(), perm=self.permission) return await updater(bot=bot, event=event, state=self.state, matcher=self) async def resolve_reject(self): handler = current_handler.get() self.handlers.insert(0, handler) if REJECT_CACHE_TARGET in self.state: self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET] async def simple_run( self, bot: Bot, event: Event, state: T_State, stack: Optional[AsyncExitStack] = None, dependency_cache: Optional[T_DependencyCache] = None, ): logger.trace( f"Matcher {self} run with incoming args: " f"bot={bot}, event={event}, state={state}" ) b_t = current_bot.set(bot) e_t = current_event.set(event) m_t = current_matcher.set(self) try: # Refresh preprocess state self.state.update(state) while self.handlers: handler = self.handlers.pop(0) current_handler.set(handler) logger.debug(f"Running handler {handler}") try: await handler( matcher=self, bot=bot, event=event, state=self.state, stack=stack, dependency_cache=dependency_cache, ) except TypeMisMatch as e: logger.debug( f"Handler {handler} param {e.param.name} value {e.value} " f"mismatch type {e.param._type_display()}, skipped" ) except SkippedException as e: logger.debug(f"Handler {handler} skipped") except StopPropagation: self.block = True finally: logger.info(f"Matcher {self} running complete") current_bot.reset(b_t) current_event.reset(e_t) current_matcher.reset(m_t) # 运行handlers async def run( self, bot: Bot, event: Event, state: T_State, stack: Optional[AsyncExitStack] = None, dependency_cache: Optional[T_DependencyCache] = None, ): try: await self.simple_run(bot, event, state, stack, dependency_cache) except RejectedException: await self.resolve_reject() type_ = await self.update_type(bot, event) permission = await self.update_permission(bot, event) Matcher.new( type_, Rule(), permission, self.handlers, temp=True, priority=0, block=True, plugin=self.plugin, module=self.module, expire_time=datetime.now() + bot.config.session_expire_timeout, default_state=self.state, default_type_updater=self.__class__._default_type_updater, default_permission_updater=self.__class__._default_permission_updater, ) except PausedException: type_ = await self.update_type(bot, event) permission = await self.update_permission(bot, event) Matcher.new( type_, Rule(), permission, self.handlers, temp=True, priority=0, block=True, plugin=self.plugin, module=self.module, expire_time=datetime.now() + bot.config.session_expire_timeout, default_state=self.state, default_type_updater=self.__class__._default_type_updater, default_permission_updater=self.__class__._default_permission_updater, ) except FinishedException: pass __autodoc__ = { "MatcherMeta": False, "Matcher.get_target": False, "Matcher.set_target": False, "Matcher.update_type": False, "Matcher.update_permission": False, "Matcher.resolve_reject": False, "Matcher.simple_run": False, }