Compare commits

...

49 Commits

Author SHA1 Message Date
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
29 changed files with 936 additions and 178 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: 系统环境

5
.gitignore vendored
View File

@@ -19,4 +19,7 @@ cookies.json
res/announcement_saved
res/announcement_saved.json
cmdpriv.json
tips.py
tips.py
.venv
bin/
.vscode

View File

@@ -1,13 +1,17 @@
# 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)
> 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)
- **客官,来都来了,不点个⭐吗?**
- 到[项目Wiki](https://github.com/RockChinQ/QChatGPT/wiki)可了解项目详细信息
- 官方交流、答疑群: 656285629
- **进群提问前请您`确保`已经找遍文档和issue均无法解决**
@@ -136,6 +140,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)
@@ -180,12 +193,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. 克隆此项目
@@ -197,7 +227,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. 运行一次主程序,生成配置文件
@@ -252,7 +282,7 @@ python3 main.py
欢迎提交新的插件
- [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、屏蔽词规则等

View File

@@ -1,5 +1,9 @@
# 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)
@@ -103,11 +107,26 @@ Use [this installer](https://github.com/RockChinQ/qcg-installer) to deploy.
- Python 3.9.x or higher
#### Configure Mirai
#### 配置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
@@ -120,7 +139,7 @@ cd QChatGPT
2. Install dependencies
```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. Generate `config.py`

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暂不能使用

22
main.py
View File

@@ -47,7 +47,7 @@ def init_db():
def ensure_dependencies():
import pkg.utils.pkgmgr as pkgmgr
pkgmgr.run_pip(["install", "openai", "Pillow", "--upgrade",
pkgmgr.run_pip(["install", "openai", "Pillow", "nakuru-project-idk", "--upgrade",
"-i", "https://pypi.douban.com/simple/",
"--trusted-host", "pypi.douban.com"])
@@ -193,9 +193,7 @@ def start(first_time_init=False):
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)
qqbot = pkg.qqbot.manager.QQBotManager(first_time_init=first_time_init)
# 加载插件
import pkg.plugin.host
@@ -210,7 +208,8 @@ def start(first_time_init=False):
def run_bot_wrapper():
global known_exception_caught
try:
qqbot.bot.run()
logging.info("使用账号: {}".format(qqbot.bot_account_id))
qqbot.adapter.run_sync()
except TypeError as e:
if str(e).__contains__("argument 'debug'"):
logging.error(
@@ -245,6 +244,8 @@ def start(first_time_init=False):
"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
@@ -264,9 +265,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:

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"

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

@@ -60,7 +60,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)

View File

@@ -11,6 +11,7 @@ import traceback
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
@@ -276,6 +277,10 @@ 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))

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

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

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,6 +21,8 @@ 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):
@@ -64,7 +66,9 @@ def random_responding():
class QQBotManager:
retry = 3
bot: Mirai = None
adapter: msadapter.MessageSourceAdapter = None
bot_account_id: int = 0
reply_filter = None
@@ -73,9 +77,133 @@ class QQBotManager:
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
self.timeout = config.process_message_timeout
self.retry = config.retry_times
# 由于YiriMirai的bot对象是单例的且shutdown方法暂时无法使用
# 故只在第一次初始化时创建bot对象重载之后使用原bot对象
# 因此bot的配置不支持热重载
if first_time_init:
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.adapter = pkg.utils.context.get_qqbot_manager().adapter
pkg.utils.context.set_qqbot_manager(self)
# 注册诸事件
# Caution: 注册新的事件处理器之后请务必在unsubscribe_all中编写相应的取消订阅代码
def on_friend_message(event: FriendMessage):
def friend_message_handler():
# 触发事件
args = {
"launcher_type": "person",
"launcher_id": event.sender.id,
"sender_id": event.sender.id,
"message_chain": event.message_chain,
}
plugin_event = plugin_host.emit(plugin_models.PersonMessageReceived, **args)
if plugin_event.is_prevented_default():
return
self.on_person_message(event)
pkg.utils.context.get_thread_ctl().submit_user_task(
friend_message_handler,
)
self.adapter.register_listener(
FriendMessage,
on_friend_message
)
def on_stranger_message(event: StrangerMessage):
def stranger_message_handler():
# 触发事件
args = {
"launcher_type": "person",
"launcher_id": event.sender.id,
"sender_id": event.sender.id,
"message_chain": event.message_chain,
}
plugin_event = plugin_host.emit(plugin_models.PersonMessageReceived, **args)
if plugin_event.is_prevented_default():
return
self.on_person_message(event)
pkg.utils.context.get_thread_ctl().submit_user_task(
stranger_message_handler,
)
# nakuru不区分好友和陌生人故仅为yirimirai注册陌生人事件
if config.msg_source_adapter == 'yirimirai':
self.adapter.register_listener(
StrangerMessage,
on_stranger_message
)
def on_group_message(event: GroupMessage):
def group_message_handler(event: GroupMessage):
# 触发事件
args = {
"launcher_type": "group",
"launcher_id": event.group.id,
"sender_id": event.sender.id,
"message_chain": event.message_chain,
}
plugin_event = plugin_host.emit(plugin_models.GroupMessageReceived, **args)
if plugin_event.is_prevented_default():
return
self.on_group_message(event)
pkg.utils.context.get_thread_ctl().submit_user_task(
group_message_handler,
event
)
self.adapter.register_listener(
GroupMessage,
on_group_message
)
def unsubscribe_all():
"""取消所有订阅
用于在热重载流程中卸载所有事件处理器
"""
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
# 加载禁用列表
if os.path.exists("banlist.py"):
@@ -99,142 +227,20 @@ class QQBotManager:
else:
self.reply_filter = pkg.qqbot.filter.ReplyFilter([])
# 由于YiriMirai的bot对象是单例的且shutdown方法暂时无法使用
# 故只在第一次初始化时创建bot对象重载之后使用原bot对象
# 因此bot的配置不支持热重载
if first_time_init:
self.first_time_init(mirai_http_api_config)
else:
self.bot = pkg.utils.context.get_qqbot_manager().bot
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):
# 触发事件
args = {
"launcher_type": "person",
"launcher_id": event.sender.id,
"sender_id": event.sender.id,
"message_chain": event.message_chain,
}
plugin_event = plugin_host.emit(plugin_models.PersonMessageReceived, **args)
if plugin_event.is_prevented_default():
return
self.on_person_message(event)
pkg.utils.context.get_thread_ctl().submit_user_task(
friend_message_handler,
event
)
@self.bot.on(StrangerMessage)
async def on_stranger_message(event: StrangerMessage):
def stranger_message_handler(event: StrangerMessage):
# 触发事件
args = {
"launcher_type": "person",
"launcher_id": event.sender.id,
"sender_id": event.sender.id,
"message_chain": event.message_chain,
}
plugin_event = plugin_host.emit(plugin_models.PersonMessageReceived, **args)
if plugin_event.is_prevented_default():
return
self.on_person_message(event)
pkg.utils.context.get_thread_ctl().submit_user_task(
stranger_message_handler,
event
)
@self.bot.on(GroupMessage)
async def on_group_message(event: GroupMessage):
def group_message_handler(event: GroupMessage):
# 触发事件
args = {
"launcher_type": "group",
"launcher_id": event.group.id,
"sender_id": event.sender.id,
"message_chain": event.message_chain,
}
plugin_event = plugin_host.emit(plugin_models.GroupMessageReceived, **args)
if plugin_event.is_prevented_default():
return
self.on_group_message(event)
pkg.utils.context.get_thread_ctl().submit_user_task(
group_message_handler,
event
)
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)
self.unsubscribe_all = unsubscribe_all
def go(self, func, *args, **kwargs):
self.pool.submit(func, *args, **kwargs)
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']
)
)
else:
raise Exception("未知的适配器类型")
self.bot = bot
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 event.sender.id == self.bot_account_id:
pass
else:
if Image in event.message_chain:
@@ -274,11 +280,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
@@ -312,7 +317,7 @@ class QQBotManager:
if 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():
# 直接调用
reply = process()
else:
@@ -334,22 +339,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

@@ -66,11 +66,9 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
# 检查是否被禁言
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

View File

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

@@ -0,0 +1,319 @@
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) + 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
)
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

File diff suppressed because one or more lines are too long

View File

@@ -48,7 +48,7 @@ def reset_logging():
logging.basicConfig(level=config.logging_level, # 设置日志输出格式
filename=log_file_name, # log日志输出的文件位置和文件名
format="[%(asctime)s.%(msecs)03d] %(filename)s (%(lineno)d) - [%(levelname)s] : %(message)s",
format="[%(asctime)s.%(msecs)03d] %(pathname)s (%(lineno)d) - [%(levelname)s] :\n%(message)s",
# 日志输出的格式
# -8表示占位符让输出左对齐输出长度都为8位
datefmt="%Y-%m-%d %H:%M:%S" # 时间输出的格式

View File

@@ -6,4 +6,5 @@ yiri-mirai~=0.2.6.1
websockets
urllib3~=1.26.10
func_timeout~=4.3.5
Pillow
Pillow
nakuru-project-idk

View File

@@ -1 +1,8 @@
[]
[
{
"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"
}
]

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

BIN
res/social.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -8,6 +8,9 @@
- [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,67 @@
# 配置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`并回车**
```
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

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