refactor(bf-bot): 插件重构为 handlers/services/utils 分层,资源归入 res/

- 旧平铺模块(data/data_utils/database_op/image_builder*/get_bf6_data/
  gametools_bf6/bf6_data/img_utils)拆分为 handlers(绑定/查询)、
  services(fetchers/processing/query)、utils(渲染/参数)分层;
  新增 repository.py / config.py / paths.py
- 字体/图片/模板资源迁入 res/,新增 BF2042 卡面素材(bg/object_icon 等)
- browser_ext/:BF2042 扩展捕获桥(WS 直连兜底通道)源码入库
- 新增 hexi/web/src/plugins/bf_bot 前端管理页
- 新增 tests:test_bf_bot_helpers / repository / tracker_api

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
sansenhoshiandClaude Code committed 2026-10-13 17:53:06 +08:00
1 parent 614e1d7e00
commit a31aab00e8
752 files changed
+6241 -2506

No files matched your search

@@ -0,0 +1,5 @@
"""Web 层:绑定管理 API 子应用(挂载到 /api/bf_bot)。"""
from .admin import build_admin_app
__all__ = ["build_admin_app"]
@@ -0,0 +1,76 @@
"""BF查询 —— Web API 子应用(挂载到 /api/bf_bot):QQ↔EAID 绑定管理。
读写全部走 repository.py(与群里 /绑定 /解绑 命令改的是同一个库文件)。
鉴权走 hexi.web_hub.web_auth(OAuth2 + SQLite),与统一管理台 /hub 共用登录态。
**每条路由都要自带 `dependencies=[auth]`** —— mount 层没有兜底,漏一条就是匿名可访问。
前端页面:hexi/web/src/plugins/bf_bot/index.tsx。
"""
from __future__ import annotations
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from hexi.web_hub.web_auth import require_admin
from ..repository import UserManager
API = require_admin
def _ok(data=None, msg: str = "ok") -> JSONResponse:
return JSONResponse({"status": 0, "msg": msg, "data": data})
def _fail(msg: str, status: int = 400) -> JSONResponse:
return JSONResponse({"status": status, "msg": msg})
def build_admin_app() -> FastAPI:
"""构建绑定管理 API 子应用(挂载到 /api/bf_bot)。"""
app = FastAPI(title="BF查询绑定管理")
auth = require_admin
@app.get("/bindings", response_class=JSONResponse, dependencies=[auth])
async def list_bindings(page: int = 1, per_page: int = 10):
"""绑定列表(QQ / EA 名称 / EA 双 ID / 标签 / 绑定时间),按绑定时间倒序分页。"""
page = max(1, page)
per_page = min(max(1, per_page), 100)
manager = UserManager()
total = manager.count_users()
users = manager.list_users(limit=per_page, offset=(page - 1) * per_page)
return _ok(
{
"bindings": [
{
"qq_id": u.get("qq_id"),
"ea_player_name": u.get("ea_player_name"),
"ea_player_id": u.get("ea_player_id"),
"ea_user_id": u.get("ea_user_id"),
"designation": u.get("designation"),
"create_time": u.get("create_time"),
}
for u in users
],
"total": total,
"page": page,
"per_page": per_page,
}
)
@app.delete("/bindings/{qq_id}", response_class=JSONResponse, dependencies=[auth])
async def delete_binding(qq_id: str):
"""解绑某 QQ(等价于群里的「解绑」)。"""
manager = UserManager()
record = manager.get_user_by_qq(qq_id)
if not record:
return _fail("未找到该 QQ 的绑定记录", 404)
rows = manager.delete_user_by_qq(qq_id)
if rows > 0:
return _ok(
{"qq_id": qq_id, "ea_player_name": record.get("ea_player_name")},
f"已解绑 {record.get('ea_player_name')}",
)
return _fail("删除失败")
return app