From c0fa137fed42529712fd6df3b267435a4357d28b Mon Sep 17 00:00:00 2001 From: Mix Date: Mon, 1 Feb 2021 16:31:51 +0800 Subject: [PATCH] :children_crossing: add support of rule `to_me` in mirai adapter --- nonebot/adapters/mirai/bot.py | 24 ++++++--- nonebot/adapters/mirai/event/base.py | 4 +- nonebot/adapters/mirai/event/message.py | 21 +++++++- nonebot/adapters/mirai/utils.py | 71 +++++++++++++++++++++++-- 4 files changed, 107 insertions(+), 13 deletions(-) diff --git a/nonebot/adapters/mirai/bot.py b/nonebot/adapters/mirai/bot.py index 3182d7a8..0a1262c7 100644 --- a/nonebot/adapters/mirai/bot.py +++ b/nonebot/adapters/mirai/bot.py @@ -13,11 +13,12 @@ from nonebot.exception import ApiNotAvailable, RequestDenied from nonebot.log import logger from nonebot.message import handle_event from nonebot.typing import overrides +from nonebot.utils import escape_tag from .config import Config as MiraiConfig from .event import Event, FriendMessage, GroupMessage, TempMessage from .message import MessageChain, MessageSegment -from .utils import catch_network_error, argument_validation +from .utils import catch_network_error, argument_validation, check_tome, Log class SessionManager: @@ -209,11 +210,22 @@ class Bot(BaseBot): @overrides(BaseBot) async def handle_message(self, message: dict): - await handle_event(bot=self, - event=Event.new({ - **message, - 'self_id': self.self_id, - })) + Log.debug(f'received message {message}') + try: + await handle_event( + bot=self, + event=await check_tome( + bot=self, + event=Event.new({ + **message, + 'self_id': self.self_id, + }), + ), + ) + except Exception as e: + logger.opt(colors=True, exception=e).exception( + 'Failed to handle message ' + f'{escape_tag(str(message))}: ') @overrides(BaseBot) async def call_api(self, api: str, **data) -> NoReturn: diff --git a/nonebot/adapters/mirai/event/base.py b/nonebot/adapters/mirai/event/base.py index 1362bd74..4a7b3809 100644 --- a/nonebot/adapters/mirai/event/base.py +++ b/nonebot/adapters/mirai/event/base.py @@ -80,8 +80,8 @@ class Event(BaseEvent): return event_class.parse_obj(data) except ValidationError as e: logger.info( - f'Failed to parse {data} to class {event_class.__name__}: {e}. ' - 'Fallback to parent class.') + f'Failed to parse {data} to class {event_class.__name__}: ' + f'{e.errors()!r}. Fallback to parent class.') event_class = event_class.__base__ raise ValueError(f'Failed to serialize {data}.') diff --git a/nonebot/adapters/mirai/event/message.py b/nonebot/adapters/mirai/event/message.py index 6021ea64..26d534d4 100644 --- a/nonebot/adapters/mirai/event/message.py +++ b/nonebot/adapters/mirai/event/message.py @@ -33,11 +33,20 @@ class MessageEvent(Event): class GroupMessage(MessageEvent): """群消息事件""" sender: GroupChatInfo + to_me: bool = False @overrides(MessageEvent) def get_session_id(self) -> str: return f'group_{self.sender.group.id}_' + self.get_user_id() + @overrides(MessageEvent) + def get_user_id(self) -> str: + return str(self.sender.id) + + @overrides(MessageEvent) + def is_tome(self) -> bool: + return self.to_me + class FriendMessage(MessageEvent): """好友消息事件""" @@ -47,15 +56,23 @@ class FriendMessage(MessageEvent): def get_user_id(self) -> str: return str(self.sender.id) - @overrides + @overrides(MessageEvent) def get_session_id(self) -> str: return 'friend_' + self.get_user_id() + @overrides(MessageEvent) + def is_tome(self) -> bool: + return True + class TempMessage(MessageEvent): """临时会话消息事件""" sender: GroupChatInfo - @overrides + @overrides(MessageEvent) def get_session_id(self) -> str: return f'temp_{self.sender.group.id}_' + self.get_user_id() + + @overrides(MessageEvent) + def is_tome(self) -> bool: + return True diff --git a/nonebot/adapters/mirai/utils.py b/nonebot/adapters/mirai/utils.py index 30adf42d..cb2b5e2d 100644 --- a/nonebot/adapters/mirai/utils.py +++ b/nonebot/adapters/mirai/utils.py @@ -1,17 +1,44 @@ +import re from functools import wraps -from typing import Callable, Coroutine, TypeVar +from typing import TYPE_CHECKING, Any, Callable, Coroutine, Optional, TypeVar import httpx -from pydantic import ValidationError, validate_arguments, Extra +from pydantic import Extra, ValidationError, validate_arguments import nonebot.exception as exception from nonebot.log import logger -from nonebot.utils import escape_tag +from nonebot.utils import escape_tag, logger_wrapper + +from .event import Event, GroupMessage +from .message import MessageSegment, MessageType + +if TYPE_CHECKING: + from .bot import Bot _AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine]) _AnyCallable = TypeVar("_AnyCallable", bound=Callable) +class Log: + _log = logger_wrapper('MIRAI') + + @classmethod + def info(cls, message: Any): + cls._log('INFO', str(message)) + + @classmethod + def debug(cls, message: Any): + cls._log('DEBUG', str(message)) + + @classmethod + def warn(cls, message: Any): + cls._log('WARNING', str(message)) + + @classmethod + def error(cls, message: Any, exception: Optional[Exception] = None): + cls._log('ERROR', str(message), exception=exception) + + class ActionFailed(exception.ActionFailed): """ :说明: @@ -89,3 +116,41 @@ def argument_validation(function: _AnyCallable) -> _AnyCallable: raise InvalidArgument return wrapper # type: ignore + + +async def check_tome(bot: "Bot", event: "Event") -> "Event": + if not isinstance(event, GroupMessage): + return event + + def _is_at(event: GroupMessage) -> bool: + for segment in event.message_chain: + segment: MessageSegment + if segment.type != MessageType.AT: + continue + if segment.data['target'] == event.self_id: + return True + return False + + def _is_nick(event: GroupMessage) -> bool: + text = event.get_plaintext() + if not text: + return False + nick_regex = '|'.join( + {i.strip() for i in bot.config.nickname if i.strip()}) + matched = re.search(rf"^({nick_regex})([\s,,]*|$)", text, re.IGNORECASE) + if matched is None: + return False + Log.info(f'User is calling me {matched.group(1)}') + return True + + def _is_reply(event: GroupMessage) -> bool: + for segment in event.message_chain: + segment: MessageSegment + if segment.type != MessageType.QUOTE: + continue + if segment.data['senderId'] == event.self_id: + return True + return False + + event.to_me = any([_is_at(event), _is_reply(event), _is_nick(event)]) + return event