过滤器与触发器
过滤器
事件过滤器定义在 mirai_extensions.trigger.filter
模块,提供对事件进行选择性过滤和解析的功能。
捕获函数
事件过滤器允许用户传入自定义的捕获函数。
捕获函数可以在创建过滤器时设置,或者通过装饰器的方式设置。
# 方式一
def filter_one_func(event: FriendMessage):
if event.sender.id == 12345678:
return event.sender.nickname or ''
filter_one = Filter(FriendMessage, func=filter_one_func)
# 方式二
@Filter(FriendMessage)
def filter_two(event: FriendMessage):
if event.sender.id == 12345678:
return event.sender.nickname or ''
当使用类装饰器的方式创建过滤器时,被装饰函数的名称将失效。比如上例中,filter_two
将不再是函数, 而是成为 Filter 实例。
上例中的过滤器将检测好友消息的发送对象,只有来自 12345678 的消息会被捕获。其他情况下,过滤器返回默认值 None, 不会被捕获。
过滤器的捕获函数只能是同步函数。
副作用与阻塞
请尽量不要在过滤器中执行有副作用(影响到外部)的代码,以及导致阻塞或长时间挂起的代码(包括异步等待)。
这是由事件触发器的原理决定的。收到新事件时,事件触发器是否被调用是不确定的(如果事件已经被某一个触发器拦截,那么就不会传递给其他触发器),而且,当一个事件触发器的过滤器被阻塞或挂起后,其他事件触发器也不会被调用,直到过滤器完成并返回一个值。
我们推荐在过滤器中只进行简单的判断。
混入
事件过滤器提供了混入(mixin)机制,允许混入其他过滤器。
过滤器会先检查混入的过滤器,若任何一个未捕获,直接停止捕获,返回“未捕获”状态。
在创建过滤器时,可以通过 mixin
参数指定混入的过滤器。
@Filter(FriendMessage, mixin=[filter_one])
def filter_three(event: FriendMessage):
return event.message_chain.get_first(At)
这样,过滤器 filter_three
将会先检查 filter_one
,若没有捕获,则直接返回“未捕获”状态。
被混入的过滤器的返回值将丢失。过滤器混入仅用于检查事件是否被捕获,事件的解析仍有当前过滤器完成。
自定义过滤器
可以通过继承 Filter
类,创建自定义的过滤器。
过滤器的捕获过程发生在 _catch
方法中,重写这个方法,可以实现自定义的捕获过程。
class MyFilter(Filter[FriendMessage]):
def __init__(self):
super().__init__(FriendMessage)
def _catch(self, event: FriendMessage) -> Optional[str]:
if event.sender.id == 12345678:
return event.sender.nickname or ''
类型标注
Filter
类是一个泛型类,可以通过类型标注,指定接收事件的类型。
不过,因为 Python 的泛型并不做运行时检查,只被静态类型检查器使用,所以不做标注也是可以的。
BaseFilter
如果一个过滤器并不能用来实际的捕获,而只是用作其他过滤器的混入,可以从 BaseFilter
类继承。
class MyBaseFilter(BaseFilter[FriendMessage]):
def __init__(self, qq: int):
self.qq = qq
def _catch(self, event: FriendMessage) -> Optional[FriendMessage]:
if event.sender.id == self.qq:
return event
@Filter(FriendMessage, mixin=[MyBaseFilter(12345678)])
def filter_four(event: FriendMessage):
return event.sender.nickname or ''
预定义的过滤器
在 mirai_extensions.trigger.message
模块中,有一些预定义的过滤器。
FriendFilter
GroupFilter
GroupMemberFilter
QuoteFilter
继承自 BaseFilter
,分别检查是否是对应的好友、群、群成员,是否回复指定消息。这四个过滤器用于作为其他过滤器的混入。
FriendMessageFilter
GroupMessageFilter
TempMessageFilter
继承自 Filter
,分别检查是否是对应的好友、群中的某个群成员、某个群成员的临时会话的消息。这三个过滤器可以自定义捕获函数。实际上,它们就是预先提供了混入上面四个过滤器的 Filter
。
触发器
触发器定义在 mirai_extensions.trigger.trigger
模块中。
目前,触发器仅在中断控制器中使用。
触发器抽象了“等待符合条件的事件触发”的逻辑。为触发器指定过滤器后,可以根据过滤器的返回值来决定是否触发事件。
创建触发器
在创建触发器时,指定使用的过滤器,以及触发器的优先级(可选)。
@Filter(FriendMessage)
def filter_one(event: FriendMessage):
...
trigger = Trigger(filter_one, priority=1)
在中断控制器中使用
中断控制器的 inc.wait
方法可以传入触发器。
inc.wait
也可传入过滤器,这时候,中断控制器会基于传入的过滤器,自动创建触发器。
主动使用触发器
触发器在设计上类似于 asyncio.Future
,有已完成和未完成两种状态。
触发器创建后,默认为未完成状态。通过 catch
方法尝试捕获事件,捕获成功后将进入已完成状态。使用 wait
方法等待一个触发器完成。
触发器的状态可以通过 done
方法来查询,触发器已完成时,done
方法将返回 True
。
题外话
大家可以看到,这个库的名字叫 trigger
(触发器),但是翻一下文档,会发现除了这里,全部都是讲的“过滤器”。
这可以算作设计上的失误吧。在早期版本 0.1.0 和 0.2.0 中,这个库的确是以触发器为主体,当时的过滤器只是触发器内部的一个函数。
然后我在给 0.2.0 写文档的时候,突然发现触发器的设计实际上非常不自然。于是,我决定把过滤器从触发器中分离出来,并且赋予过滤器更多的功能。所以,这个库虽然名字还叫 trigger
,但是实际上已经以过滤器为主了。