Files

160 lines
6.2 KiB
Python
Raw Permalink Normal View History

"""MC 服务器信息查询 —— Web API 子应用(挂载到 /api/mc_server_status)。
服务器数据(`hexi/data/mc_status_data.json`:`{群号: {名字: [地址, 类型]}}`)
的唯一 Web 读写入口:列出 / 新增 / 编辑(含改名)/ 删除 / 测试连接。
写操作全部落在 services/servers.py(校验只有那一份),和群里的
`添加服务器`/`删除服务器` 命令改的是同一份内存态 + 同一个文件。
鉴权走 hexi.web_hub.web_auth(OAuth2 + SQLite),与统一管理台 /hub 共用登录态。
**每条路由都要自带 `dependencies=[auth]`** —— mount 层没有兜底,漏一条就是匿名可访问。
前端页面:hexi/web/src/plugins/mc_server_status/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 .services import mc as mc_service
from .services import servers as store
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})
async def _group_names() -> dict[str, str]:
"""群号 → 群名;拿不到 bot(未连接)时返回空表,前端只显示群号。"""
try:
from nonebot import get_adapter
from nonebot.adapters.onebot.v11 import Adapter
bots = get_adapter(Adapter).bots
bot = next(iter(bots.values()), None)
if bot is None:
return {}
return {
str(g["group_id"]): g.get("group_name") or ""
for g in await bot.get_group_list()
}
except Exception: # noqa: BLE001 — 未连接/适配器未加载都按拿不到处理
return {}
def _payload() -> dict:
"""整表视图:群列表(含群名与在线标记)+ 类型选项。"""
groups = store.snapshot()
return {
"groups": [
{
"group_id": str(gid),
"servers": [
{"name": name, "address": value[0], "type": value[1]}
for name, value in sorted(servers.items())
],
}
for gid, servers in sorted(groups.items())
],
"types": store.SERVER_TYPES,
"address_hint": store.ADDRESS_HINT,
}
async def _payload_with_names() -> dict:
"""跑一次 payload 再把群名补上(两条路由共用的收尾)。"""
data = _payload()
names = await _group_names()
for row in data["groups"]:
row["group_name"] = names.get(row["group_id"], "")
row["online"] = row["group_id"] in names
data["bot_online"] = bool(names)
return data
def build_admin_app() -> FastAPI | None:
"""构建 MC 服务器数据管理 API 子应用(挂载到 /api/mc_server_status)。"""
app = FastAPI(title="MC Server Status API")
auth = require_admin
@app.get("/overview", response_class=JSONResponse, dependencies=[auth])
async def overview():
"""一次拿全:所有群 + 服务器列表 + 类型选项。"""
return _ok(await _payload_with_names())
@app.post("/server", response_class=JSONResponse, dependencies=[auth])
async def save_server(data: dict):
"""新增/更新一台服务器;带 old_name 且与 name 不同即为改名。"""
try:
group_id = store.parse_group_id(data.get("group_id"))
name = store.normalize_name(data.get("name"))
created = store.set_server(
group_id,
name,
str(data.get("address") or ""),
str(data.get("type") or ""),
old_name=str(data.get("old_name") or "").strip() or None,
)
except store.ServerError as e:
return _fail(str(e))
return _ok(
await _payload_with_names(),
f"服务器「{name}」已{'添加' if created else '保存'}",
)
@app.delete("/server/{group_id}/{name}", response_class=JSONResponse, dependencies=[auth])
async def remove_server(group_id: str, name: str):
"""删掉某群里的一台服务器(删空了连带删群)。"""
try:
store.delete_server(group_id, name)
except store.ServerError as e:
return _fail(str(e))
return _ok(await _payload_with_names(), f"服务器「{name}」已删除")
@app.delete("/group/{group_id}", response_class=JSONResponse, dependencies=[auth])
async def remove_group(group_id: str):
"""删掉整个群的服务器配置。"""
try:
store.delete_group(group_id)
except store.ServerError as e:
return _fail(str(e))
return _ok(await _payload_with_names(), f"群 {group_id} 的服务器配置已删除")
@app.post("/test", response_class=JSONResponse, dependencies=[auth])
async def test_server(data: dict):
"""测试连接:给了群号+名字就测存量那台,否则测临时填的地址(不落库)。
对不上的地址走 mcstatus 自己的 2 秒超时,返回的是查询失败文案而不是报错。
"""
group_id = str(data.get("group_id") or "").strip()
name = str(data.get("name") or "").strip()
address = str(data.get("address") or "").strip()
server_type = str(data.get("type") or "").strip()
if group_id.isdigit() and name:
found = store.lookup(int(group_id), name)
if found:
address, server_type = found[0], found[1]
if not address:
return _fail("先填服务器地址")
try:
address = store.normalize_address(address)
server_type = store.normalize_type(server_type)
except store.ServerError as e:
return _fail(str(e))
message = await mc_service.check_mc_status(name or "测试", address, server_type)
# check_mc_status 正常返回 str;带图标的 Java 服返回 Message(Web 侧只要纯文本)
text = str(message) if not isinstance(message, str) else message
return _ok({"ok": "查询失败" not in text, "message": text}, "测试完成")
return app