Skip to main content

四:使用机器人管理群

使用机器人管理群,可以说是机器人最常见的应用之一了。这一节,我们将实现一个简单的群管理机器人。

预期的功能

我们要实现最简单的群管理功能:垃圾信息撤回与警告。

垃圾信息判断

我们使用百度云的文本审核 API。和上一节一样,外部 API 的调用方式不是重点,这里只放代码,细节略过。

from functools import lru_cache

import httpx


@lru_cache()
def get_access_token(api_key, secret_key):
url = 'https://aip.baidubce.com/oauth/2.0/token'
params = {
'grant_type': 'client_credentials',
'client_id': api_key,
'client_secret': secret_key,
}
resp = httpx.get(url, params=params)
resp.raise_for_status()
return resp.json()['access_token']

BD_API_KEY = '你的 API KEY'
SECRET_KEY = '你的 SECRET KEY'

async def text_censor(s: str) -> bool:
"""文本审核。返回 True 表示文本无问题。"""
access_token = get_access_token(BD_API_KEY, SECRET_KEY)
url = "https://aip.baidubce.com/rest/2.0/solution/v1/text_censor/v2/user_defined"
async with httpx.AsyncClient() as client:
try:
resp = await client.post(
url, params={'access_token': access_token}, data={'text': s}
)
resp.raise_for_status()
return resp.json()['conclusionType'] != 2
except (httpx.NetworkError, httpx.HTTPStatusError, KeyError):
return True

其实只检查 conclusionType 有点过于严格了,这里仅作为示例,实际应用中还需要检查其他信息,进行辅助判断。

调用 API

我们先模仿上一节的方式,搭好一个基本的框架:

@bot.on(GroupMessage)
async def text_censor_event(event: GroupMessage):
msg = "".join(map(str, event.message_chain[Plain]))
if not await text_censor(msg):
...

撤回消息

判断出文本是垃圾信息之后,我们需要调用 API 来撤回这条消息。

但是,如果你去翻一下 YiriMirai 的源码或者 API 文档,会发现 Mirai 类没有像 send 一样提供一个撤回消息的方法。

……当然我们没有理由不支持撤回消息。实际上,用 bot.recall 就可以撤回消息。

但是为什么找不到这个方法的定义呢?因为 YiriMirai 的很多方法都是“动态”的,Mirai 类通过 __getattr__ 方法,直接将方法调用与 mirai-api-http 的接口绑定。

因此,你可以在 mirai-api-http 的文档中寻找你需要的 API,然后直接用 bot.API名称 调用即可。

所以,要撤回消息,可以这样写:

await bot.recall(event.message_chain.message_id)
异步

YiriMirai 中所有的 API 调用都是异步的,必须要 await 才能真正调用。

错误处理

机器人在群里撤回其他人的消息,需要机器人有管理员权限。当机器人权限不足,或者尝试撤回其他管理员的消息时,会因为权限不足引发 ApiError 异常。

我们认为权限判断和异常处理应该是使用者的工作。你可以像这样捕获这个异常:

from mirai.exceptions import ApiError

try:
await bot.recall(event.message_chain.message_id)
except ApiError:
pass

发送私聊消息

撤回消息之后,我们先在群中发送一条警告:

await bot.send(event, f'{event.sender.member_name},不能说奇怪的话哟~')

然后机器人以私聊消息的形式,向你的账号发送一条报告。

这听起来不难,但是看一看 bot.send 方法的定义,你会发现它没有通过 QQ 号发送消息的功能。

这是因为 bot.send 不是 mirai-api-http 提供的 API,而是 YiriMirai 提供的一个简便方法。它实际上是对 send_friend_message send_group_message send_temp_message 等 API 的封装。如果只给定一个账号,不能确定这到底是 QQ 号还是群号,send 就无法调用合适的方法。

所以,想要向给定的账号发送私聊消息,要使用 send_friend_message 这个 API。幸运的是,YiriMirai 有良好的封装,send_friend_messagesend 用起来几乎一样方便。

await bot.send_friend_message(12345678, f'检测到异常发言: {repr(event)}') # 改成你的 QQ 号
名称转写

在 mirai-api-http 的文档中,可以发现发送私聊消息的接口名称是 sendFriendMessage,但是这里我们用的是 send_friend_message。这是为什么呢?

为了符合 PEP 8 规范,YiriMirai 对所有的 API 都进行了名称转写,全部使用小写字母和下划线分隔。例如,sendFriendMessage 转写为 send_friend_messagefile/info 转写为 file_info。API 的原始名称与转写后的名称都是可以通过点号调用的,除非原始名称带有斜杠。你可以仿照这个例子,得出其他 API 的命名。

现在我们把调用 API 的部分综合起来,完成消息处理器:

from mirai.exceptions import ApiError

@bot.on(GroupMessage)
async def text_censor_event(event: GroupMessage):
msg = "".join(map(str, event.message_chain[Plain]))
if not await text_censor(msg):
try:
await bot.recall(event.message_chain.message_id)
except ApiError:
pass
await bot.send(event, f'{event.sender.member_name},不能说奇怪的话哟~')
await bot.send_friend_message(12345678, f'检测到异常发言: {repr(event.sender)}') # 改成你的 QQ 号

总结

这一节我们为机器人实现了简单的群反垃圾信息功能。

本节的示例代码在这里

准备好了的话,就前往下一节吧。