From 5b96ac122f1967d075d970cf0f3a3c545e08504d Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Sun, 23 Apr 2023 23:40:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=80=82=E9=85=8Dnakuru=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- main.py | 10 ++- pkg/qqbot/manager.py | 28 ++++-- pkg/qqbot/sources/nakuru.py | 175 ++++++++++++++++++++++++++++++++---- 4 files changed, 189 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 6d5de378..42034518 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,6 @@ res/announcement_saved res/announcement_saved.json cmdpriv.json tips.py -.venv \ No newline at end of file +.venv +bin/ +.vscode \ No newline at end of file diff --git a/main.py b/main.py index fbdd961a..4980c096 100644 --- a/main.py +++ b/main.py @@ -243,6 +243,8 @@ def start(first_time_init=False): "mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format( e)) else: + import traceback + traceback.print_exc() logging.error( "捕捉到未知异常:{}, 请前往 https://github.com/RockChinQ/QChatGPT/issues 查找或提issue".format(e)) known_exception_caught = True @@ -262,9 +264,11 @@ def start(first_time_init=False): if first_time_init: if not known_exception_caught: - logging.info("QQ: {}, MAH: {}".format(config.mirai_http_api_config['qq'], config.mirai_http_api_config['host']+":"+str(config.mirai_http_api_config['port']))) - logging.info('程序启动完成,如长时间未显示 ”成功登录到账号xxxxx“ ,并且不回复消息,请查看 ' - 'https://github.com/RockChinQ/QChatGPT/issues/37') + import config + if config.msg_source_adapter == "yirimirai": + logging.info("QQ: {}, MAH: {}".format(config.mirai_http_api_config['qq'], config.mirai_http_api_config['host']+":"+str(config.mirai_http_api_config['port']))) + logging.info('程序启动完成,如长时间未显示 ”成功登录到账号xxxxx“ ,并且不回复消息,请查看 ' + 'https://github.com/RockChinQ/QChatGPT/issues/37') else: sys.exit(1) else: diff --git a/pkg/qqbot/manager.py b/pkg/qqbot/manager.py index ce5b1c90..2a160c95 100644 --- a/pkg/qqbot/manager.py +++ b/pkg/qqbot/manager.py @@ -80,7 +80,6 @@ class QQBotManager: def __init__(self, first_time_init=True): import config - mirai_http_api_config = config.mirai_http_api_config self.timeout = config.process_message_timeout self.retry = config.retry_times @@ -88,12 +87,26 @@ class QQBotManager: # 故只在第一次初始化时创建bot对象,重载之后使用原bot对象 # 因此,bot的配置不支持热重载 if first_time_init: + logging.info("Use adapter:" + config.msg_source_adapter) if config.msg_source_adapter == 'yirimirai': from pkg.qqbot.sources.yirimirai import YiriMiraiAdapter + + mirai_http_api_config = config.mirai_http_api_config self.bot_account_id = config.mirai_http_api_config['qq'] self.adapter = YiriMiraiAdapter(mirai_http_api_config) elif config.msg_source_adapter == 'nakuru': - pass + from pkg.qqbot.sources.nakuru import NakuruProjectAdapter + self.adapter = NakuruProjectAdapter(config.nakuru_config) + # nakuru库有bug,这个接口没法带access_token,会失败 + # 所以目前自行发请求 + import requests + resp = requests.get( + url="http://{}:{}/get_login_info".format(config.nakuru_config['host'], config.nakuru_config['http_port']), + headers={ + 'Authorization': "Bearer " + config.nakuru_config['token'] if 'token' in config.nakuru_config else "" + } + ) + self.bot_account_id = int(resp.json()['data']['user_id']) else: self.adapter = pkg.utils.context.get_qqbot_manager().adapter @@ -146,10 +159,12 @@ class QQBotManager: pkg.utils.context.get_thread_ctl().submit_user_task( stranger_message_handler, ) - self.adapter.register_listener( - StrangerMessage, - on_stranger_message - ) + # nakuru不区分好友和陌生人,故仅为yirimirai注册陌生人事件 + if config.msg_source_adapter == 'yirimirai': + self.adapter.register_listener( + StrangerMessage, + on_stranger_message + ) def on_group_message(event: GroupMessage): @@ -272,7 +287,6 @@ class QQBotManager: def on_group_message(self, event: GroupMessage): import config reply = '' - def process(text=None) -> str: replys = "" if At(self.bot_account_id) in event.message_chain: diff --git a/pkg/qqbot/sources/nakuru.py b/pkg/qqbot/sources/nakuru.py index addc2ed4..46e17c79 100644 --- a/pkg/qqbot/sources/nakuru.py +++ b/pkg/qqbot/sources/nakuru.py @@ -2,48 +2,141 @@ import mirai from ..adapter import MessageSourceAdapter, MessageConverter, EventConverter import nakuru +import nakuru.entities.components as nkc import asyncio import typing +import traceback +import logging +import json class NakuruProjectMessageConverter(MessageConverter): @staticmethod def yiri2target(message_chain: mirai.MessageChain) -> list: - pass + msg_list = [] + if type(message_chain) is mirai.MessageChain: + msg_list = message_chain.__root__ + elif type(message_chain) is list: + msg_list = message_chain + else: + raise Exception("Unknown message type: " + str(message_chain) + type(message_chain)) + + nakuru_msg_list = [] + + # 遍历并转换 + for component in msg_list: + if type(component) is mirai.Plain: + nakuru_msg_list.append(nkc.Plain(component.text)) + elif type(component) is mirai.Image: + if component.url is not None: + nakuru_msg_list.append(nkc.Image.fromURL(component.url)) + elif component.base64 is not None: + nakuru_msg_list.append(nkc.Image.fromBase64(component.base64)) + elif component.path is not None: + nakuru_msg_list.append(nkc.Image.fromFileSystem(component.path)) + elif type(component) is mirai.Face: + nakuru_msg_list.append(nkc.Face(id=component.face_id)) + elif type(component) is mirai.At: + nakuru_msg_list.append(nkc.At(qq=component.target)) + elif type(component) is mirai.AtAll: + nakuru_msg_list.append(nkc.AtAll()) + elif type(component) is mirai.Voice: + pass + else: + pass + + return nakuru_msg_list @staticmethod def target2yiri(message_chain: typing.Any) -> mirai.MessageChain: - pass + assert type(message_chain) is list + + yiri_msg_list = [] + for component in message_chain: + if type(component) is nkc.Plain: + yiri_msg_list.append(mirai.Plain(text=component.text)) + elif type(component) is nkc.Image: + yiri_msg_list.append(mirai.Image(url=component.url)) + elif type(component) is nkc.Face: + yiri_msg_list.append(mirai.Face(face_id=component.id)) + elif type(component) is nkc.At: + yiri_msg_list.append(mirai.At(target=component.qq)) + elif type(component) is nkc.AtAll: + yiri_msg_list.append(mirai.AtAll()) + else: + pass + logging.debug("转换后的消息链: " + str(yiri_msg_list)) + return mirai.MessageChain(yiri_msg_list) class NakuruProjectEventConverter(EventConverter): @staticmethod def yiri2target(event: typing.Type[mirai.Event]): if event is mirai.GroupMessage: - return "GroupMessage" + return nakuru.GroupMessage elif event is mirai.FriendMessage: - return "FriendMessage" - elif event is mirai.StrangerMessage: - return "FriendMessage" + return nakuru.FriendMessage else: - raise Exception("Unknown event type: " + str(event)) + raise Exception("未支持转换的事件类型: " + str(event)) @staticmethod def target2yiri(event: typing.Any) -> mirai.Event: - pass + if type(event) is nakuru.FriendMessage: + return mirai.FriendMessage( + sender=mirai.models.entities.Friend( + id=event.sender.user_id, + nickname=event.sender.nickname, + remark=event.sender.nickname + ), + message_chain=NakuruProjectMessageConverter.target2yiri(event.message), + time=event.time + ) + elif type(event) is nakuru.GroupMessage: + permission = "MEMBER" + + if event.sender.role == "admin": + permission = "ADMINISTRATOR" + elif event.sender.role == "owner": + permission = "OWNER" + + import mirai.models.entities as entities + return mirai.GroupMessage( + sender=mirai.models.entities.GroupMember( + id=event.sender.user_id, + member_name=event.sender.nickname, + permission=permission, + group=mirai.models.entities.Group( + id=event.group_id, + name=event.sender.nickname, + permission=entities.Permission.Member + ), + special_title=event.sender.title, + join_timestamp=0, + last_speak_timestamp=0, + mute_time_remaining=0, + ), + message_chain=NakuruProjectMessageConverter.target2yiri(event.message), + time=event.time + ) + else: + raise Exception("未支持转换的事件类型: " + str(event)) + class NakuruProjectAdapter(MessageSourceAdapter): """nakuru-project适配器""" bot: nakuru.CQHTTP - message_converter: NakuruProjectMessageConverter - event_converter: NakuruProjectEventConverter + message_converter: NakuruProjectMessageConverter = NakuruProjectMessageConverter() + event_converter: NakuruProjectEventConverter = NakuruProjectEventConverter() + + listener_list: list[dict] def __init__(self, config: dict): """初始化nakuru-project的对象""" self.bot = nakuru.CQHTTP(**config) + self.listener_list = [] def send_message( self, @@ -67,29 +160,77 @@ class NakuruProjectAdapter(MessageSourceAdapter): message: mirai.MessageChain, quote_origin: bool = False ): - pass + if type(message_source) is mirai.GroupMessage: + task = self.bot.sendGroupMessage( + message_source.sender.group.id, + self.message_converter.yiri2target(message), + # quote=message_source.message_id if quote_origin else None + ) + elif type(message_source) is mirai.FriendMessage: + task = self.bot.sendFriendMessage( + message_source.sender.id, + self.message_converter.yiri2target(message), + # quote=message_source.message_id if quote_origin else None + ) + else: + raise Exception("Unknown message source type: " + str(type(message_source))) + + asyncio.run(task) def is_muted(self, group_id: int) -> bool: - pass + return False def register_listener( self, event_type: typing.Type[mirai.Event], callback: typing.Callable[[mirai.Event], None] ): - def listener_wrapper(app: nakuru.CQHTTP, source: nakuru.GroupMessage): - callback(self.event_converter.target2yiri(source)) - - self.bot.receiver(self.event_converter.yiri2target(event_type))(listener_wrapper) + try: + logging.debug("注册监听器: " + str(event_type) + " -> " + str(callback)) + async def listener_wrapper(app: nakuru.CQHTTP, source: self.event_converter.yiri2target(event_type)): + callback(self.event_converter.target2yiri(source)) + self.listener_list.append( + { + "event_type": event_type, + "callable": callback, + "wrapper": listener_wrapper, + } + ) + self.bot.receiver(self.event_converter.yiri2target(event_type).__name__)(listener_wrapper) + logging.debug("注册完成") + except Exception as e: + traceback.print_exc() + raise e def unregister_listener( self, event_type: typing.Type[mirai.Event], callback: typing.Callable[[mirai.Event], None] ): - pass + nakuru_event_name = self.event_converter.yiri2target(event_type) + + new_event_list = [] + + # 从本对象的监听器列表中查找并删除 + target_wrapper = None + for listener in self.listener_list: + if listener["event_type"] == event_type and listener["callable"] == callback: + target_wrapper = listener["wrapper"] + self.listener_list.remove(listener) + break + + if target_wrapper is None: + raise Exception("未找到对应的监听器") + + for func in self.bot.event[nakuru_event_name]: + if func.callable != target_wrapper: + new_event_list.append(func) + + self.bot.event[nakuru_event_name] = new_event_list def run_sync(self): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) self.bot.run() def kill(self) -> bool: