diff --git a/pkg/config/impls/yaml.py b/pkg/config/impls/yaml.py new file mode 100644 index 0000000..f451800 --- /dev/null +++ b/pkg/config/impls/yaml.py @@ -0,0 +1,59 @@ +import os +import shutil +import yaml + +from .. import model as file_model + + +class YAMLConfigFile(file_model.ConfigFile): + """YAML配置文件""" + + def __init__( + self, config_file_name: str, template_file_name: str = None, template_data: dict = None + ) -> None: + self.config_file_name = config_file_name + self.template_file_name = template_file_name + self.template_data = template_data + + def exists(self) -> bool: + return os.path.exists(self.config_file_name) + + async def create(self): + if self.template_file_name is not None: + shutil.copyfile(self.template_file_name, self.config_file_name) + elif self.template_data is not None: + with open(self.config_file_name, "w", encoding="utf-8") as f: + yaml.dump(self.template_data, f, indent=4, allow_unicode=True) + else: + raise ValueError("template_file_name or template_data must be provided") + + async def load(self, completion: bool=True) -> dict: + + if not self.exists(): + await self.create() + + if self.template_file_name is not None: + with open(self.template_file_name, "r", encoding="utf-8") as f: + self.template_data = yaml.load(f, Loader=yaml.FullLoader) + + with open(self.config_file_name, "r", encoding="utf-8") as f: + try: + cfg = yaml.load(f, Loader=yaml.FullLoader) + except yaml.YAMLError as e: + raise Exception(f"配置文件 {self.config_file_name} 语法错误: {e}") + + if completion: + + for key in self.template_data: + if key not in cfg: + cfg[key] = self.template_data[key] + + return cfg + + async def save(self, cfg: dict): + with open(self.config_file_name, "w", encoding="utf-8") as f: + yaml.dump(cfg, f, indent=4, allow_unicode=True) + + def save_sync(self, cfg: dict): + with open(self.config_file_name, "w", encoding="utf-8") as f: + yaml.dump(cfg, f, indent=4, allow_unicode=True) \ No newline at end of file diff --git a/pkg/config/manager.py b/pkg/config/manager.py index 7983407..88ed652 100644 --- a/pkg/config/manager.py +++ b/pkg/config/manager.py @@ -1,7 +1,7 @@ from __future__ import annotations from . import model as file_model -from .impls import pymodule, json as json_file +from .impls import pymodule, json as json_file, yaml as yaml_file managers: ConfigManager = [] @@ -31,7 +31,16 @@ class ConfigManager: async def load_python_module_config(config_name: str, template_name: str, completion: bool=True) -> ConfigManager: - """加载Python模块配置文件""" + """加载Python模块配置文件 + + Args: + config_name (str): 配置文件名 + template_name (str): 模板文件名 + completion (bool): 是否自动补全内存中的配置文件 + + Returns: + ConfigManager: 配置文件管理器 + """ cfg_inst = pymodule.PythonModuleConfigFile( config_name, template_name @@ -44,7 +53,14 @@ async def load_python_module_config(config_name: str, template_name: str, comple async def load_json_config(config_name: str, template_name: str=None, template_data: dict=None, completion: bool=True) -> ConfigManager: - """加载JSON配置文件""" + """加载JSON配置文件 + + Args: + config_name (str): 配置文件名 + template_name (str): 模板文件名 + template_data (dict): 模板数据 + completion (bool): 是否自动补全内存中的配置文件 + """ cfg_inst = json_file.JSONConfigFile( config_name, template_name, @@ -54,4 +70,28 @@ async def load_json_config(config_name: str, template_name: str=None, template_d cfg_mgr = ConfigManager(cfg_inst) await cfg_mgr.load_config(completion=completion) - return cfg_mgr \ No newline at end of file + return cfg_mgr + + +async def load_yaml_config(config_name: str, template_name: str=None, template_data: dict=None, completion: bool=True) -> ConfigManager: + """加载YAML配置文件 + + Args: + config_name (str): 配置文件名 + template_name (str): 模板文件名 + template_data (dict): 模板数据 + completion (bool): 是否自动补全内存中的配置文件 + + Returns: + ConfigManager: 配置文件管理器 + """ + cfg_inst = yaml_file.YAMLConfigFile( + config_name, + template_name, + template_data + ) + + cfg_mgr = ConfigManager(cfg_inst) + await cfg_mgr.load_config(completion=completion) + + return cfg_mgr diff --git a/pkg/platform/sources/qqbotpy.py b/pkg/platform/sources/qqbotpy.py index faa5f10..a79013c 100644 --- a/pkg/platform/sources/qqbotpy.py +++ b/pkg/platform/sources/qqbotpy.py @@ -3,16 +3,15 @@ from __future__ import annotations import logging import typing import datetime -import asyncio import re import traceback -import json -import threading import mirai import botpy import botpy.message as botpy_message import botpy.types.message as botpy_message_type +import pydantic +import pydantic.networks from .. import adapter as adapter_model from ...pipeline.longtext.strategies import forward @@ -23,10 +22,12 @@ from ...config import manager as cfg_mgr class OfficialGroupMessage(mirai.GroupMessage): pass +class OfficialFriendMessage(mirai.FriendMessage): + pass event_handler_mapping = { mirai.GroupMessage: ["on_at_message_create", "on_group_at_message_create"], - mirai.FriendMessage: ["on_direct_message_create"], + mirai.FriendMessage: ["on_direct_message_create", "on_c2c_message_create"], } @@ -193,7 +194,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter): @staticmethod def extract_message_chain_from_obj( - message: typing.Union[botpy_message.Message, botpy_message.DirectMessage], + message: typing.Union[botpy_message.Message, botpy_message.DirectMessage, botpy_message.GroupMessage, botpy_message.C2CMessage], message_id: str = None, bot_account_id: int = 0, ) -> mirai.MessageChain: @@ -206,7 +207,7 @@ class OfficialMessageConverter(adapter_model.MessageConverter): ) ) - if type(message) is not botpy_message.DirectMessage: + if type(message) not in [botpy_message.DirectMessage, botpy_message.C2CMessage]: yiri_msg_list.append(mirai.At(target=bot_account_id)) if hasattr(message, "mentions"): @@ -255,7 +256,7 @@ class OfficialEventConverter(adapter_model.EventConverter): def target2yiri( self, - event: typing.Union[botpy_message.Message, botpy_message.DirectMessage] + event: typing.Union[botpy_message.Message, botpy_message.DirectMessage, botpy_message.GroupMessage, botpy_message.C2CMessage], ) -> mirai.Event: import mirai.models.entities as mirai_entities @@ -295,7 +296,7 @@ class OfficialEventConverter(adapter_model.EventConverter): ).timestamp() ), ) - elif type(event) == botpy_message.DirectMessage: # 私聊,转私聊事件 + elif type(event) == botpy_message.DirectMessage: # 频道私聊,转私聊事件 return mirai.FriendMessage( sender=mirai_entities.Friend( id=event.guild_id, @@ -311,7 +312,7 @@ class OfficialEventConverter(adapter_model.EventConverter): ).timestamp() ), ) - elif type(event) == botpy_message.GroupMessage: + elif type(event) == botpy_message.GroupMessage: # 群聊,转群聊事件 replacing_member_id = self.member_openid_mapping.save_openid(event.author.member_openid) @@ -339,6 +340,25 @@ class OfficialEventConverter(adapter_model.EventConverter): ).timestamp() ), ) + elif type(event) == botpy_message.C2CMessage: # 私聊,转私聊事件 + + user_id_alter = self.member_openid_mapping.save_openid(event.author.user_openid) # 实测这里的user_openid与group的member_openid是一样的 + + return OfficialFriendMessage( + sender=mirai_entities.Friend( + id=user_id_alter, + nickname=user_id_alter, + remark=user_id_alter, + ), + message_chain=OfficialMessageConverter.extract_message_chain_from_obj( + event, event.id + ), + time=int( + datetime.datetime.strptime( + event.timestamp, "%Y-%m-%dT%H:%M:%S%z" + ).timestamp() + ), + ) @adapter_model.adapter_class("qq-botpy") @@ -368,6 +388,7 @@ class OfficialAdapter(adapter_model.MessageSourceAdapter): group_openid_mapping: OpenIDMapping[str, int] = None group_msg_seq = None + c2c_msg_seq = None def __init__(self, cfg: dict, ap: app.Application): """初始化适配器""" @@ -375,6 +396,7 @@ class OfficialAdapter(adapter_model.MessageSourceAdapter): self.ap = ap self.group_msg_seq = 1 + self.c2c_msg_seq = 1 switchs = {} @@ -454,18 +476,58 @@ class OfficialAdapter(adapter_model.MessageSourceAdapter): ] await self.bot.api.post_dms(**args) elif type(message_source) == OfficialGroupMessage: - if "image" in args or "file_image" in args: + + if "file_image" in args: # 暂不支持发送文件图片 continue + args["group_openid"] = self.group_openid_mapping.getkey( message_source.sender.group.id ) + if "image" in args: + uploadMedia = await self.bot.api.post_group_file( + group_openid=args["group_openid"], + file_type=1, + url=str(args['image']) + ) + + del args['image'] + args['media'] = uploadMedia + args['msg_type'] = 7 + args["msg_id"] = cached_message_ids[ str(message_source.message_chain.message_id) ] args["msg_seq"] = self.group_msg_seq self.group_msg_seq += 1 + await self.bot.api.post_group_message(**args) + elif type(message_source) == OfficialFriendMessage: + if "file_image" in args: + continue + args["openid"] = self.member_openid_mapping.getkey( + message_source.sender.id + ) + + if "image" in args: + uploadMedia = await self.bot.api.post_c2c_file( + openid=args["openid"], + file_type=1, + url=str(args['image']) + ) + + del args['image'] + args['media'] = uploadMedia + args['msg_type'] = 7 + + args["msg_id"] = cached_message_ids[ + str(message_source.message_chain.message_id) + ] + + args["msg_seq"] = self.c2c_msg_seq + self.c2c_msg_seq += 1 + + await self.bot.api.post_c2c_message(**args) async def is_muted(self, group_id: int) -> bool: return False diff --git a/pkg/plugin/manager.py b/pkg/plugin/manager.py index 13a114a..4e0784a 100644 --- a/pkg/plugin/manager.py +++ b/pkg/plugin/manager.py @@ -140,7 +140,7 @@ class PluginManager: for plugin in self.plugins: if plugin.enabled: if event.__class__ in plugin.event_handlers: - self.ap.logger.debug(f'插件 {plugin.plugin_name} 触发事件 {event.__class__.__name__}') + self.ap.logger.debug(f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__}') is_prevented_default_before_call = ctx.is_prevented_default() @@ -150,7 +150,7 @@ class PluginManager: ctx ) except Exception as e: - self.ap.logger.error(f'插件 {plugin.plugin_name} 触发事件 {event.__class__.__name__} 时发生错误: {e}') + self.ap.logger.error(f'插件 {plugin.plugin_name} 处理事件 {event.__class__.__name__} 时发生错误: {e}') self.ap.logger.debug(f"Traceback: {traceback.format_exc()}") emitted_plugins.append(plugin) diff --git a/res/wiki/1-功能使用.md b/res/wiki/1-功能使用.md index a7b1c89..e788f72 100644 --- a/res/wiki/1-功能使用.md +++ b/res/wiki/1-功能使用.md @@ -1,3 +1,6 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + ## 功能点列举
diff --git a/res/wiki/2-功能常见问题.md b/res/wiki/2-功能常见问题.md index ecb3558..88f881c 100644 --- a/res/wiki/2-功能常见问题.md +++ b/res/wiki/2-功能常见问题.md @@ -1,3 +1,6 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + 使用过程中的一些疑问,这里不是解决异常的地方,遇到异常请见`常见错误`页 ### ❓ 如何更新代码到最新版本? diff --git a/res/wiki/3-常见错误.md b/res/wiki/3-常见错误.md index 35d5dff..58f9519 100644 --- a/res/wiki/3-常见错误.md +++ b/res/wiki/3-常见错误.md @@ -1 +1,4 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + 搜索[主仓库issue](https://github.com/RockChinQ/QChatGPT/issues)和[安装器issue](https://github.com/RockChinQ/qcg-installer/issues) \ No newline at end of file diff --git a/res/wiki/4-技术信息.md b/res/wiki/4-技术信息.md index c728b0e..ac1ee76 100644 --- a/res/wiki/4-技术信息.md +++ b/res/wiki/4-技术信息.md @@ -1,3 +1,6 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + 以下是QChatGPT实现原理等技术信息,贡献之前请仔细阅读 > 太久没更了,过时了,建议读源码,~~注释还挺全的~~ diff --git a/res/wiki/5-插件使用.md b/res/wiki/5-插件使用.md index 38c530b..a8be95c 100644 --- a/res/wiki/5-插件使用.md +++ b/res/wiki/5-插件使用.md @@ -1,3 +1,6 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + QChatGPT 插件使用Wiki ## 简介 diff --git a/res/wiki/6-插件使用-内容函数.md b/res/wiki/6-插件使用-内容函数.md index b8fbe3a..b4055b2 100644 --- a/res/wiki/6-插件使用-内容函数.md +++ b/res/wiki/6-插件使用-内容函数.md @@ -1,3 +1,6 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + > 说白了就是ChatGPT官方插件那种东西 内容函数是基于[GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling)实现的,这是一种嵌入对话中,由GPT自动调用的函数。 diff --git a/res/wiki/7-插件开发.md b/res/wiki/7-插件开发.md index 73934b1..c9cd265 100644 --- a/res/wiki/7-插件开发.md +++ b/res/wiki/7-插件开发.md @@ -1,3 +1,6 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + QChatGPT 插件开发Wiki > 请先阅读[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/5-%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8) diff --git a/res/wiki/8-官方接口、ChatGPT网页版、ChatGPT-API区别.md b/res/wiki/8-官方接口、ChatGPT网页版、ChatGPT-API区别.md index 308f191..64bcc8d 100644 --- a/res/wiki/8-官方接口、ChatGPT网页版、ChatGPT-API区别.md +++ b/res/wiki/8-官方接口、ChatGPT网页版、ChatGPT-API区别.md @@ -1,3 +1,6 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + ## 多个对话接口有何区别? 出于对稳定性的高要求,本项目主线接入的是GPT-3模型接口,此接口由OpenAI官方开放,稳定性强。 diff --git a/res/wiki/9-go-cqhttp配置.md b/res/wiki/9-go-cqhttp配置.md index 1f04fcd..f8aad24 100644 --- a/res/wiki/9-go-cqhttp配置.md +++ b/res/wiki/9-go-cqhttp配置.md @@ -1,3 +1,6 @@ +> [!WARNING] +> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top) + # 配置go-cqhttp用于登录QQ > 若您是从旧版本升级到此版本以使用go-cqhttp的用户,请您按照`config-template.py`的内容修改`config.py`,添加`msg_source_adapter`配置项并将其设为`nakuru`,同时添加`nakuru_config`字段按照说明配置。