mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-08 14:56:03 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1235fc1339 | ||
|
|
47e308b99d | ||
|
|
81c2c3c0e5 | ||
|
|
3c2db5097a | ||
|
|
ce56f79687 | ||
|
|
ee0d6dcdae | ||
|
|
bcf1d92f73 | ||
|
|
ffdec16ce6 | ||
|
|
b2f6e84adc | ||
|
|
f76c457e1f | ||
|
|
80bd0a20df | ||
|
|
efeaf73339 | ||
|
|
91b5100a24 | ||
|
|
d1a06f4730 | ||
|
|
b0b186e951 | ||
|
|
4c8fedef6e | ||
|
|
718c221d01 | ||
|
|
077e77eee5 | ||
|
|
b51ca06c7c | ||
|
|
2f092f4a87 | ||
|
|
f1ff9c05c4 | ||
|
|
c9c8603ccc | ||
|
|
47e281fb61 | ||
|
|
dc625647eb | ||
|
|
66cf1b05be |
34
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
34
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -3,17 +3,6 @@ description: 报错或漏洞请使用这个模板创建,不使用此模板创
|
|||||||
title: "[Bug]: "
|
title: "[Bug]: "
|
||||||
labels: ["bug?"]
|
labels: ["bug?"]
|
||||||
body:
|
body:
|
||||||
- type: dropdown
|
|
||||||
attributes:
|
|
||||||
label: 部署方式
|
|
||||||
description: "主程序使用的部署方式"
|
|
||||||
options:
|
|
||||||
- 手动部署
|
|
||||||
- 安装器部署
|
|
||||||
- 一键安装包部署
|
|
||||||
- Docker部署
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: 消息平台适配器
|
label: 消息平台适配器
|
||||||
@@ -27,37 +16,24 @@ body:
|
|||||||
required: false
|
required: false
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: 系统环境
|
label: 运行环境
|
||||||
description: 操作系统、系统架构、**主机地理位置**,地理位置最好写清楚,涉及网络问题排查。
|
description: 操作系统、系统架构、**Python版本**、**主机地理位置**
|
||||||
placeholder: 例如: CentOS x64 中国大陆、Windows11 美国
|
placeholder: 例如: CentOS x64 Python 3.10.3、Docker 的直接写 Docker 就行
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
attributes:
|
|
||||||
label: Python环境
|
|
||||||
description: 运行程序的Python版本
|
|
||||||
placeholder: 例如: Python 3.10
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: QChatGPT版本
|
label: QChatGPT版本
|
||||||
description: QChatGPT版本号
|
description: QChatGPT版本号
|
||||||
placeholder: 例如: v2.6.0,可以使用`!version`命令查看
|
placeholder: 例如:v3.3.0,可以使用`!version`命令查看,或者到 pkg/utils/constants.py 查看
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 异常情况
|
label: 异常情况
|
||||||
description: 完整描述异常情况,什么时候发生的、发生了什么,尽可能详细
|
description: 完整描述异常情况,什么时候发生的、发生了什么。**请附带日志信息。**
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: 日志信息
|
|
||||||
description: 请提供完整的 **登录框架 和 QChatGPT控制台**的相关日志信息(若有),不提供日志信息**无法**为您排查问题,请尽可能详细
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: 启用的插件
|
label: 启用的插件
|
||||||
|
|||||||
26
.github/pull_request_template.md
vendored
26
.github/pull_request_template.md
vendored
@@ -2,24 +2,16 @@
|
|||||||
|
|
||||||
实现/解决/优化的内容:
|
实现/解决/优化的内容:
|
||||||
|
|
||||||
### 事务
|
## 检查清单
|
||||||
|
|
||||||
- [ ] 已阅读仓库[贡献指引](https://github.com/RockChinQ/QChatGPT/blob/master/CONTRIBUTING.md)
|
### PR 作者完成
|
||||||
- [ ] 已与维护者在issues或其他平台沟通此PR大致内容
|
|
||||||
|
|
||||||
## 以下内容可在起草PR后、合并PR前逐步完成
|
- [ ] 阅读仓库[贡献指引](https://github.com/RockChinQ/QChatGPT/blob/master/CONTRIBUTING.md)了吗?
|
||||||
|
- [ ] 与项目所有者沟通过了吗?
|
||||||
|
|
||||||
### 功能
|
### 项目所有者完成
|
||||||
|
|
||||||
- [ ] 已编写完善的配置文件字段说明(若有新增)
|
- [ ] 相关 issues 链接了吗?
|
||||||
- [ ] 已编写面向用户的新功能说明(若有必要)
|
- [ ] 配置项写好了吗?迁移写好了吗?生效了吗?
|
||||||
- [ ] 已测试新功能或更改
|
- [ ] 依赖写到 requirements.txt 和 core/bootutils/deps.py 了吗
|
||||||
|
- [ ] 文档编写了吗?
|
||||||
### 兼容性
|
|
||||||
|
|
||||||
- [ ] 已处理版本兼容性
|
|
||||||
- [ ] 已处理插件兼容问题
|
|
||||||
|
|
||||||
### 风险
|
|
||||||
|
|
||||||
可能导致或已知的问题:
|
|
||||||
13
README.md
13
README.md
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://qchatgpt.rockchin.top/logo.png" alt="QChatGPT" width="180" />
|
<img src="https://qchatgpt.rockchin.top/chrome-512.png" alt="QChatGPT" width="180" />
|
||||||
</p>
|
</p>
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
@@ -42,5 +42,14 @@
|
|||||||
<a href="https://github.com/RockChinQ/qcg-center">遥测服务端源码</a> |
|
<a href="https://github.com/RockChinQ/qcg-center">遥测服务端源码</a> |
|
||||||
<a href="https://github.com/the-lazy-me/QChatGPT-Wiki">官方文档储存库</a>
|
<a href="https://github.com/the-lazy-me/QChatGPT-Wiki">官方文档储存库</a>
|
||||||
|
|
||||||
<img alt="回复效果(带有联网插件)" src="https://qchatgpt.rockchin.top/assets/image/QChatGPT-0516.png" width="500px"/>
|
<hr/>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
京东云 4090 单卡 15C90G 实例 <br/>
|
||||||
|
仅需1.89/小时,包月1225元起 <br/>
|
||||||
|
可选预装Stable Diffusion等应用,随用随停,计费透明,欢迎首选支持 <br/>
|
||||||
|
https://3.cn/24A-2NXd
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img alt="回复效果(带有联网插件)" src="https://qchatgpt.rockchin.top/QChatGPT-0516.png" width="500px"/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
59
pkg/config/impls/yaml.py
Normal file
59
pkg/config/impls/yaml.py
Normal 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)
|
||||||
@@ -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,
|
||||||
@@ -55,3 +71,27 @@ async def load_json_config(config_name: str, template_name: str=None, template_d
|
|||||||
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
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ required_deps = {
|
|||||||
"aiohttp": "aiohttp",
|
"aiohttp": "aiohttp",
|
||||||
"psutil": "psutil",
|
"psutil": "psutil",
|
||||||
"async_lru": "async-lru",
|
"async_lru": "async-lru",
|
||||||
|
"ollama": "ollama",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import mirai
|
import mirai
|
||||||
|
import mirai.models
|
||||||
|
import mirai.models.message
|
||||||
|
|
||||||
from ...core import app
|
from ...core import app
|
||||||
|
|
||||||
@@ -63,6 +65,7 @@ class ContentFilterStage(stage.PipelineStage):
|
|||||||
"""请求llm前处理消息
|
"""请求llm前处理消息
|
||||||
只要有一个不通过就不放行,只放行 PASS 的消息
|
只要有一个不通过就不放行,只放行 PASS 的消息
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not self.ap.pipeline_cfg.data['income-msg-check']:
|
if not self.ap.pipeline_cfg.data['income-msg-check']:
|
||||||
return entities.StageProcessResult(
|
return entities.StageProcessResult(
|
||||||
result_type=entities.ResultType.CONTINUE,
|
result_type=entities.ResultType.CONTINUE,
|
||||||
@@ -145,8 +148,10 @@ class ContentFilterStage(stage.PipelineStage):
|
|||||||
|
|
||||||
contain_non_text = False
|
contain_non_text = False
|
||||||
|
|
||||||
|
text_components = [mirai.Plain, mirai.models.message.Source]
|
||||||
|
|
||||||
for me in query.message_chain:
|
for me in query.message_chain:
|
||||||
if not isinstance(me, mirai.Plain):
|
if type(me) not in text_components:
|
||||||
contain_non_text = True
|
contain_non_text = True
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@@ -173,10 +173,11 @@ class AiocqhttpEventConverter(adapter.EventConverter):
|
|||||||
if event.message_type == "group":
|
if event.message_type == "group":
|
||||||
permission = "MEMBER"
|
permission = "MEMBER"
|
||||||
|
|
||||||
if event.sender["role"] == "admin":
|
if "role" in event.sender:
|
||||||
permission = "ADMINISTRATOR"
|
if event.sender["role"] == "admin":
|
||||||
elif event.sender["role"] == "owner":
|
permission = "ADMINISTRATOR"
|
||||||
permission = "OWNER"
|
elif event.sender["role"] == "owner":
|
||||||
|
permission = "OWNER"
|
||||||
converted_event = mirai.GroupMessage(
|
converted_event = mirai.GroupMessage(
|
||||||
sender=mirai.models.entities.GroupMember(
|
sender=mirai.models.entities.GroupMember(
|
||||||
id=event.sender["user_id"], # message_seq 放哪?
|
id=event.sender["user_id"], # message_seq 放哪?
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -72,12 +72,13 @@ class AnthropicMessages(api.LLMAPIRequester):
|
|||||||
|
|
||||||
for i, ce in enumerate(m.content):
|
for i, ce in enumerate(m.content):
|
||||||
if ce.type == "image_url":
|
if ce.type == "image_url":
|
||||||
|
base64_image, image_format = await image.qq_image_url_to_base64(ce.image_url.url)
|
||||||
alter_image_ele = {
|
alter_image_ele = {
|
||||||
"type": "image",
|
"type": "image",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "base64",
|
"type": "base64",
|
||||||
"media_type": "image/jpeg",
|
"media_type": f"image/{image_format}",
|
||||||
"data": await image.qq_image_url_to_base64(ce.image_url.url)
|
"data": base64_image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
msg_dict["content"][i] = alter_image_ele
|
msg_dict["content"][i] = alter_image_ele
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ class OpenAIChatCompletions(api.LLMAPIRequester):
|
|||||||
) -> llm_entities.Message:
|
) -> llm_entities.Message:
|
||||||
chatcmpl_message = chat_completion.choices[0].message.dict()
|
chatcmpl_message = chat_completion.choices[0].message.dict()
|
||||||
|
|
||||||
|
# 确保 role 字段存在且不为 None
|
||||||
|
if 'role' not in chatcmpl_message or chatcmpl_message['role'] is None:
|
||||||
|
chatcmpl_message['role'] = 'assistant'
|
||||||
|
|
||||||
message = llm_entities.Message(**chatcmpl_message)
|
message = llm_entities.Message(**chatcmpl_message)
|
||||||
|
|
||||||
return message
|
return message
|
||||||
@@ -136,7 +140,5 @@ class OpenAIChatCompletions(api.LLMAPIRequester):
|
|||||||
self,
|
self,
|
||||||
original_url: str,
|
original_url: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
base64_image, image_format = await image.qq_image_url_to_base64(original_url)
|
||||||
base64_image = await image.qq_image_url_to_base64(original_url)
|
return f"data:image/{image_format};base64,{base64_image}"
|
||||||
|
|
||||||
return f"data:image/jpeg;base64,{base64_image}"
|
|
||||||
|
|||||||
@@ -101,5 +101,5 @@ class OllamaChatCompletions(api.LLMAPIRequester):
|
|||||||
self,
|
self,
|
||||||
original_url: str,
|
original_url: str,
|
||||||
) -> str:
|
) -> str:
|
||||||
base64_image: str = await image.qq_image_url_to_base64(original_url)
|
base64_image, image_format = await image.qq_image_url_to_base64(original_url)
|
||||||
return f"data:image/jpeg;base64,{base64_image}"
|
return f"data:image/{image_format};base64,{base64_image}"
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
semantic_version = "v3.3.0"
|
semantic_version = "v3.3.1.1"
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ import aiohttp
|
|||||||
|
|
||||||
async def qq_image_url_to_base64(
|
async def qq_image_url_to_base64(
|
||||||
image_url: str
|
image_url: str
|
||||||
) -> str:
|
) -> typing.Tuple[str, str]:
|
||||||
"""将QQ图片URL转为base64
|
"""将QQ图片URL转为base64,并返回图片格式
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
image_url (str): QQ图片URL
|
image_url (str): QQ图片URL
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: base64编码
|
typing.Tuple[str, str]: base64编码和图片格式
|
||||||
"""
|
"""
|
||||||
parsed = urlparse(image_url)
|
parsed = urlparse(image_url)
|
||||||
query = parse_qs(parsed.query)
|
query = parse_qs(parsed.query)
|
||||||
@@ -35,7 +35,12 @@ async def qq_image_url_to_base64(
|
|||||||
) as resp:
|
) as resp:
|
||||||
resp.raise_for_status() # 检查HTTP错误
|
resp.raise_for_status() # 检查HTTP错误
|
||||||
file_bytes = await resp.read()
|
file_bytes = await resp.read()
|
||||||
|
content_type = resp.headers.get('Content-Type')
|
||||||
|
if not content_type or not content_type.startswith('image/'):
|
||||||
|
image_format = 'jpeg'
|
||||||
|
else:
|
||||||
|
image_format = content_type.split('/')[-1]
|
||||||
|
|
||||||
base64_str = base64.b64encode(file_bytes).decode()
|
base64_str = base64.b64encode(file_bytes).decode()
|
||||||
|
|
||||||
return base64_str
|
return base64_str, image_format
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
> [!WARNING]
|
||||||
|
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
|
||||||
|
|
||||||
## 功能点列举
|
## 功能点列举
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
> [!WARNING]
|
||||||
|
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
|
||||||
|
|
||||||
使用过程中的一些疑问,这里不是解决异常的地方,遇到异常请见`常见错误`页
|
使用过程中的一些疑问,这里不是解决异常的地方,遇到异常请见`常见错误`页
|
||||||
|
|
||||||
### ❓ 如何更新代码到最新版本?
|
### ❓ 如何更新代码到最新版本?
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
> [!WARNING]
|
||||||
|
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
|
||||||
|
|
||||||
以下是QChatGPT实现原理等技术信息,贡献之前请仔细阅读
|
以下是QChatGPT实现原理等技术信息,贡献之前请仔细阅读
|
||||||
|
|
||||||
> 太久没更了,过时了,建议读源码,~~注释还挺全的~~
|
> 太久没更了,过时了,建议读源码,~~注释还挺全的~~
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
> [!WARNING]
|
||||||
|
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
|
||||||
|
|
||||||
QChatGPT 插件使用Wiki
|
QChatGPT 插件使用Wiki
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|||||||
@@ -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自动调用的函数。
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
> [!WARNING]
|
||||||
|
> 此 Wiki 已弃用,所有文档已迁移到 [项目主页](https://qchatgpt.rockchin.top)
|
||||||
|
|
||||||
## 多个对话接口有何区别?
|
## 多个对话接口有何区别?
|
||||||
|
|
||||||
出于对稳定性的高要求,本项目主线接入的是GPT-3模型接口,此接口由OpenAI官方开放,稳定性强。
|
出于对稳定性的高要求,本项目主线接入的是GPT-3模型接口,此接口由OpenAI官方开放,稳定性强。
|
||||||
|
|||||||
@@ -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`字段按照说明配置。
|
||||||
|
|||||||
Reference in New Issue
Block a user