Merge pull request #881 from RockChinQ/version/3.3.1.0

Version/3.3.1.0
This commit is contained in:
Junyan Qin 2024-09-08 15:13:39 +08:00 committed by GitHub
commit bcf1d92f73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 204 additions and 16 deletions

59
pkg/config/impls/yaml.py Normal file
View File

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

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from . import model as file_model 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 = [] managers: ConfigManager = []
@ -31,7 +31,16 @@ class ConfigManager:
async def load_python_module_config(config_name: str, template_name: str, completion: bool=True) -> 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( cfg_inst = pymodule.PythonModuleConfigFile(
config_name, config_name,
template_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: 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( cfg_inst = json_file.JSONConfigFile(
config_name, config_name,
template_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) cfg_mgr = ConfigManager(cfg_inst)
await cfg_mgr.load_config(completion=completion) await cfg_mgr.load_config(completion=completion)
return cfg_mgr 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

View File

@ -3,16 +3,15 @@ from __future__ import annotations
import logging import logging
import typing import typing
import datetime import datetime
import asyncio
import re import re
import traceback import traceback
import json
import threading
import mirai import mirai
import botpy import botpy
import botpy.message as botpy_message import botpy.message as botpy_message
import botpy.types.message as botpy_message_type import botpy.types.message as botpy_message_type
import pydantic
import pydantic.networks
from .. import adapter as adapter_model from .. import adapter as adapter_model
from ...pipeline.longtext.strategies import forward from ...pipeline.longtext.strategies import forward
@ -23,10 +22,12 @@ from ...config import manager as cfg_mgr
class OfficialGroupMessage(mirai.GroupMessage): class OfficialGroupMessage(mirai.GroupMessage):
pass pass
class OfficialFriendMessage(mirai.FriendMessage):
pass
event_handler_mapping = { event_handler_mapping = {
mirai.GroupMessage: ["on_at_message_create", "on_group_at_message_create"], 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 @staticmethod
def extract_message_chain_from_obj( 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, message_id: str = None,
bot_account_id: int = 0, bot_account_id: int = 0,
) -> mirai.MessageChain: ) -> 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)) yiri_msg_list.append(mirai.At(target=bot_account_id))
if hasattr(message, "mentions"): if hasattr(message, "mentions"):
@ -255,7 +256,7 @@ class OfficialEventConverter(adapter_model.EventConverter):
def target2yiri( def target2yiri(
self, 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: ) -> mirai.Event:
import mirai.models.entities as mirai_entities import mirai.models.entities as mirai_entities
@ -295,7 +296,7 @@ class OfficialEventConverter(adapter_model.EventConverter):
).timestamp() ).timestamp()
), ),
) )
elif type(event) == botpy_message.DirectMessage: # 私聊,转私聊事件 elif type(event) == botpy_message.DirectMessage: # 频道私聊,转私聊事件
return mirai.FriendMessage( return mirai.FriendMessage(
sender=mirai_entities.Friend( sender=mirai_entities.Friend(
id=event.guild_id, id=event.guild_id,
@ -311,7 +312,7 @@ class OfficialEventConverter(adapter_model.EventConverter):
).timestamp() ).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) replacing_member_id = self.member_openid_mapping.save_openid(event.author.member_openid)
@ -339,6 +340,25 @@ class OfficialEventConverter(adapter_model.EventConverter):
).timestamp() ).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") @adapter_model.adapter_class("qq-botpy")
@ -368,6 +388,7 @@ class OfficialAdapter(adapter_model.MessageSourceAdapter):
group_openid_mapping: OpenIDMapping[str, int] = None group_openid_mapping: OpenIDMapping[str, int] = None
group_msg_seq = None group_msg_seq = None
c2c_msg_seq = None
def __init__(self, cfg: dict, ap: app.Application): def __init__(self, cfg: dict, ap: app.Application):
"""初始化适配器""" """初始化适配器"""
@ -375,6 +396,7 @@ class OfficialAdapter(adapter_model.MessageSourceAdapter):
self.ap = ap self.ap = ap
self.group_msg_seq = 1 self.group_msg_seq = 1
self.c2c_msg_seq = 1
switchs = {} switchs = {}
@ -454,18 +476,58 @@ class OfficialAdapter(adapter_model.MessageSourceAdapter):
] ]
await self.bot.api.post_dms(**args) await self.bot.api.post_dms(**args)
elif type(message_source) == OfficialGroupMessage: elif type(message_source) == OfficialGroupMessage:
if "image" in args or "file_image" in args:
if "file_image" in args: # 暂不支持发送文件图片
continue continue
args["group_openid"] = self.group_openid_mapping.getkey( args["group_openid"] = self.group_openid_mapping.getkey(
message_source.sender.group.id 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[ args["msg_id"] = cached_message_ids[
str(message_source.message_chain.message_id) str(message_source.message_chain.message_id)
] ]
args["msg_seq"] = self.group_msg_seq args["msg_seq"] = self.group_msg_seq
self.group_msg_seq += 1 self.group_msg_seq += 1
await self.bot.api.post_group_message(**args) 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: async def is_muted(self, group_id: int) -> bool:
return False return False

View File

@ -140,7 +140,7 @@ class PluginManager:
for plugin in self.plugins: for plugin in self.plugins:
if plugin.enabled: if plugin.enabled:
if event.__class__ in plugin.event_handlers: 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() is_prevented_default_before_call = ctx.is_prevented_default()
@ -150,7 +150,7 @@ class PluginManager:
ctx ctx
) )
except Exception as e: 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()}") self.ap.logger.debug(f"Traceback: {traceback.format_exc()}")
emitted_plugins.append(plugin) emitted_plugins.append(plugin)

