Add HeXi bot codebase: custom plugins, web frontends, tests

- hexi core: message handling, rate limiting, cooldown, plugin manager
- Custom plugins: BF stats, daily check-in, quotes, persona cards, etc.
- Community plugins vendored under hexi/plugins with local fixes
- Web admin frontends (learning-chat, persona-admin), unified hexi/web
- Tests for rate_limit/cooldown/memes/persona; poetry.lock

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-01 13:13:40 +08:00
co-authored by Claude
parent 1783c60afa
commit b61d09f09f
3201 changed files with 160436 additions and 171 deletions
@@ -0,0 +1,145 @@
# nonebot_plugin_helldivers_tools
绝地潜兵小助手。命令:
- `简报`:获取星系战争概况(现在走 **[helldivers-2/api](https://github.com/helldivers-2/api)** 社区接口,用 Playwright 渲染自建 HTML 卡片,**不再打开外部网页截图**)。
- `随机战备`:随机一套战备(自建 HTML 卡片,与 `简报` 一样用 `nonebot_plugin_htmlrender` 出图)。
## 为什么改成 API?
旧方案用 Playwright 打开 `hd2galaxy.com` 截全页图,要等 `networkidle`(本地化 30s 左右),还要在页面 DOM 里做星球名替换,慢且脆弱。
新方案:
1. 调 `helldivers-2/api` 的 `/api/v1/war`、`/assignments`、`/campaigns`、`/planets`(带缓存,避免触发限流);
2. 用**自己的 HTML 模板**(内联 CSS + base64 logo + 系统字体)渲染一张战况卡片;
3. 交给 **[nonebot_plugin_htmlrender](https://github.com/nonebotjs/nonebot-plugin-htmlrender)** 的 `get_new_page()` 出图。因为模板是本地内容、无外部资源,`set_content` 后秒级完成,不再卡 30s。
> 说明:由 `nonebot_plugin_htmlrender` 管理 Playwright 内核(机器人日志里 `HTMLRender Started.` 即已加载)。`简报` 与 `随机战备` 都依赖 `require("nonebot_plugin_htmlrender")`,与 `nonebot_plugin_picstatus` 的做法一致。
## 新增/改动文件
| 文件 | 作用 |
| --- | --- |
| `config.py` | NoneBot 插件配置 |
| `hd2_api.py` | 异步 API 客户端(httpx + 进程内缓存 + 并发聚合) |
| `war_renderer.py` | 战况 HTML 模板 + 用 `nonebot_plugin_htmlrender` 渲染为 PNG |
| `equipment_renderer.py` | `随机战备` HTML 模板 + 用 `nonebot_plugin_htmlrender` 渲染为 PNG |
| `__init__.py` | `简报` 与 `随机战备` 命令改为走 API / 本地 + 渲染(移除了 `screen_shot` 依赖) |
## 配置
NoneBot 配置(`.env` / `.env.dev`)读取以下环境变量,均为可选,用默认值即可开箱即用:
| 环境变量 | 默认值 | 说明 |
| --- | --- | --- |
| `hd2_api_base_url` | `https://api.helldivers2.dev` | 自托管 helldivers-2/api 的根地址;不填用公共社区 API |
| `hd2_api_language` | `zh-Hans` | 请求本地化语言(自托管默认支持 zh-Hans / zh-Hant) |
| `hd2_cache_ttl` | `60` | 缓存秒数,避免频繁打接口触发限流(5次/10秒) |
| `hd2_super_client` | `hexi-bot.local` | 请求头,社区 API 必备 |
| `hd2_super_contact` | `https://github.com/sansenhoshi/nonebot_plugin_helldivers_tools` | 请求头,社区 API 必备 |
> **⚠️ 两个 `X-Super-*` 头必须都存在**:`RateLimitMiddleware` 在 `ValidateClients=true` 时(公共实例默认开启)要求请求**同时**带 `X-Super-Client` 和 `X-Super-Contact`,缺一即返回 `400 Bad Request / "The X-Super-Client and X-Super-Contact headers are required"`。客户端已保证始终发这两个头,`hd2_super_contact` 留空也会用 `hd2_super_client` 兜底。
示例:
```ini
hd2_api_base_url=http://127.0.0.1:8080
hd2_api_language=zh-Hans
hd2_cache_ttl=60
```
## 自托管 helldivers-2/api
项目是 .NET 10 + Docker。用 Docker Compose 最省事。
### 1) 克隆(务必带子模块)
```bash
git clone --recurse-submodules https://github.com/helldivers-2/api.git
cd api
```
> `src/Helldivers-2-Models/json` 是子模块,里面是星球名/本地化数据,**不带上它接口会没数据**。
### 2) 放一个 `docker-compose.yml`(在 `api` 根目录)
> 本仓库已写好一份:`HeXi/deploy/helldivers-api/docker-compose.yml`,复制到 `api` 仓库根目录即可。要点:`ASPNETCORE_URLS=http://+:8080`(否则默认监听 5000)、`Helldivers__API__Authentication__Enabled=false`(必须)、`ValidateClients=false`、含健康检查/重启/日志轮转。
```yaml
services:
helldivers-api:
build:
context: .
dockerfile: src/Helldivers-2-API/Dockerfile
image: helldivers2-api
container_name: helldivers2-api
ports:
- "8080:8080"
environment:
# 自托管务必关鉴权:默认 Authentication.Enabled=true 但没 SigningKey,直接启动会抛 ArgumentNullException(issue #90)
- Helldivers__API__Authentication__Enabled=false
# 抬高本地限流,多核打也不怕
- Helldivers__API__RateLimit=300
- Helldivers__API__RateLimitWindow=10
# 与 ArrowHead 官方接口同步频率(秒)
- Helldivers__Synchronization__IntervalSeconds=60
restart: unless-stopped
```
### 3) 构建并启动
```bash
docker compose up -d --build
```
### 4) 验证
```bash
# 应该返回 JSON(当前战争信息)
curl http://127.0.0.1:8080/api/v1/war
curl http://127.0.0.1:8080/api/v1/assignments
```
### 5) 让机器人指向它
在机器人 `.env` / `.env.dev` 里(本项目在 `HeXi/.env`)写:
```ini
hd2_api_base_url=http://127.0.0.1:8080
hd2_api_language=zh-Hans
```
然后重启机器人,发 `简报` 即可。默认配置的 `Languages` 已含 `zh-Hans`,星球名/大指令就是中文。
### 常见问题
- **`docker compose up` 报 `ArgumentNullException (Parameter 's')`**:忘关鉴权了,确认有 `Helldivers__API__Authentication__Enabled=false`。
- **`/api/v1/war` 一直是 200 但 `planet.name` 为空**:子模块没拉全,重新 `git submodule update --init --recursive` 后重建。
- **返回 400 / "X-Super-Client and X-Super-Contact headers are required"**:这是实例开了 `ValidateClients`;我方客户端已永远带这两个头,正常不会触发。
- **容器能起但数组为空**:容器要联网访问 ArrowHead 官方接口才能同步,检查机器能否访问外网。
- **想用 Fly.io**:仓库自带 `fly.toml`,直接 `fly deploy`。
### 备选部署(不用 Docker)
数据(星球名/本地化)由 `Helldivers-2-SourceGen` 在**编译期**写进二进制,运行时**不读任何 json 文件**,所以直接跑发布产物完全可行。
**Windows 本机跑(只需 .NET 10 SDK 构建一次):**
```powershell
git clone --recurse-submodules https://github.com/helldivers-2/api.git && cd api
dotnet publish src/Helldivers-2-API/Helldivers-2-API.csproj `
-c Release -r win-x64 --self-contained true -p:PublishAot=false -o .\publish
$env:ASPNETCORE_URLS="http://127.0.0.1:8080"
$env:Helldivers__API__Authentication__Enabled="false"
$env:Helldivers__API__RateLimit="300"
.\publish\Helldivers-2-API.exe
```
> `-p:PublishAot=false`:项目默认 AOT,Windows 上要 VC++ 工具链,关掉避免踩坑。
**VPS + systemd**:把发布产物换成 `linux-x64` 上传,用 systemd 挂服务(`Environment=Helldivers__API__Authentication__Enabled=false`)。
**Fly.io / 其它 PaaS**:仓库自带 `fly.toml`,`fly deploy` 即可;Render/Railway/Azure 同理。
## 接口字段依赖
- `war.statistics`:`playerCount`、`missionSuccessRate`、`terminidKills`、`automatonKills`、`illuminateKills`、`deaths`
- `assignments[0]`:`title`、`description`、`briefing`、`reward/rewards`、`expiration`
- `campaigns[]` → `planet`:`name`、`currentOwner`、`health`、`maxHealth`、`statistics.playerCount`
- `planets[]` → `currentOwner`(星系控制统计用)
## 翻译兜底
接口带 `Accept-Language: zh-Hans` 时,`LocalizedMessage` 字段会返回中文。若某字段仍为英文,渲染层会用 `data/plantes_mix.json` 做兜底翻译。