Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: 如何在钩子函数“运行预处理”中修改message内容 #443

Closed
chanchancl opened this issue Jul 20, 2021 · 9 comments
Closed
Labels
question Further information is requested

Comments

@chanchancl
Copy link

chanchancl commented Jul 20, 2021

主要是想使用一个函数对消息进行预处理,将繁体字转为简体字,方便matcher的匹配

例如

@run_preprocessor
async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State):
    event.message = convert(event.message, "zh-cn")
@chanchancl chanchancl added the enhancement New feature or request label Jul 20, 2021
@mnixry mnixry added question Further information is requested and removed enhancement New feature or request labels Jul 20, 2021
@mnixry
Copy link
Member

mnixry commented Jul 20, 2021

你这种写法思路看起来是正确的, 只要实现好convert函数就应该没有问题
好像需要使用event_preprocessor来替代

@mnixry mnixry changed the title Feature: 在钩子函数“运行预处理”中,如何修改message内容 Question: 如何在钩子函数“运行预处理”中修改message内容 Jul 20, 2021
@j1g5awi
Copy link
Member

j1g5awi commented Jul 20, 2021

run_preprocessor 装饰器是在事件分发之后执行,你想要在 Matcher 匹配前执行需要用 event_preprocessor 装饰器。

@yanyongyu
Copy link
Member

直接赋值即可,注意类型应为Message

@chanchancl
Copy link
Author

通过一些测试,如下的方法是可以完成需求的,但总感觉特别别扭,大佬有没有更好的实现方法。。
比如将 父类转换为子类。

@event_preprocessor
async def _(bot: Bot, event: Event, state: T_State):
    if not isinstance(event, MessageEvent):
        return
    event.__class__ = MessageEvent
    event.message = Message(convert(str(event.message), "zh-cn"))

@mnixry
Copy link
Member

mnixry commented Jul 21, 2021

通过一些测试,如下的方法是可以完成需求的,但总感觉特别别扭,大佬有没有更好的实现方法。。
比如将 父类转换为子类。

如果你的代码编辑器足够智能, 你应该是不需要这一行的

@event_preprocessor
async def _(bot: Bot, event: Event, state: T_State):
    if not isinstance(event, MessageEvent):
       return
-   event.__class__ = MessageEvent
    event.message = Message(convert(str(event.message), "zh-cn"))

另外, 直接将Message转换成字符串再转回, 可能会导致CQ码注入漏洞

我推荐直接遍历Message对象(它继承于list), 并对所有type=text的文本进行翻译

@event_preprocessor
async def _(bot: Bot, event: Event, state: T_State):
    if not isinstance(event, MessageEvent):
        return
    for segment in event.message:
        if not segment.is_text():
            continue
        segment.data['text'] = convert(segment.data['text'], 'zh-cn') # <-- 这个函数看起来不是异步函数, 有可能造成事件循环阻塞

大概是这样

拓展阅读

@chanchancl
Copy link
Author

chanchancl commented Jul 21, 2021

@mnixry 多谢大佬,
这个类型转换确实是不需要的,event本身就有 message 字段

另外我对convert稍微包装了一下,让其成为一个异步函数,应该可以避免阻塞事件循环。

再问几个问题

  1. event.message 为何是一个数组,一个消息message 中可以有多个消息段,这些消息段是怎样划分的。

比如一个消息中,同时包含文本,图片等,是以这些消息的类型来划分的吗?

  1. 通过打印类型,我知道event实际上是 nonebot.adapters.cqhttp.GroupMessageEvent, 如果将来我需要使用GroupMessageEvent中的字段,比如group_id,在这种情况下,该如何对event进行转换

最后再贴上最终的代码

async def AsyncTranslate(text, dst):
    return convert(text, dst)


@event_preprocessor
async def _(bot: Bot, event: Event, state: T_State):
    if not isinstance(event, MessageEvent):
        return

    for segment in event.message:
        if not segment.is_text():
            continue
        segment.data['text'] = await AsyncTranslate(str(event.message), "zh-cn")