View File

@ -1,3 +1,6 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
## 功能点列举 ## 功能点列举
<details> <details>

View File

@ -1,3 +1,6 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
使用过程中的一些疑问,这里不是解决异常的地方,遇到异常请见`常见错误`页 使用过程中的一些疑问,这里不是解决异常的地方,遇到异常请见`常见错误`页
### ❓ 如何更新代码到最新版本? ### ❓ 如何更新代码到最新版本?

View File

@ -1 +1,4 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
搜索[主仓库issue](https://github.com/RockChinQ/QChatGPT/issues)和[安装器issue](https://github.com/RockChinQ/qcg-installer/issues) 搜索[主仓库issue](https://github.com/RockChinQ/QChatGPT/issues)和[安装器issue](https://github.com/RockChinQ/qcg-installer/issues)

View File

@ -1,3 +1,6 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
以下是QChatGPT实现原理等技术信息贡献之前请仔细阅读 以下是QChatGPT实现原理等技术信息贡献之前请仔细阅读
> 太久没更了,过时了,建议读源码,~~注释还挺全的~~ > 太久没更了,过时了,建议读源码,~~注释还挺全的~~

View File

@ -1,3 +1,6 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
QChatGPT 插件使用Wiki QChatGPT 插件使用Wiki
## 简介 ## 简介

View File

@ -1,3 +1,6 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
> 说白了就是ChatGPT官方插件那种东西 > 说白了就是ChatGPT官方插件那种东西
内容函数是基于[GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling)实现的这是一种嵌入对话中由GPT自动调用的函数。 内容函数是基于[GPT的Function Calling能力](https://platform.openai.com/docs/guides/gpt/function-calling)实现的这是一种嵌入对话中由GPT自动调用的函数。

View File

@ -1,3 +1,6 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
QChatGPT 插件开发Wiki QChatGPT 插件开发Wiki
> 请先阅读[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/5-%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8) > 请先阅读[插件使用页](https://github.com/RockChinQ/QChatGPT/wiki/5-%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8)

View File

@ -1,3 +1,6 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
## 多个对话接口有何区别? ## 多个对话接口有何区别?
出于对稳定性的高要求本项目主线接入的是GPT-3模型接口此接口由OpenAI官方开放稳定性强。 出于对稳定性的高要求本项目主线接入的是GPT-3模型接口此接口由OpenAI官方开放稳定性强。

View File

@ -1,3 +1,6 @@
> [!WARNING]
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
# 配置go-cqhttp用于登录QQ # 配置go-cqhttp用于登录QQ
> 若您是从旧版本升级到此版本以使用go-cqhttp的用户请您按照`config-template.py`的内容修改`config.py`,添加`msg_source_adapter`配置项并将其设为`nakuru`,同时添加`nakuru_config`字段按照说明配置。 > 若您是从旧版本升级到此版本以使用go-cqhttp的用户请您按照`config-template.py`的内容修改`config.py`,添加`msg_source_adapter`配置项并将其设为`nakuru`,同时添加`nakuru_config`字段按照说明配置。