feat: 添加性能优化和架构文档,更新依赖和核心模块

refactor(browser_manager): 实现页面池机制以提升性能
refactor(image_manager): 添加模板缓存并集成页面池
refactor(bili_parser): 迁移到异步HTTP请求并实现会话复用
docs: 新增性能优化、架构设计和最佳实践文档
chore: 更新requirements.txt添加新依赖
This commit is contained in:
2026-01-13 03:56:31 +08:00
parent 5996f6eeaf
commit 24af862924
18 changed files with 589 additions and 489 deletions

419
README.md
View File

@@ -1,4 +1,4 @@
# Calglau BOT by NEO Bot Framework # Calglau BOT by NEO Bot Framework
> **[INTERNAL USE ONLY]** > **[INTERNAL USE ONLY]**
> >
@@ -6,390 +6,71 @@
**Powered by NEO Bot Framework** **Powered by NEO Bot Framework**
## 📖 项目概述 ## 项目概述
**Calglau BOT** 是一个基于 NEO Bot Framework 构建的、功能丰富的 QQ 机器人。它被设计为一个模块化、易于扩展的内部工具,通过插件化的方式集成了多种实用与娱乐功能 **Calglau BOT** 是一个基于 NEO Bot Framework 构建的高性能 QQ 机器人。别指望这里有什么花里胡哨的废话,这就是一个为了解决实际问题而生的工具。我们用最硬核的技术栈,解决最麻烦的社群管理和自动化需求
本项目旨在提供一个稳定、高性能且开发体验优秀的机器人平台,服务于我们的社群管理和日常自动化需求 简单来说:它很快,很稳,而且不挑食
### 核心特性 ### 核心特性
> **[INTERNAL USE ONLY]**
>
> 本仓库为 Calglau BOT 的内部开发版本,请遵守相关保密协议。
**Powered by NEO Bot Framework** * **模块化插件架构**:所有功能都在 `plugins/` 目录里躺着。想加功能?写个 Python 文件扔进去就行。支持热重载,改完代码直接生效,不用重启,不用中断服务。
* **极致性能优化**
* **Python 3.14 JIT**:我们直接上了最新的 Python 版本,开启 JIT 即时编译,速度起飞。
* **Mypyc 编译**:核心模块直接编译成 C 扩展,拒绝解释器的龟速。
* **Playwright 页面池**:浏览器页面预热池,渲染图片零等待。别再问为什么发图这么快了。
* **全局连接复用**HTTP 和 Redis 连接池化管理,拒绝重复握手浪费时间。
* **开发者友好**:完整的类型提示,清晰的 API 设计。写代码就该是种享受,而不是在屎山里游泳。
* **集成 Redis 缓存**:能缓存的都缓存了。群信息、用户信息、帮助图片,绝不让数据库多喘一口气。
* **正向 WebSocket 连接**:保持最简单的连接方式,只要能上网就能跑,不需要公网 IP不需要内网穿透。
## 📖 项目概述 ### 技术栈
**Calglau BOT** 是一个基于 NEO Bot Framework 构建的、功能丰富的 QQ 机器人。它被设计为一个模块化、易于扩展的内部工具,通过插件化的方式集成了多种实用与娱乐功能。 * **核心框架**: Python 3.14 (JIT Enabled) & NEO Bot Framework
* **编译优化**: Mypyc (C Extension)
本项目旨在提供一个稳定、高性能且开发体验优秀的机器人平台,服务于我们的社群管理和日常自动化需求。 * **异步核心**: `asyncio` + `uvloop` (Linux) / 原生 Loop (Windows)
* **网络通信**: `websockets` (OneBot v11), `aiohttp` (Shared Session)
### ✨ 核心特性 * **浏览器引擎**: `Playwright` (Chromium) + Page Pool
* **数据序列化**: `orjson` (比标准库快 N 倍)
* **模块化插件架构**:所有功能均以独立插件形式存在于 `plugins/` 目录,易于开发、维护和热重载。
* **高性能异步核心**:基于 `asyncio``websockets`,确保在高并发消息下依然响应迅速。
* **开发者友好**:内置插件热重载,修改代码无需重启;完整的类型提示和清晰的 API 设计,提升开发效率。
* **集成 Redis 缓存**:自动缓存常用 API 调用(如群信息),减少重复请求,提升响应速度。
* **内置帮助系统**:通过 `/help` 指令可自动生成并展示所有已加载插件的功能说明。
### 🛠️ 技术栈
* **核心框架**: Python 3.8+ & NEO Bot Framework
* **异步库**: `asyncio`
* **网络通信**: `websockets` (OneBot v11)
* **缓存**: `Redis` * **缓存**: `Redis`
* **日志**: `Loguru` * **日志**: `Loguru`
* **文件监控**: `watchdog` (用于热重载)
---
* **模块化插件架构**:所有功能均以独立插件形式存在于 `plugins/` 目录,易于开发、维护和热重载。
* **高性能异步核心**:基于 `asyncio``websockets`,确保在高并发消息下依然响应迅速。
* **开发者友好**:内置插件热重载,修改代码无需重启;完整的类型提示和清晰的 API 设计,提升开发效率。
* **集成 Redis 缓存**:自动缓存常用 API 调用(如群信息),减少重复请求,提升响应速度。
* **内置帮助系统**:通过 `/help` 指令可自动生成并展示所有已加载插件的功能说明。
### 🛠️ 技术栈
* **核心框架**: Python 3.8+ & NEO Bot Framework
* **异步库**: `asyncio`
* **网络通信**: `websockets` (OneBot v11)
* **缓存**: `Redis`
* **日志**: `Loguru`
* **文件监控**: `watchdog` (用于热重载)
--- ---
## 📂 项目结构 ## 项目结构
``` ```
. .
├── plugins/ # 插件目录,所有机器人的功能模块都在这 ├── plugins/ # 插件目录,业务逻辑都在这
│ ├── admin.py │ ├── admin.py # 管理员指令
│ ├── bili_parser.py │ ├── bili_parser.py # B站解析 (高性能版)
│ ├── code_py.py │ ├── code_py.py # 代码沙箱
│ ├── echo.py │ ├── echo.py # 复读机
│ ├── forward_test.py │ ├── forward_test.py # 合并转发测试
│ ├── jrcd.py │ ├── jrcd.py # 今日运势
│ └── thpic.py │ └── thpic.py # 东方图片
├── core/ # NEO 框架核心代码,通常无需修改 ├── core/ # 框架核心,非请勿动
│ ├── api/ │ ├── api/ # OneBot API 封装
│ ├── data/ # 数据存储目录 (管理员列表, 权限配置) │ ├── managers/ # 各种管理器 (指令, 浏览器, 图片, 插件)
│ ├── admin.json │ ├── utils/ # 工具函数
│ └── permissions.json ├── ws.py # WebSocket 通信层 (已编译)
── bot.py ── bot.py # Bot 实例
│ ├── ... ├── data/ # 数据存储
── ws.py ── admin.json # 管理员名单
├── html/ # 静态网页文件 │ └── permissions.json # 权限配置
├── plugins/ # 插件目录,所有机器人的功能模块都在这里 ├── templates/ # Jinja2 模板
│ ├── admin.py ├── setup_mypyc.py # 编译脚本
│ ├── bili_parser.py └── main.py # 启动入口
│ ├── code_py.py
│ ├── echo.py
│ ├── forward_test.py
│ ├── jrcd.py
│ └── thpic.py
├── core/ # NEO 框架核心代码,通常无需修改
│ ├── api/
│ ├── bot.py
│ ├── ...
│ └── ws.py
├── data/ # 数据存储目录 (管理员列表, 权限配置)
│ ├── admin.json
│ └── permissions.json
├── html/ # 静态网页文件
│ ├── 404.html
│ └── index.html
├── models/ # 数据模型 (事件, 消息段等)
│ ├── ...
├── models/ # 数据模型 (事件, 消息段等)
│ ├── ...
├── .gitignore
├── config.toml # 主配置文件
├── main.py # 项目启动入口
└── requirements.txt # Python 依赖
``` ```
--- ## 快速开始
## 📚 详细开发文档 别废话,直接跑起来。
**想要深入了解框架的工作原理或开发更复杂的插件?** 1. **装环境**: Python 3.14Redis还有个 OneBot 客户端 (推荐 NapCat)。
2. **装依赖**: `pip install -r requirements.txt`
3. **装浏览器**: `playwright install chromium`
4. **编译核心 (可选)**: `python setup_mypyc.py build_ext --inplace`
5. **启动**: `python -X jit main.py`
👉 **[点击这里,查阅完整的开发文档](./docs/index.md)** 详细文档去 `docs/` 目录看,别什么都问我。
---
## 🚀 快速开始
### 1. 环境准备
* **Python 3.12 或更高版本**
* **我觉得**: 在开发和调试阶段使用官方的 **CPython** 解释器,以获得最佳的第三方库兼容性和调试体验。
* **你也可以觉得**: 在生产环境部署时,可以考虑使用 **PyPy** 以获取潜在的性能提升,但这可能会牺牲一定的兼容性。
* Redis 服务
* 一个正在运行的 OneBot v11 实现端 (推荐 **NapCatQQ**)
* **Python 3.12 或更高版本**
* **我觉得**: 在开发和调试阶段使用官方的 **CPython** 解释器,以获得最佳的第三方库兼容性和调试体验。
* **你也可以觉得**: 在生产环境部署时,可以考虑使用 **PyPy** 以获取潜在的性能提升,但这可能会牺牲一定的兼容性。
* Redis 服务
* 一个正在运行的 OneBot v11 实现端 (推荐 **NapCatQQ**)
### 2. 安装依赖
克隆本项目后,在项目根目录执行:
克隆本项目后,在项目根目录执行:
```bash
pip install -r requirements.txt
```
### 3. 配置
**[内部开发]**
为了方便内部开发和调试,项目中的 `config.toml` 文件已预先配置为连接到官方的 DEV 调试服务器。
**因此,在拉取仓库后,您通常无需对 `config.toml` 文件进行任何修改即可直接运行。**
如果您需要连接到本地或其他特定环境,可以参考以下配置结构进行修改。配置示例:
### 3. 配置
**[内部开发]**
为了方便内部开发和调试,项目中的 `config.toml` 文件已预先配置为连接到官方的 DEV 调试服务器。
**因此,在拉取仓库后,您通常无需对 `config.toml` 文件进行任何修改即可直接运行。**
如果您需要连接到本地或其他特定环境,可以参考以下配置结构进行修改。配置示例:
```toml
# config.toml
# config.toml
[napcat_ws]
# OneBot 实现端的 WebSocket 地址
uri = "ws://127.0.0.1:3001"
# Access Token (如果有)
token = ""
# 断线重连间隔(秒)
reconnect_interval = 5
# OneBot 实现端的 WebSocket 地址
uri = "ws://127.0.0.1:3001"
# Access Token (如果有)
token = ""
# 断线重连间隔(秒)
reconnect_interval = 5
[bot]
# 机器人指令的起始符号,可配置多个
command_prefixes = ["/", "!", ""]
[redis]
# Redis 连接信息
host = "127.0.0.1"
port = 6379
db = 0
password = ""
# 机器人指令的起始符号,可配置多个
command_prefixes = ["/", "!", ""]
[redis]
# Redis 连接信息
host = "127.0.0.1"
port = 6379
db = 0
password = ""
```
### 4. 运行
```bash
python main.py
```
机器人启动后,将自动连接到 OneBot 实现端。控制台会输出加载的插件列表和连接状态。
---
## 🛠️ 插件开发指南
Calglau BOT 的所有功能都通过插件实现。开发新功能非常简单,并且得益于热重载,你无需在开发过程中频繁重启机器人。
### 🔥 热重载工作流
1. 保持 `python main.py` 进程运行。
2.`plugins/` 目录下创建或修改任意 `.py` 文件。
3. **保存文件**
4. 观察控制台输出 `[HotReload] 插件重载完成` 的提示。你的新代码已即时生效。
机器人启动后,将自动连接到 OneBot 实现端。控制台会输出加载的插件列表和连接状态。
---
## 🛠️ 插件开发指南
Calglau BOT 的所有功能都通过插件实现。开发新功能非常简单,并且得益于热重载,你无需在开发过程中频繁重启机器人。
### 🔥 热重载工作流
1. 保持 `python main.py` 进程运行。
2.`plugins/` 目录下创建或修改任意 `.py` 文件。
3. **保存文件**
4. 观察控制台输出 `[HotReload] 插件重载完成` 的提示。你的新代码已即时生效。
### 创建一个新插件
1.`plugins/` 目录下新建一个 Python 文件,例如 `weather.py`
2. 在该文件中编写你的逻辑。
#### 1. 定义插件元数据 (`__plugin_meta__`)
为了让 `/help` 指令能自动发现你的插件,请在文件顶部定义 `__plugin_meta__` 字典:
### 创建一个新插件
1.`plugins/` 目录下新建一个 Python 文件,例如 `weather.py`
2. 在该文件中编写你的逻辑。
#### 1. 定义插件元数据 (`__plugin_meta__`)
为了让 `/help` 指令能自动发现你的插件,请在文件顶部定义 `__plugin_meta__` 字典:
```python
# plugins/weather.py
# plugins/weather.py
__plugin_meta__ = {
"name": "天气查询",
"description": "提供城市天气查询功能。",
"usage": "/weather [城市名] - 查询指定城市的实时天气。",
}
```
#### 2. 编写指令处理器
使用 `@matcher.command()` 装饰器来注册一个聊天指令。
"name": "天气查询",
"description": "提供城市天气查询功能。",
"usage": "/weather [城市名] - 查询指定城市的实时天气。",
}
```
#### 2. 编写指令处理器
使用 `@matcher.command()` 装饰器来注册一个聊天指令。
```python
# plugins/weather.py
# plugins/weather.py
from core.command_manager import matcher
from models import MessageEvent
# ... (元数据定义) ...
# ... (元数据定义) ...
@matcher.command("weather")
async def handle_weather_command(event: MessageEvent, args: list[str]):
async def handle_weather_command(event: MessageEvent, args: list[str]):
"""
处理 /weather 指令
:param event: 消息事件对象,用于回复等操作
:param args: 用户发送的参数列表 (已按空格分割)
处理 /weather 指令
:param event: 消息事件对象,用于回复等操作
:param args: 用户发送的参数列表 (已按空格分割)
"""
if not args:
await event.reply("请输入要查询的城市名,例如:/weather 北京")
await event.reply("请输入要查询的城市名,例如:/weather 北京")
return
city = args[0]
# 此处应调用天气 API 获取数据
# (示例代码,省略了真实 API 调用)
weather_data = f"{city}的天气是25°C。"
await event.reply(weather_data)
city = args[0]
# 此处应调用天气 API 获取数据
# (示例代码,省略了真实 API 调用)
weather_data = f"{city}的天气是25°C。"
await event.reply(weather_data)
```
#### 3. 监听事件
除了指令,你还可以监听各种事件,例如新成员入群。
#### 3. 监听事件
除了指令,你还可以监听各种事件,例如新成员入群。
```python
from core.command_manager import matcher
from models import GroupIncreaseNoticeEvent
from models import GroupIncreaseNoticeEvent
from core.bot import Bot
@matcher.on_notice("group_increase")
async def welcome_new_member(bot: Bot, event: GroupIncreaseNoticeEvent):
"""当有新成员加入群聊时触发"""
welcome_message = f"欢迎新成员 @{event.user_id} 加入本群!"
await bot.send_group_msg(event.group_id, welcome_message)
async def welcome_new_member(bot: Bot, event: GroupIncreaseNoticeEvent):
"""当有新成员加入群聊时触发"""
welcome_message = f"欢迎新成员 @{event.user_id} 加入本群!"
await bot.send_group_msg(event.group_id, welcome_message)
```
---
## 📦 当前功能插件
| 插件文件 (`plugins/`) | 功能描述 |
|-----------------------|----------|
| `admin.py` | 机器人管理员权限管理 |
| `bili_parser.py` | 自动解析 Bilibili 视频链接分享卡片 |
| `code_py.py` | 执行 Python 代码片段 (高危,仅限管理员) |
| `echo.py` | 提供 `/echo` 复读和 `/赞我` 功能 |
| `forward_test.py` | 演示如何发送合并转发消息 |
| `jrcd.py` | 娱乐功能:今日人品、牛牛词典 |
| `thpic.py` | 发送一张随机的东方 Project 图片 |
---
## 🗺️ 路线图 (Roadmap)
- [ ] **Web 仪表盘**: 开发一个简单的 Web 页面,用于查看机器人状态和插件列表。
- [ ] **权限系统重构**: 引入更精细化的权限节点,允许按插件或指令控制用户权限。
- [ ] **数据库集成**: 引入 `SQLite` 或其他数据库,用于需要持久化存储数据的功能。
- [ ] **新插件开发**:
- [ ] 天气查询插件
- [ ] GIL实现
- [ ] coming soon...
---
## 📦 当前功能插件
| 插件文件 (`plugins/`) | 功能描述 |
|-----------------------|----------|
| `admin.py` | 机器人管理员权限管理 |
| `bili_parser.py` | 自动解析 Bilibili 视频链接分享卡片 |
| `code_py.py` | 执行 Python 代码片段 (高危,仅限管理员) |
| `echo.py` | 提供 `/echo` 复读和 `/赞我` 功能 |
| `forward_test.py` | 演示如何发送合并转发消息 |
| `jrcd.py` | 娱乐功能:今日人品、牛牛词典 |
| `thpic.py` | 发送一张随机的东方 Project 图片 |
---
## 🗺️ 路线图 (Roadmap)
- [ ] **Web 仪表盘**: 开发一个简单的 Web 页面,用于查看机器人状态和插件列表。
- [ ] **权限系统重构**: 引入更精细化的权限节点,允许按插件或指令控制用户权限。
- [ ] **数据库集成**: 引入 `SQLite` 或其他数据库,用于需要持久化存储数据的功能。
- [ ] **新插件开发**:
- [ ] 天气查询插件
- [ ] GIL实现
- [ ] coming soon...

View File

@@ -23,5 +23,3 @@ tls_verify = true
ca_cert_path = "c:/Users/镀铬酸钾/Documents/NeoBot/ca/ca.crt" ca_cert_path = "c:/Users/镀铬酸钾/Documents/NeoBot/ca/ca.crt"
client_cert_path = "c:/Users/镀铬酸钾/Documents/NeoBot/ca/client-cert.pem" client_cert_path = "c:/Users/镀铬酸钾/Documents/NeoBot/ca/client-cert.pem"
client_key_path = "c:/Users/镀铬酸钾/Documents/NeoBot/ca/client-key.pem" client_key_path = "c:/Users/镀铬酸钾/Documents/NeoBot/ca/client-key.pem"

View File

@@ -15,6 +15,8 @@ class BrowserManager:
_instance = None _instance = None
_playwright: Optional[Playwright] = None _playwright: Optional[Playwright] = None
_browser: Optional[Browser] = None _browser: Optional[Browser] = None
_page_pool: Optional[asyncio.Queue] = None
_pool_size: int = 3
def __new__(cls): def __new__(cls):
if cls._instance is None: if cls._instance is None:
@@ -36,6 +38,73 @@ class BrowserManager:
logger.exception(f"无头浏览器启动失败: {e}") logger.exception(f"无头浏览器启动失败: {e}")
self._browser = None self._browser = None
async def init_pool(self, size: int = 3):
"""
初始化页面池
"""
if not self._browser:
await self.initialize()
if not self._browser:
logger.error("浏览器初始化失败,无法创建页面池")
return
self._pool_size = size
self._page_pool = asyncio.Queue(maxsize=size)
logger.info(f"正在初始化页面池 (大小: {size})...")
for i in range(size):
try:
page = await self._browser.new_page()
await self._page_pool.put(page)
except Exception as e:
logger.error(f"创建页面池页面 {i+1} 失败: {e}")
logger.success(f"页面池初始化完成,当前可用页面: {self._page_pool.qsize()}")
async def get_page(self) -> Optional[Page]:
"""
从池中获取一个页面。如果池未初始化或为空,则尝试创建一个新页面(不入池)。
"""
if self._page_pool and not self._page_pool.empty():
try:
page = self._page_pool.get_nowait()
# 简单的健康检查
if page.is_closed():
logger.warning("检测到池中页面已关闭,重新创建一个...")
if self._browser:
page = await self._browser.new_page()
else:
return None
return page
except asyncio.QueueEmpty:
pass
# 如果池空了或者没初始化,回退到临时创建
logger.debug("页面池为空或未初始化,创建临时页面")
return await self.get_new_page()
async def release_page(self, page: Page):
"""
归还页面到池中。如果池已满或未初始化,则关闭页面。
"""
if not page or page.is_closed():
return
if self._page_pool:
try:
# 重置页面状态 (例如清空内容),防止数据污染
# 注意: goto('about:blank') 比 close() 快得多
await page.goto("about:blank")
self._page_pool.put_nowait(page)
return
except asyncio.QueueFull:
pass
# 池满或未启用池,直接关闭
await page.close()
async def get_new_page(self) -> Optional[Page]: async def get_new_page(self) -> Optional[Page]:
""" """
获取一个新的页面 (Page) 获取一个新的页面 (Page)
@@ -58,6 +127,16 @@ class BrowserManager:
""" """
关闭浏览器和 Playwright 关闭浏览器和 Playwright
""" """
# 清空页面池
if self._page_pool:
while not self._page_pool.empty():
try:
page = self._page_pool.get_nowait()
await page.close()
except Exception:
pass
self._page_pool = None
if self._browser: if self._browser:
await self._browser.close() await self._browser.close()
self._browser = None self._browser = None

View File

@@ -29,6 +29,8 @@ class ImageManager:
# core/managers/image_manager.py -> core/managers -> core -> core/data/temp # core/managers/image_manager.py -> core/managers -> core -> core/data/temp
self.temp_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "temp") self.temp_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data", "temp")
os.makedirs(self.temp_dir, exist_ok=True) os.makedirs(self.temp_dir, exist_ok=True)
# 模板缓存
self._template_cache: Dict[str, Template] = {}
async def render_template(self, template_name: str, data: Dict[str, Any], output_name: str = "output.png", quality: int = 80, image_type: str = "png") -> Optional[str]: async def render_template(self, template_name: str, data: Dict[str, Any], output_name: str = "output.png", quality: int = 80, image_type: str = "png") -> Optional[str]:
""" """
@@ -50,15 +52,20 @@ class ImageManager:
return None return None
try: try:
# 1. 渲染 HTML # 1. 渲染 HTML (使用缓存)
with open(template_path, "r", encoding="utf-8") as f: if template_name in self._template_cache:
template_str = f.read() template = self._template_cache[template_name]
else:
with open(template_path, "r", encoding="utf-8") as f:
template_str = f.read()
template = Template(template_str)
self._template_cache[template_name] = template
template = Template(template_str)
html_content = template.render(**data) html_content = template.render(**data)
# 2. 使用浏览器截图 # 2. 使用浏览器截图
page = await browser_manager.get_new_page() # 改为从池中获取页面
page = await browser_manager.get_page()
if not page: if not page:
logger.error("无法获取浏览器页面") logger.error("无法获取浏览器页面")
return None return None
@@ -76,10 +83,11 @@ class ImageManager:
if image_type == 'jpeg': if image_type == 'jpeg':
screenshot_args['quality'] = quality screenshot_args['quality'] = quality
screenshot_bytes = await page.screenshot(**screenshot_args) screenshot_bytes = await page.screenshot(**screenshot_args) # type: ignore
finally: finally:
await page.close() # 归还页面到池中,而不是直接关闭
await browser_manager.release_page(page)
# 3. 保存文件 # 3. 保存文件
output_path = os.path.join(self.temp_dir, output_name) output_path = os.path.join(self.temp_dir, output_name)

34
core/utils/json_utils.py Normal file
View File

@@ -0,0 +1,34 @@
"""
JSON 工具模块
统一使用高性能的 orjson 库进行 JSON 序列化和反序列化。
如果 orjson 不可用,则回退到标准库 json。
"""
from typing import Any, Union
import json
# 在模块加载时检查 orjson 是否可用
try:
import orjson
_orjson_available = True
except ImportError:
_orjson_available = False
def dumps(obj: Any) -> str:
"""
将对象序列化为 JSON 字符串。
"""
if _orjson_available:
# orjson.dumps 返回 bytes需要 decode
return orjson.dumps(obj).decode("utf-8")
else:
return json.dumps(obj, ensure_ascii=False)
def loads(json_str: Union[str, bytes]) -> Any:
"""
将 JSON 字符串反序列化为对象。
"""
if _orjson_available:
return orjson.loads(json_str)
else:
return json.loads(json_str)

View File

@@ -0,0 +1,62 @@
# 核心架构
NEO Bot Framework 不是那种随便写写的玩具。它的架构设计只有一个核心目标:**极致性能与稳定性的平衡**。
我们不搞花里胡哨的抽象,只做最有效的工程实践。
## 1. 运行时架构
### Python 3.14 + JIT
我们激进地采用了 Python 3.14,并默认开启 JIT (Just-In-Time) 编译器。
* **原理**: JIT 会在运行时分析热点代码,将其编译为机器码,跳过字节码解释过程。
* **收益**: CPU 密集型任务(如复杂的正则匹配、数据处理)性能提升显著。
### Mypyc 编译 (AOT)
光有 JIT 还不够。核心模块(`core/ws.py`, `core/managers/*.py`)支持通过 Mypyc 编译为 C 扩展。
* **原理**: Mypyc 利用 Python 的类型提示,将 Python 代码直接翻译成 C 代码,并编译为二进制 `.pyd``.so` 文件。
* **收益**: 核心路径的执行速度接近 C 语言,完全摆脱 GIL 的部分束缚。
### 异步 IO 模型
* **Linux**: 强制使用 `uvloop`,这是目前最快的 Python 异步事件循环,基于 libuvNode.js 同款)。
* **Windows**: 使用原生 `ProactorEventLoop` (IOCP),虽然不如 uvloop但在 Windows 上是最优解。
* **: 我们禁用了 `winloop`,因为它与 Playwright 存在兼容性问题。
## 2. 网络架构
### 正向 WebSocket + FastAPI 混合模式
这是一个独特的混合架构,兼顾了部署便利性和功能扩展性。
* **连接层 (Client)**: Bot 主动通过 WebSocket 连接到 OneBot (NapCat)。
* **优势**: 不需要公网 IP不需要内网穿透只要能上网就能跑。
* **服务层 (Server)**: Bot 内部启动一个 FastAPI 服务。
* **优势**: 提供 HTTP API、Webhook 接收、静态资源托管如帮助图片、Web 控制台)。
```mermaid
graph LR
subgraph Local [本地环境 / 服务器]
Bot[NEO Bot]
FastAPI[FastAPI Server]
Browser[Playwright Pool]
end
subgraph Remote [OneBot / 外部]
NapCat[NapCatQQ]
User[用户浏览器]
end
Bot -- WebSocket (Client) --> NapCat
User -- HTTP --> FastAPI
Bot -- 控制 --> Browser
```
## 3. 资源管理架构
### 单例管理器 (Singleton Managers)
所有的核心组件(指令、权限、浏览器、图片)都是全局单例。
* **零开销访问**: 任何插件都可以直接 import 使用,无需传递上下文。
* **状态一致性**: 全局共享状态,拒绝数据同步问题。
### 资源池化 (Pooling)
我们拒绝“用完即扔”的浪费行为。
* **Browser Pool**: 浏览器页面预先创建,用完归还,拒绝反复启动进程。
* **Connection Pool**: Redis 和 HTTP 请求共享连接池,拒绝反复握手。

View File

@@ -61,6 +61,7 @@ graph TD
* 当用户在 QQ 群里发送消息时OneBot v11 实现端(如 NapCatQQ会将其打包成一个 JSON 格式的数据,并通过 WebSocket 连接发送给框架。 * 当用户在 QQ 群里发送消息时OneBot v11 实现端(如 NapCatQQ会将其打包成一个 JSON 格式的数据,并通过 WebSocket 连接发送给框架。
* `core/ws.py` 中的 `_listen_loop` 方法持续监听连接,接收到这个原始的 JSON 字符串。 * `core/ws.py` 中的 `_listen_loop` 方法持续监听连接,接收到这个原始的 JSON 字符串。
* **: 这里使用了 `orjson` 进行极速反序列化。
### 2. 事件对象实例化 (`models/events/factory.py`) ### 2. 事件对象实例化 (`models/events/factory.py`)

View File

@@ -0,0 +1,73 @@
# 性能优化详解
NEO Bot 能跑这么快,不是因为运气好,是因为我们做了大量微小的优化工作。这里详细拆解每一个性能黑科技。
## 1. Playwright 页面池 (Page Pool)
### 痛点
传统的 Bot 发图流程:
1. 用户发指令。
2. Bot 启动浏览器 (耗时 500ms+)。
3. 创建新页面 (耗时 100ms+)。
4. 渲染,截图。
5. 关闭浏览器。
这种模式下,发一张图至少要等 1 秒以上,并发高了直接卡死。
### 解决方案
`BrowserManager` 维护了一个**页面池**。
* **启动时**: 自动预热 3 个页面(可配置),挂在后台待命。
* **运行时**: 需要截图时,直接从池里 `get_page()`,耗时 **0ms**
* **结束后**: 截图完成,页面执行 `about:blank` 洗白,然后 `release_page()` 放回池里。
### 收益
图片生成响应时间从 **1.5s** 降低到 **0.2s** (仅渲染耗时)。
## 2. Jinja2 模板缓存
### 痛点
每次渲染 HTML都要从硬盘读文件然后解析模板语法。硬盘 IO 是慢的,解析也是慢的。
### 解决方案
`ImageManager` 引入了内存缓存 `_template_cache`
* 第一次读取模板后,编译好的 `Template` 对象直接存入字典。
* 后续请求直接从内存拿对象渲染。
### 收益
模板加载时间归零。
## 3. 全局 HTTP 连接复用
### 痛点
插件(如 B站解析每次请求 API 都创建一个新的 `aiohttp.ClientSession`
这意味着每次都要进行DNS 解析 -> TCP 握手 -> SSL 握手。这在 HTTPS 下非常慢。
### 解决方案
我们在插件层面实现了 `get_session()`
* 全局共享一个 `ClientSession`
* 复用底层的 TCP 连接 (Keep-Alive)。
### 收益
API 请求延迟降低 50% 以上,大幅减少服务器 TCP 连接数。
## 4. orjson 极速序列化
### 痛点
Python 自带的 `json` 库性能平平,特别是在处理 OneBot 这种大量 JSON 通信的场景下。
### 解决方案
我们全面替换为 `orjson`
* Rust 编写,速度是标准库的 10 倍以上。
* 支持直接返回 `bytes`,减少内存复制。
## 5. Mypyc 编译
### 痛点
Python 是解释型语言,执行效率天生低。
### 解决方案
利用 `setup_mypyc.py` 将核心模块编译为 C 扩展。
* `core/ws.py`: WebSocket 消息处理循环。
* `core/managers/*.py`: 事件分发逻辑。
这些高频调用的代码变成了机器码,执行效率直逼 C++。

View File

@@ -64,6 +64,24 @@
* **连接管理**: 负责初始化和管理与 Redis 服务器的异步连接。 * **连接管理**: 负责初始化和管理与 Redis 服务器的异步连接。
* **提供实例**: 通过 `redis_manager.redis` 属性,为其他模块提供一个可用的 `redis` 客户端实例。 * **提供实例**: 通过 `redis_manager.redis` 属性,为其他模块提供一个可用的 `redis` 客户端实例。
### 6. `BrowserManager` (全局实例: `browser_manager`)
* **文件**: `core/managers/browser_manager.py`
* **全局实例**: `from core.managers.browser_manager import browser_manager`
* **核心职责**:
* **浏览器生命周期管理**: 负责 Playwright 浏览器的启动和关闭。
* **页面池 (Page Pool)**: 维护一个预热的浏览器页面池(默认 3 个)。
* **资源复用**: 提供 `get_page()``release_page()` 接口,让渲染任务直接复用现有页面,避免了每次创建新页面的巨大开销。
### 7. `ImageManager` (全局实例: `image_manager`)
* **文件**: `core/managers/image_manager.py`
* **全局实例**: `from core.managers.image_manager import image_manager`
* **核心职责**:
* **图片渲染**: 基于 Jinja2 模板和 Playwright 浏览器生成图片。
* **模板缓存**: 自动缓存编译后的 Jinja2 模板,避免重复 IO 和解析。
* **资源调度**: 自动从 `BrowserManager` 借用和归还页面,开发者无需关心底层浏览器操作。
## 如何在插件中使用管理器 ## 如何在插件中使用管理器
在您的插件中,只需通过 `import` 语句导入相应管理器的全局实例即可使用。 在您的插件中,只需通过 `import` 语句导入相应管理器的全局实例即可使用。

View File

@@ -1,113 +1,97 @@
# 快速上手 # 快速上手
本指南将引导您完成 NEO Bot Framework 的本地开发环境搭建、配置和首次运行 这篇文档教你怎么把 NEO Bot 跑起来。如果你连这都搞不定,建议先去补补 Python 基础
## 1. 环境准备 ## 1. 环境准备
在开始之前,请确保您的开发环境中已安装以下软件: 别拿老古董环境来跑新代码,我们用的是最新的技术栈。
* **Python**: 版本要求 `3.12` 或更高。 * **Python**: 必须 `3.14` 或更高。
* 我们推荐使用官方的 CPython 解释器 * 推荐开启 JIT (`-X jit`)
* 您可以通过在终端运行 `python --version` 来检查您的 Python 版本 * 别问为什么不用 3.8,问就是慢
* **Git**: 用于克隆项目仓库。 * **Git**: 拉代码用的,这都要教?
* **Redis**: 一个键值对数据库,用于缓存和数据共享 * **Redis**: 必须装
* 对于 Windows 用户,可以考虑使用 `memurai` 或通过 WSL2 安装 Redis * Windows 用户自己想办法 (WSL2 或者 Memurai)
* 对于 macOS 用户,可以使用 `brew install redis` * Linux/macOS 用户直接包管理器装
* 安装后,请确保 Redis 服务正在运行 * 没 Redis 跑不起来,别试了
* **OneBot v11 实现**: 机器人框架需要连接到一个实现了 OneBot v11 协议的客户端 * **OneBot v11 客户**: 机器人本体
* **推荐**: [NapCatQQ](https://github.com/NapNeko/NapCatQQ) * **强烈推荐**: [NapCatQQ](https://github.com/NapNeko/NapCatQQ)
* 别用那些几百年不更新的协议端,出了问题别找我。
## 2. 克隆与安装 ## 2. 安装步骤
### 克隆项目 ### 拉代码
打开您的终端,并克隆项目仓库到本地:
```bash ```bash
git clone [项目仓库地址] git clone [项目仓库地址]
cd [项目目录] cd [项目目录]
``` ```
### 创建虚拟环境 (推荐) ### 搞个虚拟环境
为了保持项目依赖的隔离,强烈建议您创建一个 Python 虚拟环境 别把系统环境搞脏了,这是基本礼貌
```bash ```bash
# 创建虚拟环境 # 创建
python -m venv venv python -m venv venv
# 激活虚拟环境 # 激活 (Windows)
# Windows
.\venv\Scripts\activate .\venv\Scripts\activate
# macOS / Linux
# 激活 (Linux/macOS)
source venv/bin/activate source venv/bin/activate
``` ```
### 装依赖 ### 装依赖
激活虚拟环境后,使用 `pip` 安装所有必需的第三方库:
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt
``` ```
### 装浏览器内核
我们用了 Playwright 做渲染,所以得装个 Chromium。
```bash
playwright install chromium
```
### 编译核心 (可选,但推荐)
想体验极致速度?把核心模块编译成 C 扩展。
```bash
python setup_mypyc.py build_ext --inplace
```
*Windows 上需要 Visual Studio Build ToolsLinux 上需要 GCC。编译失败就跳过反正 JIT 也够快了。*
## 3. 配置 ## 3. 配置
项目的核心配置位于根目录下的 `config.toml` 文件中 根目录 `config.toml`
对于内部开发,该文件通常已预先配置好,可以直接连接到测试服务器。如果您需要连接到自己的环境,请修改以下关键部分:
```toml ```toml
# config.toml
[napcat_ws] [napcat_ws]
# 的 OneBot v11 实现端的 WebSocket 地址 # 的 OneBot 地址
# 格式通常为 ws://<IP地址>:<端口号> # 我们用的是正向连接,也就是 Bot 主动去连 OneBot
uri = "ws://127.0.0.1:3001" uri = "ws://127.0.0.1:3001"
# Access Token (访问令牌),如果您的 OneBot 端设置了
token = "" token = ""
[redis] [redis]
# Redis 服务的连接信息
host = "127.0.0.1" host = "127.0.0.1"
port = 6379 port = 6379
db = 0 db = 0
password = "" # 如果您的 Redis 设置了密码
``` ```
## 4. 首次运行 ## 4. 启动
完成以上所有步骤后,您就可以启动机器人了。在项目根目录运行: 一切就绪,起飞。
```bash ```bash
python main.py # 开启 JIT 模式启动
python -X jit main.py
``` ```
如果一切顺利,您将在控制台看到类似以下的输出: 看到 `连接成功!` 就说明跑通了。如果报错,先看日志,别上来就问。
```
2026-01-07 22:42:41.718 | INFO | ... - 管理员管理器初始化完成
2026-01-07 22:42:41.826 | INFO | ... - 正在从 plugins 加载插件...
2026-01-07 22:42:41.994 | SUCCESS | ... - Redis 连接成功!
...
2026-01-07 22:42:42.618 | SUCCESS | ... - 连接成功!
```
看到 `连接成功!` 的日志,即表示您的机器人已成功连接到 OneBot 客户端并准备好接收消息。
## 5. 常见问题排查 (FAQ)
* **Q: 启动时报错 `redis.exceptions.ConnectionError`**
* **A**: 请检查您的 Redis 服务是否已启动,以及 `config.toml` 中的 `host``port` 是否正确。
* **Q: 无法连接到 WebSocket提示 `ConnectionRefusedError`**
* **A**: 请确认您的 OneBot v11 客户端(如 NapCatQQ是否正在运行并检查 `config.toml` 中的 `uri` 地址和端口是否匹配。
* **Q: 修改了插件代码但没有生效**
* **A**: 框架默认开启了热重载功能。请检查控制台是否有 `[HotReload]` 相关的日志输出。如果没有,请确认 `watchdog` 库已正确安装。
现在,您的开发环境已经准备就绪。接下来,您可以尝试修改一个现有插件或[创建您的第一个插件](./plugin-development/index.md)

View File

@@ -2,33 +2,27 @@
欢迎来到 NEO Bot Framework 的官方开发文档。 欢迎来到 NEO Bot Framework 的官方开发文档。
本文档旨在为开发者提供一个清晰、全面的指南,帮助您理解框架的设计理念、核心功能,并快速上手插件开发 这里没有废话,只有干货。这份文档会教你如何驾驭这个基于 Python 3.14 的高性能机器人框架
## 📖 文档结构 ## 📖 文档结构
本站点的文档分为以下几个主要部分: ### 1. 基础入门
* [快速上手](./getting-started.md): 环境配置、安装、启动。别跳过,除非你想报错。
* [项目结构](./project-structure.md): 了解各个目录是干嘛的。
* [部署指南](./deployment.md): 怎么在服务器上长期运行。
* **基础入门** ### 2. 核心概念 (必读)
* [快速上手](./getting-started.md): 从零开始配置和运行您的第一个机器人实例 * [核心架构](./core-concepts/architecture.md): 了解我们是如何把 Python 性能榨干的
* [项目结构解析](./project-structure.md): 详细介绍框架的目录和文件结构 * [性能优化](./core-concepts/performance.md): 页面池、JIT、Mypyc...黑科技详解
* [事件流转](./core-concepts/event-flow.md): 一条消息是如何在系统里流转的(含详细图解)。
* [单例管理器](./core-concepts/singleton-managers.md): 掌握 `matcher`, `browser_manager` 等核心组件。
* **核心概念** ### 3. 插件开发
* [事件流转](./core-concepts/event-flow.md): 深入理解一个事件从接收到处理的完整生命周期 * [基础指南](./plugin-development/index.md): 怎么写一个最简单的插件
* [单例管理器](./core-concepts/singleton-managers.md): 了解框架中核心管理器(如 `CommandManager`, `PermissionManager`)的设计与使用 * [指令处理](./plugin-development/command-handling.md): 怎么注册命令、解析参数
* [最佳实践](./plugin-development/best-practices.md): **重要!** 避免写出卡死机器人的垃圾代码。
* **插件开发** ## 🤝 贡献
* [基础指南](./plugin-development/index.md): 学习如何创建一个插件,包括元数据定义和热重载工作流。
* [指令处理](./plugin-development/command-handling.md): 掌握如何使用 `@matcher.command()` 装饰器注册和处理聊天指令。
* **部署** 发现 Bug 了?觉得文档写得烂?
* [部署指南](./deployment.md): 了解如何在生产环境中部署和维护机器人。 直接提 Issue 或者 PR。代码质量是第一位的Talk is cheap, show me the code.
## 🤝 如何贡献
我们欢迎任何形式的贡献,无论是代码提交、文档修正还是功能建议。
* **报告问题**: 如果您在使用中遇到任何问题或 Bug请通过内部渠道提交 Issue。
* **提交代码**: 请遵循项目的编码规范,并通过 Pull Request 流程提交您的代码。
* **完善文档**: 如果您发现文档中有任何错误或遗漏,可以直接提出修改建议。
我们希望这份文档能让您的开发之旅更加顺畅。如果您有任何疑问,请随时与我们联系。

View File

@@ -0,0 +1,67 @@
# 插件开发最佳实践
写插件很简单,但写出**高性能、不炸裂**的插件需要遵守规矩。
## 1. 绝对不要阻塞事件循环 (Don't Block the Loop!)
这是死罪。NEO Bot 是单线程异步架构,如果你在主线程里 `time.sleep(5)`,整个机器人就会卡死 5 秒,谁都别想说话。
* **错误**: `time.sleep(1)`, `requests.get(...)`, 大量 CPU 计算。
* **正确**: `await asyncio.sleep(1)`, `await session.get(...)`
如果你必须运行同步代码(比如图像处理、复杂计算):
```python
from core.utils.executor import run_in_thread_pool
# 扔到线程池里去跑,别占着主线程
result = await run_in_thread_pool(heavy_function, arg1, arg2)
```
## 2. 复用资源
别每次都创建新的连接。
* **HTTP 请求**: 使用插件内提供的 `get_session()` 或全局 `aiohttp` session。
* **浏览器**: 必须使用 `browser_manager.get_page()`,严禁自己 `playwright.chromium.launch()`
## 3. 善用缓存
如果你的插件需要查外部 API比如查天气、查 B 站),记得加缓存。
Redis 就在那里,不用白不用。
```python
from core.managers.redis_manager import redis_manager
# 存
await redis_manager.set("weather:beijing", "sunny", ex=3600)
# 取
weather = await redis_manager.get("weather:beijing")
```
## 4. 类型提示 (Type Hinting)
我们开启了 Mypyc 编译,这意味着你的代码最好有规范的类型提示。
这不仅是为了编译,也是为了让你自己少写 Bug。
```python
# 好的写法
async def handle(event: MessageEvent, args: list[str]) -> None:
...
# 烂写法
async def handle(event, args):
...
```
## 5. 异常处理
别让你的插件因为一个报错就搞崩整个机器人。
虽然框架层有捕获机制,但你自己处理好异常是基本素养。
```python
try:
await do_something()
except Exception as e:
logger.error(f"插件炸了: {e}")
await event.reply("出错了,请稍后再试。")
```

16
import sys.py Normal file
View File

@@ -0,0 +1,16 @@
import sys
import sysconfig
print(f"Python Version: {sys.version}")
# 检查 GIL 状态
try:
# Python 3.13+ free-threading build 才有这个属性
is_gil_enabled = sys._is_gil_enabled()
print(f"GIL Enabled: {is_gil_enabled}")
except AttributeError:
print("GIL Status: Unknown (sys._is_gil_enabled not found, likely GIL-enabled build)")
# 检查 JIT 状态
# 目前没有直接的 API 检查 JIT 是否开启,通常看性能或启动日志
print("JIT Support: Experimental (Enable with -X jit)")

19
main.py
View File

@@ -10,6 +10,21 @@ import time
from watchdog.observers import Observer from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler from watchdog.events import FileSystemEventHandler
# 尝试使用高性能事件循环
try:
if sys.platform == 'win32':
# winloop 与 Playwright 存在兼容性问题 (不支持 startupinfo),暂时禁用
# import winloop
# asyncio.set_event_loop_policy(winloop.EventLoopPolicy())
# print("已启用 winloop 高性能事件循环")
print("Windows 平台检测到 Playwright已自动禁用 winloop 以确保兼容性")
else:
import uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
print("已启用 uvloop 高性能事件循环")
except ImportError:
print("未检测到高性能事件循环库 (uvloop/winloop),将使用默认事件循环")
# 初始化日志系统,必须在其他 core 模块导入之前执行 # 初始化日志系统,必须在其他 core 模块导入之前执行
from core.utils.logger import logger from core.utils.logger import logger
@@ -118,8 +133,8 @@ async def main():
# 初始化管理员管理器 # 初始化管理员管理器
await admin_manager.initialize() await admin_manager.initialize()
# 初始化浏览器管理器 # 初始化浏览器管理器 (使用页面池)
await browser_manager.initialize() await browser_manager.init_pool(size=3)
# 启动文件监控 # 启动文件监控
# 监控 plugins 目录 # 监控 plugins 目录

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
import json import json
import requests import aiohttp
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from typing import Optional, Dict, Any, Union from typing import Optional, Dict, Any, Union
from cachetools import TTLCache from cachetools import TTLCache
@@ -23,6 +23,15 @@ HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
} }
# 全局共享的 ClientSession
_session: Optional[aiohttp.ClientSession] = None
async def get_session() -> aiohttp.ClientSession:
global _session
if _session is None or _session.closed:
_session = aiohttp.ClientSession()
return _session
def format_count(num: int) -> str: def format_count(num: int) -> str:
if not isinstance(num, int): if not isinstance(num, int):
@@ -40,20 +49,23 @@ def format_duration(seconds: int) -> str:
return f"{minutes:02d}:{seconds:02d}" return f"{minutes:02d}:{seconds:02d}"
def get_real_url(short_url: str) -> Optional[str]: async def get_real_url(short_url: str) -> Optional[str]:
try: try:
response = requests.head(short_url, headers=HEADERS, allow_redirects=False, timeout=5) session = await get_session()
if response.status_code == 302: async with session.head(short_url, headers=HEADERS, allow_redirects=False, timeout=5) as response:
return response.headers.get('Location') if response.status == 302:
except requests.RequestException as e: return response.headers.get('Location')
print(f"获取真实URL失败: {e}") except Exception as e:
logger.error(f"获取真实URL失败: {e}")
return None return None
def parse_video_info(video_url: str) -> Optional[Dict[str, Any]]: async def parse_video_info(video_url: str) -> Optional[Dict[str, Any]]:
try: try:
response = requests.get(video_url, headers=HEADERS, timeout=5) session = await get_session()
response.raise_for_status() async with session.get(video_url, headers=HEADERS, timeout=5) as response:
soup = BeautifulSoup(response.text, 'html.parser') response.raise_for_status()
text = await response.text()
soup = BeautifulSoup(text, 'html.parser')
script_tag = soup.find('script', text=re.compile('window.__INITIAL_STATE__')) script_tag = soup.find('script', text=re.compile('window.__INITIAL_STATE__'))
if not script_tag or not script_tag.string: if not script_tag or not script_tag.string:
@@ -98,12 +110,12 @@ def parse_video_info(video_url: str) -> Optional[Dict[str, Any]]:
"followers": up_data.get('fans', 0), "followers": up_data.get('fans', 0),
} }
except (requests.RequestException, KeyError, AttributeError, json.JSONDecodeError) as e: except (aiohttp.ClientError, KeyError, AttributeError, json.JSONDecodeError) as e:
print(f"解析视频信息失败: {e}") logger.error(f"解析视频信息失败: {e}")
return None return None
def get_direct_video_url(video_url: str) -> Optional[str]: async def get_direct_video_url(video_url: str) -> Optional[str]:
""" """
调用第三方API解析B站视频直链 调用第三方API解析B站视频直链
:param video_url: B站视频的完整URL :param video_url: B站视频的完整URL
@@ -111,12 +123,13 @@ def get_direct_video_url(video_url: str) -> Optional[str]:
""" """
api_url = f"https://api.mir6.com/api/bzjiexi?url={video_url}&type=json" api_url = f"https://api.mir6.com/api/bzjiexi?url={video_url}&type=json"
try: try:
response = requests.get(api_url, headers=HEADERS, timeout=10) async with aiohttp.ClientSession() as session:
response.raise_for_status() async with session.get(api_url, headers=HEADERS, timeout=10) as response:
data = response.json() response.raise_for_status()
if data.get("code") == 200 and data.get("data"): data = await response.json()
return data["data"][0].get("video_url") if data.get("code") == 200 and data.get("data"):
except (requests.RequestException, json.JSONDecodeError, KeyError, IndexError) as e: return data["data"][0].get("video_url")
except (aiohttp.ClientError, json.JSONDecodeError, KeyError, IndexError) as e:
logger.error(f"[bili_parser] 调用第三方API解析视频失败: {e}") logger.error(f"[bili_parser] 调用第三方API解析视频失败: {e}")
return None return None
@@ -178,7 +191,7 @@ async def process_bili_link(event: MessageEvent, url: str):
:param url: 待处理的B站链接 :param url: 待处理的B站链接
""" """
if "b23.tv" in url: if "b23.tv" in url:
real_url = get_real_url(url) real_url = await get_real_url(url)
if not real_url: if not real_url:
logger.error(f"[bili_parser] 无法从 {url} 获取真实URL。") logger.error(f"[bili_parser] 无法从 {url} 获取真实URL。")
await event.reply("无法解析B站短链接。") await event.reply("无法解析B站短链接。")
@@ -186,7 +199,7 @@ async def process_bili_link(event: MessageEvent, url: str):
else: else:
real_url = url.split('?')[0] real_url = url.split('?')[0]
video_info = parse_video_info(real_url) video_info = await parse_video_info(real_url)
if not video_info: if not video_info:
logger.error(f"[bili_parser] 无法从 {real_url} 解析视频信息。") logger.error(f"[bili_parser] 无法从 {real_url} 解析视频信息。")
await event.reply("无法获取视频信息可能是B站接口变动或视频不存在。") await event.reply("无法获取视频信息可能是B站接口变动或视频不存在。")
@@ -197,7 +210,7 @@ async def process_bili_link(event: MessageEvent, url: str):
if video_info['duration'] > 300: # 5分钟 = 300秒 if video_info['duration'] > 300: # 5分钟 = 300秒
video_message = "视频时长超过5分钟不进行解析。" video_message = "视频时长超过5分钟不进行解析。"
else: else:
direct_url = get_direct_video_url(real_url) direct_url = await get_direct_video_url(real_url)
if direct_url: if direct_url:
video_message = MessageSegment.video(direct_url) video_message = MessageSegment.video(direct_url)
else: else:

View File

@@ -7,4 +7,9 @@ playwright>=1.57.0
jinja2>=3.1.6 jinja2>=3.1.6
docker>=7.1.0 docker>=7.1.0
requests>=2.32.5 requests>=2.32.5
aiohttp>=3.9.0
Pillow>=10.0.0 Pillow>=10.0.0
orjson>=3.9.10
uvloop>=0.19.0; sys_platform != 'win32'
winloop>=0.1.0; sys_platform == 'win32'
mypy>=1.8.0

42
setup_mypyc.py Normal file
View File

@@ -0,0 +1,42 @@
"""
Mypyc 编译脚本
用于将核心 Python 模块编译为 C 扩展,以提升性能。
使用方法:
python setup_mypyc.py build_ext --inplace
注意:
1. 需要安装 C 编译器 (Windows 上需要 Visual Studio Build Tools, Linux 上需要 GCC)。
2. 编译后的文件 (.pyd 或 .so) 是平台相关的,不能跨平台复制。
3. 建议在部署的目标环境 (Linux) 上运行此脚本。
"""
from distutils.core import setup
from mypyc.build import mypycify
import os
import sys
# 待编译的模块列表
# 注意Mypyc 对动态特性支持有限,只选择计算密集或类型明确的模块
modules = [
'core/utils/json_utils.py', # JSON 处理
'core/managers/command_manager.py', # 指令匹配和分发
'core/ws.py', # WebSocket 核心
'core/managers/plugin_manager.py', # 插件管理器
]
# 确保文件存在
valid_modules = []
for m in modules:
if os.path.exists(m):
valid_modules.append(m)
else:
print(f"Warning: Module {m} not found, skipping.")
if not valid_modules:
print("No valid modules found to compile.")
sys.exit(1)
setup(
name='neobot_core_compiled',
ext_modules=mypycify(valid_modules),
)

10
x = 5.py Normal file
View File

@@ -0,0 +1,10 @@
x = 5
# 它有自己的身份
print(id(x))
# 它有自己的类型
print(type(x))
# 它甚至有自己的工具!
print(x.bit_length())