🚸 add support of rule to_me in mirai adapter

This commit is contained in:
Mix 2021-02-01 16:31:51 +08:00
parent 5a63827f22
commit c0fa137fed
4 changed files with 107 additions and 13 deletions

View File

@ -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'<r>{escape_tag(str(message))}</r>: ')
@overrides(BaseBot)
async def call_api(self, api: str, **data) -> NoReturn:

View File

@ -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}.')

View File

@ -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

View File

@ -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