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

Feat: 适配aiocqhttp #678

Merged
merged 1 commit into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions pkg/core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,24 @@ def __init__(self):
async def initialize(self):
pass

# async def wait_loop(self):

async def run(self):
await self.plugin_mgr.load_plugins()
await self.plugin_mgr.initialize_plugins()

tasks = []

try:
tasks = [
asyncio.create_task(self.im_mgr.run()),
asyncio.create_task(self.ctrl.run())
]
await asyncio.wait(tasks)
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)

except asyncio.CancelledError:
self.logger.info("程序退出.")
pass
except Exception as e:
self.logger.error(f"应用运行致命异常: {e}")
self.logger.debug(f"Traceback: {traceback.format_exc()}")
self.logger.info("程序退出.")
1 change: 1 addition & 0 deletions pkg/core/bootutils/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ async def init_logging() -> logging.Logger:
handler.setFormatter(color_formatter)
qcg_logger.addHandler(handler)

qcg_logger.debug("日志初始化完成,日志级别:%s" % level)
logging.basicConfig(
level=logging.INFO, # 设置日志输出格式
format="[DEPR][%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n%(message)s",
Expand Down
2 changes: 1 addition & 1 deletion pkg/platform/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def run_async(self):
"""异步运行"""
raise NotImplementedError

def kill(self) -> bool:
async def kill(self) -> bool:
"""关闭适配器

Returns:
Expand Down
5 changes: 5 additions & 0 deletions pkg/platform/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ async def initialize(self):
mirai_http_api_config = self.ap.platform_cfg.data['yiri-mirai-config']
self.bot_account_id = mirai_http_api_config['qq']
self.adapter = YiriMiraiAdapter(mirai_http_api_config)
elif self.ap.platform_cfg.data['platform-adapter'] == 'aiocqhttp':
from pkg.platform.sources.aiocqhttp import AiocqhttpAdapter

aiocqhttp_config = self.ap.platform_cfg.data['aiocqhttp-config']
self.adapter = AiocqhttpAdapter(aiocqhttp_config, self.ap)
# elif config['msg_source_adapter'] == 'nakuru':
# from pkg.platform.sources.nakuru import NakuruProjectAdapter
# self.adapter = NakuruProjectAdapter(config['nakuru_config'])
Expand Down
267 changes: 267 additions & 0 deletions pkg/platform/sources/aiocqhttp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
from __future__ import annotations
import typing
import asyncio
import traceback
import time
import datetime

import mirai
import mirai.models.message as yiri_message
import aiocqhttp

from .. import adapter
from ...pipeline.longtext.strategies import forward
from ...core import app


class AiocqhttpMessageConverter(adapter.MessageConverter):

@staticmethod
def yiri2target(message_chain: mirai.MessageChain) -> typing.Tuple[list, int, datetime.datetime]:
msg_list = aiocqhttp.Message()

msg_id = 0
msg_time = None

for msg in message_chain:
if type(msg) is mirai.Plain:
msg_list.append(aiocqhttp.MessageSegment.text(msg.text))
elif type(msg) is yiri_message.Source:
msg_id = msg.id
msg_time = msg.time
elif type(msg) is mirai.Image:
msg_list.append(aiocqhttp.MessageSegment.image(msg.path))
elif type(msg) is mirai.At:
msg_list.append(aiocqhttp.MessageSegment.at(msg.target))
elif type(msg) is mirai.AtAll:
msg_list.append(aiocqhttp.MessageSegment.at("all"))
elif type(msg) is mirai.Face:
msg_list.append(aiocqhttp.MessageSegment.face(msg.face_id))
elif type(msg) is mirai.Voice:
msg_list.append(aiocqhttp.MessageSegment.record(msg.path))
elif type(msg) is forward.Forward:
print("aiocqhttp 暂不支持转发消息组件的转换,使用普通消息链发送")

for node in msg.node_list:
msg_list.extend(AiocqhttpMessageConverter.yiri2target(node.message_chain)[0])

else:
msg_list.append(aiocqhttp.MessageSegment.text(str(msg)))

return msg_list, msg_id, msg_time

@staticmethod
def target2yiri(message: str, message_id: int = -1):
message = aiocqhttp.Message(message)

yiri_msg_list = []

yiri_msg_list.append(
yiri_message.Source(id=message_id, time=datetime.datetime.now())
)

for msg in message:
if msg.type == "at":
if msg.data["qq"] == "all":
yiri_msg_list.append(yiri_message.AtAll())
else:
yiri_msg_list.append(
yiri_message.At(
target=msg.data["qq"],
)
)
elif msg.type == "text":
yiri_msg_list.append(yiri_message.Plain(text=msg.data["text"]))
elif msg.type == "image":
yiri_msg_list.append(yiri_message.Image(url=msg.data["url"]))

chain = mirai.MessageChain(yiri_msg_list)

return chain


class AiocqhttpEventConverter(adapter.EventConverter):

@staticmethod
def yiri2target(event: mirai.Event, bot_account_id: int):

msg, msg_id, msg_time = AiocqhttpMessageConverter.yiri2target(event.message_chain)

if type(event) is mirai.GroupMessage:
role = "member"

if event.sender.permission == "ADMINISTRATOR":
role = "admin"
elif event.sender.permission == "OWNER":
role = "owner"

payload = {
"post_type": "message",
"message_type": "group",
"time": int(msg_time.timestamp()),
"self_id": bot_account_id,
"sub_type": "normal",
"anonymous": None,
"font": 0,
"message": str(msg),
"raw_message": str(msg),
"sender": {
"age": 0,
"area": "",
"card": "",
"level": "",
"nickname": event.sender.member_name,
"role": role,
"sex": "unknown",
"title": "",
"user_id": event.sender.id,
},
"user_id": event.sender.id,
"message_id": msg_id,
"group_id": event.group.id,
"message_seq": 0,
}

return aiocqhttp.Event.from_payload(payload)
elif type(event) is mirai.FriendMessage:

payload = {
"post_type": "message",
"message_type": "private",
"time": int(msg_time.timestamp()),
"self_id": bot_account_id,
"sub_type": "friend",
"target_id": bot_account_id,
"message": str(msg),
"raw_message": str(msg),
"font": 0,
"sender": {
"age": 0,
"nickname": event.sender.nickname,
"sex": "unknown",
"user_id": event.sender.id,
},
"message_id": msg_id,
"user_id": event.sender.id,
}

return aiocqhttp.Event.from_payload(payload)

@staticmethod
def target2yiri(event: aiocqhttp.Event):
yiri_chain = AiocqhttpMessageConverter.target2yiri(
event.message, event.message_id
)

if event.message_type == "group":
permission = "MEMBER"

if event.sender["role"] == "admin":
permission = "ADMINISTRATOR"
elif event.sender["role"] == "owner":
permission = "OWNER"
converted_event = mirai.GroupMessage(
sender=mirai.models.entities.GroupMember(
id=event.sender["user_id"], # message_seq 放哪?
member_name=event.sender["nickname"],
permission=permission,
group=mirai.models.entities.Group(
id=event.group_id,
name=event.sender["nickname"],
permission=mirai.models.entities.Permission.Member,
),
special_title=event.sender["title"],
join_timestamp=0,
last_speak_timestamp=0,
mute_time_remaining=0,
),
message_chain=yiri_chain,
time=event.time,
)
return converted_event
elif event.message_type == "private":
return mirai.FriendMessage(
sender=mirai.models.entities.Friend(
id=event.sender["user_id"],
nickname=event.sender["nickname"],
remark="",
),
message_chain=yiri_chain,
time=event.time,
)


class AiocqhttpAdapter(adapter.MessageSourceAdapter):

bot: aiocqhttp.CQHttp

bot_account_id: int

message_converter: AiocqhttpMessageConverter = AiocqhttpMessageConverter()
event_converter: AiocqhttpEventConverter = AiocqhttpEventConverter()

config: dict

ap: app.Application

def __init__(self, config: dict, ap: app.Application):
self.config = config
self.ap = ap

self.bot = aiocqhttp.CQHttp()

async def send_message(
self, target_type: str, target_id: str, message: mirai.MessageChain
):
return super().send_message(target_type, target_id, message)

async def reply_message(
self,
message_source: mirai.MessageEvent,
message: mirai.MessageChain,
quote_origin: bool = False,
):

aiocq_event = AiocqhttpEventConverter.yiri2target(message_source, self.bot_account_id)
aiocq_msg = AiocqhttpMessageConverter.yiri2target(message)[0]
if quote_origin:
aiocq_msg = aiocqhttp.MessageSegment.reply(aiocq_event.message_id) + aiocq_msg

return await self.bot.send(
aiocq_event,
aiocq_msg
)

async def is_muted(self, group_id: int) -> bool:
return False

def register_listener(
self,
event_type: typing.Type[mirai.Event],
callback: typing.Callable[[mirai.Event], None],
):
async def on_message(event: aiocqhttp.Event):
self.bot_account_id = event.self_id
self.ap.im_mgr.bot_account_id = event.self_id
try:
return await callback(self.event_converter.target2yiri(event))
except:
traceback.print_exc()

if event_type == mirai.GroupMessage:
self.bot.on_message("group")(on_message)
elif event_type == mirai.FriendMessage:
self.bot.on_message("private")(on_message)

def unregister_listener(
self,
event_type: typing.Type[mirai.Event],
callback: typing.Callable[[mirai.Event], None],
):
return super().unregister_listener(event_type, callback)

async def run_async(self):
await self.bot._server_app.run_task(**self.config)

async def kill(self) -> bool:
return False
10 changes: 3 additions & 7 deletions pkg/platform/sources/nakuru.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import nakuru.entities.components as nkc

from .. import adapter as adapter_model
from ...platform import blob
from ...utils import context
from ...pipeline.longtext.strategies import forward


class NakuruProjectMessageConverter(adapter_model.MessageConverter):
Expand Down Expand Up @@ -49,7 +48,7 @@ def yiri2target(message_chain: mirai.MessageChain) -> list:
nakuru_msg_list.append(nkc.Record.fromURL(component.url))
elif component.path is not None:
nakuru_msg_list.append(nkc.Record.fromFileSystem(component.path))
elif type(component) is blob.Forward:
elif type(component) is forward.Forward:
# 转发消息
yiri_forward_node_list = component.node_list
nakuru_forward_node_list = []
Expand Down Expand Up @@ -324,8 +323,5 @@ def run_sync(self):
asyncio.set_event_loop(loop)
self.bot.run()

async def run_async(self):
return await self.bot._run()

def kill(self) -> bool:
return False
return False
2 changes: 1 addition & 1 deletion pkg/platform/sources/yirimirai.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,5 @@ def unregister_listener(
async def run_async(self):
return await MiraiRunner(self.bot)._run()

def kill(self) -> bool:
async def kill(self) -> bool:
return False
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ openai
dulwich~=0.21.6
colorlog~=6.6.0
yiri-mirai-rc
aiocqhttp
websockets
urllib3
func_timeout~=4.3.5
Pillow
nakuru-project-idk
CallingGPT
tiktoken
PyYaml
Expand Down
6 changes: 5 additions & 1 deletion templates/platform.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@
"platform-adapter": "yiri-mirai",
"yiri-mirai-config": {
"adapter": "WebSocketAdapter",
"host": "localhost",
"host": "127.0.0.1",
"port": 8080,
"verifyKey": "yirimirai",
"qq": 123456789
},
"aiocqhttp-config": {
"host": "127.0.0.1",
"port": 8080
},
"track-function-calls": true,
"quote-origin": false,
"at-sender": false,
Expand Down
Loading