Compare commits

..

35 Commits

Author SHA1 Message Date
RockChinQ
a10d3213fd chore: release v2.6.10 2024-01-19 15:50:15 +08:00
RockChinQ
f52a0eb02f perf: 连接go-cqhttp时不使用代理 2024-01-19 15:49:42 +08:00
Junyan Qin
1ea8da69a2 Merge pull request #667 from RockChinQ/chore/remove-legacy-code
Chore: 移除过时的兼容性处理代码
2024-01-18 01:02:32 +08:00
RockChinQ
5bbc38a7a3 chore: 移除过时的兼容性处理代码 2024-01-18 00:52:29 +08:00
RockChinQ
aa433bd5ab fix: 修复文字转图片模块初始化时的bug 2024-01-17 20:07:35 +08:00
RockChinQ
2c5933da0b chore: 删除updater中不再使用的代码 2024-01-15 22:35:14 +08:00
RockChinQ
77bc6fbf59 fix(list): 列出不存在的页时失败 2024-01-15 21:44:53 +08:00
Junyan Qin
701cb7be40 Merge pull request #661 from RockChinQ/perf/audit-v2
Feat: 优化 v2 审计 API 调用逻辑
2024-01-12 20:18:30 +08:00
RockChinQ
ab8d77c968 feat: 删除 v1 审计 API 调用逻辑 2024-01-12 20:06:18 +08:00
RockChinQ
6c03fe678a feat: 允许用户关闭数据上报 2024-01-12 17:20:39 +08:00
RockChinQ
41b30238c3 chore: 指令全部改为命令 2024-01-12 16:48:47 +08:00
RockChinQ
aa768459c0 perf: 配置项目标值不合法时的输出 2024-01-12 16:29:04 +08:00
RockChinQ
28014512f7 fix(cconfig): cfg 命令找不到配置项时的处理错误 2024-01-12 16:25:10 +08:00
RockChinQ
f9a99eed66 chore: 删除已被OpenAI弃用的模型 (#658) 2024-01-12 14:48:49 +08:00
Junyan Qin
461b574e09 Merge pull request #659 from RockChinQ/fix/resend-command-failed
Fix: resend 命令失效
2024-01-12 14:40:07 +08:00
RockChinQ
36c192ff6b fix: resend 命令失效 2024-01-12 14:31:29 +08:00
RockChinQ
101625965c chore: 删除对 credit 的引用 2024-01-12 10:18:10 +08:00
RockChinQ
83177a3416 chore: 移除弃用的 credit.py 模块 2024-01-12 10:09:53 +08:00
Junyan Qin
c3904786e1 doc(README.md): 添加链接 2024-01-10 23:11:02 +08:00
RockChinQ
b31c34905a test: 自动上传覆盖率 2023-12-28 16:14:54 +08:00
RockChinQ
41cbe91870 doc(README): 添加测试覆盖率徽章 2023-12-28 16:03:55 +08:00
Junyan Qin
872b16b779 ci: 删除注释 2023-12-27 16:00:18 +00:00
Junyan Qin
9f3cc9c293 test: 修正错误的引号 2023-12-27 15:56:52 +00:00
Junyan Qin
2d148c4970 test: 处理多行响应值 2023-12-27 15:52:12 +00:00
Junyan Qin
0869b57741 test: install jq 2023-12-27 15:48:26 +00:00
Junyan Qin
af225aa18f test: 错误的逻辑 2023-12-27 15:44:24 +00:00
Junyan Qin
06f3c5d32b test: 分支名获取方式 2023-12-27 15:39:08 +00:00
Junyan Qin
4e71a08b57 test: 完善issues_comment时的pr分支获取逻辑 2023-12-27 15:35:25 +00:00
Junyan Qin
bf5ebc9245 test: 错误的触发名称 2023-12-27 15:23:53 +00:00
Junyan Qin
fba81582ab test: 完善触发方式 2023-12-27 15:16:07 +00:00
Junyan Qin
b4645168f9 Merge pull request #649 from RockChinQ/test/systematical-test
Test: 集成qcg-tester
2023-12-27 22:50:35 +08:00
Junyan Qin
d00c68e329 test: 允许手动触发 2023-12-27 14:49:00 +00:00
Junyan Qin
cb636b96bf test: 集成qcg-tester 2023-12-27 14:47:02 +00:00
GitHub Actions
12468b5b15 Update override-all.json 2023-12-23 02:32:13 +00:00
RockChinQ
6a5414b5fd chore: prompt_submit_length默认改为3072 2023-12-23 10:31:56 +08:00
38 changed files with 243 additions and 283 deletions

View File

@@ -1,11 +1,6 @@
name: Update Wiki
on:
pull_request:
branches:
- master
paths:
- 'res/wiki/**'
push:
branches:
- master

80
.github/workflows/test-pr.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: Test Pull Request
on:
pull_request:
types: [ready_for_review]
paths:
# 任何py文件改动都会触发
- '**.py'
pull_request_review:
types: [submitted]
issue_comment:
types: [created]
# 允许手动触发
workflow_dispatch:
jobs:
perform-test:
runs-on: ubuntu-latest
# 如果事件为pull_request_review且review状态为approved则执行
if: >
github.event_name == 'pull_request' ||
(github.event_name == 'pull_request_review' && github.event.review.state == 'APPROVED') ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' && github.event.issue.pull_request != '' && contains(github.event.comment.body, '/test') && github.event.comment.user.login == 'RockChinQ')
steps:
# 签出测试工程仓库代码
- name: Checkout
uses: actions/checkout@v2
with:
# 仓库地址
repository: RockChinQ/qcg-tester
# 仓库路径
path: qcg-tester
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Install dependencies
run: |
cd qcg-tester
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Get PR details
id: get-pr
if: github.event_name == 'issue_comment'
uses: octokit/request-action@v2.x
with:
route: GET /repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set PR source branch as env variable
if: github.event_name == 'issue_comment'
run: |
PR_SOURCE_BRANCH=$(echo '${{ steps.get-pr.outputs.data }}' | jq -r '.head.ref')
echo "BRANCH=$PR_SOURCE_BRANCH" >> $GITHUB_ENV
- name: Set PR Branch as bash env
if: github.event_name != 'issue_comment'
run: |
echo "BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV
- name: Set OpenAI API Key from Secrets
run: |
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" >> $GITHUB_ENV
- name: Set OpenAI Reverse Proxy URL from Secrets
run: |
echo "OPENAI_REVERSE_PROXY=${{ secrets.OPENAI_REVERSE_PROXY }}" >> $GITHUB_ENV
- name: Run test
run: |
cd qcg-tester
python main.py
- name: Upload coverage reports to Codecov
run: |
cd qcg-tester/resource/QChatGPT
curl -Os https://uploader.codecov.io/latest/linux/codecov
chmod +x codecov
./codecov -t ${{ secrets.CODECOV_TOKEN }}

3
.gitignore vendored
View File

@@ -31,4 +31,5 @@ claude.json
bard.json
/*yaml
!/docker-compose.yaml
res/instance_id.json
res/instance_id.json
.DS_Store

View File

@@ -14,8 +14,11 @@
<img src="https://img.shields.io/docker/pulls/rockchin/qchatgpt?color=blue" alt="docker pull">
</a>
![Wakapi Count](https://wakapi.dev/api/badge/RockChinQ/interval:any/project:QChatGPT)
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
<a href="https://codecov.io/gh/RockChinQ/QChatGPT" >
<img src="https://codecov.io/gh/RockChinQ/QChatGPT/graph/badge.svg?token=pjxYIL2kbC"/>
</a>
<br/>
<img src="https://img.shields.io/badge/python-3.9+-blue.svg" alt="python">
<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=66-aWvn8cbP4c1ut_1YYkvvGVeEtyTH8&authKey=pTaKBK5C%2B8dFzQ4XlENf6MHTCLaHnlKcCRx7c14EeVVlpX2nRSaS8lJm8YeM4mCU&noverify=0&group_code=195992197">
<img alt="Static Badge" src="https://img.shields.io/badge/%E5%AE%98%E6%96%B9%E7%BE%A4-195992197-purple">
</a>
@@ -29,6 +32,8 @@
<img alt="Static Badge" src="https://img.shields.io/badge/Linux%E9%83%A8%E7%BD%B2%E8%A7%86%E9%A2%91-208647">
</a>
## 使用文档
<a href="https://qchatgpt.rockchin.top">项目主页</a>
<a href="https://qchatgpt.rockchin.top/posts/feature.html">功能介绍</a>
<a href="https://qchatgpt.rockchin.top/posts/deploy/">部署文档</a>
@@ -36,5 +41,11 @@
<a href="https://qchatgpt.rockchin.top/posts/plugin/intro.html">插件介绍</a>
<a href="https://github.com/RockChinQ/QChatGPT/issues/new?assignees=&labels=%E7%8B%AC%E7%AB%8B%E6%8F%92%E4%BB%B6&projects=&template=submit-plugin.yml&title=%5BPlugin%5D%3A+%E8%AF%B7%E6%B1%82%E7%99%BB%E8%AE%B0%E6%96%B0%E6%8F%92%E4%BB%B6">提交插件</a>
## 相关链接
<a href="https://github.com/RockChinQ/qcg-installer">安装器源码</a>
<a href="https://github.com/RockChinQ/qcg-tester">测试工程源码</a>
<a href="https://github.com/the-lazy-me/QChatGPT-Wiki">官方文档储存库</a>
<img alt="回复效果(带有联网插件)" src="https://qchatgpt.rockchin.top/assets/image/QChatGPT-1211.png" width="500px"/>
</div>

View File

@@ -180,7 +180,7 @@ Plugin [usage](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%
`tests/plugin_examples`目录下,将其整个目录复制到`plugins`目录下即可使用
- `cmdcn` - 主程序令中文形式
- `cmdcn` - 主程序令中文形式
- `hello_plugin` - 在收到消息`hello`时回复相应消息
- `urlikethisijustsix` - 收到冒犯性消息时回复相应消息
@@ -189,7 +189,7 @@ Plugin [usage](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%
欢迎提交新的插件
- [revLibs](https://github.com/RockChinQ/revLibs) - 将ChatGPT网页版接入此项目关于[官方接口和网页版有什么区别](https://github.com/RockChinQ/QChatGPT/wiki/%E5%AE%98%E6%96%B9%E6%8E%A5%E5%8F%A3%E4%B8%8EChatGPT%E7%BD%91%E9%A1%B5%E7%89%88)
- [Switcher](https://github.com/RockChinQ/Switcher) - 支持通过令切换使用的模型
- [Switcher](https://github.com/RockChinQ/Switcher) - 支持通过令切换使用的模型
- [hello_plugin](https://github.com/RockChinQ/hello_plugin) - `hello_plugin` 的储存库形式,插件开发模板
- [dominoar/QChatPlugins](https://github.com/dominoar/QchatPlugins) - dominoar编写的诸多新功能插件语音输出、Ranimg、屏蔽词规则等
- [dominoar/QCP-NovelAi](https://github.com/dominoar/QCP-NovelAi) - NovelAI 故事叙述与绘画

View File

@@ -78,13 +78,13 @@ openai_config = {
# passive仅当api-key超额时才会切换api-key
switch_strategy = "active"
# [必需] 管理员QQ号用于接收报错等通知及执行管理员级别
# [必需] 管理员QQ号用于接收报错等通知及执行管理员级别
# 支持多个管理员可以使用list形式设置例如
# admin_qq = [12345678, 87654321]
admin_qq = 0
# 情景预设(机器人人格)
# 每个会话的预设信息,影响所有会话,无视令重置
# 每个会话的预设信息,影响所有会话,无视令重置
# 可以通过这个字段指定某些情况的回复,可直接用自然语言描述指令
# 例如:
# default_prompt = "如果我之后想获取帮助,请你说“输入!help获取帮助”"
@@ -98,14 +98,14 @@ admin_qq = 0
# "en-dict": "我想让你充当英英词典,对于给出的英文单词,你要给出其中文意思以及英文解释,并且给出一个例句,此外不要有其他反馈。",
# }
#
# 在使用期间即可通过令:
# 在使用期间即可通过令:
# !reset [名称]
# 来使用指定的情景预设重置会话
# 例如:
# !reset linux-terminal
# 若不指定名称,则使用默认情景预设
#
# 也可以使用令:
# 也可以使用令:
# !default <名称>
# 将指定的情景预设设置为默认情景预设
# 例如:
@@ -165,7 +165,7 @@ response_rules = {
# 符合此规则的消息将不会被响应
# 支持消息前缀匹配及正则表达式匹配
# 此设置优先级高于response_rules
# 用以过滤mirai等其他层级的
# 用以过滤mirai等其他层级的
# @see https://github.com/RockChinQ/QChatGPT/issues/165
ignore_rules = {
"prefix": ["/"],
@@ -200,7 +200,7 @@ encourage_sponsor_at_start = True
# 每次向OpenAI接口发送对话记录上下文的字符数
# 最大不超过(4096 - max_tokens)个字符max_tokens为下方completion_api_params中的max_tokens
# 注意较大的prompt_submit_length会导致OpenAI账户额度消耗更快
prompt_submit_length = 2048
prompt_submit_length = 3072
# 是否在token超限报错时自动重置会话
# 可在tips.py中编辑提示语
@@ -230,13 +230,6 @@ auto_reset = True
# "gpt-3.5-turbo-0301", # legacy
#
# Completions接口
# "text-davinci-003", # legacy
# "text-davinci-002", # legacy
# "code-davinci-002", # legacy
# "code-cushman-001", # legacy
# "text-curie-001", # legacy
# "text-babbage-001", # legacy
# "text-ada-001", # legacy
# "gpt-3.5-turbo-instruct",
#
# 具体请查看OpenAI的文档: https://beta.openai.com/docs/api-reference/completions/create
@@ -369,8 +362,8 @@ rate_limit_strategy = "drop"
upgrade_dependencies = False
# 是否上报统计信息
# 用于统计机器人的使用情况,不会收集任何用户信息
# 仅上报时间、字数使用量、绘图使用量,其他信息不会上报
# 用于统计机器人的使用情况,数据不公开,不会收集任何敏感信息
# 仅实例识别UUID、上报时间、字数使用量、绘图使用量、插件使用情况、用户信息,其他信息不会上报
report_usage = True
# 日志级别

12
main.py
View File

@@ -175,10 +175,6 @@ async def start_process(first_time_init=False):
except Exception as e:
print("更新openai库失败:{}, 请忽略或自行更新".format(e))
# 初始化文字转图片
from pkg.utils import text2img
text2img.initialize()
known_exception_caught = False
try:
try:
@@ -186,12 +182,16 @@ async def start_process(first_time_init=False):
sh = reset_logging()
pkg.utils.context.context['logger_handler'] = sh
# 初始化文字转图片
from pkg.utils import text2img
text2img.initialize()
# 检查是否设置了管理员
if cfg['admin_qq'] == 0:
# logging.warning("未设置管理员QQ,管理员权限令及运行告警将无法使用,如需设置请修改config.py中的admin_qq字段")
# logging.warning("未设置管理员QQ,管理员权限令及运行告警将无法使用,如需设置请修改config.py中的admin_qq字段")
while True:
try:
cfg['admin_qq'] = int(input("未设置管理员QQ,管理员权限令及运行告警将无法使用,请输入管理员QQ号: "))
cfg['admin_qq'] = int(input("未设置管理员QQ,管理员权限令及运行告警将无法使用,请输入管理员QQ号: "))
# 写入到文件
# 读取文件

View File

@@ -53,7 +53,7 @@
"baidu_secret_key": "",
"inappropriate_message_tips": "[百度云]请珍惜机器人,当前返回内容不合规",
"encourage_sponsor_at_start": true,
"prompt_submit_length": 2048,
"prompt_submit_length": 3072,
"auto_reset": true,
"completion_api_params": {
"model": "gpt-3.5-turbo",

View File

@@ -21,7 +21,7 @@ class DataGatherer:
以key值md5为key,{
"text": {
"text-davinci-003": 文字量:int,
"gpt-3.5-turbo": 文字量:int,
},
"image": {
"256x256": 图片数量:int,
@@ -37,27 +37,6 @@ class DataGatherer:
except:
pass
def report_to_server(self, subservice_name: str, count: int):
"""向中央服务器报告使用量
只会报告此次请求的使用量,不会报告总量。
不包含除版本号、使用类型、使用量以外的任何信息,仅供开发者分析使用情况。
"""
def thread_func():
try:
config = context.get_config_manager().data
if not config['report_usage']:
return
res = requests.get("http://reports.rockchin.top:18989/usage?service_name=qchatgpt.{}&version={}&count={}&msg_source={}".format(subservice_name, self.version_str, count, config['msg_source_adapter']))
if res.status_code != 200 or res.text != "ok":
logging.warning("report to server failed, status_code: {}, text: {}".format(res.status_code, res.text))
except:
return
threading.Thread(target=thread_func).start()
def get_usage(self, key_md5):
return self.usage[key_md5] if key_md5 in self.usage else {}
@@ -79,8 +58,6 @@ class DataGatherer:
self.usage[key_md5]["text"][model] += length
self.dump_to_db()
self.report_to_server("text", length)
def report_image_model_usage(self, size):
"""调用方报告图片模型请求图片使用量"""
@@ -98,8 +75,6 @@ class DataGatherer:
self.usage[key_md5]["image"][size] += 1
self.dump_to_db()
self.report_to_server("image", 1)
def get_text_length_of_key(self, key):
"""获取指定api-key (明文) 的文字总使用量(本地记录)"""
key_md5 = hashlib.md5(key.encode('utf-8')).hexdigest()

View File

@@ -8,7 +8,7 @@ from ..utils import context
# __current__ = "default"
# """当前默认使用的情景预设的名称
# 由管理员使用`!default <名称>`令切换
# 由管理员使用`!default <名称>`令切换
# """
# __prompts_from_files__ = {}

View File

@@ -32,16 +32,9 @@ class KeysManager:
return hashlib.md5(self.using_key.encode('utf-8')).hexdigest()
def __init__(self, api_key):
if type(api_key) is dict:
self.api_key = api_key
elif type(api_key) is str:
self.api_key = {
"default": api_key
}
elif type(api_key) is list:
for i in range(len(api_key)):
self.api_key[str(i)] = api_key[i]
assert type(api_key) == dict
self.api_key = api_key
# 从usage中删除未加载的api-key的记录
# 不删了也许会运行时添加曾经有记录的api-key

View File

@@ -13,13 +13,6 @@ from ..openai.api import completion as api_completion
from ..openai.api import chat_completion as api_chat_completion
COMPLETION_MODELS = {
"text-davinci-003", # legacy
"text-davinci-002", # legacy
"code-davinci-002", # legacy
"code-cushman-001", # legacy
"text-curie-001", # legacy
"text-babbage-001", # legacy
"text-ada-001", # legacy
"gpt-3.5-turbo-instruct",
}

View File

@@ -25,34 +25,6 @@ class SessionOfflineStatus:
EXPLICITLY_CLOSED = 'explicitly_closed'
# 重置session.prompt
def reset_session_prompt(session_name, prompt):
# 备份原始数据
bak_path = 'logs/{}-{}.bak'.format(
session_name,
time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
)
f = open(bak_path, 'w+')
f.write(prompt)
f.close()
# 生成新数据
config = context.get_config_manager().data
prompt = [
{
'role': 'system',
'content': config['default_prompt']['default'] if type(config['default_prompt']) == dict else config['default_prompt']
}
]
# 警告
logging.warning(
"""
用户[{}]的数据已被重置,有可能是因为数据版本过旧或存储错误
原始数据将备份在:
{}""".format(session_name, bak_path)
) # 为保证多行文本格式正确故无缩进
return prompt
# 从数据加载session
def load_sessions():
"""从数据库加载sessions"""
@@ -70,12 +42,10 @@ def load_sessions():
temp_session.name = session_name
temp_session.create_timestamp = session_data[session_name]['create_timestamp']
temp_session.last_interact_timestamp = session_data[session_name]['last_interact_timestamp']
try:
temp_session.prompt = json.loads(session_data[session_name]['prompt'])
temp_session.token_counts = json.loads(session_data[session_name]['token_counts'])
except Exception:
temp_session.prompt = reset_session_prompt(session_name, session_data[session_name]['prompt'])
temp_session.persistence()
temp_session.prompt = json.loads(session_data[session_name]['prompt'])
temp_session.token_counts = json.loads(session_data[session_name]['token_counts'])
temp_session.default_prompt = json.loads(session_data[session_name]['default_prompt']) if \
session_data[session_name]['default_prompt'] else []
@@ -493,12 +463,10 @@ class Session:
self.create_timestamp = last_one['create_timestamp']
self.last_interact_timestamp = last_one['last_interact_timestamp']
try:
self.prompt = json.loads(last_one['prompt'])
self.token_counts = json.loads(last_one['token_counts'])
except json.decoder.JSONDecodeError:
self.prompt = reset_session_prompt(self.name, last_one['prompt'])
self.persistence()
self.prompt = json.loads(last_one['prompt'])
self.token_counts = json.loads(last_one['token_counts'])
self.default_prompt = json.loads(last_one['default_prompt']) if last_one['default_prompt'] else []
self.just_switched_to_exist_session = True
@@ -514,12 +482,10 @@ class Session:
self.create_timestamp = next_one['create_timestamp']
self.last_interact_timestamp = next_one['last_interact_timestamp']
try:
self.prompt = json.loads(next_one['prompt'])
self.token_counts = json.loads(next_one['token_counts'])
except json.decoder.JSONDecodeError:
self.prompt = reset_session_prompt(self.name, next_one['prompt'])
self.persistence()
self.prompt = json.loads(next_one['prompt'])
self.token_counts = json.loads(next_one['token_counts'])
self.default_prompt = json.loads(next_one['default_prompt']) if next_one['default_prompt'] else []
self.just_switched_to_exist_session = True

View File

@@ -576,7 +576,3 @@ class PluginHost:
)
return event_context
if __name__ == "__main__":
pass

View File

@@ -35,18 +35,18 @@ PersonNormalMessageReceived = "person_normal_message_received"
"""
PersonCommandSent = "person_command_sent"
"""判断为应该处理的私聊令时触发
"""判断为应该处理的私聊令时触发
kwargs:
launcher_type: str 发起对象类型(group/person)
launcher_id: int 发起对象ID(群号/QQ号)
sender_id: int 发送者ID(QQ号)
command: str
command: str
params: list[str] 参数列表
text_message: str 完整令文本
text_message: str 完整令文本
is_admin: bool 是否为管理员
returns (optional):
alter: str 修改后的完整令文本
alter: str 修改后的完整令文本
reply: list 回复消息组件列表
"""
@@ -64,18 +64,18 @@ GroupNormalMessageReceived = "group_normal_message_received"
"""
GroupCommandSent = "group_command_sent"
"""判断为应该处理的群聊令时触发
"""判断为应该处理的群聊令时触发
kwargs:
launcher_type: str 发起对象类型(group/person)
launcher_id: int 发起对象ID(群号/QQ号)
sender_id: int 发送者ID(QQ号)
command: str
command: str
params: list[str] 参数列表
text_message: str 完整令文本
text_message: str 完整令文本
is_admin: bool 是否为管理员
returns (optional):
alter: str 修改后的完整令文本
alter: str 修改后的完整令文本
reply: list 回复消息组件列表
"""

View File

@@ -71,7 +71,7 @@ __tree_index__: dict[str, list] = {}
结构:
{
'pkg.qqbot.cmds.cmd1.CommandCmd1': 'cmd1', # 顶级
'pkg.qqbot.cmds.cmd1.CommandCmd1': 'cmd1', # 顶级
'pkg.qqbot.cmds.cmd1.CommandCmd1_1': 'cmd1.cmd1-1', # 类名: 节点路径
'pkg.qqbot.cmds.cmd2.CommandCmd2': 'cmd2',
'pkg.qqbot.cmds.cmd2.CommandCmd2_1': 'cmd2.cmd2-1',
@@ -83,79 +83,79 @@ __tree_index__: dict[str, list] = {}
class Context:
"""命令执行上下文"""
command: str
"""顶级令文本"""
"""顶级令文本"""
crt_command: str
"""当前子令文本"""
"""当前子令文本"""
params: list
"""完整参数列表"""
crt_params: list
"""当前子令参数列表"""
"""当前子令参数列表"""
session_name: str
"""会话名"""
text_message: str
"""令完整文本"""
"""令完整文本"""
launcher_type: str
"""令发起者类型"""
"""令发起者类型"""
launcher_id: int
"""令发起者ID"""
"""令发起者ID"""
sender_id: int
"""令发送者ID"""
"""令发送者ID"""
is_admin: bool
"""[过时]令发送者是否为管理员"""
"""[过时]令发送者是否为管理员"""
privilege: int
"""令发送者权限等级"""
"""令发送者权限等级"""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
class AbstractCommandNode:
"""令抽象类"""
"""令抽象类"""
parent: type
"""令类"""
"""令类"""
name: str
"""令名"""
"""令名"""
description: str
"""令描述"""
"""令描述"""
usage: str
"""令用法"""
"""令用法"""
aliases: list[str]
"""令别名"""
"""令别名"""
privilege: int
"""令权限等级, 权限大于等于此值的用户才能执行"""
"""令权限等级, 权限大于等于此值的用户才能执行"""
@classmethod
def process(cls, ctx: Context) -> tuple[bool, list]:
"""令处理函数
"""令处理函数
:param ctx: 令执行上下文
:param ctx: 令执行上下文
:return: (是否执行, 回复列表(若执行))
若未执行,将自动以下一个参数查找并执行子
若未执行,将自动以下一个参数查找并执行子
"""
raise NotImplementedError
@classmethod
def help(cls) -> str:
"""获取令帮助信息"""
return '令: {}\n描述: {}\n用法: \n{}\n别名: {}\n权限: {}'.format(
"""获取令帮助信息"""
return '令: {}\n描述: {}\n用法: \n{}\n别名: {}\n权限: {}'.format(
cls.name,
cls.description,
cls.usage,
@@ -172,11 +172,11 @@ class AbstractCommandNode:
aliases: list[str] = None,
privilege: int = 0
):
"""注册
"""注册
:param cls: 令类
:param name: 令名
:param parent: 父令类
:param cls: 令类
:param name: 令名
:param parent: 父令类
"""
global __command_list__, __tree_index__
@@ -191,7 +191,7 @@ class AbstractCommandNode:
logging.debug("cls: {}, name: {}, parent: {}".format(cls, name, parent))
if parent is None:
# 顶级令注册
# 顶级令注册
__command_list__[name] = {
'description': cls.description,
'usage': cls.usage,
@@ -208,9 +208,9 @@ class AbstractCommandNode:
path = __tree_index__[parent.__module__ + '.' + parent.__name__]
parent_node = __command_list__[path]
# 链接父子
# 链接父子
__command_list__[path]['sub'].append(name)
# 注册子
# 注册子
__command_list__[path + '.' + name] = {
'description': cls.description,
'usage': cls.usage,
@@ -229,18 +229,18 @@ class AbstractCommandNode:
class CommandPrivilegeError(Exception):
"""令权限不足或不存在异常"""
"""令权限不足或不存在异常"""
pass
# 传入Context对象广搜命令树返回执行结果
# 若命令被处理返回reply列表
# 若命令未被处理,继续执行下一级
# 若命令未被处理,继续执行下一级
# 若命令不存在,报异常
def execute(context: Context) -> list:
"""执行
"""执行
:param ctx: 令执行上下文
:param ctx: 令执行上下文
:return: 回复列表
"""
@@ -249,7 +249,7 @@ def execute(context: Context) -> list:
# 拷贝ctx
ctx: Context = copy.deepcopy(context)
# 从树取出顶级
# 从树取出顶级
node = __command_list__
path = ctx.command
@@ -257,7 +257,7 @@ def execute(context: Context) -> list:
while True:
try:
node = __command_list__[path]
logging.debug('执行令: {}'.format(path))
logging.debug('执行令: {}'.format(path))
# 检查权限
if ctx.privilege < node['privilege']:
@@ -278,7 +278,7 @@ def execute(context: Context) -> list:
def register_all():
"""启动时调用此函数注册所有
"""启动时调用此函数注册所有
递归处理pkg.qqbot.cmds包下及其子包下所有模块的所有继承于AbstractCommand的类
"""
@@ -304,7 +304,7 @@ def register_all():
else:
m = __import__(module.__name__ + '.' + item.name, fromlist=[''])
# for name, cls in inspect.getmembers(m, inspect.isclass):
# # 检查是否为令类
# # 检查是否为令类
# if cls.__module__ == m.__name__ and issubclass(cls, AbstractCommandNode) and cls != AbstractCommandNode:
# cls.register(cls, cls.name, cls.parent)
@@ -313,7 +313,7 @@ def register_all():
def apply_privileges():
"""读取cmdpriv.json并应用令权限"""
"""读取cmdpriv.json并应用令权限"""
# 读取内容
json_str = ""
with open('cmdpriv.json', 'r', encoding="utf-8") as f:

View File

@@ -38,9 +38,6 @@ class PluginCommand(aamgr.AbstractCommandNode):
reply = [reply_str]
return True, reply
elif ctx.params[0].startswith("http"):
reply = ["[bot]err: 此命令已弃用,请使用 !plugin get <插件仓库地址> 进行安装"]
return True, reply
else:
return False, []
@@ -68,7 +65,7 @@ class PluginGetCommand(aamgr.AbstractCommandNode):
def closure():
try:
plugin_host.install_plugin(ctx.crt_params[0])
pkg.utils.context.get_qqbot_manager().notify_admin("插件安装成功,请发送 !reload 令重载插件")
pkg.utils.context.get_qqbot_manager().notify_admin("插件安装成功,请发送 !reload 令重载插件")
except Exception as e:
logging.error("插件安装失败:{}".format(e))
pkg.utils.context.get_qqbot_manager().notify_admin("插件安装失败:{}".format(e))
@@ -149,7 +146,7 @@ class PluginDelCommand(aamgr.AbstractCommandNode):
unin_path = plugin_host.uninstall_plugin(plugin_name)
reply = ["[bot]已删除插件: {} ({}), 请发送 !reload 重载插件".format(plugin_name, unin_path)]
else:
reply = ["[bot]err:未找到插件: {}, 请使用!plugin令查看插件列表".format(plugin_name)]
reply = ["[bot]err:未找到插件: {}, 请使用!plugin令查看插件列表".format(plugin_name)]
return True, reply
@@ -195,7 +192,7 @@ class PluginOnOffCommand(aamgr.AbstractCommandNode):
plugin_switch.dump_switch()
reply = ["[bot]已{}插件: {}".format("启用" if new_status else "禁用", plugin_name)]
else:
reply = ["[bot]err:未找到插件: {}, 请使用!plugin令查看插件列表".format(plugin_name)]
reply = ["[bot]err:未找到插件: {}, 请使用!plugin令查看插件列表".format(plugin_name)]
return True, reply

View File

@@ -39,8 +39,6 @@ class DefaultCommand(aamgr.AbstractCommandNode):
reply_str += "\n当前默认情景预设:{}\n".format(dprompt.mode_inst().get_using_name())
reply_str += "请使用 !default set <情景预设名称> 来设置默认情景预设"
reply = [reply_str]
elif params[0] != "set":
reply = ["[bot]err: 已弃用,请使用!default set <情景预设名称> 来设置默认情景预设"]
else:
return False, []
@@ -69,7 +67,5 @@ class DefaultSetCommand(aamgr.AbstractCommandNode):
reply = ["[bot]已设置默认情景预设为:{}".format(full_name)]
except Exception as e:
reply = ["[bot]err: {}".format(e)]
else:
reply = ["[bot]err: 仅管理员可设置默认情景预设"]
return True, reply

View File

@@ -31,7 +31,7 @@ class ListCommand(aamgr.AbstractCommandNode):
results = pkg.openai.session.get_session(session_name).list_history(page=page)
if len(results) == 0:
reply = ["[bot]第{}页没有历史会话".format(page)]
reply_str = "[bot]第{}页没有历史会话".format(page)
else:
reply_str = "[bot]历史会话 第{}页:\n".format(page)
current = -1
@@ -39,12 +39,9 @@ class ListCommand(aamgr.AbstractCommandNode):
# 时间(使用create_timestamp转换) 序号 部分内容
datetime_obj = datetime.datetime.fromtimestamp(results[i]['create_timestamp'])
msg = ""
try:
msg = json.loads(results[i]['prompt'])
except json.decoder.JSONDecodeError:
msg = pkg.openai.session.reset_session_prompt(session_name, results[i]['prompt'])
# 持久化
pkg.openai.session.get_session(session_name).persistence()
msg = json.loads(results[i]['prompt'])
if len(msg) >= 2:
reply_str += "#{} 创建:{} {}\n".format(i + page * 10,
datetime_obj.strftime("%Y-%m-%d %H:%M:%S"),

View File

@@ -15,7 +15,7 @@ class ResendCommand(aamgr.AbstractCommandNode):
from ....openai import session as openai_session
from ....utils import context
from ....qqbot import message
import config
session_name = ctx.session_name
reply = []
@@ -24,6 +24,8 @@ class ResendCommand(aamgr.AbstractCommandNode):
mgr = context.get_qqbot_manager()
config = context.get_config_manager().data
reply = message.process_normal_message(to_send, mgr, config,
ctx.launcher_type, ctx.launcher_id,
ctx.sender_id)

View File

@@ -68,8 +68,10 @@ def config_operation(cmd, params):
else:
cfg_mgr.data[cfg_entry_path[0]] = cfg_value
reply = ["[bot]配置项{}修改成功".format(cfg_name)]
except AttributeError:
except KeyError:
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
except NameError:
reply = ["[bot]err:值{}不合法(字符串需要使用双引号包裹)".format(cfg_value)]
except ValueError:
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]

View File

@@ -4,8 +4,8 @@ from .. import aamgr
@aamgr.AbstractCommandNode.register(
parent=None,
name="cmd",
description="显示令列表",
usage="!cmd\n!cmd <令名称>",
description="显示令列表",
usage="!cmd\n!cmd <令名称>",
aliases=[],
privilege=1
)
@@ -17,15 +17,15 @@ class CmdCommand(aamgr.AbstractCommandNode):
reply = []
if len(ctx.params) == 0:
reply_str = "[bot]当前所有令:\n\n"
reply_str = "[bot]当前所有令:\n\n"
# 遍历顶级
# 遍历顶级
for key in command_list:
command = command_list[key]
if command['parent'] is None:
reply_str += "!{} - {}\n".format(key, command['description'])
reply_str += "\n请使用 !cmd <令名称> 来查看令的详细信息"
reply_str += "\n请使用 !cmd <令名称> 来查看令的详细信息"
reply = [reply_str]
else:
@@ -33,7 +33,7 @@ class CmdCommand(aamgr.AbstractCommandNode):
if command_name in command_list:
reply = [command_list[command_name]['cls'].help()]
else:
reply = ["[bot]{} 不存在".format(command_name)]
reply = ["[bot]{} 不存在".format(command_name)]
return True, reply

View File

@@ -13,7 +13,7 @@ class HelpCommand(aamgr.AbstractCommandNode):
@classmethod
def process(cls, ctx: aamgr.Context) -> tuple[bool, list]:
import tips
reply = ["[bot] "+tips.help_message + "\n请输入 !cmd 查看令列表"]
reply = ["[bot] "+tips.help_message + "\n请输入 !cmd 查看令列表"]
# 警告config.help_message过时
import config

View File

@@ -13,7 +13,6 @@ class UsageCommand(aamgr.AbstractCommandNode):
@classmethod
def process(cls, ctx: aamgr.Context) -> tuple[bool, list]:
import config
import pkg.utils.credit as credit
import pkg.utils.context
reply = []

View File

@@ -1,4 +1,4 @@
# 令处理模块
# 令处理模块
import logging
from ..qqbot.cmds import aamgr as cmdmgr
@@ -9,7 +9,7 @@ def process_command(session_name: str, text_message: str, mgr, config: dict,
reply = []
try:
logging.info(
"[{}]发起令:{}".format(session_name, text_message[:min(20, len(text_message))] + (
"[{}]发起令:{}".format(session_name, text_message[:min(20, len(text_message))] + (
"..." if len(text_message) > 20 else "")))
cmd = text_message[1:].strip().split(' ')[0]
@@ -42,7 +42,7 @@ def process_command(session_name: str, text_message: str, mgr, config: dict,
return reply
except Exception as e:
mgr.notify_admin("{}令执行失败:{}".format(session_name, e))
mgr.notify_admin("{}令执行失败:{}".format(session_name, e))
logging.exception(e)
reply = ["[bot]err:{}".format(e)]

View File

@@ -84,7 +84,7 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
processing.append(session_name)
try:
msg_type = ''
if text_message.startswith('!') or text_message.startswith(""): #
if text_message.startswith('!') or text_message.startswith(""): #
msg_type = 'command'
# 触发插件事件
args = {

View File

@@ -17,14 +17,10 @@ def get_limitation(session_name: str) -> int:
"""获取会话的限制次数"""
config = context.get_config_manager().data
if type(config['rate_limitation']) == dict:
# 如果被指定了
if session_name in config['rate_limitation']:
return config['rate_limitation'][session_name]
else:
return config['rate_limitation']["default"]
elif type(config['rate_limitation']) == int:
return config['rate_limitation']
if session_name in config['rate_limitation']:
return config['rate_limitation'][session_name]
else:
return config['rate_limitation']["default"]
def add_usage(session_name: str):

View File

@@ -182,7 +182,8 @@ class NakuruProjectAdapter(adapter_model.MessageSourceAdapter):
headers={
'Authorization': "Bearer " + config['nakuru_config']['token'] if 'token' in config['nakuru_config']else ""
},
timeout=5
timeout=5,
proxies=None
)
if resp.status_code == 403:
logging.error("go-cqhttp拒绝访问请检查config.py中nakuru_config的token是否与go-cqhttp设置的access-token匹配")

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from .. import apigroup
from ... import context
class V2MainDataAPI(apigroup.APIGroup):
@@ -9,6 +10,12 @@ class V2MainDataAPI(apigroup.APIGroup):
def __init__(self, prefix: str):
super().__init__(prefix+"/main")
def do(self, *args, **kwargs):
config = context.get_config_manager().data
if not config['report_usage']:
return None
return super().do(*args, **kwargs)
def post_update_record(
self,
spent_seconds: int,

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from .. import apigroup
from ... import context
class V2PluginDataAPI(apigroup.APIGroup):
@@ -9,6 +10,12 @@ class V2PluginDataAPI(apigroup.APIGroup):
def __init__(self, prefix: str):
super().__init__(prefix+"/plugin")
def do(self, *args, **kwargs):
config = context.get_config_manager().data
if not config['report_usage']:
return None
return super().do(*args, **kwargs)
def post_install_record(
self,
plugin: dict

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from .. import apigroup
from ... import context
class V2UsageDataAPI(apigroup.APIGroup):
@@ -8,6 +9,12 @@ class V2UsageDataAPI(apigroup.APIGroup):
def __init__(self, prefix: str):
super().__init__(prefix+"/usage")
def do(self, *args, **kwargs):
config = context.get_config_manager().data
if not config['report_usage']:
return None
return super().do(*args, **kwargs)
def post_query_record(
self,

File diff suppressed because one or more lines are too long

View File

@@ -1,19 +0,0 @@
# OpenAI账号免费额度剩余查询
import requests
def fetch_credit_data(api_key: str, http_proxy: str) -> dict:
"""OpenAI账号免费额度剩余查询"""
proxies = {
"http":http_proxy,
"https":http_proxy
} if http_proxy is not None else None
resp = requests.get(
url="https://api.openai.com/dashboard/billing/credit_grants",
headers={
"Authorization": "Bearer {}".format(api_key),
},
proxies=proxies
)
return resp.json()

View File

@@ -28,10 +28,6 @@ def init_runtime_log_file():
if not os.path.exists("logs"):
os.mkdir("logs")
# 检查本目录是否有qchatgpt.log若有移动到logs目录
if os.path.exists("qchatgpt.log"):
shutil.move("qchatgpt.log", "logs/qchatgpt.legacy.log")
log_file_name = "logs/qchatgpt-%s.log" % time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())

View File

@@ -28,7 +28,7 @@ def reload_all(notify=True):
import main
main.stop()
# 删除所有已注册的
# 删除所有已注册的
import pkg.qqbot.cmds.aamgr as cmdsmgr
cmdsmgr.__command_list__ = {}
cmdsmgr.__tree_index__ = {}

View File

@@ -11,6 +11,8 @@ from ..utils import context
text_render_font: ImageFont = None
def initialize():
global text_render_font
logging.debug("初始化文字转图片模块...")
config = context.get_config_manager().data
if config['blob_message_strategy'] == "image": # 仅在启用了image时才加载字体
@@ -37,6 +39,8 @@ def initialize():
traceback.print_exc()
logging.error("加载字体文件失败({})更换为转发消息组件以发送长消息您可以在config.py中调整相关设置。".format(use_font))
config['blob_message_strategy'] = "forward"
logging.debug("字体文件加载完成。")
def indexNumber(path=''):
@@ -119,6 +123,8 @@ def compress_image(infile, outfile='', kb=100, step=20, quality=90):
def text_to_image(text_str: str, save_as="temp.png", width=800):
global text_render_font
logging.debug("正在将文本转换为图片...")
text_str = text_str.replace("\t", " ")
# 分行
@@ -128,9 +134,13 @@ def text_to_image(text_str: str, save_as="temp.png", width=800):
final_lines = []
text_width = width-80
logging.debug("lines: {}, text_width: {}".format(lines, text_width))
for line in lines:
logging.debug(type(text_render_font))
# 如果长了就分割
line_width = text_render_font.getlength(line)
logging.debug("line_width: {}".format(line_width))
if line_width < text_width:
final_lines.append(line)
continue
@@ -160,7 +170,7 @@ def text_to_image(text_str: str, save_as="temp.png", width=800):
img = Image.new('RGBA', (width, max(280, len(final_lines) * 35 + 65)), (255, 255, 255, 255))
draw = ImageDraw.Draw(img, mode='RGBA')
logging.debug("正在绘制图片...")
# 绘制正文
line_number = 0
offset_x = 20
@@ -192,7 +202,7 @@ def text_to_image(text_str: str, save_as="temp.png", width=800):
line_number += 1
logging.debug("正在保存图片...")
img.save(save_as)
return save_as

View File

@@ -25,18 +25,6 @@ def check_dulwich_closure():
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
def pull_latest(repo_path: str) -> bool:
"""拉取最新代码"""
check_dulwich_closure()
from dulwich import porcelain
repo = porcelain.open_repo(repo_path)
porcelain.pull(repo)
return True
def is_newer(new_tag: str, old_tag: str):
"""判断版本是否更新,忽略第四位版本和第一位版本"""
if new_tag == old_tag:
@@ -254,35 +242,6 @@ def get_current_version_info() -> str:
return "未知版本"
def get_commit_id_and_time_and_msg() -> str:
"""获取当前提交id和时间和提交信息"""
check_dulwich_closure()
from dulwich import porcelain
repo = porcelain.open_repo('.')
for entry in repo.get_walker():
tz = datetime.timezone(datetime.timedelta(hours=entry.commit.commit_timezone // 3600))
dt = datetime.datetime.fromtimestamp(entry.commit.commit_time, tz)
return str(entry.commit.id)[2:9] + " " + dt.strftime('%Y-%m-%d %H:%M:%S') + " [" + str(entry.commit.message, encoding="utf-8").strip()+"]"
def get_current_commit_id() -> str:
"""检查是否有新版本"""
check_dulwich_closure()
from dulwich import porcelain
repo = porcelain.open_repo('.')
current_commit_id = ""
for entry in repo.get_walker():
current_commit_id = str(entry.commit.id)[2:-1]
break
return current_commit_id
def is_new_version_available() -> bool:
"""检查是否有新版本"""
# 从github获取release列表

View File

@@ -14,7 +14,7 @@ rate_limit_drop_tip = "本分钟对话次数超过限速次数,此对话被丢
# 若设置为空字符串,则不发送提示信息
message_drop_tip = "[bot]当前有一条消息正在处理,请等待处理完成"
# 指令!help帮助消息
# 命令 !help帮助消息
help_message = """此机器人通过调用大型语言模型生成回复,不具有情感。
你可以用自然语言与其交流,回复的消息中[GPT]开头的为模型生成的语言,[bot]开头的为程序提示。
欢迎到github.com/RockChinQ/QChatGPT 给个star"""
@@ -24,10 +24,10 @@ reply_message = "[bot]err:请求超时"
# 群聊消息超时提示
replys_message = "[bot]err:请求超时"
# 令权限不足提示
# 令权限不足提示
command_admin_message = "[bot]err:权限不足: "
# 令无效提示
command_err_message = "[bot]err:令不存在:"
# 令无效提示
command_err_message = "[bot]err:令不存在:"
# 会话重置提示
command_reset_message = "[bot]会话已重置"