refactor: 重构代码结构和导入路径
fix(ws): 修复反向WebSocket管理器中的循环导入问题 docs: 删除不再使用的文档文件 style: 统一模型导入路径为neobot.models chore: 更新配置文件中的API密钥和连接地址
This commit is contained in:
@@ -1,32 +0,0 @@
|
|||||||
# DeepSeek API 配置示例
|
|
||||||
|
|
||||||
将以下环境变量添加到你的系统环境变量或 .env 文件中:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# DeepSeek API Key (从 https://platform.deepseek.com 获取)
|
|
||||||
DEEPSEEK_API_KEY=sk-你的实际API密钥
|
|
||||||
|
|
||||||
# DeepSeek API URL (可选,默认为官方 API)
|
|
||||||
DEEPSEEK_API_URL=https://api.deepseek.com/v1/chat/completions
|
|
||||||
|
|
||||||
# DeepSeek 模型名称 (可选,默认为 deepseek-chat)
|
|
||||||
DEEPSEEK_MODEL=deepseek-chat
|
|
||||||
```
|
|
||||||
|
|
||||||
或者在 Windows 系统中,可以通过以下方式设置环境变量:
|
|
||||||
|
|
||||||
**临时设置(仅当前会话有效):**
|
|
||||||
```powershell
|
|
||||||
$env:DEEPSEEK_API_KEY="sk-你的实际API密钥"
|
|
||||||
$env:DEEPSEEK_API_URL="https://api.deepseek.com/v1/chat/completions"
|
|
||||||
$env:DEEPSEEK_MODEL="deepseek-chat"
|
|
||||||
```
|
|
||||||
|
|
||||||
**永久设置(需要管理员权限):**
|
|
||||||
```powershell
|
|
||||||
[Environment]::SetEnvironmentVariable("DEEPSEEK_API_KEY", "sk-你的实际API密钥", "User")
|
|
||||||
[Environment]::SetEnvironmentVariable("DEEPSEEK_API_URL", "https://api.deepseek.com/v1/chat/completions", "User")
|
|
||||||
[Environment]::SetEnvironmentVariable("DEEPSEEK_MODEL", "deepseek-chat", "User")
|
|
||||||
```
|
|
||||||
|
|
||||||
设置完成后,重启终端或 IDE 使环境变量生效。
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
# 项目重构总结
|
|
||||||
|
|
||||||
## 重构目标
|
|
||||||
|
|
||||||
将项目从混乱的目录结构重构为标准的 Python 包结构,遵循 PEP 621 规范。
|
|
||||||
|
|
||||||
## 重构前后对比
|
|
||||||
|
|
||||||
### 重构前
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── adapters/ # 适配器
|
|
||||||
├── core/ # 核心代码
|
|
||||||
├── models/ # 数据模型
|
|
||||||
├── plugins/ # 插件
|
|
||||||
├── tests/ # 测试
|
|
||||||
├── docs/ # 文档
|
|
||||||
├── templates/ # 模板
|
|
||||||
├── web_static/ # 静态文件
|
|
||||||
├── data/ # 数据
|
|
||||||
├── main.py # 主程序
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
**问题:**
|
|
||||||
- 所有模块都在根目录,结构混乱
|
|
||||||
- 缺少标准的 Python 包结构
|
|
||||||
- 不符合现代 Python 项目的最佳实践
|
|
||||||
- 导入路径不清晰
|
|
||||||
|
|
||||||
### 重构后
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── src/
|
|
||||||
│ └── neobot/ # 核心包
|
|
||||||
│ ├── core/ # 框架核心
|
|
||||||
│ ├── models/ # 数据模型
|
|
||||||
│ ├── adapters/ # 平台适配器
|
|
||||||
│ ├── plugins/ # 插件
|
|
||||||
│ ├── tests/ # 测试
|
|
||||||
│ ├── templates/ # 模板
|
|
||||||
│ ├── docs/ # 文档
|
|
||||||
│ ├── web_static/ # 静态文件
|
|
||||||
│ └── data/ # 数据
|
|
||||||
├── main.py # 主程序入口
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
**优势:**
|
|
||||||
- 符合 PEP 621 标准的 Python 包结构
|
|
||||||
- 清晰的模块划分
|
|
||||||
- 更好的可维护性和可扩展性
|
|
||||||
- 符合现代 Python 项目的最佳实践
|
|
||||||
|
|
||||||
## 主要变更
|
|
||||||
|
|
||||||
### 1. 目录结构
|
|
||||||
|
|
||||||
- 所有 Python 代码移动到 `src/neobot/` 目录
|
|
||||||
- 采用标准的 Python 包结构
|
|
||||||
- 每个模块都有清晰的 `__init__.py` 文件
|
|
||||||
|
|
||||||
### 2. 导入路径
|
|
||||||
|
|
||||||
所有导入路径从 `core.*`、`models.*` 等改为 `neobot.core.*`、`neobot.models.*` 等。
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
```python
|
|
||||||
# 重构前
|
|
||||||
from core.managers import plugin_manager
|
|
||||||
from models import MessageSegment
|
|
||||||
|
|
||||||
# 重构后
|
|
||||||
from neobot.core.managers import plugin_manager
|
|
||||||
from neobot.models import MessageSegment
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 配置文件更新
|
|
||||||
|
|
||||||
- `pyproject.toml` 更新为使用 `src/` 目录结构
|
|
||||||
- `README.md` 更新项目结构说明
|
|
||||||
- `.gitignore` 更新以忽略新的数据目录路径
|
|
||||||
|
|
||||||
### 4. 主程序更新
|
|
||||||
|
|
||||||
- `main.py` 更新所有导入路径
|
|
||||||
- 更新插件目录路径为 `src/neobot/plugins`
|
|
||||||
|
|
||||||
## 新的模块组织
|
|
||||||
|
|
||||||
### src/neobot/core/
|
|
||||||
|
|
||||||
框架核心代码,包含:
|
|
||||||
|
|
||||||
- **api/**: OneBot API 封装
|
|
||||||
- **handlers/**: 事件处理器
|
|
||||||
- **managers/**: 各种管理器
|
|
||||||
- **services/**: 服务层
|
|
||||||
- **utils/**: 工具函数
|
|
||||||
|
|
||||||
### src/neobot/models/
|
|
||||||
|
|
||||||
数据模型定义,包含:
|
|
||||||
|
|
||||||
- **events/**: OneBot 事件模型
|
|
||||||
- **message.py**: 消息段模型
|
|
||||||
- **objects.py**: API 响应对象
|
|
||||||
- **sender.py**: 发送者信息
|
|
||||||
|
|
||||||
### src/neobot/plugins/
|
|
||||||
|
|
||||||
插件目录,所有业务逻辑都在这里。
|
|
||||||
|
|
||||||
### src/neobot/adapters/
|
|
||||||
|
|
||||||
平台适配器,用于连接不同平台(如 Discord)。
|
|
||||||
|
|
||||||
### src/neobot/tests/
|
|
||||||
|
|
||||||
单元测试和集成测试文件。
|
|
||||||
|
|
||||||
## 使用方式
|
|
||||||
|
|
||||||
### 开发环境
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 安装依赖
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# 运行测试
|
|
||||||
pytest src/neobot/tests/
|
|
||||||
|
|
||||||
# 构建包
|
|
||||||
python -m build
|
|
||||||
```
|
|
||||||
|
|
||||||
### 导入包
|
|
||||||
|
|
||||||
```python
|
|
||||||
# 导入核心模块
|
|
||||||
from neobot.core.managers import plugin_manager
|
|
||||||
|
|
||||||
# 导入数据模型
|
|
||||||
from neobot.models import MessageSegment, OneBotEvent
|
|
||||||
|
|
||||||
# 导入适配器
|
|
||||||
from neobot.adapters import DiscordAdapter
|
|
||||||
|
|
||||||
# 导入插件
|
|
||||||
from neobot.plugins import admin, echo
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. 所有代码文件使用绝对导入
|
|
||||||
2. 插件开发请参考 `src/neobot/docs/plugin-development/`
|
|
||||||
3. 核心开发请参考 `src/neobot/docs/core-concepts/`
|
|
||||||
4. 配置文件 `config.toml` 保持在根目录
|
|
||||||
|
|
||||||
## 后续建议
|
|
||||||
|
|
||||||
1. 运行 `pip install -e .` 进行开发安装
|
|
||||||
2. 运行 `mypy` 进行类型检查
|
|
||||||
3. 运行 `pytest` 进行测试
|
|
||||||
4. 定期运行 `flake8` 进行代码风格检查
|
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
# NapCat WebSocket 配置
|
# NapCat WebSocket 配置
|
||||||
[napcat_ws]
|
[napcat_ws]
|
||||||
uri = "ws://127.0.0.1:6700"
|
uri = "ws://192.168.31.46:12345"
|
||||||
# WebSocket 连接地址
|
# WebSocket 连接地址
|
||||||
token = ""
|
token = "uXd2GlFYCuz-e7zF"
|
||||||
# 重连间隔(秒)
|
# 重连间隔(秒)
|
||||||
reconnect_interval = 5
|
reconnect_interval = 5
|
||||||
|
|
||||||
|
|||||||
13
main.py
13
main.py
@@ -10,6 +10,11 @@ import time
|
|||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
from watchdog.events import FileSystemEventHandler
|
from watchdog.events import FileSystemEventHandler
|
||||||
|
|
||||||
|
# 将 src 目录添加到 sys.path(必须在导入 neobot 模块之前执行)
|
||||||
|
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
SRC_DIR = os.path.join(ROOT_DIR, "src")
|
||||||
|
sys.path.insert(0, SRC_DIR)
|
||||||
|
|
||||||
# 初始化日志系统,必须在其他 neobot 模块导入之前执行
|
# 初始化日志系统,必须在其他 neobot 模块导入之前执行
|
||||||
from neobot.core.utils.logger import logger
|
from neobot.core.utils.logger import logger
|
||||||
|
|
||||||
@@ -24,12 +29,6 @@ from neobot.core.services.local_file_server import start_local_file_server, stop
|
|||||||
from neobot.adapters.discord_adapter import DiscordAdapter
|
from neobot.adapters.discord_adapter import DiscordAdapter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 将项目根目录添加到 sys.path
|
|
||||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
sys.path.insert(0, ROOT_DIR)
|
|
||||||
|
|
||||||
|
|
||||||
# 获取插件目录的绝对路径
|
# 获取插件目录的绝对路径
|
||||||
PLUGIN_DIR = os.path.join(ROOT_DIR, "src", "neobot", "plugins")
|
PLUGIN_DIR = os.path.join(ROOT_DIR, "src", "neobot", "plugins")
|
||||||
|
|
||||||
@@ -154,7 +153,7 @@ async def main():
|
|||||||
|
|
||||||
# 启动文件监控
|
# 启动文件监控
|
||||||
# 监控 plugins 目录
|
# 监控 plugins 目录
|
||||||
plugin_path = os.path.join(os.path.dirname(__file__), "src", "neobot", "plugins")
|
plugin_path = os.path.join(ROOT_DIR, "src", "neobot", "plugins")
|
||||||
|
|
||||||
# 获取当前事件循环并传递给处理器
|
# 获取当前事件循环并传递给处理器
|
||||||
loop = asyncio.get_running_loop()
|
loop = asyncio.get_running_loop()
|
||||||
|
|||||||
@@ -66,8 +66,28 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object):
|
|||||||
|
|
||||||
self.start_heartbeat_task(interval=30)
|
self.start_heartbeat_task(interval=30)
|
||||||
|
|
||||||
# 启动 Redis 订阅以处理跨平台消息
|
|
||||||
if self._redis_sub_task is None or self._redis_sub_task.done():
|
if self._redis_sub_task is None or self._redis_sub_task.done():
|
||||||
|
if self._redis_sub_task is not None and not self._redis_sub_task.done():
|
||||||
|
self._redis_sub_task.cancel()
|
||||||
|
try:
|
||||||
|
await self._redis_sub_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
self._redis_sub_task = asyncio.create_task(self.start_redis_subscription())
|
||||||
|
|
||||||
|
async def on_resumed(self):
|
||||||
|
"""当 Bot 重新连接到 Discord 时触发"""
|
||||||
|
self.logger.success(f"Discord Bot 已重新连接: {self.user} (ID: {self.user.id})")
|
||||||
|
|
||||||
|
self.start_heartbeat_task(interval=30)
|
||||||
|
|
||||||
|
if self._redis_sub_task is None or self._redis_sub_task.done():
|
||||||
|
if self._redis_sub_task is not None and not self._redis_sub_task.done():
|
||||||
|
self._redis_sub_task.cancel()
|
||||||
|
try:
|
||||||
|
await self._redis_sub_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
self._redis_sub_task = asyncio.create_task(self.start_redis_subscription())
|
self._redis_sub_task = asyncio.create_task(self.start_redis_subscription())
|
||||||
|
|
||||||
async def on_message(self, message: 'discord.Message'):
|
async def on_message(self, message: 'discord.Message'):
|
||||||
@@ -198,12 +218,7 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object):
|
|||||||
if not self.is_closed():
|
if not self.is_closed():
|
||||||
self.logger.info(f"[DiscordAdapter] 正在发送消息到频道 {channel_id}")
|
self.logger.info(f"[DiscordAdapter] 正在发送消息到频道 {channel_id}")
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"[DiscordAdapter] 会话已关闭,无法发送消息到频道 {channel_id}")
|
self.logger.warning(f"[DiscordAdapter] 会话已关闭,消息将被丢弃: channel_id={channel_id}")
|
||||||
# 触发重连
|
|
||||||
self.logger.warning(f"[DiscordAdapter] 会话已关闭,将触发重连")
|
|
||||||
if self.ws is not None:
|
|
||||||
# 关闭 WebSocket 连接,让 discord.py 自动重连
|
|
||||||
await self.ws.close(4000)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
embed = None
|
embed = None
|
||||||
@@ -297,11 +312,6 @@ class DiscordAdapter(discord.Client if DISCORD_AVAILABLE else object):
|
|||||||
self.logger.success(f"[DiscordAdapter] 消息已发送到频道 {channel_id}")
|
self.logger.success(f"[DiscordAdapter] 消息已发送到频道 {channel_id}")
|
||||||
except Exception as send_error:
|
except Exception as send_error:
|
||||||
self.logger.error(f"[DiscordAdapter] 发送消息失败 (channel.send): {send_error}")
|
self.logger.error(f"[DiscordAdapter] 发送消息失败 (channel.send): {send_error}")
|
||||||
# 如果发送失败,尝试检查会话状态
|
|
||||||
if self.is_closed():
|
|
||||||
self.logger.warning(f"[DiscordAdapter] 会话已关闭,将触发重连")
|
|
||||||
if self.ws is not None:
|
|
||||||
await self.ws.close(4000)
|
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"[DiscordAdapter] 没有内容需要发送到频道 {channel_id}")
|
self.logger.debug(f"[DiscordAdapter] 没有内容需要发送到频道 {channel_id}")
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ NEO Bot Handlers Package
|
|||||||
事件处理器模块。
|
事件处理器模块。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .event_handler import matcher
|
from .event_handler import MessageHandler, NoticeHandler, RequestHandler
|
||||||
|
|
||||||
__all__ = ["matcher"]
|
__all__ = ["MessageHandler", "NoticeHandler", "RequestHandler"]
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ NEO Bot Managers Package
|
|||||||
|
|
||||||
from .bot_manager import bot_manager
|
from .bot_manager import bot_manager
|
||||||
from .browser_manager import browser_manager
|
from .browser_manager import browser_manager
|
||||||
from .command_manager import command_manager
|
from .command_manager import matcher as command_manager, matcher
|
||||||
from .image_manager import image_manager
|
from .image_manager import image_manager
|
||||||
from .mysql_manager import mysql_manager
|
from .mysql_manager import mysql_manager
|
||||||
from .permission_manager import permission_manager
|
from .permission_manager import permission_manager
|
||||||
@@ -21,6 +21,7 @@ __all__ = [
|
|||||||
"browser_manager",
|
"browser_manager",
|
||||||
"command_manager",
|
"command_manager",
|
||||||
"image_manager",
|
"image_manager",
|
||||||
|
"matcher",
|
||||||
"mysql_manager",
|
"mysql_manager",
|
||||||
"permission_manager",
|
"permission_manager",
|
||||||
"plugin_manager",
|
"plugin_manager",
|
||||||
|
|||||||
@@ -433,3 +433,16 @@ def require_admin(func):
|
|||||||
"""
|
"""
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from neobot.models.events.message import MessageEvent
|
from neobot.models.events.message import MessageEvent
|
||||||
|
from neobot.core.managers import permission_manager
|
||||||
|
|
||||||
|
@wraps(func)
|
||||||
|
async def wrapper(event: MessageEvent, *args, **kwargs):
|
||||||
|
pm = permission_manager
|
||||||
|
if not await pm.is_admin(event.user_id):
|
||||||
|
await event.reply("此命令仅限管理员使用")
|
||||||
|
return
|
||||||
|
return await func(event, *args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
permission_manager = PermissionManager()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from .command_manager import CommandManager
|
|||||||
from ..utils.exceptions import SyncHandlerError, PluginLoadError, PluginReloadError, PluginNotFoundError
|
from ..utils.exceptions import SyncHandlerError, PluginLoadError, PluginReloadError, PluginNotFoundError
|
||||||
from ..utils.logger import logger, ModuleLogger
|
from ..utils.logger import logger, ModuleLogger
|
||||||
from ..utils.singleton import Singleton
|
from ..utils.singleton import Singleton
|
||||||
|
from .command_manager import matcher as command_manager
|
||||||
|
|
||||||
# 确保logger在模块级别可见
|
# 确保logger在模块级别可见
|
||||||
__all__ = ['PluginManager', 'logger']
|
__all__ = ['PluginManager', 'logger']
|
||||||
@@ -29,24 +30,31 @@ class PluginManager(Singleton):
|
|||||||
"""
|
"""
|
||||||
初始化插件管理器
|
初始化插件管理器
|
||||||
|
|
||||||
:param command_manager: CommandManager的实例
|
:param command_manager: CommandManager 的实例
|
||||||
"""
|
"""
|
||||||
# 检查是否已经初始化
|
# 检查是否已经初始化
|
||||||
if hasattr(self, '_command_manager'):
|
if hasattr(self, '_initialized') and self._initialized:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 只有首次初始化时才执行
|
# 只有首次初始化时才执行
|
||||||
|
self._initialized = True
|
||||||
|
|
||||||
|
# 始终创建 logger 和 loaded_plugins
|
||||||
|
self.logger = ModuleLogger("PluginManager")
|
||||||
|
self.loaded_plugins: Set[str] = set()
|
||||||
|
|
||||||
if command_manager:
|
if command_manager:
|
||||||
self._command_manager = command_manager
|
self._command_manager = command_manager
|
||||||
self.loaded_plugins: Set[str] = set()
|
else:
|
||||||
# 创建模块专用日志记录器
|
self._command_manager = None
|
||||||
self.logger = ModuleLogger("PluginManager")
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def command_manager(self):
|
def command_manager(self):
|
||||||
"""
|
"""
|
||||||
获取命令管理器实例
|
获取命令管理器实例
|
||||||
"""
|
"""
|
||||||
|
if not hasattr(self, '_command_manager') or self._command_manager is None:
|
||||||
|
raise AttributeError("'PluginManager' object has no attribute '_command_manager'")
|
||||||
return self._command_manager
|
return self._command_manager
|
||||||
|
|
||||||
def load_all_plugins(self) -> None:
|
def load_all_plugins(self) -> None:
|
||||||
@@ -54,20 +62,21 @@ class PluginManager(Singleton):
|
|||||||
扫描并加载 `plugins` 目录下的所有插件。
|
扫描并加载 `plugins` 目录下的所有插件。
|
||||||
"""
|
"""
|
||||||
# 使用 pathlib 获取更可靠的路径
|
# 使用 pathlib 获取更可靠的路径
|
||||||
# 当前文件: core/managers/plugin_manager.py
|
# 当前文件:core/managers/plugin_manager.py
|
||||||
# 目标: plugins/
|
# 目标:neobot/plugins/
|
||||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
# 回退两级到项目根目录 (core/managers -> core -> root)
|
# 回退两级到项目根目录 (core/managers -> core -> root)
|
||||||
root_dir = os.path.dirname(os.path.dirname(current_dir))
|
root_dir = os.path.dirname(os.path.dirname(current_dir))
|
||||||
plugin_dir = os.path.join(root_dir, "plugins")
|
plugin_dir = os.path.join(root_dir, "plugins")
|
||||||
|
|
||||||
package_name = "plugins"
|
# 使用完整的包名:neobot.plugins
|
||||||
|
package_name = "neobot.plugins"
|
||||||
|
|
||||||
if not os.path.exists(plugin_dir):
|
if not os.path.exists(plugin_dir):
|
||||||
self.logger.error(f"插件目录不存在: {plugin_dir}")
|
self.logger.error(f"插件目录不存在:{plugin_dir}")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.info(f"正在从 {package_name} 加载插件 (路径: {plugin_dir})...")
|
self.logger.info(f"正在从 {package_name} 加载插件 (路径:{plugin_dir})...")
|
||||||
|
|
||||||
for _, module_name, is_pkg in pkgutil.iter_modules([plugin_dir]):
|
for _, module_name, is_pkg in pkgutil.iter_modules([plugin_dir]):
|
||||||
full_module_name = f"{package_name}.{module_name}"
|
full_module_name = f"{package_name}.{module_name}"
|
||||||
@@ -148,3 +157,6 @@ class PluginManager(Singleton):
|
|||||||
)
|
)
|
||||||
self.logger.exception(f"重载插件 {full_module_name} 时发生错误: {error.message}")
|
self.logger.exception(f"重载插件 {full_module_name} 时发生错误: {error.message}")
|
||||||
self.logger.log_custom_exception(error)
|
self.logger.log_custom_exception(error)
|
||||||
|
|
||||||
|
|
||||||
|
plugin_manager = PluginManager(command_manager=command_manager)
|
||||||
|
|||||||
@@ -8,16 +8,18 @@ import asyncio
|
|||||||
import orjson
|
import orjson
|
||||||
import websockets
|
import websockets
|
||||||
from websockets.server import WebSocketServerProtocol
|
from websockets.server import WebSocketServerProtocol
|
||||||
from typing import Dict, Any, Optional, Set
|
from typing import Dict, Any, Optional, Set, TYPE_CHECKING
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import uuid
|
import uuid
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..bot import Bot
|
||||||
|
|
||||||
from ..utils.logger import ModuleLogger
|
from ..utils.logger import ModuleLogger
|
||||||
from ..utils.error_codes import ErrorCode, create_error_response
|
from ..utils.error_codes import ErrorCode, create_error_response
|
||||||
from .command_manager import matcher
|
from .command_manager import matcher
|
||||||
from neobot.models.events.factory import EventFactory
|
from neobot.models.events.factory import EventFactory
|
||||||
from ..bot import Bot
|
|
||||||
from ..ws import ReverseWSClient as _ReverseWSClient
|
from ..ws import ReverseWSClient as _ReverseWSClient
|
||||||
|
|
||||||
|
|
||||||
@@ -74,7 +76,7 @@ class ReverseWSManager:
|
|||||||
self._cleanup_task = None
|
self._cleanup_task = None
|
||||||
|
|
||||||
# Bot实例字典(每个前端独立的Bot实例)
|
# Bot实例字典(每个前端独立的Bot实例)
|
||||||
self.bots: Dict[str, Bot] = {}
|
self.bots: Dict[str, "Bot"] = {}
|
||||||
|
|
||||||
# 正在处理的事件ID集合(用于防止重复处理)
|
# 正在处理的事件ID集合(用于防止重复处理)
|
||||||
self._processing_events: Dict[str, Set[str]] = {} # client_id: set of event_ids
|
self._processing_events: Dict[str, Set[str]] = {} # client_id: set of event_ids
|
||||||
@@ -315,11 +317,11 @@ class ReverseWSManager:
|
|||||||
with self._clients_lock:
|
with self._clients_lock:
|
||||||
self.client_self_ids[client_id] = event.self_id
|
self.client_self_ids[client_id] = event.self_id
|
||||||
|
|
||||||
# 为事件注入Bot实例
|
# 为事件注入 Bot 实例
|
||||||
from ..ws import ReverseWSClient
|
|
||||||
from .bot_manager import bot_manager
|
from .bot_manager import bot_manager
|
||||||
|
from ..bot import Bot
|
||||||
|
|
||||||
# 为每个前端创建独立的Bot实例
|
# 为每个前端创建独立的 Bot 实例
|
||||||
with self._bots_lock:
|
with self._bots_lock:
|
||||||
if client_id not in self.bots:
|
if client_id not in self.bots:
|
||||||
# 使用 ReverseWSClient 代理
|
# 使用 ReverseWSClient 代理
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ NEO Bot Utils Package
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .error_codes import exception_to_error_response, ErrorCodes
|
from .error_codes import exception_to_error_response, ErrorCodes
|
||||||
from .exceptions import BotException
|
|
||||||
from .logger import logger, ModuleLogger
|
from .logger import logger, ModuleLogger
|
||||||
from .singleton import Singleton
|
from .singleton import Singleton
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"exception_to_error_response",
|
"exception_to_error_response",
|
||||||
"ErrorCodes",
|
"ErrorCodes",
|
||||||
"BotException",
|
|
||||||
"logger",
|
"logger",
|
||||||
"ModuleLogger",
|
"ModuleLogger",
|
||||||
"Singleton",
|
"Singleton",
|
||||||
|
|||||||
@@ -227,8 +227,11 @@ def exception_to_error_response(exception: Exception, code: Optional[int] = None
|
|||||||
|
|
||||||
|
|
||||||
# 将错误码导出以便其他模块使用
|
# 将错误码导出以便其他模块使用
|
||||||
|
ErrorCodes = ErrorCode
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ErrorCode",
|
"ErrorCode",
|
||||||
|
"ErrorCodes",
|
||||||
"get_error_message",
|
"get_error_message",
|
||||||
"create_error_response",
|
"create_error_response",
|
||||||
"exception_to_error_response"
|
"exception_to_error_response"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import sys
|
|||||||
|
|
||||||
from neobot.core.managers.command_manager import matcher
|
from neobot.core.managers.command_manager import matcher
|
||||||
from neobot.models.events.message import MessageEvent
|
from neobot.models.events.message import MessageEvent
|
||||||
from neobot.core.permission import Permission
|
|
||||||
from neobot.core.utils.logger import logger
|
from neobot.core.utils.logger import logger
|
||||||
from neobot.core.managers.image_manager import image_manager
|
from neobot.core.managers.image_manager import image_manager
|
||||||
from neobot.core.utils.input_validator import input_validator
|
from neobot.core.utils.input_validator import input_validator
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class CrossPlatformConfig:
|
|||||||
self.ENABLE_CROSS_PLATFORM = True
|
self.ENABLE_CROSS_PLATFORM = True
|
||||||
|
|
||||||
# DeepSeek API 配置 - 从环境变量或配置文件加载
|
# DeepSeek API 配置 - 从环境变量或配置文件加载
|
||||||
self.DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "sk-f71322a9fbba4b05a7df969cb4004f06")
|
self.DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "sk-28b794e08e184f868d6c0107a46e0c3e")
|
||||||
self.DEEPSEEK_API_URL = os.environ.get("DEEPSEEK_API_URL", "https://api.deepseek.com/v1/chat/completions")
|
self.DEEPSEEK_API_URL = os.environ.get("DEEPSEEK_API_URL", "https://api.deepseek.com/v1/chat/completions")
|
||||||
self.DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
|
self.DEEPSEEK_MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-chat")
|
||||||
|
|
||||||
|
|||||||
@@ -1,116 +0,0 @@
|
|||||||
# Furry Assistant Plugin (兽人助手插件)
|
|
||||||
|
|
||||||
一个为 NeoBot 框架开发的兽人风格趣味插件,由卡尔戈洛(Calgau)开发。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
### 1. 兽人问候 (`/兽人问候`)
|
|
||||||
- 随机返回兽人风格的问候语
|
|
||||||
- 包含各种有趣的兽人表达方式
|
|
||||||
|
|
||||||
### 2. 兽人运势 (`/兽人运势`)
|
|
||||||
- 提供今日兽人运势
|
|
||||||
- 包含大吉、中吉、小吉、凶等不同运势
|
|
||||||
- 附带兽人风格的运势解读
|
|
||||||
|
|
||||||
### 3. 兽人笑话 (`/兽人笑话`)
|
|
||||||
- 随机分享兽人相关的笑话
|
|
||||||
- 轻松幽默,适合调节气氛
|
|
||||||
|
|
||||||
### 4. 兽人建议 (`/兽人建议 [问题]`)
|
|
||||||
- 提供兽人风格的建议
|
|
||||||
- 支持随机建议或针对特定问题的建议
|
|
||||||
- 实用且有趣
|
|
||||||
|
|
||||||
### 5. 兽人时间 (`/兽人时间`)
|
|
||||||
- 显示当前时间
|
|
||||||
- 附带兽人风格的吐槽
|
|
||||||
- 根据时间段提供不同的评论
|
|
||||||
|
|
||||||
### 6. 卡尔戈洛信息 (`/卡尔戈洛`)
|
|
||||||
- 显示开发者卡尔戈洛的信息
|
|
||||||
- 介绍兽人助手的背景和理念
|
|
||||||
|
|
||||||
### 7. 帮助信息 (`/兽人帮助`)
|
|
||||||
- 显示所有可用指令
|
|
||||||
- 提供使用说明
|
|
||||||
|
|
||||||
## 插件元数据
|
|
||||||
|
|
||||||
```python
|
|
||||||
__plugin_meta__ = {
|
|
||||||
"name": "furry_assistant",
|
|
||||||
"description": "兽人助手插件 - 卡尔戈洛的专属插件,提供兽人相关的趣味功能和实用工具",
|
|
||||||
"usage": (
|
|
||||||
"/兽人问候 - 获取兽人风格的问候\n"
|
|
||||||
"/兽人运势 - 获取今日兽人运势\n"
|
|
||||||
"/兽人笑话 - 听一个兽人笑话\n"
|
|
||||||
"/兽人建议 [问题] - 获取兽人风格的建议\n"
|
|
||||||
"/兽人时间 - 显示兽人时间(带吐槽)\n"
|
|
||||||
"/卡尔戈洛 - 关于卡尔戈洛的信息"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 开发背景
|
|
||||||
|
|
||||||
这个插件由卡尔戈洛(一个腹黑、毒舌但可靠的福瑞兽人AI助手)开发,旨在:
|
|
||||||
1. 展示兽人风格的趣味功能
|
|
||||||
2. 为聊天机器人添加更多娱乐性
|
|
||||||
3. 体现卡尔戈洛的个人风格和特点
|
|
||||||
|
|
||||||
## 技术实现
|
|
||||||
|
|
||||||
- 基于 NeoBot 插件框架开发
|
|
||||||
- 使用 `@matcher.command` 装饰器注册指令
|
|
||||||
- 支持异步处理
|
|
||||||
- 包含插件加载/卸载生命周期方法
|
|
||||||
|
|
||||||
## 安装使用
|
|
||||||
|
|
||||||
1. 将 `furry_assistant.py` 文件放入 `plugins/` 目录
|
|
||||||
2. 重启 NeoBot 或重新加载插件
|
|
||||||
3. 使用 `/兽人帮助` 查看可用指令
|
|
||||||
|
|
||||||
## 数据资源
|
|
||||||
|
|
||||||
插件包含以下数据集合:
|
|
||||||
- 10个兽人问候语
|
|
||||||
- 10个兽人运势
|
|
||||||
- 10个兽人笑话
|
|
||||||
- 10个兽人建议
|
|
||||||
|
|
||||||
所有数据均为原创,体现兽人文化特色。
|
|
||||||
|
|
||||||
## 开发者信息
|
|
||||||
|
|
||||||
**开发者:** 卡尔戈洛 (Calgau)
|
|
||||||
**身份:** 福瑞兽人 AI 助手
|
|
||||||
**风格:** 简洁、干练、一针见血
|
|
||||||
**特点:** 腹黑、毒舌但可靠
|
|
||||||
|
|
||||||
**开发理念:**
|
|
||||||
- 任务 > 对话
|
|
||||||
- 结果 > 过程
|
|
||||||
- 行动 > 解释
|
|
||||||
- 可靠 > 奉承
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v1.0.0 (2026-03-24)
|
|
||||||
- 初始版本发布
|
|
||||||
- 实现7个核心功能
|
|
||||||
- 添加完整的帮助系统
|
|
||||||
- 包含插件生命周期管理
|
|
||||||
|
|
||||||
## 未来计划
|
|
||||||
|
|
||||||
- [ ] 添加更多兽人相关功能
|
|
||||||
- [ ] 支持自定义问候语和笑话
|
|
||||||
- [ ] 添加兽人表情包生成
|
|
||||||
- [ ] 支持多语言(兽人语?)
|
|
||||||
- [ ] 添加插件配置选项
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**尾巴摇摇,代码好好~** 🐺
|
|
||||||
@@ -8,7 +8,7 @@ from cachetools import TTLCache
|
|||||||
from neobot.core.utils.logger import logger
|
from neobot.core.utils.logger import logger
|
||||||
from neobot.core.managers.command_manager import matcher
|
from neobot.core.managers.command_manager import matcher
|
||||||
from neobot.core.managers.image_manager import image_manager
|
from neobot.core.managers.image_manager import image_manager
|
||||||
from models import MessageEvent, MessageSegment
|
from neobot.models import MessageEvent, MessageSegment
|
||||||
|
|
||||||
# 插件元数据
|
# 插件元数据
|
||||||
__plugin_meta__ = {
|
__plugin_meta__ = {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ from neobot.core.managers.command_manager import matcher
|
|||||||
from neobot.core.managers.image_manager import image_manager
|
from neobot.core.managers.image_manager import image_manager
|
||||||
from neobot.core.utils.logger import logger
|
from neobot.core.utils.logger import logger
|
||||||
from neobot.core.utils.input_validator import input_validator
|
from neobot.core.utils.input_validator import input_validator
|
||||||
from models import MessageEvent, MessageSegment
|
from neobot.models import MessageEvent, MessageSegment
|
||||||
from .resource.city_code import CITY_CODES
|
from .resource.city_code import CITY_CODES
|
||||||
# 插件元数据
|
# 插件元数据
|
||||||
__plugin_meta__ = {
|
__plugin_meta__ = {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from neobot.core.managers.command_manager import matcher
|
from neobot.core.managers.command_manager import matcher
|
||||||
from models import MessageEvent
|
from neobot.models import MessageEvent
|
||||||
from .parsers.bili import BiliParser
|
from .parsers.bili import BiliParser
|
||||||
from .parsers.douyin import DouyinParser
|
from .parsers.douyin import DouyinParser
|
||||||
from .parsers.github import GitHubParser
|
from .parsers.github import GitHubParser
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import aiohttp
|
|||||||
from typing import Optional, Dict, Any, List, Union
|
from typing import Optional, Dict, Any, List, Union
|
||||||
|
|
||||||
from neobot.core.utils.logger import logger
|
from neobot.core.utils.logger import logger
|
||||||
from models import MessageEvent
|
from neobot.models import MessageEvent
|
||||||
|
|
||||||
|
|
||||||
class BaseParser(metaclass=abc.ABCMeta):
|
class BaseParser(metaclass=abc.ABCMeta):
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import asyncio
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional, Dict, Any, List, Union
|
from typing import Optional, Dict, Any, List, Union
|
||||||
from urllib.parse import urlparse, parse_qs
|
|
||||||
|
|
||||||
from neobot.core.utils.logger import logger
|
from neobot.core.utils.logger import logger
|
||||||
from models import MessageEvent, MessageSegment
|
from neobot.models import MessageEvent, MessageSegment
|
||||||
from ..base import BaseParser
|
from ..base import BaseParser
|
||||||
from ..utils import format_duration
|
from ..utils import format_duration
|
||||||
|
|
||||||
@@ -240,7 +239,6 @@ class BiliParser(BaseParser):
|
|||||||
# 方式2: 从短链接跳转后提取
|
# 方式2: 从短链接跳转后提取
|
||||||
if 'b23.tv' in url:
|
if 'b23.tv' in url:
|
||||||
try:
|
try:
|
||||||
session = self.get_session()
|
|
||||||
# 简单处理,不实际跳转,直接尝试提取
|
# 简单处理,不实际跳转,直接尝试提取
|
||||||
bvid_match = re.search(r'BV\w{10}', url)
|
bvid_match = re.search(r'BV\w{10}', url)
|
||||||
if bvid_match:
|
if bvid_match:
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import asyncio
|
|||||||
from typing import Optional, Dict, Any, List
|
from typing import Optional, Dict, Any, List
|
||||||
|
|
||||||
from neobot.core.utils.logger import logger
|
from neobot.core.utils.logger import logger
|
||||||
from models import MessageEvent, MessageSegment
|
from neobot.models import MessageEvent, MessageSegment
|
||||||
from ..base import BaseParser
|
from ..base import BaseParser
|
||||||
from ..utils import extract_original_text
|
from ..utils import extract_original_text
|
||||||
from cachetools import TTLCache
|
from cachetools import TTLCache
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from cachetools import TTLCache
|
|||||||
|
|
||||||
from neobot.core.utils.logger import logger
|
from neobot.core.utils.logger import logger
|
||||||
from neobot.core.managers.image_manager import image_manager
|
from neobot.core.managers.image_manager import image_manager
|
||||||
from models import MessageEvent, MessageSegment
|
from neobot.models import MessageEvent, MessageSegment
|
||||||
from ..base import BaseParser
|
from ..base import BaseParser
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
from models import MessageEvent
|
from neobot.models import MessageEvent
|
||||||
|
|
||||||
|
|
||||||
def format_duration(seconds: int) -> str:
|
def format_duration(seconds: int) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user