@mnixry
Copy link
Member

mnixry commented Jul 21, 2021

event.message 为何是一个数组

这是NoneBot的设计,可以同时包括多种形式的消息,能够对富文本等进行良好的支持, 同时能够规避由字符串转义带来的注入问题

这种数组消息格式同时也是OneBot标准所规范的

一个消息message 中可以有多个消息段,这些消息段是怎样划分的。
比如一个消息中,同时包含文本,图片等,是以这些消息的类型来划分的吗?

这个和OneBot协议的实现有关, 在大多数实现中, 它的确是按照消息类型和元素在消息中的位置决定的

通过打印类型,我知道event实际上是 nonebot.adapters.cqhttp.GroupMessageEvent, 如果将来我需要使用GroupMessageEvent中的字段,比如group_id,在这种情况下,该如何对event进行转换

一样的, 你只需要加上isinstance(event,GroupMessageEvent)判断就可以在代码段内使用GroupMessageEvent特有的字段了

顺带一提, 事件类型的转换是在adapter接收事件的时候就完成的:

相关代码

async def handle_message(self, message: dict):
"""
:说明:
调用 `_check_reply <#async-check-reply-bot-event>`_, `_check_at_me <#check-at-me-bot-event>`_, `_check_nickname <#check-nickname-bot-event>`_ 处理事件并转换为 `Event <#class-event>`_
"""
if not message:
return
if "post_type" not in message:
ResultStore.add_result(message)
return
try:
post_type = message['post_type']
detail_type = message.get(f"{post_type}_type")
detail_type = f".{detail_type}" if detail_type else ""
sub_type = message.get("sub_type")
sub_type = f".{sub_type}" if sub_type else ""
models = get_event_model(post_type + detail_type + sub_type)
for model in models:
try:
event = model.parse_obj(message)
break
except Exception as e:
log("DEBUG", "Event Parser Error", e)
else:
event = Event.parse_obj(message)
# Check whether user is calling me
await _check_reply(self, event)
_check_at_me(self, event)
_check_nickname(self, event)
await handle_event(self, event)

另外,在事件处理函数中, 我们还支持通过类型标记事件处理函数进行重载来简化代码

最后再贴上最终的代码

async def AsyncTranslate(text, dst):
    return convert(text, dst)

你这样并不能完成从同步到异步的转换, 如果想要实现这种转换, 请考虑使用nonebot.utils.run_sync

@chanchancl
Copy link
Author

@mnixry 再次感谢

以前还真没发现 isinstance 有这种功能,试了一下,发现确实如果通过 not isinstance 判断之后,后面就可以使用相关的字段了。

剩下的我应该再去读读onebot的文档,我之前一直是用 websocket 裸连go-cqhttp,这和使用框架之间还是有一些区别

@event_preprocessor
async def _(bot: Bot, event: Event, state: T_State):
    if not isinstance(event, MessageEvent):
        return

    for segment in event.message:
        if not segment.is_text():
            continue
        segment.data['text'] = await run_sync(convert(str(event.message), "zh-cn"))
    
    if not isinstance(event, GroupMessageEvent):
        return
    log.logger.debug(f"test group {event.group_id}")

@mnixry
Copy link
Member

mnixry commented Jul 21, 2021

以前还真没发现 isinstance 有这种功能,试了一下,发现确实如果通过 not isinstance 判断之后,后面就可以使用相关的字段了。

这个代码提示应该是编辑器提供的能力, 可能是以前你用的编辑器太菜了


感谢回复, 鉴于问题看起来已经解决, 我将关闭这个Issue, 如果您有任何相关的问题可以随时重新打开

如果有任何其他的问题, 您可以在讨论版创建提问或者加入我们的QQ群, 我们将不吝解答

如果对NoneBot2有更多的反馈或者建议, 可以创建新的Issue以便于我们进行进一步的追踪和处理

@mnixry mnixry closed this as completed Jul 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Development

No branches or pull requests

4 participants