feat: 添加Discord适配器与跨平台消息互通功能

新增Discord适配器支持,实现Discord与QQ之间的消息互通
添加通用数据模型用于跨平台消息转换
扩展配置系统以支持Discord和日志配置
重构日志系统以使用配置中的日志级别
在反向WebSocket管理器中注册Bot实例
更新主程序以支持Discord客户端启动
添加测试脚本验证核心功能
This commit is contained in:
2026-03-15 13:36:17 +08:00
parent 2a6e9b8f89
commit f868553342
12 changed files with 1490 additions and 13 deletions

View File

@@ -7,7 +7,7 @@ from pathlib import Path
import tomllib
from pydantic import ValidationError
from .config_models import ConfigModel, NapCatWSModel, BotModel, RedisModel, DockerModel, ImageManagerModel, MySQLModel, ReverseWSModel, ThreadingModel, BilibiliModel, LocalFileServerModel
from .config_models import ConfigModel, NapCatWSModel, BotModel, RedisModel, DockerModel, ImageManagerModel, MySQLModel, ReverseWSModel, ThreadingModel, BilibiliModel, LocalFileServerModel, DiscordModel, LoggingModel
from .utils.logger import ModuleLogger
from .utils.exceptions import ConfigError, ConfigNotFoundError, ConfigValidationError
@@ -156,7 +156,20 @@ class Config:
获取本地文件服务器配置
"""
return self._model.local_file_server
@property
def discord(self) -> DiscordModel:
"""
获取 Discord 配置
"""
return self._model.discord
@property
def logging(self) -> LoggingModel:
"""
获取日志配置
"""
return self._model.logging
# 实例化全局配置对象

View File

@@ -107,6 +107,23 @@ class LocalFileServerModel(BaseModel):
port: int = 3003
class DiscordModel(BaseModel):
"""
对应 `config.toml` 中的 `[discord]` 配置块。
"""
enabled: bool = False
token: str = ""
class LoggingModel(BaseModel):
"""
对应 `config.toml` 中的 `[logging]` 配置块。
"""
level: str = "DEBUG"
file_level: str = "DEBUG"
console_level: str = "INFO"
class ConfigModel(BaseModel):
"""
顶层配置模型,整合了所有子配置块。
@@ -121,5 +138,7 @@ class ConfigModel(BaseModel):
threading: ThreadingModel = Field(default_factory=ThreadingModel)
bilibili: BilibiliModel = Field(default_factory=BilibiliModel)
local_file_server: LocalFileServerModel = Field(default_factory=LocalFileServerModel)
discord: DiscordModel = Field(default_factory=DiscordModel)
logging: LoggingModel = Field(default_factory=LoggingModel)

View File

@@ -317,6 +317,7 @@ class ReverseWSManager:
# 为事件注入Bot实例
from ..ws import ReverseWSClient
from .bot_manager import bot_manager
# 为每个前端创建独立的Bot实例
with self._bots_lock:
@@ -325,6 +326,10 @@ class ReverseWSManager:
temp_ws = ReverseWSClient(self, client_id)
temp_ws.self_id = event.self_id if hasattr(event, 'self_id') else 0
self.bots[client_id] = Bot(temp_ws)
# 注册到 BotManager
if event.self_id:
bot_manager.register_bot(self.bots[client_id])
event.bot = self.bots[client_id]
@@ -465,7 +470,7 @@ class ReverseWSManager:
clients_to_send.append((cid, self.clients[cid]))
for cid, websocket in clients_to_send:
await websocket.send(orjson.dumps(payload))
await websocket.send(orjson.dumps(payload).decode('utf-8'))
return await asyncio.wait_for(future, timeout=30.0)
except asyncio.TimeoutError:

View File

@@ -8,6 +8,13 @@ import os
from pathlib import Path
from loguru import logger
# 导入全局配置
try:
from ..config_loader import global_config
USE_CONFIG = True
except ImportError:
USE_CONFIG = False
# 定义日志格式添加进程ID和线程ID作为上下文信息
LOG_FORMAT = (
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
@@ -30,14 +37,21 @@ DEBUG_LOG_FORMAT = (
# 移除 loguru 默认的处理器
logger.remove()
# 获取当前环境
ENVIRONMENT = os.getenv("NEOBOT_ENV", "development")
# 获取日志级别配置
if USE_CONFIG:
LOG_LEVEL = global_config.logging.level
FILE_LEVEL = global_config.logging.file_level
CONSOLE_LEVEL = global_config.logging.console_level
else:
LOG_LEVEL = "DEBUG"
FILE_LEVEL = "DEBUG"
CONSOLE_LEVEL = "INFO"
# 添加控制台输出处理器
logger.add(
sys.stderr,
level="INFO" if ENVIRONMENT == "production" else "DEBUG",
format=LOG_FORMAT if ENVIRONMENT == "production" else DEBUG_LOG_FORMAT,
level=CONSOLE_LEVEL,
format=LOG_FORMAT,
colorize=True,
enqueue=True # 异步写入
)
@@ -50,7 +64,7 @@ log_file_path = log_dir / "{time:YYYY-MM-DD}.log"
# 添加文件输出处理器
logger.add(
log_file_path,
level="DEBUG",
level=FILE_LEVEL,
format=DEBUG_LOG_FORMAT,
colorize=False,
rotation="00:00", # 每天午夜创建新文件

View File

@@ -291,7 +291,7 @@ class WS:
self._pending_requests[echo_id] = future
try:
await self.ws.send(orjson.dumps(payload))
await self.ws.send(orjson.dumps(payload).decode('utf-8'))
return await asyncio.wait_for(future, timeout=30.0)
except asyncio.TimeoutError:
with self._pending_requests_lock: