Compare commits

..

179 Commits

Author SHA1 Message Date
Rock Chin
dce134d08d Release v2.4.7 2023-06-16 19:43:13 +08:00
Junyan Qin
cca471d068 Merge pull request #500 from RockChinQ/perf-more-model-support
Perf more model support
2023-06-16 19:40:29 +08:00
Rock Chin
ddb211b74a feat: 支持新的模型 2023-06-16 19:35:26 +08:00
Rock Chin
cef70751ff chore: 修改配置文件说明 2023-06-16 19:35:06 +08:00
JunYan Qin
2d2219fc6e 更新 README.md 2023-06-13 11:59:30 +08:00
JunYan Qin
514a6b4192 更新 README.md 2023-06-13 11:59:07 +08:00
JunYan Qin
7a552b3434 Merge pull request #496 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.8
chore(deps): update openai requirement from ~=0.27.7 to ~=0.27.8
2023-06-12 23:20:02 +08:00
dependabot[bot]
ecebd1b0e0 chore(deps): update openai requirement from ~=0.27.7 to ~=0.27.8
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.7...v0.27.8)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 09:01:51 +00:00
Rock Chin
8dc34d2a88 doc(README.md): 添加卖号网站 2023-06-12 14:13:36 +08:00
Rock Chin
d52644ceec doc: 更新README.md 2023-06-12 12:24:59 +08:00
Rock Chin
3052510591 Release v2.4.6 2023-06-08 14:01:18 +08:00
Rock Chin
777a5617db Merge pull request #492 from RockChinQ/feat-ignore-major-vernum
[Feat] 更新时忽略主版本号不同的版本
2023-06-08 14:00:32 +08:00
Rock Chin
e17c1087e9 feat(updater.py): 更新时忽略主版本号不同的版本 2023-06-08 13:57:24 +08:00
Rock Chin
633695175a Merge pull request #491 from RockChinQ/feat-tokens-auto-reset
[Feat] token超限报错时自动重置会话
2023-06-08 13:49:49 +08:00
Rock Chin
9e78bf3d21 perf: 更严格的重置条件判断 2023-06-08 13:49:20 +08:00
Rock Chin
43aa68a55d feat: 支持在token超限时自动重置会话 2023-06-08 13:45:54 +08:00
Rock Chin
b8308f8c57 Merge branch 'feat-tokens-auto-reset' of https://github.com/RockChinQ/QChatGPT into feat-tokens-auto-reset 2023-06-08 13:43:36 +08:00
Rock Chin
466bfbddeb perf: 提示语格式 2023-06-08 13:43:33 +08:00
GitHub Actions
b6da07b225 Update override-all.json 2023-06-08 05:20:55 +00:00
Rock Chin
2f2159239a chore: 添加开关和提示语配置项 2023-06-08 13:20:33 +08:00
Rock Chin
67d1ca8a65 Merge pull request #490 from RockChinQ/feat-global-group-private-enable
[Feat] 支持设置全局群聊/私聊消息禁用
2023-06-07 23:49:53 +08:00
Rock Chin
497a393e83 doc: 修改wiki 2023-06-07 23:49:09 +08:00
Rock Chin
782c0e22ea feat: 支持设置全局群聊、私聊禁用 2023-06-07 23:47:13 +08:00
Rock Chin
2932fc6dfd chore(banlist-template.py): 添加配置项 2023-06-07 23:23:21 +08:00
Rock Chin
0a9eab2113 chore(requirements.txt): 更新requests版本 2023-06-06 09:37:00 +08:00
Rock Chin
50a673a8ec doc: 添加插件列表list 2023-06-05 22:37:19 +08:00
Rock Chin
9e25d0f9e4 Release v2.4.5 2023-05-31 18:31:25 +08:00
Rock Chin
23cd7be711 Merge pull request #487 from RockChinQ/feat-banlist-syntax-check
feat: 初始化流程异常处理
2023-05-31 18:25:46 +08:00
Rock Chin
025b9e33f1 feat: 初始化流程异常处理 2023-05-31 18:24:01 +08:00
Rock Chin
bab2f64913 doc(README_en.md): 添加wakapi计时 2023-05-29 11:12:07 +08:00
Rock Chin
b00e09aa9c doc: 添加wakapi计时 2023-05-29 11:10:49 +08:00
Rock Chin
0b109fdc7a Merge pull request #479 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.7
chore(deps): update openai requirement from ~=0.27.6 to ~=0.27.7
2023-05-22 17:13:50 +08:00
dependabot[bot]
018fea2ddb chore(deps): update openai requirement from ~=0.27.6 to ~=0.27.7
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.6...v0.27.7)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-22 09:05:00 +00:00
Rock Chin
f8a3cc4352 doc: 折起OpenAI注册步骤 2023-05-21 18:25:35 +08:00
Rock Chin
6ab853acc1 doc: 修改关于HuggingChat的说明 2023-05-21 17:50:35 +08:00
Rock Chin
e825dea02f chore: 排除hugchat.json 2023-05-21 17:47:42 +08:00
Rock Chin
cf8740d16e Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-05-21 17:33:47 +08:00
Rock Chin
9c4809e26f chore: 发布revLibs相关公告 2023-05-21 17:33:44 +08:00
Rock Chin
0a232fd9ef Merge pull request #477 from RockChinQ/feature-detailed-cfg-cmd
[Feat] 支持使用!cfg指令修改子配置项
2023-05-21 15:59:59 +08:00
Rock Chin
23016a0791 doc: 更新wiki说明 2023-05-21 15:58:21 +08:00
Rock Chin
cdcc67ff23 feat(!cfg): 使用eval()函数进行类型转换 2023-05-21 15:53:56 +08:00
Rock Chin
92274bfc34 feat(!cfg): 支持使用点号索引子配置项 2023-05-21 15:49:56 +08:00
Rock Chin
2fed6f61ba Release v2.4.4 2023-05-21 15:15:28 +08:00
Rock Chin
59b2cd26d2 Merge pull request #476 from RockChinQ/hotfix-471-at-no-response-aft-reload
[Fix] 热重载之后不响应群内at
2023-05-21 15:12:43 +08:00
Rock Chin
f7b87e99d2 fix(manager.py): 热重载之后不响应群内at 2023-05-21 15:11:34 +08:00
Rock Chin
70bc985145 perf(nakuru.py): access-token被拒时报警 2023-05-18 21:06:32 +08:00
Rock Chin
070dbe9108 chore: 排除venv/目录 2023-05-18 21:05:45 +08:00
Rock Chin
a63fa6d955 chore: yiri-mirai使用0.2.7 2023-05-18 21:05:30 +08:00
Rock Chin
c7703809b0 Merge pull request #475 from RockChinQ/actively-delay
[Feat] 支持设置消息回复强制延迟以降低风控概率
2023-05-18 20:15:52 +08:00
GitHub Actions
37eb74338f Update override-all.json 2023-05-18 12:14:19 +00:00
Rock Chin
77d5585b7c feat: 修改强制延迟默认范围 2023-05-18 20:13:53 +08:00
Rock Chin
6cab3ef029 Merge branch 'actively-delay' of https://github.com/RockChinQ/QChatGPT into actively-delay 2023-05-18 20:12:39 +08:00
Rock Chin
820a7b78fc feat: 处理过程支持强制延迟 2023-05-18 20:12:36 +08:00
GitHub Actions
c51dffef3a Update override-all.json 2023-05-18 12:10:33 +00:00
Rock Chin
983bc3da3c chore: 添加强制延迟配置项 2023-05-18 20:10:08 +08:00
Rock Chin
09be956a58 Merge pull request #474 from RockChinQ/command-notfound-err
[Perf] 修改指令不存在时的提示信息
2023-05-18 19:45:25 +08:00
Rock Chin
5eded50c53 perf: 修改指令不存在时的提示信息 2023-05-18 19:44:20 +08:00
Rock Chin
6d8eebd314 doc: 添加微信赞赏码 2023-05-16 15:36:07 +08:00
Rock Chin
19a0572b5f Release v2.4.3.1 2023-05-15 17:38:03 +08:00
Rock Chin
6272e98474 Merge pull request #467 from RockChinQ/perf-plugin-update
[Perf] 优化插件更新相关操作
2023-05-14 18:45:36 +08:00
Rock Chin
45042fe7d4 doc: 更新插件更新命令wiki 2023-05-14 18:44:14 +08:00
Rock Chin
d85e840126 perf: 优化插件更新操作,支持更新单个插件 2023-05-14 18:41:20 +08:00
Rock Chin
804889f1de perf: 加载模块的输出改为debug级别 2023-05-14 17:30:05 +08:00
Rock Chin
919c996434 doc: 添加HuggingChat 2023-05-14 17:14:46 +08:00
Rock Chin
00823b3d62 doc(README): 添加HuggingChat 2023-05-14 17:14:28 +08:00
Rock Chin
af54efd24a doc(README.md): 添加系统状态插件 2023-05-14 14:58:48 +08:00
Rock Chin
b1c9b121f6 Update go-cqhttp配置.md 2023-05-08 21:51:03 +08:00
Rock Chin
7b5649d153 Merge pull request #461 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.6
chore(deps): update openai requirement from ~=0.27.5 to ~=0.27.6
2023-05-08 18:48:20 +08:00
dependabot[bot]
52bf716d84 chore(deps): update openai requirement from ~=0.27.5 to ~=0.27.6
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.5...v0.27.6)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 10:34:21 +00:00
Rock Chin
c149dd7b66 Merge pull request #462 from RockChinQ/dependabot/pip/dulwich-approx-eq-0.21.5
chore(deps): update dulwich requirement from ~=0.21.3 to ~=0.21.5
2023-05-08 18:24:59 +08:00
dependabot[bot]
65d5a1ed63 chore(deps): update dulwich requirement from ~=0.21.3 to ~=0.21.5
Updates the requirements on [dulwich](https://github.com/dulwich/dulwich) to permit the latest version.
- [Release notes](https://github.com/dulwich/dulwich/releases)
- [Changelog](https://github.com/jelmer/dulwich/blob/master/NEWS)
- [Commits](https://github.com/dulwich/dulwich/compare/dulwich-0.21.3...dulwich-0.21.5)

---
updated-dependencies:
- dependency-name: dulwich
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 09:01:55 +00:00
Rock Chin
5516754bbb doc(README.md): 添加docker部署提示 2023-05-02 14:30:25 +08:00
Rock Chin
08082f2ee3 Merge pull request #452 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.5
chore(deps): update openai requirement from ~=0.27.4 to ~=0.27.5
2023-05-01 17:26:18 +08:00
dependabot[bot]
8489266080 chore(deps): update openai requirement from ~=0.27.4 to ~=0.27.5
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.4...v0.27.5)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-01 09:06:44 +00:00
Rock Chin
51c7e0b235 doc(README.md): 四群群号 2023-04-28 00:37:29 +08:00
Rock Chin
628b6b0bb4 Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-04-27 15:01:47 +08:00
Rock Chin
7e024d860d doc: 增加LightQChat的公告 2023-04-27 15:01:44 +08:00
Rock Chin
c2f6273f70 Merge pull request #442 from oliverkirk-sudo/master
修复异常输出时的类型问题
2023-04-26 17:31:19 +08:00
oliverkirk-sudo
96e401ec7b 修复异常输出时的类型问题 2023-04-26 17:27:33 +08:00
Rock Chin
ae8ac65447 feat: 更换使用清华源 (#438) 2023-04-26 11:52:07 +08:00
Rock Chin
2d4f59f36e doc: 强调02 2023-04-26 11:18:07 +08:00
Rock Chin
0e85467e02 Release v2.4.2 2023-04-25 10:27:57 +08:00
Rock Chin
eb41cf5481 fix(plugin.py): 兼容性问题 2023-04-25 10:27:07 +08:00
Rock Chin
b970a42d07 fix(plugin.py): send_message封装实现的兼容性问题 2023-04-25 10:26:03 +08:00
Rock Chin
8c9d123e1c Merge pull request #433 from RockChinQ/detailed-response-rules
[Feat] 细化到单个群的响应规则
2023-04-25 09:39:56 +08:00
Rock Chin
ab2a95e347 Merge branch 'detailed-response-rules' of https://github.com/RockChinQ/QChatGPT into detailed-response-rules 2023-04-25 09:31:56 +08:00
Rock Chin
2184c558a4 feat: 支持配置细化到单个群的响应规则 2023-04-25 09:31:44 +08:00
GitHub Actions
83cb8588fd Update override-all.json 2023-04-25 01:28:56 +00:00
Rock Chin
007e82c533 feat: 配置文件支持 2023-04-25 09:28:31 +08:00
Rock Chin
499f8580a7 doc: 修改wiki格式 2023-04-25 08:45:58 +08:00
Rock Chin
a7dc3c5dab Release v2.4.1 2023-04-25 00:01:40 +08:00
Rock Chin
d01d3a3c53 perf: 启动时提示使用的QQ号 2023-04-24 23:57:57 +08:00
Rock Chin
580e062dbf feat: 上报使用量时带上msg_source_adapter 2023-04-24 23:51:00 +08:00
Rock Chin
c8cee8410c doc: 完善格式 2023-04-24 20:04:33 +08:00
Rock Chin
6bf331c2e3 doc: 完善wiki 2023-04-24 19:53:20 +08:00
Rock Chin
4c4930737c chore: issue模板新增登录框架字段 2023-04-24 19:28:00 +08:00
Rock Chin
9de01e9525 Release v2.4.0 2023-04-24 16:09:46 +08:00
Rock Chin
c6a16f5974 Merge pull request #427 from RockChinQ/nakuru-support
[Feat] 支持通过nakuru-project框架连接go-cqhttp
2023-04-24 16:07:12 +08:00
Rock Chin
253ef44d17 chore: 公告 2023-04-24 16:05:47 +08:00
Rock Chin
15a1f00b73 doc(README.md): 添加go-cqhttp公告 2023-04-24 16:04:25 +08:00
Rock Chin
b5fa2ea8b8 feat(main.py): 添加nakuru-project-idk的依赖更新项 2023-04-24 16:01:43 +08:00
Rock Chin
449e024771 doc: 添加针对老用户的说明 2023-04-24 15:59:07 +08:00
Rock Chin
1bee7a146b feat: 支持语音组件 2023-04-24 15:55:21 +08:00
Rock Chin
270a632789 doc: 修改标号 2023-04-24 15:48:28 +08:00
Rock Chin
418bb05b4c doc: 添加go-cqhttp配置说明 2023-04-24 15:46:58 +08:00
Rock Chin
052b834151 doc: 完善config-template.py的说明 2023-04-24 15:46:26 +08:00
Rock Chin
58ee204a75 doc: wiki添加go-cqhttp配置步骤 2023-04-24 15:41:28 +08:00
Rock Chin
0a02ee8c04 feat: 启动时添加nakuru的提示检查 2023-04-24 15:04:07 +08:00
Rock Chin
950ef4a181 doc: 更新README.md 2023-04-24 14:57:28 +08:00
Rock Chin
7b7cdd8adb perf: 在日志文件包含输出文件路径 2023-04-24 13:52:22 +08:00
Rock Chin
471768e760 feat: 支持发送转发消息 2023-04-24 12:46:33 +08:00
Rock Chin
c7517d31a4 chore: 更换使用nakuru-project-idk包 2023-04-24 11:37:01 +08:00
Rock Chin
7d10d0398e fix: nakuru热重载失败 2023-04-24 11:21:51 +08:00
Rock Chin
a2bc25c08b feat: 支持引用原消息回复 2023-04-24 10:57:43 +08:00
Rock Chin
3cb49fe2d8 feat: 支持检测群内禁言 2023-04-24 10:34:51 +08:00
Rock Chin
5b96ac122f feat: 适配nakuru基本功能 2023-04-23 23:40:08 +08:00
Rock Chin
612033f478 feat: nakuru适配器基础模型 2023-04-23 15:58:37 +08:00
GitHub Actions
48ee940d8e Update override-all.json 2023-04-23 01:32:36 +00:00
Rock Chin
e74df0b37d chore: 添加nakuru相关配置; 使用nakuru-project-test临时包 2023-04-23 09:32:01 +08:00
GitHub Actions
640afdc49c Update override-all.json 2023-04-22 13:51:02 +00:00
Rock Chin
6b39df5b9b chore: 删除NoneBot2相关配置 2023-04-22 21:50:41 +08:00
Rock Chin
e7e698765e fix(plugin.py): 缺少的换行符 2023-04-22 17:40:41 +08:00
Rock Chin
43fea13dab Merge pull request #418 from RockChinQ/im-impl-decoupling
[Refactor] 新增抽象层以解耦消息来源(MessageSource)组件
2023-04-21 18:10:42 +08:00
GitHub Actions
bc899e5bd0 Update override-all.json 2023-04-21 09:52:31 +00:00
Rock Chin
160086feb9 refactor: 完成MessageSource适配器解耦 2023-04-21 17:51:58 +08:00
Rock Chin
016391c976 refactor: 不再向QQBotManager中传递config中可读的参数 2023-04-21 17:15:32 +08:00
Rock Chin
91746448a3 feat: 消息源适配器模型及YiriMirai的适配器 2023-04-21 16:36:59 +08:00
Rock Chin
5cb0543237 doc(README.md): 更新wiki链接 2023-04-20 20:50:00 +08:00
Rock Chin
fac29a24a8 doc(README.md): social.png更改成圆角 2023-04-20 10:54:06 +08:00
Rock Chin
4d3a2a21d0 Update README_en.md 2023-04-20 00:22:05 +08:00
Rock Chin
6d4f88041c Update README.md 2023-04-20 00:21:37 +08:00
Rock Chin
18587d3690 doc(README.md): 修改social图格式 2023-04-20 00:15:11 +08:00
Rock Chin
423090dccd doc(README.md): 更改使用social图 2023-04-20 00:13:11 +08:00
Rock Chin
78e88baab3 doc(README.md): 优化LOGO图格式 2023-04-20 00:08:00 +08:00
Rock Chin
6a276767b3 doc(README.md): 添加LOGO 2023-04-20 00:06:52 +08:00
Rock Chin
2cb26c7c70 doc: 添加LOGO文件 2023-04-20 00:04:01 +08:00
Rock Chin
ff66c88060 doc(README.md): 优化图片格式 2023-04-17 10:18:23 +08:00
Rock Chin
611e82b8f9 doc(README.md): 添加使用截图 2023-04-17 10:15:50 +08:00
Rock Chin
59bdee7137 feat: 添加IM框架模型 2023-04-15 23:38:52 +08:00
Rock Chin
e8dbd426ae Release v2.3.9 2023-04-15 17:36:59 +08:00
Rock Chin
40d6e809a0 Merge pull request #417 from RockChinQ/354-feature-single-concurrency
[Feat] 支持设置单会话内同时仅处理一条消息
2023-04-15 17:35:36 +08:00
GitHub Actions
236c540d18 Update override-all.json 2023-04-15 09:34:16 +00:00
Rock Chin
d6ca059f6c feat: 支持设置单会话内同时仅处理一条消息 2023-04-15 17:33:57 +08:00
Rock Chin
52c06a60ca fix: 公告功能bug 2023-04-15 16:54:50 +08:00
Rock Chin
6353644ec3 test: 测试公告 2023-04-15 16:49:11 +08:00
Rock Chin
20df9ded3d Merge pull request #416 from RockChinQ/413-feature-json-format-anouns
[Feat] 支持JSON格式的公告
2023-04-15 16:47:03 +08:00
Rock Chin
7569b18a4c feat: 支持JSON格式的公告 2023-04-15 16:45:26 +08:00
Rock Chin
b9da4f4951 Merge pull request #415 from RockChinQ/413-feature-json-format-anouns
[Feat] 新增`announcement.json`文件
2023-04-15 16:33:03 +08:00
Rock Chin
89b9e29257 Update pull_request_template.md 2023-04-15 16:25:24 +08:00
Rock Chin
d605de9de4 feat: 添加公告模板及公告发布脚本 2023-04-15 09:38:46 +08:00
Rock Chin
d46c94d7c3 Release v2.3.8 2023-04-14 23:47:00 +08:00
Rock Chin
2db9c00530 Merge pull request #414 from RockChinQ/detailed-rate-limit
[Feat] 速度限制支持细化到单个人或群
2023-04-14 19:46:24 +08:00
GitHub Actions
66d8d159f9 Update override-all.json 2023-04-14 11:44:26 +00:00
Rock Chin
9fa1446284 feat: 支持细化到个人和群的限速 2023-04-14 19:44:03 +08:00
Rock Chin
b3e4cb48c7 Merge pull request #412 from RockChinQ/349-bugfix-auto-deps-solving-failure
[Fix] 循环依赖导致的依赖自动解决失败
2023-04-14 18:44:40 +08:00
Rock Chin
0bca7b2247 fix: 循环引用导致的依赖自动解决失败 2023-04-14 18:42:09 +08:00
Rock Chin
7812e03c9d chore: 删除requirements.txt中对websockets的版本要求以防冲突 2023-04-14 18:27:44 +08:00
Rock Chin
7a852ae5af Merge pull request #410 from 2675hujilo/tips
删除tips-custom-template.py中无用字段
2023-04-14 17:43:30 +08:00
26751
706d9e61c1 删除tips-custom-template.py中无用字段 2023-04-14 02:00:45 +08:00
Rock Chin
8f0ed4ff4b Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-04-12 15:28:59 +08:00
Rock Chin
3415b6f121 doc: 添加lieyanqzu/WeatherPlugin 2023-04-12 15:28:56 +08:00
Rock Chin
256ba6fb86 Merge pull request #406 from RockChinQ/dependabot/pip/openai-approx-eq-0.27.4
chore(deps): update openai requirement from ~=0.27.2 to ~=0.27.4
2023-04-10 18:31:39 +08:00
dependabot[bot]
d30b2b9afe chore(deps): update openai requirement from ~=0.27.2 to ~=0.27.4
Updates the requirements on [openai](https://github.com/openai/openai-python) to permit the latest version.
- [Release notes](https://github.com/openai/openai-python/releases)
- [Commits](https://github.com/openai/openai-python/compare/v0.27.2...v0.27.4)

---
updated-dependencies:
- dependency-name: openai
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 09:03:02 +00:00
Rock Chin
be943ca1fc doc: 链接文档 2023-04-08 19:39:42 +08:00
Rock Chin
1ddab2a97a doc: README.md in English 2023-04-08 19:32:31 +08:00
Rock Chin
e15fd4695c Merge branch 'master' of https://github.com/RockChinQ/QChatGPT 2023-04-08 18:26:10 +08:00
Rock Chin
ffa4b1b4a1 fix(modelmgr): 使用异步请求时的异常类型丢失 2023-04-08 18:26:08 +08:00
Rock Chin
f8eee3a2a6 Merge pull request #399 from RockChinQ/optional-config-override
[Feat] override.json可选应用
2023-04-08 16:26:56 +08:00
Rock Chin
eeee7a8343 feat: 仅在提供命令行参数时应用override.json的内容 2023-04-08 16:21:40 +08:00
Rock Chin
8447b73fcb doc(README.md): 删除ChatAPI2D插件 2023-04-08 16:15:11 +08:00
Rock Chin
2863945d5f feat(config-template): 更改为常量表示超时时间 2023-04-08 15:36:35 +08:00
Rock Chin
cb1f8ca6f7 doc(README.md): 添加wenyinos/ChatAPI2D插件 2023-04-08 00:33:47 +08:00
Rock Chin
1d9964bcb1 Release v2.3.7 2023-04-08 00:21:21 +08:00
GitHub Actions Bot
15cb8016d3 Update cmdpriv-template.json 2023-04-07 16:20:13 +00:00
Rock Chin
895cc0a2c5 ci: test 2023-04-08 00:19:37 +08:00
Rock Chin
20bf349e4e ci: cmdpriv模板脚本 2023-04-08 00:18:00 +08:00
Rock Chin
e297763da1 fix: !cfg指令失效 2023-04-08 00:13:19 +08:00
Rock Chin
e471970654 ci: test 2023-04-07 20:32:51 +08:00
Rock Chin
12faaaced8 ci: 仅在wiki文件更新时提交 2023-04-07 20:31:33 +08:00
47 changed files with 1855 additions and 439 deletions

View File

@@ -14,6 +14,15 @@ body:
- Docker部署
validations:
required: true
- type: dropdown
attributes:
label: 登录框架
description: "连接QQ使用的框架"
options:
- Mirai
- go-cqhttp
validations:
required: false
- type: input
attributes:
label: 系统环境

View File

@@ -4,7 +4,7 @@
### 事务
- [ ] 已阅读仓库[贡献指引](../CONTRIBUTING.md)
- [ ] 已阅读仓库[贡献指引](https://github.com/RockChinQ/QChatGPT/blob/master/CONTRIBUTING.md)
- [ ] 已与维护者在issues或其他平台沟通此PR大致内容
## 以下内容可在起草PR后、合并PR前逐步完成

View File

@@ -26,6 +26,8 @@ jobs:
- name: Commit and Push Changes
run: |
cd wiki
git add .
git commit -m "Update wiki"
git push
if git diff --name-only; then
git add .
git commit -m "Update wiki"
git push
fi

View File

@@ -42,7 +42,7 @@ jobs:
- name: Check for changes in cmdpriv-template.json
id: check_changes
run: |
if git diff --name-only | grep -q "cmdpriv-template.json"; then
if git diff --name-only | grep -q "res/templates/cmdpriv-template.json"; then
echo "::set-output name=changes_detected::true"
else
echo "::set-output name=changes_detected::false"
@@ -53,6 +53,6 @@ jobs:
run: |
git config --global user.name "GitHub Actions Bot"
git config --global user.email "<github-actions@github.com>"
git add cmdpriv-template.json
git add res/templates/cmdpriv-template.json
git commit -m "Update cmdpriv-template.json"
git push

7
.gitignore vendored
View File

@@ -17,5 +17,12 @@ scenario/
override.json
cookies.json
res/announcement_saved
res/announcement_saved.json
cmdpriv.json
tips.py
.venv
bin/
.vscode
test_*
venv/
hugchat.json

View File

@@ -1,15 +1,28 @@
# QChatGPT🤖
<p align="center">
<img src="res/social.png" alt="QChatGPT" width="640" />
</p>
[English](README_en.md) | 简体中文
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/QChatGPT?style=flat-square)](https://github.com/RockChinQ/QChatGPT/releases/latest)
![Wakapi Count](https://wakapi.dev/api/badge/RockChinQ/interval:any/project:QChatGPT)
> 2023/4/24 支持使用go-cqhttp登录QQ请查看[此文档](https://github.com/RockChinQ/QChatGPT/wiki/go-cqhttp%E9%85%8D%E7%BD%AE)
> 2023/3/18 现已支持GPT-4 API内测请查看`config-template.py`中的`completion_api_params`
> 2023/3/15 逆向库已支持New Bing使用方法查看[插件文档](https://github.com/RockChinQ/revLibs)
- **客官,来都来了,不点个⭐吗?**
**QChatGPT需要Python版本>=3.9**
- 到[项目Wiki](https://github.com/RockChinQ/QChatGPT/wiki)可了解项目详细信息
- 官方交流、答疑群: 656285629
- **进群提问前请您`确保`已经找遍文档和issue均无法解决**
- 社区群(内有一键部署包、图形化界面等资源): 362515018
- QQ频道机器人见[QQChannelChatGPT](https://github.com/Soulter/QQChannelChatGPT)
- 欢迎各种形式的贡献,请查看[贡献指引](CONTRIBUTING.md)
- 购买ChatGPT账号: [此链接](http://fk.kimi.asia)
## 🍺模型适配一览
@@ -24,6 +37,7 @@
- ChatGPT网页版GPT-3.5模型, 由[插件](https://github.com/RockChinQ/revLibs)接入
- ChatGPT网页版GPT-4模型, 目前需要ChatGPT Plus订阅, 由[插件](https://github.com/RockChinQ/revLibs)接入
- New Bing逆向库, 由[插件](https://github.com/RockChinQ/revLibs)接入
- HuggingChat, 由[插件](https://github.com/RockChinQ/revLibs)接入, 仅支持英文
### 故事续写
@@ -132,6 +146,15 @@
- 允许用户自定义报错、帮助等提示信息
- 请查看`tips.py`
</details>
### 🏞️截图
<img alt="私聊GPT-3.5" src="res/screenshots/person_gpt3.5.png" width="400"/>
<br/>
<img alt="群聊GPT-3.5" src="res/screenshots/group_gpt3.5.png" width="400"/>
<br/>
<img alt="New Bing" src="res/screenshots/person_newbing.png" width="400"/>
</details>
详情请查看[Wiki功能使用页](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E5%8A%9F%E8%83%BD%E7%82%B9%E5%88%97%E4%B8%BE)
@@ -142,6 +165,9 @@
### - 注册OpenAI账号
<details>
<summary>点此查看步骤</summary>
> 若您要直接使用非OpenAI的模型如New Bing可跳过此步骤直接进行之后的部署完成后按照相关插件的文档进行配置即可
参考以下文章自行注册
@@ -152,6 +178,8 @@
注册成功后请前往[个人中心查看](https://beta.openai.com/account/api-keys)api_key
完成注册后,使用以下自动化或手动部署步骤
</details>
### - 自动化部署
<details>
@@ -159,6 +187,8 @@
#### Docker方式
> docker方式目前仅支持使用mirai登录若您不**熟悉**docker的操作及相关知识强烈建议您使用其他方式部署我们**不会且难以**解决您主机上多个容器的连接问题。
请查看[此文档](res/docs/docker_deploy.md)
由[@mikumifa](https://github.com/mikumifa)贡献
@@ -176,12 +206,29 @@
- 请使用Python 3.9.x以上版本
#### 配置Mirai
#### ① 配置QQ登录框架
按照[此教程](https://yiri-mirai.wybxc.cc/tutorials/01/configuration)配置Mirai及YiriMirai
启动mirai-console后使用`login`命令登录QQ账号保持mirai-console运行状态
目前支持mirai和go-cqhttp配置任意一个即可
#### 配置主程序
<details>
<summary>mirai</summary>
1. 按照[此教程](https://yiri-mirai.wybxc.cc/tutorials/01/configuration)配置Mirai及mirai-api-http
2. 启动mirai-console后使用`login`命令登录QQ账号保持mirai-console运行状态
3. 在下一步配置主程序时请在config.py中将`msg_source_adapter`设为`yirimirai`
</details>
<details>
<summary>go-cqhttp</summary>
1. 按照[此文档](https://github.com/RockChinQ/QChatGPT/wiki/go-cqhttp%E9%85%8D%E7%BD%AE)配置go-cqhttp
2. 启动go-cqhttp确保登录成功保持运行
3. 在下一步配置主程序时请在config.py中将`msg_source_adapter`设为`nakuru`
</details>
#### ② 配置主程序
1. 克隆此项目
@@ -193,7 +240,7 @@ cd QChatGPT
2. 安装依赖
```bash
pip3 install requests yiri-mirai openai colorlog func_timeout dulwich Pillow
pip3 install requests yiri-mirai openai colorlog func_timeout dulwich Pillow nakuru-project-idk
```
3. 运行一次主程序,生成配置文件
@@ -246,9 +293,9 @@ python3 main.py
### 更多
欢迎提交新的插件
[插件列表](https://github.com/stars/RockChinQ/lists/qchatgpt-%E6%8F%92%E4%BB%B6)欢迎提出issue以提交新的插件
- [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)
- [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%E3%80%81ChatGPT%E7%BD%91%E9%A1%B5%E7%89%88%E3%80%81ChatGPT-API%E5%8C%BA%E5%88%AB)
- [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、屏蔽词规则等
@@ -257,6 +304,8 @@ python3 main.py
- [RockChinQ/WaitYiYan](https://github.com/RockChinQ/WaitYiYan) - 实时获取百度`文心一言`等待列表人数
- [chordfish-k/QChartGPT_Emoticon_Plugin](https://github.com/chordfish-k/QChartGPT_Emoticon_Plugin) - 使机器人根据回复内容发送表情包
- [oliverkirk-sudo/ChatPoeBot](https://github.com/oliverkirk-sudo/ChatPoeBot) - 接入[Poe](https://poe.com/)上的机器人
- [lieyanqzu/WeatherPlugin](https://github.com/lieyanqzu/WeatherPlugin) - 天气查询插件
- [SysStatPlugin](https://github.com/RockChinQ/SysStatPlugin) - 查看系统状态
</details>
## 😘致谢
@@ -269,6 +318,6 @@ python3 main.py
以及所有[贡献者](https://github.com/RockChinQ/QChatGPT/graphs/contributors)和其他为本项目提供支持的朋友们。
<!-- ## 👍赞赏
## 👍赞赏
<img alt="赞赏码" src="res/mm_reward_qrcode_1672840549070.png" width="400" height="400"/> -->
<img alt="赞赏码" src="res/mm_reward_qrcode_1672840549070.png" width="400" height="400"/>

215
README_en.md Normal file
View File

@@ -0,0 +1,215 @@
# QChatGPT🤖
<p align="center">
<img src="res/social.png" alt="QChatGPT" width="640" />
</p>
English | [简体中文](README.md)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/QChatGPT?style=flat-square)](https://github.com/RockChinQ/QChatGPT/releases/latest)
![Wakapi Count](https://wakapi.dev/api/badge/RockChinQ/interval:any/project:QChatGPT)
- Refer to [Wiki](https://github.com/RockChinQ/QChatGPT/wiki) to get further information.
- Official QQ group: 656285629
- Community QQ group: 362515018
- QQ channel robot: [QQChannelChatGPT](https://github.com/Soulter/QQChannelChatGPT)
- Any contribution is welcome, please refer to [CONTRIBUTING.md](CONTRIBUTING.md)
## 🍺List of supported models
<details>
<summary>Details</summary>
### Chat
- OpenAI GPT-3.5 (ChatGPT API), default model
- OpenAI GPT-3, supported natively, switch to it in `config.py`
- OpenAI GPT-4, supported natively, qualification for internal testing required, switch to it in `config.py`
- ChatGPT website edition (GPT-3.5), see [revLibs plugin](https://github.com/RockChinQ/revLibs)
- ChatGPT website edition (GPT-4), ChatGPT plus subscription required, see [revLibs plugin](https://github.com/RockChinQ/revLibs)
- New Bing, see [revLibs plugin](https://github.com/RockChinQ/revLibs)
- HuggingChat, see [revLibs plugin](https://github.com/RockChinQ/revLibs), English only
### Story
- NovelAI API, see [QCPNovelAi plugin](https://github.com/dominoar/QCPNovelAi)
### Image
- OpenAI DALL·E, supported natively, see [Wiki(cn)](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E5%8A%9F%E8%83%BD%E7%82%B9%E5%88%97%E4%B8%BE)
- NovelAI API, see [QCPNovelAi plugin](https://github.com/dominoar/QCPNovelAi)
### Voice
- TTS+VITS, see [QChatPlugins](https://github.com/dominoar/QChatPlugins)
- Plachta/VITS-Umamusume-voice-synthesizer, see [chat_voice plugin](https://github.com/oliverkirk-sudo/chat_voice)
</details>
Install this [plugin](https://github.com/RockChinQ/Switcher) to switch between different models.
## ✅Function Points
<details>
<summary>Details</summary>
- ✅Sensitive word filtering, avoid being banned
- ✅Multiple responding rules, including regular expression matching
- ✅Multiple api-key management, automatic switching when exceeding
- ✅Support for customizing the preset prompt text
- ✅Chat, story, image, voice, etc. models are supported
- ✅Support for hot reloading and hot updating
- ✅Support for plugin loading
- ✅Blacklist mechanism for private chat and group chat
- ✅Excellent long message processing strategy
- ✅Reply rate limitation
- ✅Support for network proxy
- ✅Support for customizing the output format
</details>
More details, see [Wiki(cn)](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E5%8A%9F%E8%83%BD%E7%82%B9%E5%88%97%E4%B8%BE)
## 🔩Deployment
**If you encounter any problems during deployment, please search in the issue of [QChatGPT](https://github.com/RockChinQ/QChatGPT/issues) or [qcg-installer](https://github.com/RockChinQ/qcg-installer/issues) first.**
### - Register OpenAI account
> If you want to use a model other than OpenAI (such as New Bing), you can skip this step and directly refer to following steps, and then configure it according to the relevant plugin documentation.
To register OpenAI account, please refer to the following articles(in Chinese):
> [国内注册ChatGPT的方法(100%可用)](https://www.pythonthree.com/register-openai-chatgpt/)
> [手把手教你如何注册ChatGPT超级详细](https://guxiaobei.com/51461)
Check your api-key in [personal center](https://beta.openai.com/account/api-keys) after registration, and then follow the following steps to deploy.
### - Deploy Automatically
<details>
<summary>Details</summary>
#### Docker
See [this document(cn)](res/docs/docker_deploy.md)
Contributed by [@mikumifa](https://github.com/mikumifa)
#### Installer
Use [this installer](https://github.com/RockChinQ/qcg-installer) to deploy.
- The installer currently only supports some platforms, please refer to the repository document for details, and manually deploy for other platforms
</details>
### - Deploy Manually
<details>
<summary>Manually deployment supports any platforms</summary>
- Python 3.9.x or higher
#### 配置QQ登录框架
Currently supports mirai and go-cqhttp, configure either one
<details>
<summary>mirai</summary>
Follow [this tutorial(cn)](https://yiri-mirai.wybxc.cc/tutorials/01/configuration) to configure Mirai and YiriMirai.
After starting mirai-console, use the `login` command to log in to the QQ account, and keep the mirai-console running.
</details>
<details>
<summary>go-cqhttp</summary>
1. Follow [this tutorial(cn)](https://github.com/RockChinQ/QChatGPT/wiki/go-cqhttp%E9%85%8D%E7%BD%AE) to configure go-cqhttp.
2. Start go-cqhttp, make sure it is logged in and running.
</details>
#### Configure QChatGPT
1. Clone the repository
```bash
git clone https://github.com/RockChinQ/QChatGPT
cd QChatGPT
```
2. Install dependencies
```bash
pip3 install requests yiri-mirai openai colorlog func_timeout dulwich Pillow nakuru-project-idk
```
3. Generate `config.py`
```bash
python3 main.py
```
4. Edit `config.py`
5. Run
```bash
python3 main.py
```
Any problems, please refer to the issues page.
</details>
## 🚀Usage
**After deployment, please read: [Commands(cn)](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E6%9C%BA%E5%99%A8%E4%BA%BA%E6%8C%87%E4%BB%A4)**
**For more details, please refer to the [Wiki(cn)](https://github.com/RockChinQ/QChatGPT/wiki/%E5%8A%9F%E8%83%BD%E4%BD%BF%E7%94%A8#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F)**
## 🧩Plugin Ecosystem
Plugin [usage](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E4%BD%BF%E7%94%A8) and [development](https://github.com/RockChinQ/QChatGPT/wiki/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91) are supported.
<details>
<summary>List of plugins (cn)</summary>
### Examples
`tests/plugin_examples`目录下,将其整个目录复制到`plugins`目录下即可使用
- `cmdcn` - 主程序指令中文形式
- `hello_plugin` - 在收到消息`hello`时回复相应消息
- `urlikethisijustsix` - 收到冒犯性消息时回复相应消息
### More Plugins
欢迎提交新的插件
- [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) - 支持通过指令切换使用的模型
- [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 故事叙述与绘画
- [oliverkirk-sudo/chat_voice](https://github.com/oliverkirk-sudo/chat_voice) - 文字转语音输出使用HuggingFace上的[VITS-Umamusume-voice-synthesizer模型](https://huggingface.co/spaces/Plachta/VITS-Umamusume-voice-synthesizer)
- [RockChinQ/WaitYiYan](https://github.com/RockChinQ/WaitYiYan) - 实时获取百度`文心一言`等待列表人数
- [chordfish-k/QChartGPT_Emoticon_Plugin](https://github.com/chordfish-k/QChartGPT_Emoticon_Plugin) - 使机器人根据回复内容发送表情包
- [oliverkirk-sudo/ChatPoeBot](https://github.com/oliverkirk-sudo/ChatPoeBot) - 接入[Poe](https://poe.com/)上的机器人
- [lieyanqzu/WeatherPlugin](https://github.com/lieyanqzu/WeatherPlugin) - 天气查询插件
</details>
## 😘Thanks
- [@the-lazy-me](https://github.com/the-lazy-me) video tutorial creator
- [@mikumifa](https://github.com/mikumifa) Docker deployment
- [@dominoar](https://github.com/dominoar) Plugin development
- [@万神的星空](https://github.com/qq255204159) Packages publisher
- [@ljcduo](https://github.com/ljcduo) GPT-4 API internal test account
And all [contributors](https://github.com/RockChinQ/QChatGPT/graphs/contributors) and other friends who support this project.
<!-- ## 👍赞赏
<img alt="赞赏码" src="res/mm_reward_qrcode_1672840549070.png" width="400" height="400"/> -->

View File

@@ -1,7 +1,13 @@
# 配置文件: 注释里标[必需]的参数必须修改, 其他参数根据需要修改, 但请勿删除
import logging
# [必需] Mirai的配置
# 消息处理协议适配器
# 目前支持以下适配器:
# - "yirimirai": mirai的通信框架YiriMirai框架适配器, 请同时填写下方mirai_http_api_config
# - "nakuru": go-cqhttp通信框架请同时填写下方nakuru_config
msg_source_adapter = "yirimirai"
# [必需(与nakuru二选一取决于msg_source_adapter)] Mirai的配置
# 请到配置mirai的步骤中的教程查看每个字段的信息
# adapter: 选择适配器目前支持HTTPAdapter和WebSocketAdapter
# host: 运行mirai的主机地址
@@ -18,6 +24,15 @@ mirai_http_api_config = {
"qq": 1234567890
}
# [必需(与mirai二选一取决于msg_source_adapter)]
# 使用nakuru-project框架连接go-cqhttp的配置
nakuru_config = {
"host": "localhost", # go-cqhttp的地址
"port": 6700, # go-cqhttp的正向websocket端口
"http_port": 5700, # go-cqhttp的正向http端口
"token": "" # 若在go-cqhttp的config.yml设置了access_token, 则填写此处
}
# [必需] OpenAI的配置
# api_key: OpenAI的API Key
# http_proxy: 请求OpenAI时使用的代理None为不使用https和socks5暂不能使用
@@ -108,13 +123,36 @@ preset_mode = "normal"
# 注意:由消息前缀(prefix)匹配的消息中将会删除此前缀,正则表达式(regexp)匹配的消息不会删除匹配的部分
# 前缀匹配优先级高于正则表达式匹配
# 正则表达式简明教程https://www.runoob.com/regexp/regexp-tutorial.html
#
# 支持针对不同群设置不同的响应规则,例如:
# response_rules = {
# "default": {
# "at": True,
# "prefix": ["/ai", "!ai", "ai", "ai"],
# "regexp": [],
# "random_rate": 0.0,
# },
# "12345678": {
# "at": False,
# "prefix": ["/ai", "!ai", "ai", "ai"],
# "regexp": [],
# "random_rate": 0.0,
# },
# }
#
# 以上设置将会在群号为12345678的群中关闭at响应
# 未单独设置的群将使用default规则
response_rules = {
"at": True, # 是否响应at机器人的消息
"prefix": ["/ai", "!ai", "ai", "ai"],
"regexp": [], # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办"
"random_rate": 0.0, # 随机响应概率0.0-1.00.0为不随机响应1.0为响应所有消息, 仅在前几项判断不通过时生效
"default": {
"at": True, # 是否响应at机器人的消息
"prefix": ["/ai", "!ai", "ai", "ai"],
"regexp": [], # "为什么.*", "怎么?样.*", "怎么.*", "如何.*", "[Hh]ow to.*", "[Ww]hy not.*", "[Ww]hat is.*", ".*怎么办", ".*咋办"
"random_rate": 0.0, # 随机响应概率0.0-1.00.0为不随机响应1.0为响应所有消息, 仅在前几项判断不通过时生效
},
}
# 消息忽略规则
# 适用于私聊及群聊
# 符合此规则的消息将不会被响应
@@ -157,16 +195,22 @@ encourage_sponsor_at_start = True
# 注意较大的prompt_submit_length会导致OpenAI账户额度消耗更快
prompt_submit_length = 2048
# 是否在token超限报错时自动重置会话
# 可在tips.py中编辑提示语
auto_reset = True
# OpenAI补全API的参数
# 请在下方填写模型,程序自动选择接口
# 现已支持的模型有:
#
# 'gpt-4'
# 'gpt-4-0314'
# 'gpt-4-0613'
# 'gpt-4-32k'
# 'gpt-4-32k-0314'
# 'gpt-4-32k-0613'
# 'gpt-3.5-turbo'
# 'gpt-3.5-turbo-0301'
# 'gpt-3.5-turbo-16k'
# 'gpt-3.5-turbo-0613'
# 'gpt-3.5-turbo-16k-0613'
# 'text-davinci-003'
# 'text-davinci-002'
# 'code-davinci-002'
@@ -203,6 +247,14 @@ process_message_timeout = 30
# 回复消息时是否显示[GPT]前缀
show_prefix = False
# 回复前的强制延迟时间,降低机器人被腾讯风控概率
# *此机制对命令和消息、私聊及群聊均生效
# 每次处理时从以下的范围取一个随机秒数,
# 当此次消息处理时间低于此秒数时,将会强制延迟至此秒数
# 例如:[1.5, 3]则每次处理时会随机取一个1.5-3秒的随机数若处理时间低于此随机数则强制延迟至此随机秒数
# 若您不需要此功能请将force_delay_range设置为[0, 0]
force_delay_range = [1.5, 3]
# 应用长消息处理策略的阈值
# 当回复消息长度超过此值时,将使用长消息处理策略
blob_message_threshold = 256
@@ -212,6 +264,12 @@ blob_message_threshold = 256
# - "forward": 将长消息转换为转发消息组件发送
blob_message_strategy = "forward"
# 允许等待
# 同一会话内,是否等待上一条消息处理完成后再处理下一条消息
# 若设置为False若上一条未处理完时收到了新消息将会丢弃新消息
# 丢弃消息时的提示信息可以在tips.py中修改
wait_last_done = True
# 文字转图片时使用的字体文件路径
# 当策略为"image"时生效
# 若在Windows系统下程序会自动使用Windows自带的微软雅黑字体
@@ -233,26 +291,49 @@ hide_exce_info_to_user = False
sys_pool_num = 8
# 执行管理员请求和指令的线程池并行线程数量,一般和管理员数量相等
admin_pool_num = 2
admin_pool_num = 4
# 执行用户请求和指令的线程池并行线程数量
# 如需要更高的并发,可以增大该值
user_pool_num = 6
user_pool_num = 8
# 每个会话的过期时间,单位为秒
# 默认值20分钟
session_expire_time = 60 * 20
session_expire_time = 1200
# 会话限速
# 单会话内每分钟可进行的对话次数
# 若不需要限速,可以设置为一个很大的值
# 默认值60次基本上不会触发限速
rate_limitation = 60
#
# 若要设置针对某特定群的限速,请使用如下格式:
# {
# "group_<群号>": 60,
# "default": 60,
# }
# 若要设置针对某特定用户私聊的限速,请使用如下格式:
# {
# "person_<用户QQ>": 60,
# "default": 60,
# }
# 同时设置多个群和私聊的限速,示例:
# {
# "group_12345678": 60,
# "group_87654321": 60,
# "person_234567890": 60,
# "person_345678901": 60,
# "default": 60,
# }
#
# 注意: 未指定的都使用default的限速值default不可删除
rate_limitation = {
"default": 60,
}
# 会话限速策略
# - "wait": 每次对话获取到回复时,等待一定时间再发送回复,保证其不会超过限速均值
# - "drop": 此分钟内,若对话次数超过限速次数,则丢弃之后的对话,每自然分钟重置
rate_limit_strategy = "wait"
rate_limit_strategy = "drop"
# 是否在启动时进行依赖库更新
upgrade_dependencies = True

355
main.py
View File

@@ -11,6 +11,8 @@ import traceback
sys.path.append(".")
from pkg.utils.log import init_runtime_log_file, reset_logging
try:
import colorlog
except ImportError:
@@ -18,6 +20,7 @@ except ImportError:
import pkg.utils.pkgmgr as pkgmgr
try:
pkgmgr.install_requirements("requirements.txt")
pkgmgr.install_upgrade("websockets")
import colorlog
except ImportError:
print("依赖不满足,请查看 https://github.com/RockChinQ/qcg-installer/issues/15")
@@ -30,13 +33,9 @@ from urllib3.exceptions import InsecureRequestWarning
import pkg.utils.context
log_colors_config = {
'DEBUG': 'green', # cyan white
'INFO': 'white',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'cyan',
}
# 是否使用override.json覆盖配置
# 仅在启动时提供 --override 或 -r 参数时生效
use_override = False
def init_db():
@@ -48,62 +47,25 @@ def init_db():
def ensure_dependencies():
import pkg.utils.pkgmgr as pkgmgr
pkgmgr.run_pip(["install", "openai", "Pillow", "--upgrade",
"-i", "https://pypi.douban.com/simple/",
"--trusted-host", "pypi.douban.com"])
pkgmgr.run_pip(["install", "openai", "Pillow", "nakuru-project-idk", "--upgrade",
"-i", "https://pypi.tuna.tsinghua.edu.cn/simple",
"--trusted-host", "pypi.tuna.tsinghua.edu.cn"])
known_exception_caught = False
log_file_name = "qchatgpt.log"
def init_runtime_log_file():
"""为此次运行生成日志文件
格式: qchatgpt-yyyy-MM-dd-HH-mm-ss.log
"""
global log_file_name
# 检查logs目录是否存在
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())
def reset_logging():
global log_file_name
def override_config():
import config
if pkg.utils.context.context['logger_handler'] is not None:
logging.getLogger().removeHandler(pkg.utils.context.context['logger_handler'])
for handler in logging.getLogger().handlers:
logging.getLogger().removeHandler(handler)
logging.basicConfig(level=config.logging_level, # 设置日志输出格式
filename=log_file_name, # log日志输出的文件位置和文件名
format="[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : %(message)s",
# 日志输出的格式
# -8表示占位符让输出左对齐输出长度都为8位
datefmt="%Y-%m-%d %H:%M:%S" # 时间输出的格式
)
sh = logging.StreamHandler()
sh.setLevel(config.logging_level)
sh.setFormatter(colorlog.ColoredFormatter(
fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : "
"%(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
log_colors=log_colors_config
))
logging.getLogger().addHandler(sh)
pkg.utils.context.context['logger_handler'] = sh
return sh
# 检查override.json覆盖
if os.path.exists("override.json") and use_override:
override_json = json.load(open("override.json", "r", encoding="utf-8"))
for key in override_json:
if hasattr(config, key):
setattr(config, key, override_json[key])
logging.info("覆写配置[{}]为[{}]".format(key, override_json[key]))
else:
logging.error("无法覆写配置[{}]为[{}]该配置不存在请检查override.json是否正确".format(key, override_json[key]))
# 临时函数用于加载config和上下文未来统一放在config类
@@ -123,17 +85,10 @@ def load_config():
logging.warning("配置文件不完整您可以依据config-template.py检查config.py")
# 检查override.json覆盖
if os.path.exists("override.json"):
override_json = json.load(open("override.json", "r", encoding="utf-8"))
for key in override_json:
if hasattr(config, key):
setattr(config, key, override_json[key])
logging.info("覆写配置[{}]为[{}]".format(key, override_json[key]))
else:
logging.error("无法覆写配置[{}]为[{}]该配置不存在请检查override.json是否正确".format(key, override_json[key]))
override_config()
if not is_integrity:
logging.warning("以上配置已被设为默认值将在3秒后继续启动... ")
logging.warning("以上不存在的配置已被设为默认值将在3秒后继续启动... ")
time.sleep(3)
# 存进上下文
@@ -179,126 +134,139 @@ def start(first_time_init=False):
known_exception_caught = False
try:
sh = reset_logging()
pkg.utils.context.context['logger_handler'] = sh
# 检查是否设置了管理员
if not (hasattr(config, 'admin_qq') and config.admin_qq != 0):
# logging.warning("未设置管理员QQ,管理员权限指令及运行告警将无法使用,如需设置请修改config.py中的admin_qq字段")
while True:
try:
config.admin_qq = int(input("未设置管理员QQ,管理员权限指令及运行告警将无法使用,请输入管理员QQ号: "))
# 写入到文件
# 读取文件
config_file_str = ""
with open("config.py", "r", encoding="utf-8") as f:
config_file_str = f.read()
# 替换
config_file_str = config_file_str.replace("admin_qq = 0", "admin_qq = " + str(config.admin_qq))
# 写入
with open("config.py", "w", encoding="utf-8") as f:
f.write(config_file_str)
print("管理员QQ已设置如需修改请修改config.py中的admin_qq字段")
time.sleep(4)
break
except ValueError:
print("请输入数字")
import pkg.openai.manager
import pkg.database.manager
import pkg.openai.session
import pkg.qqbot.manager
import pkg.openai.dprompt
import pkg.qqbot.cmds.aamgr
try:
pkg.openai.dprompt.register_all()
pkg.qqbot.cmds.aamgr.register_all()
pkg.qqbot.cmds.aamgr.apply_privileges()
sh = reset_logging()
pkg.utils.context.context['logger_handler'] = sh
# 检查是否设置了管理员
if not (hasattr(config, 'admin_qq') and config.admin_qq != 0):
# logging.warning("未设置管理员QQ,管理员权限指令及运行告警将无法使用,如需设置请修改config.py中的admin_qq字段")
while True:
try:
config.admin_qq = int(input("未设置管理员QQ,管理员权限指令及运行告警将无法使用,请输入管理员QQ号: "))
# 写入到文件
# 读取文件
config_file_str = ""
with open("config.py", "r", encoding="utf-8") as f:
config_file_str = f.read()
# 替换
config_file_str = config_file_str.replace("admin_qq = 0", "admin_qq = " + str(config.admin_qq))
# 写入
with open("config.py", "w", encoding="utf-8") as f:
f.write(config_file_str)
print("管理员QQ已设置如需修改请修改config.py中的admin_qq字段")
time.sleep(4)
break
except ValueError:
print("请输入数字")
import pkg.openai.manager
import pkg.database.manager
import pkg.openai.session
import pkg.qqbot.manager
import pkg.openai.dprompt
import pkg.qqbot.cmds.aamgr
try:
pkg.openai.dprompt.register_all()
pkg.qqbot.cmds.aamgr.register_all()
pkg.qqbot.cmds.aamgr.apply_privileges()
except Exception as e:
logging.error(e)
traceback.print_exc()
# 配置openai api_base
if "reverse_proxy" in config.openai_config and config.openai_config["reverse_proxy"] is not None:
import openai
openai.api_base = config.openai_config["reverse_proxy"]
# 主启动流程
database = pkg.database.manager.DatabaseManager()
database.initialize_database()
openai_interact = pkg.openai.manager.OpenAIInteract(config.openai_config['api_key'])
# 加载所有未超时的session
pkg.openai.session.load_sessions()
# 初始化qq机器人
qqbot = pkg.qqbot.manager.QQBotManager(first_time_init=first_time_init)
# 加载插件
import pkg.plugin.host
pkg.plugin.host.load_plugins()
pkg.plugin.host.initialize_plugins()
if first_time_init: # 不是热重载之后的启动,则启动新的bot线程
import mirai.exceptions
def run_bot_wrapper():
global known_exception_caught
try:
logging.info("使用账号: {}".format(qqbot.bot_account_id))
qqbot.adapter.run_sync()
except TypeError as e:
if str(e).__contains__("argument 'debug'"):
logging.error(
"连接bot失败:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/82".format(e))
known_exception_caught = True
elif str(e).__contains__("As of 3.10, the *loop*"):
logging.error(
"Websockets版本过低:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/5".format(e))
known_exception_caught = True
except websockets.exceptions.InvalidStatus as e:
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
known_exception_caught = True
except mirai.exceptions.NetworkError as e:
logging.error("连接mirai-api-http失败:{}, 请检查是否已按照文档启动mirai".format(e))
known_exception_caught = True
except Exception as e:
if str(e).__contains__("404"):
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
known_exception_caught = True
elif str(e).__contains__("signal only works in main thread"):
logging.error(
"hypercorn异常:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/86".format(
e))
known_exception_caught = True
elif str(e).__contains__("did not receive a valid HTTP"):
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
else:
import traceback
traceback.print_exc()
logging.error(
"捕捉到未知异常:{}, 请前往 https://github.com/RockChinQ/QChatGPT/issues 查找或提issue".format(e))
known_exception_caught = True
raise e
finally:
time.sleep(12)
threading.Thread(
target=run_bot_wrapper
).start()
except Exception as e:
logging.error(e)
traceback.print_exc()
# 配置openai api_base
if "reverse_proxy" in config.openai_config and config.openai_config["reverse_proxy"] is not None:
import openai
openai.api_base = config.openai_config["reverse_proxy"]
# 主启动流程
database = pkg.database.manager.DatabaseManager()
database.initialize_database()
openai_interact = pkg.openai.manager.OpenAIInteract(config.openai_config['api_key'])
# 加载所有未超时的session
pkg.openai.session.load_sessions()
# 初始化qq机器人
qqbot = pkg.qqbot.manager.QQBotManager(mirai_http_api_config=config.mirai_http_api_config,
timeout=config.process_message_timeout, retry=config.retry_times,
first_time_init=first_time_init)
# 加载插件
import pkg.plugin.host
pkg.plugin.host.load_plugins()
pkg.plugin.host.initialize_plugins()
if first_time_init: # 不是热重载之后的启动,则启动新的bot线程
import mirai.exceptions
def run_bot_wrapper():
global known_exception_caught
try:
qqbot.bot.run()
except TypeError as e:
if str(e).__contains__("argument 'debug'"):
logging.error(
"连接bot失败:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/82".format(e))
known_exception_caught = True
elif str(e).__contains__("As of 3.10, the *loop*"):
logging.error(
"Websockets版本过低:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/5".format(e))
known_exception_caught = True
except websockets.exceptions.InvalidStatus as e:
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
known_exception_caught = True
except mirai.exceptions.NetworkError as e:
logging.error("连接mirai-api-http失败:{}, 请检查是否已按照文档启动mirai".format(e))
known_exception_caught = True
except Exception as e:
if str(e).__contains__("404"):
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
known_exception_caught = True
elif str(e).__contains__("signal only works in main thread"):
logging.error(
"hypercorn异常:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/86".format(
e))
known_exception_caught = True
elif str(e).__contains__("did not receive a valid HTTP"):
logging.error(
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
e))
else:
logging.error(
"捕捉到未知异常:{}, 请前往 https://github.com/RockChinQ/QChatGPT/issues 查找或提issue".format(e))
known_exception_caught = True
raise e
finally:
time.sleep(12)
threading.Thread(
target=run_bot_wrapper
).start()
if isinstance(e, KeyboardInterrupt):
logging.info("程序被用户中止")
sys.exit(0)
elif isinstance(e, SyntaxError):
logging.error("配置文件存在语法错误,请检查配置文件:\n1. 是否存在中文符号\n2. 是否已按照文件中的说明填写正确")
sys.exit(1)
else:
logging.error("初始化失败:{}".format(e))
sys.exit(1)
finally:
# 判断若是Windows输出选择模式可能会暂停程序的警告
if os.name == 'nt':
@@ -309,9 +277,14 @@ def start(first_time_init=False):
if first_time_init:
if not known_exception_caught:
logging.info("QQ: {}, MAH: {}".format(config.mirai_http_api_config['qq'], config.mirai_http_api_config['host']+":"+str(config.mirai_http_api_config['port'])))
logging.info('程序启动完成,如长时间未显示 ”成功登录到账号xxxxx“ ,并且不回复消息,请查看 '
'https://github.com/RockChinQ/QChatGPT/issues/37')
import config
if config.msg_source_adapter == "yirimirai":
logging.info("QQ: {}, MAH: {}".format(config.mirai_http_api_config['qq'], config.mirai_http_api_config['host']+":"+str(config.mirai_http_api_config['port'])))
logging.critical('程序启动完成,如长时间未显示 "成功登录到账号xxxxx" ,并且不回复消息,解决办法(请勿到群里问): '
'https://github.com/RockChinQ/QChatGPT/issues/37')
elif config.msg_source_adapter == 'nakuru':
logging.info("host: {}, port: {}, http_port: {}".format(config.nakuru_config['host'], config.nakuru_config['port'], config.nakuru_config['http_port']))
logging.critical('程序启动完成,如长时间未显示 "Protocol: connected" ,并且不回复消息,请检查config.py中的nakuru_config是否正确')
else:
sys.exit(1)
else:
@@ -348,8 +321,9 @@ def start(first_time_init=False):
try:
import pkg.utils.announcement as announcement
new_announcement = announcement.fetch_new()
if new_announcement != "":
logging.critical("[公告] {}".format(new_announcement))
if len(new_announcement) > 0:
for announcement in new_announcement:
logging.critical("[公告]<{}> {}".format(announcement['time'], announcement['content']))
except Exception as e:
logging.warning("获取公告失败:{}".format(e))
@@ -413,6 +387,11 @@ def check_file():
def main():
global use_override
# 检查是否携带了 --override 或 -r 参数
if '--override' in sys.argv or '-r' in sys.argv:
use_override = True
# 初始化相关文件
check_file()

View File

@@ -1,5 +1,6 @@
{
"comment": "这是override.json支持的字段全集, 关于override.json机制, 请查看https://github.com/RockChinQ/QChatGPT/pull/271",
"msg_source_adapter": "yirimirai",
"mirai_http_api_config": {
"adapter": "WebSocketAdapter",
"host": "localhost",
@@ -7,6 +8,12 @@
"verifyKey": "yirimirai",
"qq": 1234567890
},
"nakuru_config": {
"host": "localhost",
"port": 6700,
"http_port": 5700,
"token": ""
},
"openai_config": {
"api_key": {
"default": "openai_api_key"
@@ -20,15 +27,17 @@
},
"preset_mode": "normal",
"response_rules": {
"at": true,
"prefix": [
"/ai",
"!ai",
"ai",
"ai"
],
"regexp": [],
"random_rate": 0.0
"default": {
"at": true,
"prefix": [
"/ai",
"!ai",
"ai",
"ai"
],
"regexp": [],
"random_rate": 0.0
}
},
"ignore_rules": {
"prefix": [
@@ -44,6 +53,7 @@
"inappropriate_message_tips": "[百度云]请珍惜机器人,当前返回内容不合规",
"encourage_sponsor_at_start": true,
"prompt_submit_length": 2048,
"auto_reset": true,
"completion_api_params": {
"model": "gpt-3.5-turbo",
"temperature": 0.9,
@@ -58,17 +68,24 @@
"include_image_description": true,
"process_message_timeout": 30,
"show_prefix": false,
"force_delay_range": [
1.5,
3
],
"blob_message_threshold": 256,
"blob_message_strategy": "forward",
"wait_last_done": true,
"font_path": "",
"retry_times": 3,
"hide_exce_info_to_user": false,
"sys_pool_num": 8,
"admin_pool_num": 2,
"user_pool_num": 6,
"admin_pool_num": 4,
"user_pool_num": 8,
"session_expire_time": 1200,
"rate_limitation": 60,
"rate_limit_strategy": "wait",
"rate_limitation": {
"default": 60
},
"rate_limit_strategy": "drop",
"upgrade_dependencies": true,
"report_usage": true,
"logging_level": 20

View File

@@ -46,7 +46,7 @@ class DataGatherer:
config = pkg.utils.context.get_config()
if not config.report_usage:
return
res = requests.get("http://reports.rockchin.top:18989/usage?service_name=qchatgpt.{}&version={}&count={}".format(subservice_name, self.version_str, count))
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:

View File

@@ -20,11 +20,14 @@ COMPLETION_MODELS = {
CHAT_COMPLETION_MODELS = {
'gpt-3.5-turbo',
'gpt-3.5-turbo-0301',
'gpt-3.5-turbo-16k',
'gpt-3.5-turbo-0613',
'gpt-3.5-turbo-16k-0613',
# 'gpt-3.5-turbo-0301',
'gpt-4',
'gpt-4-0314',
'gpt-4-0613',
'gpt-4-32k',
'gpt-4-32k-0314'
'gpt-4-32k-0613'
}
EDIT_MODELS = {
@@ -60,7 +63,7 @@ class ModelRequest:
"""异步请求"""
try:
self.ret:dict = await self.request_fun(**kwargs)
self.ret: dict = await self.request_fun(**kwargs)
self.request_ready = True
except aiE.APIConnectionError as e:
self.error_info = "{}\n请检查网络连接或代理是否正常".format(e)
@@ -69,7 +72,7 @@ class ModelRequest:
self.error_info = "{}\n该错误可能是由于http_proxy格式设置错误引起的"
except Exception as e:
self.error_info = "{}\n由于请求异常产生的未知错误,请查看日志".format(e)
raise Exception(self.error_info)
raise type(e)(self.error_info)
def request(self, **kwargs):
"""向接口发起请求"""

View File

@@ -83,7 +83,7 @@ def load_sessions():
# 获取指定名称的session如果不存在则创建一个新的
def get_session(session_name: str):
def get_session(session_name: str) -> 'Session':
global sessions
if session_name not in sessions:
sessions[session_name] = Session(session_name)

View File

@@ -8,9 +8,11 @@ import sys
import shutil
import traceback
import pkg.utils.updater as updater
import pkg.utils.context as context
import pkg.plugin.switch as switch
import pkg.plugin.settings as settings
import pkg.qqbot.adapter as msadapter
from mirai import Mirai
@@ -78,7 +80,7 @@ def walk_plugin_path(module, prefix='', path_prefix=''):
__current_module_path__ = "plugins/"+path_prefix + item.name + '.py'
importlib.import_module(module.__name__ + '.' + item.name)
logging.info('加载模块: plugins/{} 成功'.format(path_prefix + item.name + '.py'))
logging.debug('加载模块: plugins/{} 成功'.format(path_prefix + item.name + '.py'))
except:
logging.error('加载模块: plugins/{} 失败: {}'.format(path_prefix + item.name + '.py', sys.exc_info()))
traceback.print_exc()
@@ -156,8 +158,8 @@ def install_plugin(repo_url: str):
import pkg.utils.pkgmgr
pkg.utils.pkgmgr.install_requirements("plugins/"+repo_url.split(".git")[0].split("/")[-1]+"/requirements.txt")
import main
main.reset_logging()
import pkg.utils.log as log
log.reset_logging()
def uninstall_plugin(plugin_name: str) -> str:
@@ -176,6 +178,43 @@ def uninstall_plugin(plugin_name: str) -> str:
return "plugins/"+plugin_path
def update_plugin(plugin_name: str):
"""更新插件"""
# 检查是否有远程地址记录
target_plugin_dir = "plugins/" + __plugins__[plugin_name]['path'].replace("\\", "/").split("plugins/")[1].split("/")[0]
remote_url = updater.get_remote_url(target_plugin_dir)
if remote_url == "https://github.com/RockChinQ/QChatGPT" or remote_url == "https://gitee.com/RockChin/QChatGPT" \
or remote_url == "" or remote_url is None or remote_url == "http://github.com/RockChinQ/QChatGPT" or remote_url == "http://gitee.com/RockChin/QChatGPT":
raise Exception("插件没有远程地址记录,无法更新")
# 把远程clone到temp/plugins/update/插件名
logging.info("克隆插件储存库: {}".format(remote_url))
from dulwich import porcelain
clone_target_dir = "temp/plugins/update/"+target_plugin_dir.split("/")[-1]+"/"
if os.path.exists(clone_target_dir):
shutil.rmtree(clone_target_dir)
if not os.path.exists(clone_target_dir):
os.makedirs(clone_target_dir)
repo = porcelain.clone(remote_url, clone_target_dir, checkout=True)
# 检查此目录是否包含requirements.txt
if os.path.exists(clone_target_dir+"requirements.txt"):
logging.info("检测到requirements.txt正在安装依赖")
import pkg.utils.pkgmgr
pkg.utils.pkgmgr.install_requirements(clone_target_dir+"requirements.txt")
import pkg.utils.log as log
log.reset_logging()
# 将temp/plugins/update/插件名 覆盖到 plugins/插件名
shutil.rmtree(target_plugin_dir)
shutil.copytree(clone_target_dir, target_plugin_dir)
class EventContext:
"""事件上下文"""
eid = 0
@@ -276,13 +315,17 @@ class PluginHost:
"""获取机器人对象"""
return context.get_qqbot_manager().bot
def get_bot_adapter(self) -> msadapter.MessageSourceAdapter:
"""获取消息源适配器"""
return context.get_qqbot_manager().adapter
def send_person_message(self, person, message):
"""发送私聊消息"""
asyncio.run(self.get_bot().send_friend_message(person, message))
self.get_bot_adapter().send_message("person", person, message)
def send_group_message(self, group, message):
"""发送群消息"""
asyncio.run(self.get_bot().send_group_message(group, message))
self.get_bot_adapter().send_message("group", group, message)
def notify_admin(self, message):
"""通知管理员"""
@@ -339,3 +382,6 @@ class PluginHost:
event_context.__return_value__))
return event_context
if __name__ == "__main__":
pass

136
pkg/qqbot/adapter.py Normal file
View File

@@ -0,0 +1,136 @@
# MessageSource的适配器
import typing
import mirai
class MessageSourceAdapter:
def __init__(self, config: dict):
pass
def send_message(
self,
target_type: str,
target_id: str,
message: mirai.MessageChain
):
"""发送消息
Args:
target_type (str): 目标类型,`person`或`group`
target_id (str): 目标ID
message (mirai.MessageChain): YiriMirai库的消息链
"""
raise NotImplementedError
def reply_message(
self,
message_source: mirai.MessageEvent,
message: mirai.MessageChain,
quote_origin: bool = False
):
"""回复消息
Args:
message_source (mirai.MessageEvent): YiriMirai消息源事件
message (mirai.MessageChain): YiriMirai库的消息链
quote_origin (bool, optional): 是否引用原消息. Defaults to False.
"""
raise NotImplementedError
def is_muted(self, group_id: int) -> bool:
"""获取账号是否在指定群被禁言"""
raise NotImplementedError
def register_listener(
self,
event_type: typing.Type[mirai.Event],
callback: typing.Callable[[mirai.Event], None]
):
"""注册事件监听器
Args:
event_type (typing.Type[mirai.Event]): YiriMirai事件类型
callback (typing.Callable[[mirai.Event], None]): 回调函数接收一个参数为YiriMirai事件
"""
raise NotImplementedError
def unregister_listener(
self,
event_type: typing.Type[mirai.Event],
callback: typing.Callable[[mirai.Event], None]
):
"""注销事件监听器
Args:
event_type (typing.Type[mirai.Event]): YiriMirai事件类型
callback (typing.Callable[[mirai.Event], None]): 回调函数接收一个参数为YiriMirai事件
"""
raise NotImplementedError
def run_sync(self):
"""以阻塞的方式运行适配器"""
raise NotImplementedError
def kill(self) -> bool:
"""关闭适配器
Returns:
bool: 是否成功关闭热重载时若此函数返回False则不会重载MessageSource底层
"""
raise NotImplementedError
class MessageConverter:
"""消息链转换器基类"""
@staticmethod
def yiri2target(message_chain: mirai.MessageChain):
"""将YiriMirai消息链转换为目标消息链
Args:
message_chain (mirai.MessageChain): YiriMirai消息链
Returns:
typing.Any: 目标消息链
"""
raise NotImplementedError
@staticmethod
def target2yiri(message_chain: typing.Any) -> mirai.MessageChain:
"""将目标消息链转换为YiriMirai消息链
Args:
message_chain (typing.Any): 目标消息链
Returns:
mirai.MessageChain: YiriMirai消息链
"""
raise NotImplementedError
class EventConverter:
"""事件转换器基类"""
@staticmethod
def yiri2target(event: typing.Type[mirai.Event]):
"""将YiriMirai事件转换为目标事件
Args:
event (typing.Type[mirai.Event]): YiriMirai事件
Returns:
typing.Any: 目标事件
"""
raise NotImplementedError
@staticmethod
def target2yiri(event: typing.Any) -> mirai.Event:
"""将目标事件的调用参数转换为YiriMirai的事件参数对象
Args:
event (typing.Any): 目标事件
Returns:
typing.Type[mirai.Event]: YiriMirai事件
"""
raise NotImplementedError

View File

@@ -260,8 +260,8 @@ def execute(context: Context) -> list:
while True:
try:
logging.debug('执行指令: {}'.format(path))
node = __command_list__[path]
logging.debug('执行指令: {}'.format(path))
# 检查权限
if ctx.privilege < node['privilege']:

View File

@@ -10,7 +10,7 @@ import pkg.utils.updater as updater
parent=None,
name="plugin",
description="插件管理",
usage="!plugin\n!plugin get <插件仓库地址>\!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>",
usage="!plugin\n!plugin get <插件仓库地址>\n!plugin update\n!plugin del <插件名>\n!plugin on <插件名>\n!plugin off <插件名>",
aliases=[],
privilege=2
)
@@ -97,37 +97,34 @@ class PluginUpdateCommand(AbstractCommandNode):
plugin_list = plugin_host.__plugins__
reply = []
def closure():
try:
import pkg.utils.context
updated = []
for key in plugin_list:
plugin = plugin_list[key]
if updater.is_repo("/".join(plugin['path'].split('/')[:-1])):
success = updater.pull_latest("/".join(plugin['path'].split('/')[:-1]))
if success:
updated.append(plugin['name'])
# 检查是否有requirements.txt
pkg.utils.context.get_qqbot_manager().notify_admin("正在安装依赖...")
for key in plugin_list:
plugin = plugin_list[key]
if os.path.exists("/".join(plugin['path'].split('/')[:-1])+"/requirements.txt"):
logging.info("{}检测到requirements.txt安装依赖".format(plugin['name']))
import pkg.utils.pkgmgr
pkg.utils.pkgmgr.install_requirements("/".join(plugin['path'].split('/')[:-1])+"/requirements.txt")
if len(ctx.crt_params) > 0:
def closure():
try:
import pkg.utils.context
import main
main.reset_logging()
updated = []
pkg.utils.context.get_qqbot_manager().notify_admin("已更新插件: {}".format(", ".join(updated)))
except Exception as e:
logging.error("插件更新失败:{}".format(e))
pkg.utils.context.get_qqbot_manager().notify_admin("插件更新失败:{} 请尝试手动更新插件".format(e))
if ctx.crt_params[0] == 'all':
for key in plugin_list:
plugin_host.update_plugin(key)
updated.append(key)
else:
if ctx.crt_params[0] in plugin_list:
plugin_host.update_plugin(ctx.crt_params[0])
updated.append(ctx.crt_params[0])
else:
raise Exception("未找到插件: {}".format(ctx.crt_params[0]))
pkg.utils.context.get_qqbot_manager().notify_admin("已更新插件: {}, 请发送 !reload 重载插件".format(", ".join(updated)))
except Exception as e:
logging.error("插件更新失败:{}".format(e))
pkg.utils.context.get_qqbot_manager().notify_admin("插件更新失败:{} 请尝试手动更新插件".format(e))
threading.Thread(target=closure).start()
reply = ["[bot]正在更新所有插件,请勿重复发起..."]
reply = ["[bot]正在更新插件,请勿重复发起..."]
threading.Thread(target=closure).start()
else:
reply = ["[bot]请指定要更新的插件, 或使用 !plugin update all 更新所有插件"]
return True, reply

View File

@@ -0,0 +1,100 @@
from ..aamgr import AbstractCommandNode, Context
import json
def config_operation(cmd, params):
reply = []
import pkg.utils.context
config = pkg.utils.context.get_config()
reply_str = ""
if len(params) == 0:
reply = ["[bot]err:请输入!cmd cfg查看使用方法"]
else:
cfg_name = params[0]
if cfg_name == 'all':
reply_str = "[bot]所有配置项:\n\n"
for cfg in dir(config):
if not cfg.startswith('__') and not cfg == 'logging':
# 根据配置项类型进行格式化如果是字典则转换为json并格式化
if isinstance(getattr(config, cfg), str):
reply_str += "{}: \"{}\"\n".format(cfg, getattr(config, cfg))
elif isinstance(getattr(config, cfg), dict):
# 不进行unicode转义并格式化
reply_str += "{}: {}\n".format(cfg,
json.dumps(getattr(config, cfg),
ensure_ascii=False, indent=4))
else:
reply_str += "{}: {}\n".format(cfg, getattr(config, cfg))
reply = [reply_str]
else:
cfg_entry_path = cfg_name.split('.')
try:
if len(params) == 1:
cfg_entry = getattr(config, cfg_entry_path[0])
if len(cfg_entry_path) > 1:
for i in range(1, len(cfg_entry_path)):
cfg_entry = cfg_entry[cfg_entry_path[i]]
if isinstance(cfg_entry, str):
reply_str = "[bot]配置项{}: \"{}\"\n".format(cfg_name, cfg_entry)
elif isinstance(cfg_entry, dict):
reply_str = "[bot]配置项{}: {}\n".format(cfg_name,
json.dumps(cfg_entry,
ensure_ascii=False, indent=4))
else:
reply_str = "[bot]配置项{}: {}\n".format(cfg_name, cfg_entry)
reply = [reply_str]
else:
cfg_value = " ".join(params[1:])
# 类型转换如果是json则转换为字典
# if cfg_value == 'true':
# cfg_value = True
# elif cfg_value == 'false':
# cfg_value = False
# elif cfg_value.isdigit():
# cfg_value = int(cfg_value)
# elif cfg_value.startswith('{') and cfg_value.endswith('}'):
# cfg_value = json.loads(cfg_value)
# else:
# try:
# cfg_value = float(cfg_value)
# except ValueError:
# pass
cfg_value = eval(cfg_value)
cfg_entry = getattr(config, cfg_entry_path[0])
if len(cfg_entry_path) > 1:
for i in range(1, len(cfg_entry_path) - 1):
cfg_entry = cfg_entry[cfg_entry_path[i]]
if isinstance(cfg_entry[cfg_entry_path[-1]], type(cfg_value)):
cfg_entry[cfg_entry_path[-1]] = cfg_value
reply = ["[bot]配置项{}修改成功".format(cfg_name)]
else:
reply = ["[bot]err:配置项{}类型不匹配".format(cfg_name)]
else:
setattr(config, cfg_entry_path[0], cfg_value)
reply = ["[bot]配置项{}修改成功".format(cfg_name)]
except AttributeError:
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
except ValueError:
reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
# else:
# reply = ["[bot]err:未找到配置项 {}".format(cfg_name)]
return reply
@AbstractCommandNode.register(
parent=None,
name="cfg",
description="配置项管理",
usage="!cfg <配置项> [配置值]\n!cfg all",
aliases=[],
privilege=2
)
class CfgCommand(AbstractCommandNode):
@classmethod
def process(cls, ctx: Context) -> tuple[bool, list]:
return True, config_operation(ctx.command, ctx.params)

View File

@@ -3,9 +3,9 @@ import json
import os
import threading
import mirai.models.bus
from mirai import At, GroupMessage, MessageEvent, Mirai, StrangerMessage, WebSocketAdapter, HTTPAdapter, \
FriendMessage, Image
FriendMessage, Image, MessageChain, Plain
from func_timeout import func_set_timeout
import pkg.openai.session
@@ -21,12 +21,22 @@ import pkg.plugin.host as plugin_host
import pkg.plugin.models as plugin_models
import tips as tips_custom
import pkg.qqbot.adapter as msadapter
# 检查消息是否符合泛响应匹配机制
def check_response_rule(text: str):
def check_response_rule(group_id:int, text: str):
config = pkg.utils.context.get_config()
rules = config.response_rules
# 检查是否有特定规则
if 'prefix' not in config.response_rules:
if str(group_id) in config.response_rules:
rules = config.response_rules[str(group_id)]
else:
rules = config.response_rules['default']
# 检查前缀匹配
if 'prefix' in rules:
for rule in rules['prefix']:
@@ -44,19 +54,39 @@ def check_response_rule(text: str):
return False, ""
def response_at():
def response_at(group_id: int):
config = pkg.utils.context.get_config()
if 'at' not in config.response_rules:
use_response_rule = config.response_rules
# 检查是否有特定规则
if 'prefix' not in config.response_rules:
if str(group_id) in config.response_rules:
use_response_rule = config.response_rules[str(group_id)]
else:
use_response_rule = config.response_rules['default']
if 'at' not in use_response_rule:
return True
return config.response_rules['at']
return use_response_rule['at']
def random_responding():
def random_responding(group_id):
config = pkg.utils.context.get_config()
if 'random_rate' in config.response_rules:
use_response_rule = config.response_rules
# 检查是否有特定规则
if 'prefix' not in config.response_rules:
if str(group_id) in config.response_rules:
use_response_rule = config.response_rules[str(group_id)]
else:
use_response_rule = config.response_rules['default']
if 'random_rate' in use_response_rule:
import random
return random.random() < config.response_rules['random_rate']
return random.random() < use_response_rule['random_rate']
return False
@@ -64,57 +94,52 @@ def random_responding():
class QQBotManager:
retry = 3
bot: Mirai = None
adapter: msadapter.MessageSourceAdapter = None
bot_account_id: int = 0
reply_filter = None
enable_banlist = False
enable_private = True
enable_group = True
ban_person = []
ban_group = []
def __init__(self, mirai_http_api_config: dict, timeout: int = 60, retry: int = 3, first_time_init=True):
self.timeout = timeout
self.retry = retry
def __init__(self, first_time_init=True):
import config
# 加载禁用列表
if os.path.exists("banlist.py"):
import banlist
self.enable_banlist = banlist.enable
self.ban_person = banlist.person
self.ban_group = banlist.group
logging.info("加载禁用列表: person: {}, group: {}".format(self.ban_person, self.ban_group))
config = pkg.utils.context.get_config()
if os.path.exists("sensitive.json") \
and config.sensitive_word_filter is not None \
and config.sensitive_word_filter:
with open("sensitive.json", "r", encoding="utf-8") as f:
sensitive_json = json.load(f)
self.reply_filter = pkg.qqbot.filter.ReplyFilter(
sensitive_words=sensitive_json['words'],
mask=sensitive_json['mask'] if 'mask' in sensitive_json else '*',
mask_word=sensitive_json['mask_word'] if 'mask_word' in sensitive_json else ''
)
else:
self.reply_filter = pkg.qqbot.filter.ReplyFilter([])
self.timeout = config.process_message_timeout
self.retry = config.retry_times
# 由于YiriMirai的bot对象是单例的且shutdown方法暂时无法使用
# 故只在第一次初始化时创建bot对象重载之后使用原bot对象
# 因此bot的配置不支持热重载
if first_time_init:
self.first_time_init(mirai_http_api_config)
logging.info("Use adapter:" + config.msg_source_adapter)
if config.msg_source_adapter == 'yirimirai':
from pkg.qqbot.sources.yirimirai import YiriMiraiAdapter
mirai_http_api_config = config.mirai_http_api_config
self.bot_account_id = config.mirai_http_api_config['qq']
self.adapter = YiriMiraiAdapter(mirai_http_api_config)
elif config.msg_source_adapter == 'nakuru':
from pkg.qqbot.sources.nakuru import NakuruProjectAdapter
self.adapter = NakuruProjectAdapter(config.nakuru_config)
self.bot_account_id = self.adapter.bot_account_id
else:
self.bot = pkg.utils.context.get_qqbot_manager().bot
self.adapter = pkg.utils.context.get_qqbot_manager().adapter
self.bot_account_id = pkg.utils.context.get_qqbot_manager().bot_account_id
pkg.utils.context.set_qqbot_manager(self)
# 注册诸事件
# Caution: 注册新的事件处理器之后请务必在unsubscribe_all中编写相应的取消订阅代码
@self.bot.on(FriendMessage)
async def on_friend_message(event: FriendMessage):
def friend_message_handler(event: FriendMessage):
def on_friend_message(event: FriendMessage):
def friend_message_handler():
# 触发事件
args = {
"launcher_type": "person",
@@ -131,13 +156,15 @@ class QQBotManager:
pkg.utils.context.get_thread_ctl().submit_user_task(
friend_message_handler,
event
)
self.adapter.register_listener(
FriendMessage,
on_friend_message
)
@self.bot.on(StrangerMessage)
async def on_stranger_message(event: StrangerMessage):
def on_stranger_message(event: StrangerMessage):
def stranger_message_handler(event: StrangerMessage):
def stranger_message_handler():
# 触发事件
args = {
"launcher_type": "person",
@@ -154,11 +181,15 @@ class QQBotManager:
pkg.utils.context.get_thread_ctl().submit_user_task(
stranger_message_handler,
event
)
# nakuru不区分好友和陌生人故仅为yirimirai注册陌生人事件
if config.msg_source_adapter == 'yirimirai':
self.adapter.register_listener(
StrangerMessage,
on_stranger_message
)
@self.bot.on(GroupMessage)
async def on_group_message(event: GroupMessage):
def on_group_message(event: GroupMessage):
def group_message_handler(event: GroupMessage):
# 触发事件
@@ -179,62 +210,76 @@ class QQBotManager:
group_message_handler,
event
)
self.adapter.register_listener(
GroupMessage,
on_group_message
)
def unsubscribe_all():
"""取消所有订阅
用于在热重载流程中卸载所有事件处理器
"""
assert isinstance(self.bot, Mirai)
bus = self.bot.bus
assert isinstance(bus, mirai.models.bus.ModelEventBus)
bus.unsubscribe(FriendMessage, on_friend_message)
bus.unsubscribe(StrangerMessage, on_stranger_message)
bus.unsubscribe(GroupMessage, on_group_message)
import config
self.adapter.unregister_listener(
FriendMessage,
on_friend_message
)
if config.msg_source_adapter == 'yirimirai':
self.adapter.unregister_listener(
StrangerMessage,
on_stranger_message
)
self.adapter.unregister_listener(
GroupMessage,
on_group_message
)
self.unsubscribe_all = unsubscribe_all
def go(self, func, *args, **kwargs):
self.pool.submit(func, *args, **kwargs)
# 加载禁用列表
if os.path.exists("banlist.py"):
import banlist
self.enable_banlist = banlist.enable
self.ban_person = banlist.person
self.ban_group = banlist.group
logging.info("加载禁用列表: person: {}, group: {}".format(self.ban_person, self.ban_group))
def first_time_init(self, mirai_http_api_config: dict):
"""热重载后不再运行此函数"""
if 'adapter' not in mirai_http_api_config or mirai_http_api_config['adapter'] == "WebSocketAdapter":
bot = Mirai(
qq=mirai_http_api_config['qq'],
adapter=WebSocketAdapter(
verify_key=mirai_http_api_config['verifyKey'],
host=mirai_http_api_config['host'],
port=mirai_http_api_config['port']
)
)
elif mirai_http_api_config['adapter'] == "HTTPAdapter":
bot = Mirai(
qq=mirai_http_api_config['qq'],
adapter=HTTPAdapter(
verify_key=mirai_http_api_config['verifyKey'],
host=mirai_http_api_config['host'],
port=mirai_http_api_config['port']
)
)
if hasattr(banlist, "enable_private"):
self.enable_private = banlist.enable_private
if hasattr(banlist, "enable_group"):
self.enable_group = banlist.enable_group
config = pkg.utils.context.get_config()
if os.path.exists("sensitive.json") \
and config.sensitive_word_filter is not None \
and config.sensitive_word_filter:
with open("sensitive.json", "r", encoding="utf-8") as f:
sensitive_json = json.load(f)
self.reply_filter = pkg.qqbot.filter.ReplyFilter(
sensitive_words=sensitive_json['words'],
mask=sensitive_json['mask'] if 'mask' in sensitive_json else '*',
mask_word=sensitive_json['mask_word'] if 'mask_word' in sensitive_json else ''
)
else:
raise Exception("未知的适配器类型")
self.bot = bot
self.reply_filter = pkg.qqbot.filter.ReplyFilter([])
def send(self, event, msg, check_quote=True):
config = pkg.utils.context.get_config()
asyncio.run(
self.bot.send(event, msg, quote=True if config.quote_origin and check_quote else False))
self.adapter.reply_message(
event,
msg,
quote_origin=True if config.quote_origin and check_quote else False
)
# 私聊消息处理
def on_person_message(self, event: MessageEvent):
import config
reply = ''
if event.sender.id == self.bot.qq:
if not self.enable_private:
logging.debug("已在banlist.py中禁用所有私聊")
elif event.sender.id == self.bot_account_id:
pass
else:
if Image in event.message_chain:
@@ -274,11 +319,10 @@ class QQBotManager:
def on_group_message(self, event: GroupMessage):
import config
reply = ''
def process(text=None) -> str:
replys = ""
if At(self.bot.qq) in event.message_chain:
event.message_chain.remove(At(self.bot.qq))
if At(self.bot_account_id) in event.message_chain:
event.message_chain.remove(At(self.bot_account_id))
# 超时则重试,重试超过次数则放弃
failed = 0
@@ -309,19 +353,21 @@ class QQBotManager:
return replys
if Image in event.message_chain:
if not self.enable_group:
logging.debug("已在banlist.py中禁用所有群聊")
elif Image in event.message_chain:
pass
else:
if At(self.bot.qq) in event.message_chain and response_at():
if At(self.bot_account_id) in event.message_chain and response_at(event.group.id):
# 直接调用
reply = process()
else:
check, result = check_response_rule(str(event.message_chain).strip())
check, result = check_response_rule(event.group.id, str(event.message_chain).strip())
if check:
reply = process(result.strip())
# 检查是否随机响应
elif random_responding():
elif random_responding(event.group.id):
logging.info("随机响应group_{}消息".format(event.group.id))
reply = process()
@@ -334,22 +380,33 @@ class QQBotManager:
if config.admin_qq != 0 and config.admin_qq != []:
logging.info("通知管理员:{}".format(message))
if type(config.admin_qq) == int:
send_task = self.bot.send_friend_message(config.admin_qq, "[bot]{}".format(message))
threading.Thread(target=asyncio.run, args=(send_task,)).start()
self.adapter.send_message(
"person",
config.admin_qq,
MessageChain([Plain("[bot]{}".format(message))])
)
else:
for adm in config.admin_qq:
send_task = self.bot.send_friend_message(adm, "[bot]{}".format(message))
threading.Thread(target=asyncio.run, args=(send_task,)).start()
self.adapter.send_message(
"person",
adm,
MessageChain([Plain("[bot]{}".format(message))])
)
def notify_admin_message_chain(self, message):
config = pkg.utils.context.get_config()
if config.admin_qq != 0 and config.admin_qq != []:
logging.info("通知管理员:{}".format(message))
if type(config.admin_qq) == int:
send_task = self.bot.send_friend_message(config.admin_qq, message)
threading.Thread(target=asyncio.run, args=(send_task,)).start()
self.adapter.send_message(
"person",
config.admin_qq,
message
)
else:
for adm in config.admin_qq:
send_task = self.bot.send_friend_message(adm, message)
threading.Thread(target=asyncio.run, args=(send_task,)).start()
self.adapter.send_message(
"person",
adm,
message
)

View File

@@ -114,8 +114,12 @@ def process_normal_message(text_message: str, mgr, config, launcher_type: str,
reply = handle_exception("{}会话调用API失败:{}".format(session_name, e),
"[bot]err:RateLimitError,请重试或联系作者,或等待修复")
except openai.error.InvalidRequestError as e:
reply = handle_exception("{}API调用参数错误:{}\n".format(
session_name, e), "[bot]err:API调用参数错误请联系管理员或等待修复")
if config.auto_reset and "This model's maximum context length is" in str(e):
session.reset()
reply = [tips_custom.session_auto_reset_message]
else:
reply = handle_exception("{}API调用参数错误:{}\n".format(
session_name, e), "[bot]err:API调用参数错误请联系管理员或等待修复")
except openai.error.ServiceUnavailableError as e:
reply = handle_exception("{}API调用服务不可用:{}".format(session_name, e), "[bot]err:API调用服务不可用请重试或联系管理员或等待修复")
except Exception as e:

View File

@@ -59,29 +59,33 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
logging.info("根据忽略规则忽略消息: {}".format(text_message))
return []
import config
if not config.wait_last_done and session_name in processing:
return MessageChain([Plain(tips_custom.message_drop_tip)])
# 检查是否被禁言
if launcher_type == 'group':
result = mgr.bot.member_info(target=launcher_id, member_id=mgr.bot.qq).get()
result = asyncio.run(result)
if result.mute_time_remaining > 0:
logging.info("机器人被禁言,跳过消息处理(group_{},剩余{}s)".format(launcher_id,
result.mute_time_remaining))
is_muted = mgr.adapter.is_muted(launcher_id)
if is_muted:
logging.info("机器人被禁言,跳过消息处理(group_{})".format(launcher_id))
return reply
import config
if config.income_msg_check:
if mgr.reply_filter.is_illegal(text_message):
return MessageChain(Plain("[bot] 你的提问中有不合适的内容, 请更换措辞~"))
return MessageChain(Plain("[bot] 消息中存在不合适的内容, 请更换措辞"))
pkg.openai.session.get_session(session_name).acquire_response_lock()
text_message = text_message.strip()
# 为强制消息延迟计时
start_time = time.time()
# 处理消息
try:
if session_name in processing:
pkg.openai.session.get_session(session_name).release_response_lock()
return MessageChain([Plain("[bot]err:正在处理中,请稍后再试")])
config = pkg.utils.context.get_config()
@@ -168,4 +172,23 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
finally:
pkg.openai.session.get_session(session_name).release_response_lock()
# 检查延迟时间
if config.force_delay_range[1] == 0:
delay_time = 0
else:
import random
# 从延迟范围中随机取一个值(浮点)
rdm = random.uniform(config.force_delay_range[0], config.force_delay_range[1])
spent = time.time() - start_time
# 如果花费时间小于延迟时间,则延迟
delay_time = rdm - spent if rdm - spent > 0 else 0
# 延迟
if delay_time > 0:
logging.info("[风控] 强制延迟{:.2f}秒(如需关闭请到config.py修改force_delay_range字段)".format(delay_time))
time.sleep(delay_time)
return MessageChain(reply)

View File

@@ -10,6 +10,20 @@ __crt_minute_usage__ = {}
__timer_thr__: threading.Thread = None
def get_limitation(session_name: str) -> int:
"""获取会话的限制次数"""
import config
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
def add_usage(session_name: str):
"""增加会话的对话次数"""
global __crt_minute_usage__
@@ -56,9 +70,7 @@ def get_rest_wait_time(session_name: str, spent: float) -> float:
"""获取会话此回合的剩余等待时间"""
global __crt_minute_usage__
import config
min_seconds_per_round = 60.0 / config.rate_limitation
min_seconds_per_round = 60.0 / get_limitation(session_name)
if session_name in __crt_minute_usage__:
return max(0, min_seconds_per_round - spent)
@@ -70,10 +82,8 @@ def is_reach_limit(session_name: str) -> bool:
"""判断会话是否超过限制"""
global __crt_minute_usage__
import config
if session_name in __crt_minute_usage__:
return __crt_minute_usage__[session_name] >= config.rate_limitation
return __crt_minute_usage__[session_name] >= get_limitation(session_name)
else:
return False

View File

321
pkg/qqbot/sources/nakuru.py Normal file
View File

@@ -0,0 +1,321 @@
import mirai
from ..adapter import MessageSourceAdapter, MessageConverter, EventConverter
import nakuru
import nakuru.entities.components as nkc
import asyncio
import typing
import traceback
import logging
import json
from pkg.qqbot.blob import Forward, ForwardMessageNode, ForwardMessageDiaplay
class NakuruProjectMessageConverter(MessageConverter):
"""消息转换器"""
@staticmethod
def yiri2target(message_chain: mirai.MessageChain) -> list:
msg_list = []
if type(message_chain) is mirai.MessageChain:
msg_list = message_chain.__root__
elif type(message_chain) is list:
msg_list = message_chain
else:
raise Exception("Unknown message type: " + str(message_chain) + str(type(message_chain)))
nakuru_msg_list = []
# 遍历并转换
for component in msg_list:
if type(component) is mirai.Plain:
nakuru_msg_list.append(nkc.Plain(component.text, False))
elif type(component) is mirai.Image:
if component.url is not None:
nakuru_msg_list.append(nkc.Image.fromURL(component.url))
elif component.base64 is not None:
nakuru_msg_list.append(nkc.Image.fromBase64(component.base64))
elif component.path is not None:
nakuru_msg_list.append(nkc.Image.fromFileSystem(component.path))
elif type(component) is mirai.Face:
nakuru_msg_list.append(nkc.Face(id=component.face_id))
elif type(component) is mirai.At:
nakuru_msg_list.append(nkc.At(qq=component.target))
elif type(component) is mirai.AtAll:
nakuru_msg_list.append(nkc.AtAll())
elif type(component) is mirai.Voice:
if component.url is not None:
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 Forward:
# 转发消息
yiri_forward_node_list = component.node_list
nakuru_forward_node_list = []
# 遍历并转换
for yiri_forward_node in yiri_forward_node_list:
try:
content_list = NakuruProjectMessageConverter.yiri2target(yiri_forward_node.message_chain)
nakuru_forward_node = nkc.Node(
name=yiri_forward_node.sender_name,
uin=yiri_forward_node.sender_id,
time=int(yiri_forward_node.time.timestamp()) if yiri_forward_node.time is not None else None,
content=content_list
)
nakuru_forward_node_list.append(nakuru_forward_node)
except Exception as e:
import traceback
traceback.print_exc()
nakuru_msg_list.append(nakuru_forward_node_list)
else:
nakuru_msg_list.append(nkc.Plain(str(component)))
return nakuru_msg_list
@staticmethod
def target2yiri(message_chain: typing.Any, message_id: int = -1) -> mirai.MessageChain:
"""将Yiri的消息链转换为YiriMirai的消息链"""
assert type(message_chain) is list
yiri_msg_list = []
import datetime
# 添加Source组件以标记message_id等信息
yiri_msg_list.append(mirai.models.message.Source(id=message_id, time=datetime.datetime.now()))
for component in message_chain:
if type(component) is nkc.Plain:
yiri_msg_list.append(mirai.Plain(text=component.text))
elif type(component) is nkc.Image:
yiri_msg_list.append(mirai.Image(url=component.url))
elif type(component) is nkc.Face:
yiri_msg_list.append(mirai.Face(face_id=component.id))
elif type(component) is nkc.At:
yiri_msg_list.append(mirai.At(target=component.qq))
elif type(component) is nkc.AtAll:
yiri_msg_list.append(mirai.AtAll())
else:
pass
logging.debug("转换后的消息链: " + str(yiri_msg_list))
chain = mirai.MessageChain(yiri_msg_list)
return chain
class NakuruProjectEventConverter(EventConverter):
"""事件转换器"""
@staticmethod
def yiri2target(event: typing.Type[mirai.Event]):
if event is mirai.GroupMessage:
return nakuru.GroupMessage
elif event is mirai.FriendMessage:
return nakuru.FriendMessage
else:
raise Exception("未支持转换的事件类型: " + str(event))
@staticmethod
def target2yiri(event: typing.Any) -> mirai.Event:
yiri_chain = NakuruProjectMessageConverter.target2yiri(event.message, event.message_id)
if type(event) is nakuru.FriendMessage: # 私聊消息事件
return mirai.FriendMessage(
sender=mirai.models.entities.Friend(
id=event.sender.user_id,
nickname=event.sender.nickname,
remark=event.sender.nickname
),
message_chain=yiri_chain,
time=event.time
)
elif type(event) is nakuru.GroupMessage: # 群聊消息事件
permission = "MEMBER"
if event.sender.role == "admin":
permission = "ADMINISTRATOR"
elif event.sender.role == "owner":
permission = "OWNER"
import mirai.models.entities as entities
return mirai.GroupMessage(
sender=mirai.models.entities.GroupMember(
id=event.sender.user_id,
member_name=event.sender.nickname,
permission=permission,
group=mirai.models.entities.Group(
id=event.group_id,
name=event.sender.nickname,
permission=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
)
else:
raise Exception("未支持转换的事件类型: " + str(event))
class NakuruProjectAdapter(MessageSourceAdapter):
"""nakuru-project适配器"""
bot: nakuru.CQHTTP
bot_account_id: int
message_converter: NakuruProjectMessageConverter = NakuruProjectMessageConverter()
event_converter: NakuruProjectEventConverter = NakuruProjectEventConverter()
listener_list: list[dict]
def __init__(self, cfg: dict):
"""初始化nakuru-project的对象"""
self.bot = nakuru.CQHTTP(**cfg)
self.listener_list = []
# nakuru库有bug这个接口没法带access_token会失败
# 所以目前自行发请求
import config
import requests
resp = requests.get(
url="http://{}:{}/get_login_info".format(config.nakuru_config['host'], config.nakuru_config['http_port']),
headers={
'Authorization': "Bearer " + config.nakuru_config['token'] if 'token' in config.nakuru_config else ""
},
timeout=5
)
if resp.status_code == 403:
logging.error("go-cqhttp拒绝访问请检查config.py中nakuru_config的token是否与go-cqhttp设置的access-token匹配")
raise Exception("go-cqhttp拒绝访问请检查config.py中nakuru_config的token是否与go-cqhttp设置的access-token匹配")
self.bot_account_id = int(resp.json()['data']['user_id'])
def send_message(
self,
target_type: str,
target_id: str,
message: typing.Union[mirai.MessageChain, list],
converted: bool = False
):
task = None
converted_msg = self.message_converter.yiri2target(message) if not converted else message
# 检查是否有转发消息
has_forward = False
for msg in converted_msg:
if type(msg) is list: # 转发消息,仅回复此消息组件
has_forward = True
converted_msg = msg
break
if has_forward:
if target_type == "group":
task = self.bot.sendGroupForwardMessage(int(target_id), converted_msg)
elif target_type == "person":
task = self.bot.sendPrivateForwardMessage(int(target_id), converted_msg)
else:
raise Exception("Unknown target type: " + target_type)
else:
if target_type == "group":
task = self.bot.sendGroupMessage(int(target_id), converted_msg)
elif target_type == "person":
task = self.bot.sendFriendMessage(int(target_id), converted_msg)
else:
raise Exception("Unknown target type: " + target_type)
asyncio.run(task)
def reply_message(
self,
message_source: mirai.MessageEvent,
message: mirai.MessageChain,
quote_origin: bool = False
):
message = self.message_converter.yiri2target(message)
if quote_origin:
# 在前方添加引用组件
message.insert(0, nkc.Reply(
id=message_source.message_chain.message_id,
)
)
if type(message_source) is mirai.GroupMessage:
self.send_message(
"group",
message_source.sender.group.id,
message,
converted=True
)
elif type(message_source) is mirai.FriendMessage:
self.send_message(
"person",
message_source.sender.id,
message,
converted=True
)
else:
raise Exception("Unknown message source type: " + str(type(message_source)))
def is_muted(self, group_id: int) -> bool:
import time
# 检查是否被禁言
group_member_info = asyncio.run(self.bot.getGroupMemberInfo(group_id, self.bot_account_id))
return group_member_info.shut_up_timestamp > int(time.time())
def register_listener(
self,
event_type: typing.Type[mirai.Event],
callback: typing.Callable[[mirai.Event], None]
):
try:
logging.debug("注册监听器: " + str(event_type) + " -> " + str(callback))
# 包装函数
async def listener_wrapper(app: nakuru.CQHTTP, source: self.event_converter.yiri2target(event_type)):
callback(self.event_converter.target2yiri(source))
# 将包装函数和原函数的对应关系存入列表
self.listener_list.append(
{
"event_type": event_type,
"callable": callback,
"wrapper": listener_wrapper,
}
)
# 注册监听器
self.bot.receiver(self.event_converter.yiri2target(event_type).__name__)(listener_wrapper)
logging.debug("注册完成")
except Exception as e:
traceback.print_exc()
raise e
def unregister_listener(
self,
event_type: typing.Type[mirai.Event],
callback: typing.Callable[[mirai.Event], None]
):
nakuru_event_name = self.event_converter.yiri2target(event_type).__name__
new_event_list = []
# 从本对象的监听器列表中查找并删除
target_wrapper = None
for listener in self.listener_list:
if listener["event_type"] == event_type and listener["callable"] == callback:
target_wrapper = listener["wrapper"]
self.listener_list.remove(listener)
break
if target_wrapper is None:
raise Exception("未找到对应的监听器")
for func in self.bot.event[nakuru_event_name]:
if func.callable != target_wrapper:
new_event_list.append(func)
self.bot.event[nakuru_event_name] = new_event_list
def run_sync(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
self.bot.run()
def kill(self) -> bool:
return False

View File

@@ -0,0 +1,116 @@
from ..adapter import MessageSourceAdapter
import mirai
import mirai.models.bus
import asyncio
import typing
class YiriMiraiAdapter(MessageSourceAdapter):
"""YiriMirai适配器"""
bot: mirai.Mirai
def __init__(self, config: dict):
"""初始化YiriMirai的对象"""
if 'adapter' not in config or \
config['adapter'] == 'WebSocketAdapter':
self.bot = mirai.Mirai(
qq=config['qq'],
adapter=mirai.WebSocketAdapter(
host=config['host'],
port=config['port'],
verify_key=config['verifyKey']
)
)
elif config['adapter'] == 'HTTPAdapter':
self.bot = mirai.Mirai(
qq=config['qq'],
adapter=mirai.HTTPAdapter(
host=config['host'],
port=config['port'],
verify_key=config['verifyKey']
)
)
else:
raise Exception('Unknown adapter for YiriMirai: ' + config['adapter'])
def send_message(
self,
target_type: str,
target_id: str,
message: mirai.MessageChain
):
"""发送消息
Args:
target_type (str): 目标类型,`person`或`group`
target_id (str): 目标ID
message (mirai.MessageChain): YiriMirai库的消息链
"""
task = None
if target_type == 'person':
task = self.bot.send_friend_message(int(target_id), message)
elif target_type == 'group':
task = self.bot.send_group_message(int(target_id), message)
else:
raise Exception('Unknown target type: ' + target_type)
asyncio.run(task)
def reply_message(
self,
message_source: mirai.MessageEvent,
message: mirai.MessageChain,
quote_origin: bool = False
):
"""回复消息
Args:
message_source (mirai.MessageEvent): YiriMirai消息源事件
message (mirai.MessageChain): YiriMirai库的消息链
quote_origin (bool, optional): 是否引用原消息. Defaults to False.
"""
asyncio.run(self.bot.send(message_source, message, quote_origin))
def is_muted(self, group_id: int) -> bool:
result = self.bot.member_info(target=group_id, member_id=self.bot.qq).get()
result = asyncio.run(result)
if result.mute_time_remaining > 0:
return True
return False
def register_listener(
self,
event_type: typing.Type[mirai.Event],
callback: typing.Callable[[mirai.Event], None]
):
"""注册事件监听器
Args:
event_type (typing.Type[mirai.Event]): YiriMirai事件类型
callback (typing.Callable[[mirai.Event], None]): 回调函数接收一个参数为YiriMirai事件
"""
self.bot.on(event_type)(callback)
def unregister_listener(
self,
event_type: typing.Type[mirai.Event],
callback: typing.Callable[[mirai.Event], None]
):
"""注销事件监听器
Args:
event_type (typing.Type[mirai.Event]): YiriMirai事件类型
callback (typing.Callable[[mirai.Event], None]): 回调函数接收一个参数为YiriMirai事件
"""
assert isinstance(self.bot, mirai.Mirai)
bus = self.bot.bus
assert isinstance(bus, mirai.models.bus.ModelEventBus)
bus.unsubscribe(event_type, callback)
def run_sync(self):
self.bot.run()
def kill(self) -> bool:
return False

View File

@@ -1,47 +1,68 @@
import base64
import os
import json
import requests
import pkg.utils.network as network
def read_latest() -> str:
def read_latest() -> list:
import pkg.utils.network as network
resp = requests.get(
url="https://api.github.com/repos/RockChinQ/QChatGPT/contents/res/announcement",
url="https://api.github.com/repos/RockChinQ/QChatGPT/contents/res/announcement.json",
proxies=network.wrapper_proxies()
)
obj_json = resp.json()
b64_content = obj_json["content"]
# 解码
content = base64.b64decode(b64_content).decode("utf-8")
return content
return json.loads(content)
def read_saved() -> str:
def read_saved() -> list:
# 已保存的在res/announcement_saved
# 检查是否存在
if not os.path.exists("res/announcement_saved"):
with open("res/announcement_saved", "w", encoding="utf-8") as f:
f.write("")
if not os.path.exists("res/announcement_saved.json"):
with open("res/announcement_saved.json", "w", encoding="utf-8") as f:
f.write("[]")
with open("res/announcement_saved", "r", encoding="utf-8") as f:
with open("res/announcement_saved.json", "r", encoding="utf-8") as f:
content = f.read()
return content
return json.loads(content)
def write_saved(content: str):
def write_saved(content: list):
# 已保存的在res/announcement_saved
with open("res/announcement_saved", "w", encoding="utf-8") as f:
f.write(content)
with open("res/announcement_saved.json", "w", encoding="utf-8") as f:
f.write(json.dumps(content, indent=4, ensure_ascii=False))
def fetch_new() -> str:
def fetch_new() -> list:
latest = read_latest()
saved = read_saved()
if latest.replace(saved, "").strip() == "":
return ""
else:
write_saved(latest)
return latest.replace(saved, "").strip()
to_show: list = []
for item in latest:
# 遍历saved检查是否有相同id的公告
for saved_item in saved:
if saved_item["id"] == item["id"]:
break
else:
# 没有相同id的公告
to_show.append(item)
write_saved(latest)
return to_show
if __name__ == '__main__':
resp = requests.get(
url="https://api.github.com/repos/RockChinQ/QChatGPT/contents/res/announcement.json",
)
obj_json = resp.json()
b64_content = obj_json["content"]
# 解码
content = base64.b64decode(b64_content).decode("utf-8")
print(json.dumps(json.loads(content), indent=4, ensure_ascii=False))

File diff suppressed because one or more lines are too long

66
pkg/utils/log.py Normal file
View File

@@ -0,0 +1,66 @@
import os
import time
import logging
import shutil
log_file_name = "qchatgpt.log"
log_colors_config = {
'DEBUG': 'green', # cyan white
'INFO': 'white',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'cyan',
}
def init_runtime_log_file():
"""为此次运行生成日志文件
格式: qchatgpt-yyyy-MM-dd-HH-mm-ss.log
"""
global log_file_name
# 检查logs目录是否存在
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())
def reset_logging():
global log_file_name
import config
import pkg.utils.context
import colorlog
if pkg.utils.context.context['logger_handler'] is not None:
logging.getLogger().removeHandler(pkg.utils.context.context['logger_handler'])
for handler in logging.getLogger().handlers:
logging.getLogger().removeHandler(handler)
logging.basicConfig(level=config.logging_level, # 设置日志输出格式
filename=log_file_name, # log日志输出的文件位置和文件名
format="[%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n%(message)s",
# 日志输出的格式
# -8表示占位符让输出左对齐输出长度都为8位
datefmt="%Y-%m-%d %H:%M:%S" # 时间输出的格式
)
sh = logging.StreamHandler()
sh.setLevel(config.logging_level)
sh.setFormatter(colorlog.ColoredFormatter(
fmt="%(log_color)s[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : "
"%(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
log_colors=log_colors_config
))
logging.getLogger().addHandler(sh)
pkg.utils.context.context['logger_handler'] = sh
return sh

View File

@@ -1,21 +1,25 @@
from pip._internal import main as pipmain
import main
import pkg.utils.log as log
def install(package):
pipmain(['install', package])
main.reset_logging()
log.reset_logging()
def install_upgrade(package):
pipmain(['install', '--upgrade', package])
log.reset_logging()
def run_pip(params: list):
pipmain(params)
main.reset_logging()
log.reset_logging()
def install_requirements(file):
pipmain(['install', '-r', file, "--upgrade"])
main.reset_logging()
log.reset_logging()
def ensure_dulwich():

View File

@@ -34,13 +34,18 @@ def pull_latest(repo_path: str) -> bool:
return True
def is_newer_ignored_bugfix_ver(new_tag: str, old_tag: str):
"""判断版本是否更新,忽略第四位版本"""
def is_newer(new_tag: str, old_tag: str):
"""判断版本是否更新,忽略第四位版本和第一位版本"""
if new_tag == old_tag:
return False
new_tag = new_tag.split(".")
old_tag = old_tag.split(".")
# 判断主版本是否相同
if new_tag[0] != old_tag[0]:
return False
if len(new_tag) < 4:
return True
@@ -97,7 +102,7 @@ def update_all(cli: bool = False) -> bool:
else:
print("更新日志: {}".format(rls_notes))
if latest_rls == {} and not is_newer_ignored_bugfix_ver(latest_tag_name, current_tag): # 没有新版本
if latest_rls == {} and not is_newer(latest_tag_name, current_tag): # 没有新版本
return False
# 下载最新版本的zip到temp目录
@@ -254,7 +259,7 @@ def is_new_version_available() -> bool:
latest_tag_name = rls['tag_name']
break
return is_newer_ignored_bugfix_ver(latest_tag_name, current_tag)
return is_newer(latest_tag_name, current_tag)
def get_rls_notes() -> list:

View File

@@ -1,9 +1,10 @@
requests~=2.28.1
openai~=0.27.2
dulwich~=0.21.3
requests~=2.31.0
openai~=0.27.8
dulwich~=0.21.5
colorlog~=6.6.0
yiri-mirai~=0.2.6.1
websockets~=10.4
yiri-mirai~=0.2.7
websockets
urllib3~=1.26.10
func_timeout~=4.3.5
Pillow
nakuru-project-idk

14
res/announcement.json Normal file
View File

@@ -0,0 +1,14 @@
[
{
"id": 0,
"time": "2023-04-24 16:05:20",
"timestamp": 1682323520,
"content": "现已支持使用go-cqhttp替换mirai作为QQ登录框架, 请更新并查看 https://github.com/RockChinQ/QChatGPT/wiki/go-cqhttp%E9%85%8D%E7%BD%AE"
},
{
"id": 1,
"time": "2023-05-21 17:33:18",
"timestamp": 1684661598,
"content": "NewBing不再需要鉴权若您正在使用revLibs逆向库插件请立即使用!plugin update revLibs命令更新插件到最新版。"
}
]

BIN
res/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -13,5 +13,5 @@ for key in cmdsmgr.__command_list__:
template[key] = cmdsmgr.__command_list__[key]['privilege']
# 写入cmdpriv-template.json
with open('cmdpriv-template.json', 'w') as f:
with open('res/templates/cmdpriv-template.json', 'w') as f:
f.write(json.dumps(template, indent=4, ensure_ascii=False))

View File

@@ -0,0 +1,32 @@
# 输出工作路径
import os
print("工作路径: " + os.getcwd())
announcement = input("请输入公告内容: ")
import json
# 读取现有的公告文件 res/announcement.json
with open("res/announcement.json", "r", encoding="utf-8") as f:
announcement_json = json.load(f)
# 将公告内容写入公告文件
# 当前自然时间
import time
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
# 获取最后一个公告的id
last_id = announcement_json[-1]["id"] if len(announcement_json) > 0 else -1
announcement = {
"id": last_id + 1,
"time": now,
"timestamp": int(time.time()),
"content": announcement
}
announcement_json.append(announcement)
# 将公告写入公告文件
with open("res/announcement.json", "w", encoding="utf-8") as f:
json.dump(announcement_json, f, indent=4, ensure_ascii=False)

BIN
res/social.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -1,3 +1,13 @@
# 是否处理群聊消息
# 为False时忽略所有群聊消息
# 优先级高于下方禁用列表
enable_group = True
# 是否处理私聊消息
# 为False时忽略所有私聊消息
# 优先级高于下方禁用列表
enable_private = True
# 是否启用禁用列表
enable = True

View File

@@ -19,6 +19,7 @@
"prompt": 1,
"resend": 1,
"reset": 1,
"cfg": 2,
"cmd": 1,
"help": 1,
"reload": 2,

View File

@@ -2,12 +2,15 @@
## 简介
调用OpenAI官方提供的API接口结合mirai和YiriMirai框架将QQ消息与语言模型连接实现更加智能的对话机器人
调用OpenAI官方提供的API接口结合mirai和YiriMirai框架将QQ消息与语言模型连接实现更加智能的对话机器人
## 技术栈
- [Mirai](https://github.com/mamoe/mirai) 高效率 QQ 机器人支持库
- [YiriMirai](https://github.com/YiriMiraiProject/YiriMirai) 一个轻量级、低耦合的基于 mirai-api-http 的 Python SDK。
- [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) cqhttp的golang实现轻量、原生跨平台.
- [nakuru-project](https://github.com/Lxns-Network/nakuru-project) - 一款为 go-cqhttp 的正向 WebSocket 设计的 Python SDK支持纯 CQ 码与消息链的转换处理
- [nakuru-project-idk](https://github.com/idoknow/nakuru-project-idk) - 由idoknow维护的nakuru-project分支
- [dulwich](https://github.com/jelmer/dulwich) Pure-Python Git implementation
- [OpenAI API](https://openai.com/api/) OpenAI API

View File

@@ -0,0 +1,70 @@
# 配置go-cqhttp用于登录QQ
> 若您是从旧版本升级到此版本以使用go-cqhttp的用户请您按照`config-template.py`的内容修改`config.py`,添加`msg_source_adapter`配置项并将其设为`nakuru`,同时添加`nakuru_config`字段按照说明配置。
## 步骤
1. 从[go-cqhttp的Release](https://github.com/Mrs4s/go-cqhttp/releases/latest)下载最新的go-cqhttp可执行文件建议直接下载可执行文件压缩包而不是安装器
2. 解压并运行,首次运行会询问需要开放的网络协议,**请填入`02`并回车,必须输入`02`❗❗❗❗❗❗❗**
<h1> 你这里必须得输入`02`,你懂么,`0`必须得输入,看好了,看好下面输入什么了吗?别他妈的搁那就输个`2`完了启动连不上还跑群里问,问一个我踢一个。 </h1>
```
C:\Softwares\go-cqhttp.old> .\go-cqhttp.exe
未找到配置文件,正在为您生成配置文件中!
请选择你需要的通信方式:
> 0: HTTP通信
> 1: 云函数服务
> 2: 正向 Websocket 通信
> 3: 反向 Websocket 通信
请输入你需要的编号(0-9),可输入多个,同一编号也可输入多个(如: 233)
您的选择是:02
```
提示已生成`config.yml`文件关闭go-cqhttp。
3. 打开go-cqhttp同目录的`config.yml`
1. 编辑账号登录信息
只需要修改下方`uin``password`为你要登录的机器人账号的QQ号和密码即可。
**若您不填写,将会在启动时请求扫码登录。**
```yaml
account: # 账号相关
uin: 1233456 # QQ账号
password: '' # 密码为空时使用扫码登录
encrypt: false # 是否开启密码加密
status: 0 # 在线状态 请参考 https://docs.go-cqhttp.org/guide/config.html#在线状态
relogin: # 重连设置
delay: 3 # 首次重连延迟, 单位秒
interval: 3 # 重连间隔
max-times: 0 # 最大重连次数, 0为无限制
```
2. 修改websocket端口
在`config.yml`下方找到以下内容
```yaml
- ws:
# 正向WS服务器监听地址
address: 0.0.0.0:8080
middlewares:
<<: *default # 引用默认中间件
```
**将`0.0.0.0:8080`改为`0.0.0.0:6700`**,保存并关闭`config.yml`。
3. 若您的服务器位于公网,强烈建议您填写`access-token` (可选)
```yaml
# 默认中间件锚点
default-middlewares: &default
# 访问密钥, 强烈推荐在公网的服务器设置
access-token: ''
```
4. 配置完成重新启动go-cqhttp
> 若启动后登录不成功,请尝试根据[此文档](https://docs.go-cqhttp.org/guide/config.html#%E8%AE%BE%E5%A4%87%E4%BF%A1%E6%81%AF)修改`device.json`的协议编号。

View File

@@ -225,7 +225,7 @@
格式: `!cfg <配置项名称> <配置项新值>`
以修改`default_prompt`示例
```
!cfg default_prompt 我是Rock Chin
!cfg default_prompt "我是Rock Chin"
```
输出示例
@@ -243,7 +243,15 @@
```
!~all
!~default_prompt
!~default_prompt 我是Rock Chin
!~default_prompt "我是Rock Chin"
```
5. 配置项名称支持使用点号(.)拼接以索引子配置项
例如: `openai_config.api_key`将索引`config`字典中的`openai_config`字典中的`api_key`字段,可以通过这个方式查看或修改此子配置项
```
!~openai_config.api_key
```
</details>
@@ -367,4 +375,5 @@ prompt_submit_length = <模型单次请求token数上限> - 情景预设中token
### 加入黑名单
编辑`banlist.py`,设置`enable = True`,并在其中的`person``group`列表中加入要封禁的人或群聊,修改完成后重启程序或进行热重载
- 支持禁用所有`私聊``群聊`,请查看`banlist.py`中的`enable_private``enable_group`字段
- 编辑`banlist.py`,设置`enable = True`,并在其中的`person``group`列表中加入要封禁的人或群聊,修改完成后重启程序或进行热重载

View File

@@ -1,5 +1,6 @@
以下是QChatGPT实现原理等技术信息贡献之前请仔细阅读
> 太久没更了,过时了,建议读源码,~~注释还挺全的~~
> 请先阅读OpenAI API的相关文档 https://beta.openai.com/docs/ 以下信息假定您已了解OpenAI模型的相关特性及其接口的调用方法。
## 术语

View File

@@ -23,15 +23,16 @@ QChatGPT 插件使用Wiki
## 管理
### !plugin
### !plugin
```
!plugin 列出所有已安装的插件
!plugin 列出所有已安装的插件
!plugin get <储存库地址> 从Git储存库安装插件(需要管理员权限)
!plugin update 更新所有插件(需要管理员权限,仅支持从储存库安装的插件)
!plugin del <插件名> 删除插件(需要管理员权限)
!plugin on <插件名> 启用插件(需要管理员权限)
!plugin off <插件名> 用插件(需要管理员权限)
!plugin update all 更新所有插件(需要管理员权限,仅支持从储存库安装的插件)
!plugin update <插件名> 更新指定插件
!plugin del <插件名> 删除插件(需要管理员权限)
!plugin on <插件名> 用插件(需要管理员权限)
!plugin off <插件名> 禁用插件(需要管理员权限)
```
### 控制插件执行顺序

View File

@@ -9,9 +9,12 @@ alter_tip_message = '[bot]err:出错了,请稍后再试'
# 若设置为空字符串,则不发送提示信息
rate_limit_drop_tip = "本分钟对话次数超过限速次数,此对话被丢弃"
# 只允许同时处理一条消息时,新消息被丢弃时的提示信息
# 当config.py中的wait_last_done为False时生效
# 若设置为空字符串,则不发送提示信息
message_drop_tip = "[bot]当前有一条消息正在处理,请等待处理完成"
# 指令help帮助消息
# config.py,line:279
# pkg/qqbot/process.py,line:122
help_message = """此机器人通过调用大型语言模型生成回复,不具有情感。
你可以用自然语言与其交流,回复的消息中[GPT]开头的为模型生成的语言,[bot]开头的为程序提示。
欢迎到github.com/RockChinQ/QChatGPT 给个star"""
@@ -24,8 +27,11 @@ replys_message = "[bot]err:请求超时"
# 指令权限不足提示
command_admin_message = "[bot]err:权限不足: "
# 指令无效提示
command_err_message = "[bot]err:指令执行出错"
command_err_message = "[bot]err:指令不存在"
# 会话重置提示
command_reset_message = "[bot]:会话已重置"
command_reset_name_message = "[bot]:会话已重置,使用场景预设:"
command_reset_message = "[bot]会话已重置"
command_reset_name_message = "[bot]会话已重置,使用场景预设:"
# 会话自动重置时的提示
session_auto_reset_message = "[bot]会话token超限已自动重置请重新发送消息"