mirror of
https://github.com/RockChinQ/QChatGPT.git
synced 2024-11-16 11:42:44 +08:00
Merge pull request #427 from RockChinQ/nakuru-support
[Feat] 支持通过nakuru-project框架连接go-cqhttp
This commit is contained in:
commit
c6a16f5974
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -19,4 +19,7 @@ cookies.json
|
||||||
res/announcement_saved
|
res/announcement_saved
|
||||||
res/announcement_saved.json
|
res/announcement_saved.json
|
||||||
cmdpriv.json
|
cmdpriv.json
|
||||||
tips.py
|
tips.py
|
||||||
|
.venv
|
||||||
|
bin/
|
||||||
|
.vscode
|
28
README.md
28
README.md
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/RockChinQ/QChatGPT?style=flat-square)](https://github.com/RockChinQ/QChatGPT/releases/latest)
|
[![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/18 现已支持GPT-4 API(内测),请查看`config-template.py`中的`completion_api_params`
|
||||||
> 2023/3/15 逆向库已支持New Bing,使用方法查看[插件文档](https://github.com/RockChinQ/revLibs)
|
> 2023/3/15 逆向库已支持New Bing,使用方法查看[插件文档](https://github.com/RockChinQ/revLibs)
|
||||||
|
|
||||||
|
@ -192,12 +193,29 @@
|
||||||
|
|
||||||
- 请使用Python 3.9.x以上版本
|
- 请使用Python 3.9.x以上版本
|
||||||
|
|
||||||
#### 配置Mirai
|
#### ① 配置QQ登录框架
|
||||||
|
|
||||||
按照[此教程](https://yiri-mirai.wybxc.cc/tutorials/01/configuration)配置Mirai及YiriMirai
|
目前支持mirai和go-cqhttp,配置任意一个即可
|
||||||
启动mirai-console后,使用`login`命令登录QQ账号,保持mirai-console运行状态
|
|
||||||
|
|
||||||
#### 配置主程序
|
<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. 克隆此项目
|
1. 克隆此项目
|
||||||
|
|
||||||
|
@ -209,7 +227,7 @@ cd QChatGPT
|
||||||
2. 安装依赖
|
2. 安装依赖
|
||||||
|
|
||||||
```bash
|
```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. 运行一次主程序,生成配置文件
|
3. 运行一次主程序,生成配置文件
|
||||||
|
|
19
README_en.md
19
README_en.md
|
@ -107,11 +107,26 @@ Use [this installer](https://github.com/RockChinQ/qcg-installer) to deploy.
|
||||||
|
|
||||||
- Python 3.9.x or higher
|
- 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.
|
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.
|
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
|
#### Configure QChatGPT
|
||||||
|
|
||||||
1. Clone the repository
|
1. Clone the repository
|
||||||
|
@ -124,7 +139,7 @@ cd QChatGPT
|
||||||
2. Install dependencies
|
2. Install dependencies
|
||||||
|
|
||||||
```bash
|
```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`
|
3. Generate `config.py`
|
||||||
|
|
|
@ -3,11 +3,11 @@ import logging
|
||||||
|
|
||||||
# 消息处理协议适配器
|
# 消息处理协议适配器
|
||||||
# 目前支持以下适配器:
|
# 目前支持以下适配器:
|
||||||
# - "yirimirai": YiriMirai框架适配器, 请填写mirai_http_api_config
|
# - "yirimirai": mirai的通信框架,YiriMirai框架适配器, 请同时填写下方mirai_http_api_config
|
||||||
# - "nonebot2": NoneBot2框架适配器, 请填写nonebot2_config
|
# - "nakuru": go-cqhttp通信框架,请同时填写下方nakuru_config
|
||||||
msg_source_adapter = "yirimirai"
|
msg_source_adapter = "yirimirai"
|
||||||
|
|
||||||
# [必需] Mirai的配置
|
# [必需(与nakuru二选一,取决于msg_source_adapter)] Mirai的配置
|
||||||
# 请到配置mirai的步骤中的教程查看每个字段的信息
|
# 请到配置mirai的步骤中的教程查看每个字段的信息
|
||||||
# adapter: 选择适配器,目前支持HTTPAdapter和WebSocketAdapter
|
# adapter: 选择适配器,目前支持HTTPAdapter和WebSocketAdapter
|
||||||
# host: 运行mirai的主机地址
|
# host: 运行mirai的主机地址
|
||||||
|
@ -24,8 +24,14 @@ mirai_http_api_config = {
|
||||||
"qq": 1234567890
|
"qq": 1234567890
|
||||||
}
|
}
|
||||||
|
|
||||||
# NoneBot2的配置
|
# [必需(与mirai二选一,取决于msg_source_adapter)]
|
||||||
nonebot2_config = {}
|
# 使用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的配置
|
# [必需] OpenAI的配置
|
||||||
# api_key: OpenAI的API Key
|
# api_key: OpenAI的API Key
|
||||||
|
|
15
main.py
15
main.py
|
@ -47,7 +47,7 @@ def init_db():
|
||||||
|
|
||||||
def ensure_dependencies():
|
def ensure_dependencies():
|
||||||
import pkg.utils.pkgmgr as pkgmgr
|
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/",
|
"-i", "https://pypi.douban.com/simple/",
|
||||||
"--trusted-host", "pypi.douban.com"])
|
"--trusted-host", "pypi.douban.com"])
|
||||||
|
|
||||||
|
@ -243,6 +243,8 @@ def start(first_time_init=False):
|
||||||
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
|
"mirai-api-http端口无法使用:{}, 解决方案: https://github.com/RockChinQ/QChatGPT/issues/22".format(
|
||||||
e))
|
e))
|
||||||
else:
|
else:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
logging.error(
|
logging.error(
|
||||||
"捕捉到未知异常:{}, 请前往 https://github.com/RockChinQ/QChatGPT/issues 查找或提issue".format(e))
|
"捕捉到未知异常:{}, 请前往 https://github.com/RockChinQ/QChatGPT/issues 查找或提issue".format(e))
|
||||||
known_exception_caught = True
|
known_exception_caught = True
|
||||||
|
@ -262,9 +264,14 @@ def start(first_time_init=False):
|
||||||
|
|
||||||
if first_time_init:
|
if first_time_init:
|
||||||
if not known_exception_caught:
|
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'])))
|
import config
|
||||||
logging.info('程序启动完成,如长时间未显示 ”成功登录到账号xxxxx“ ,并且不回复消息,请查看 '
|
if config.msg_source_adapter == "yirimirai":
|
||||||
'https://github.com/RockChinQ/QChatGPT/issues/37')
|
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:
|
else:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -8,7 +8,12 @@
|
||||||
"verifyKey": "yirimirai",
|
"verifyKey": "yirimirai",
|
||||||
"qq": 1234567890
|
"qq": 1234567890
|
||||||
},
|
},
|
||||||
"nonebot2_config": {},
|
"nakuru_config": {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 6700,
|
||||||
|
"http_port": 5700,
|
||||||
|
"token": ""
|
||||||
|
},
|
||||||
"openai_config": {
|
"openai_config": {
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"default": "openai_api_key"
|
"default": "openai_api_key"
|
||||||
|
|
|
@ -79,4 +79,58 @@ class MessageSourceAdapter:
|
||||||
bool: 是否成功关闭,热重载时若此函数返回False则不会重载MessageSource底层
|
bool: 是否成功关闭,热重载时若此函数返回False则不会重载MessageSource底层
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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
|
||||||
|
|
|
@ -80,7 +80,6 @@ class QQBotManager:
|
||||||
def __init__(self, first_time_init=True):
|
def __init__(self, first_time_init=True):
|
||||||
import config
|
import config
|
||||||
|
|
||||||
mirai_http_api_config = config.mirai_http_api_config
|
|
||||||
self.timeout = config.process_message_timeout
|
self.timeout = config.process_message_timeout
|
||||||
self.retry = config.retry_times
|
self.retry = config.retry_times
|
||||||
|
|
||||||
|
@ -88,12 +87,17 @@ class QQBotManager:
|
||||||
# 故只在第一次初始化时创建bot对象,重载之后使用原bot对象
|
# 故只在第一次初始化时创建bot对象,重载之后使用原bot对象
|
||||||
# 因此,bot的配置不支持热重载
|
# 因此,bot的配置不支持热重载
|
||||||
if first_time_init:
|
if first_time_init:
|
||||||
|
logging.info("Use adapter:" + config.msg_source_adapter)
|
||||||
if config.msg_source_adapter == 'yirimirai':
|
if config.msg_source_adapter == 'yirimirai':
|
||||||
from pkg.qqbot.sources.yirimirai import YiriMiraiAdapter
|
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.bot_account_id = config.mirai_http_api_config['qq']
|
||||||
self.adapter = YiriMiraiAdapter(mirai_http_api_config)
|
self.adapter = YiriMiraiAdapter(mirai_http_api_config)
|
||||||
elif config.msg_source_adapter == 'nonebot2':
|
elif config.msg_source_adapter == 'nakuru':
|
||||||
pass
|
from pkg.qqbot.sources.nakuru import NakuruProjectAdapter
|
||||||
|
self.adapter = NakuruProjectAdapter(config.nakuru_config)
|
||||||
|
self.bot_account_id = self.adapter.bot_account_id
|
||||||
else:
|
else:
|
||||||
self.adapter = pkg.utils.context.get_qqbot_manager().adapter
|
self.adapter = pkg.utils.context.get_qqbot_manager().adapter
|
||||||
|
|
||||||
|
@ -146,10 +150,12 @@ class QQBotManager:
|
||||||
pkg.utils.context.get_thread_ctl().submit_user_task(
|
pkg.utils.context.get_thread_ctl().submit_user_task(
|
||||||
stranger_message_handler,
|
stranger_message_handler,
|
||||||
)
|
)
|
||||||
self.adapter.register_listener(
|
# nakuru不区分好友和陌生人,故仅为yirimirai注册陌生人事件
|
||||||
StrangerMessage,
|
if config.msg_source_adapter == 'yirimirai':
|
||||||
on_stranger_message
|
self.adapter.register_listener(
|
||||||
)
|
StrangerMessage,
|
||||||
|
on_stranger_message
|
||||||
|
)
|
||||||
|
|
||||||
def on_group_message(event: GroupMessage):
|
def on_group_message(event: GroupMessage):
|
||||||
|
|
||||||
|
@ -182,14 +188,16 @@ class QQBotManager:
|
||||||
|
|
||||||
用于在热重载流程中卸载所有事件处理器
|
用于在热重载流程中卸载所有事件处理器
|
||||||
"""
|
"""
|
||||||
|
import config
|
||||||
self.adapter.unregister_listener(
|
self.adapter.unregister_listener(
|
||||||
FriendMessage,
|
FriendMessage,
|
||||||
on_friend_message
|
on_friend_message
|
||||||
)
|
)
|
||||||
self.adapter.unregister_listener(
|
if config.msg_source_adapter == 'yirimirai':
|
||||||
StrangerMessage,
|
self.adapter.unregister_listener(
|
||||||
on_stranger_message
|
StrangerMessage,
|
||||||
)
|
on_stranger_message
|
||||||
|
)
|
||||||
self.adapter.unregister_listener(
|
self.adapter.unregister_listener(
|
||||||
GroupMessage,
|
GroupMessage,
|
||||||
on_group_message
|
on_group_message
|
||||||
|
@ -272,7 +280,6 @@ class QQBotManager:
|
||||||
def on_group_message(self, event: GroupMessage):
|
def on_group_message(self, event: GroupMessage):
|
||||||
import config
|
import config
|
||||||
reply = ''
|
reply = ''
|
||||||
|
|
||||||
def process(text=None) -> str:
|
def process(text=None) -> str:
|
||||||
replys = ""
|
replys = ""
|
||||||
if At(self.bot_account_id) in event.message_chain:
|
if At(self.bot_account_id) in event.message_chain:
|
||||||
|
|
|
@ -66,7 +66,8 @@ def process_message(launcher_type: str, launcher_id: int, text_message: str, mes
|
||||||
|
|
||||||
# 检查是否被禁言
|
# 检查是否被禁言
|
||||||
if launcher_type == 'group':
|
if launcher_type == 'group':
|
||||||
if mgr.adapter.is_muted(launcher_id):
|
is_muted = mgr.adapter.is_muted(launcher_id)
|
||||||
|
if is_muted:
|
||||||
logging.info("机器人被禁言,跳过消息处理(group_{})".format(launcher_id))
|
logging.info("机器人被禁言,跳过消息处理(group_{})".format(launcher_id))
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
|
|
319
pkg/qqbot/sources/nakuru.py
Normal file
319
pkg/qqbot/sources/nakuru.py
Normal 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
|
|
@ -68,6 +68,7 @@ class YiriMiraiAdapter(MessageSourceAdapter):
|
||||||
Args:
|
Args:
|
||||||
message_source (mirai.MessageEvent): YiriMirai消息源事件
|
message_source (mirai.MessageEvent): YiriMirai消息源事件
|
||||||
message (mirai.MessageChain): YiriMirai库的消息链
|
message (mirai.MessageChain): YiriMirai库的消息链
|
||||||
|
quote_origin (bool, optional): 是否引用原消息. Defaults to False.
|
||||||
"""
|
"""
|
||||||
asyncio.run(self.bot.send(message_source, message, quote_origin))
|
asyncio.run(self.bot.send(message_source, message, quote_origin))
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ def reset_logging():
|
||||||
|
|
||||||
logging.basicConfig(level=config.logging_level, # 设置日志输出格式
|
logging.basicConfig(level=config.logging_level, # 设置日志输出格式
|
||||||
filename=log_file_name, # log日志输出的文件位置和文件名
|
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位
|
# -8表示占位符,让输出左对齐,输出长度都为8位
|
||||||
datefmt="%Y-%m-%d %H:%M:%S" # 时间输出的格式
|
datefmt="%Y-%m-%d %H:%M:%S" # 时间输出的格式
|
||||||
|
|
|
@ -6,4 +6,5 @@ yiri-mirai~=0.2.6.1
|
||||||
websockets
|
websockets
|
||||||
urllib3~=1.26.10
|
urllib3~=1.26.10
|
||||||
func_timeout~=4.3.5
|
func_timeout~=4.3.5
|
||||||
Pillow
|
Pillow
|
||||||
|
nakuru-project-idk
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
67
res/wiki/go-cqhttp配置.md
Normal file
67
res/wiki/go-cqhttp配置.md
Normal 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`的协议编号。
|
Loading…
Reference in New Issue
Block a user