Files
HeXi/hexi/plugins/nonebot_plugin_galgame_card/web_hub.py
T
sansenhoshi 131b92b319 结构调整
视频解析多图/多媒体结构 消息体适配
2026-09-08 14:25:32 +08:00

222 lines
7.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""群聊人设卡 Web API 子应用(挂载到 /api/galgame_card)。
统一鉴权走 hexi.web_hub.web_auth(OAuth2 + SQLite),与统一管理台 /hub 共用登录态。
前端由统一管理台 hexi/web 渲染。
"""
from __future__ import annotations
from typing import Optional
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from hexi.web_hub.web_auth import require_admin
from . import repository
API = require_admin
def _ok(data=None, msg: str = "ok") -> JSONResponse:
return JSONResponse({"status": 0, "msg": msg, "data": data})
def _err(code: int, msg: str) -> JSONResponse:
return JSONResponse({"status": code, "msg": msg})
async def _first_bot():
try:
from nonebot import get_adapter
from nonebot.adapters.onebot.v11 import Adapter
bots = get_adapter(Adapter).bots
return next(iter(bots.values()), None)
except Exception: # noqa: BLE001
return None
def build_admin_app() -> FastAPI | None:
"""构建群聊人设卡管理 API 子应用(挂载到 /api/galgame_card)。
鉴权由 hub 统一登录态提供(hexi.web_hub.web_auth)。
"""
app = FastAPI(title="Galgame Card API")
@app.get("/groups", dependencies=[API])
async def groups():
rows = await repository.list_groups()
corpus = await repository.corpus_counts_by_group()
impressions = await repository.impression_counts_by_group()
summaries = await repository.summary_counts_by_group()
participants = await repository.participant_counts_by_group()
result = []
for row in rows:
gid = row.group_id
result.append(
{
"group_id": gid,
"group_name": row.group_name,
"enabled": row.enabled,
"participants": participants.get(gid, 0),
"corpus_count": corpus.get(gid, 0),
"impression_count": impressions.get(gid, 0),
"summary_count": summaries.get(gid, 0),
}
)
return _ok(result)
@app.get("/groups/discover", dependencies=[API])
async def discover_groups():
bot = await _first_bot()
if bot is None:
return _err(-100, "未连接 OneBot,无法获取群列表")
group_list = await bot.get_group_list()
managed_ids = {row.group_id for row in await repository.list_groups()}
result = [
{
"group_id": int(g["group_id"]),
"group_name": g.get("group_name", ""),
"managed": int(g["group_id"]) in managed_ids,
}
for g in group_list
]
await repository.backfill_group_names(
{int(g["group_id"]): g.get("group_name", "") for g in group_list}
)
return _ok(result)
@app.post("/groups", dependencies=[API])
async def add_group(data: dict):
gid = int(data.get("group_id") or 0)
if gid <= 0:
return _err(400, "group_id 不合法")
await repository.add_group(gid, str(data.get("group_name") or "").strip())
return _ok(msg="已加入管理列表")
@app.post("/groups/{group_id}/enabled", dependencies=[API])
async def set_group_enabled(group_id: int, data: dict):
await repository.set_group_enabled(group_id, bool(data.get("enabled")))
return _ok(msg="已开启采集" if data.get("enabled") else "已关闭采集")
@app.get("/groups/{group_id}/participants", dependencies=[API])
async def participants(group_id: int):
members = await repository.joined_users(group_id)
stats = dict(await repository.chat_log_stats_by_user(group_id))
result = [
{"user_id": uid, "corpus_count": stats.get(uid, 0)} for uid in members
]
return _ok(result)
@app.post("/groups/{group_id}/participants", dependencies=[API])
async def add_participant(group_id: int, data: dict):
user_id = int(data.get("user_id") or 0)
if user_id <= 0:
return _err(400, "user_id 不合法")
if await repository.is_joined(user_id, group_id):
return _err(400, "已在人设名单中")
await repository.join(user_id, group_id)
return _ok(msg="已加入")
@app.delete("/groups/{group_id}/participants/{user_id}", dependencies=[API])
async def remove_participant(group_id: int, user_id: int):
await repository.leave(user_id, group_id)
return _ok(msg="已移除")
@app.get("/groups/{group_id}/logs", dependencies=[API])
async def logs(
group_id: int,
user_id: Optional[int] = None,
page: int = 1,
per_page: int = 50,
):
offset = max(0, (page - 1) * per_page)
items = await repository.list_logs(group_id, user_id, offset, per_page)
total = await repository.count_logs_filtered(group_id, user_id)
result = [
{
"id": row.id,
"user_id": row.user_id,
"nickname": row.nickname,
"content": row.content,
"target_user_id": row.target_user_id,
"target_inherited": row.target_inherited,
"follows_id": row.follows_id,
"created_at": row.created_at.strftime("%Y-%m-%d %H:%M:%S"),
}
for row in items
]
return _ok({"items": result, "total": total})
@app.get("/groups/{group_id}/impressions", dependencies=[API])
async def impressions(group_id: int, page: int = 1, per_page: int = 50):
offset = max(0, (page - 1) * per_page)
items = await repository.list_impressions_group(group_id, offset, per_page)
result = [
{
"id": row.id,
"user_id": row.user_id,
"content": row.content,
"cover_from_id": row.cover_from_id,
"cover_to_id": row.cover_to_id,
"created_at": row.created_at.strftime("%Y-%m-%d %H:%M:%S"),
}
for row in items
]
return _ok(
{
"items": result,
"total": await repository.count_impressions_group(group_id),
}
)
@app.get("/groups/{group_id}/summaries", dependencies=[API])
async def summaries(group_id: int):
items = await repository.list_summaries_group(group_id)
result = [
{
"version": row.version,
"card_text": row.card_text,
"corpus_count": row.corpus_count,
"impression_count": row.impression_count,
"model": row.model,
"created_at": row.created_at.strftime("%Y-%m-%d %H:%M:%S"),
}
for row in items
]
return _ok(result)
@app.delete("/groups/{group_id}/logs", dependencies=[API])
async def delete_logs(group_id: int, user_id: Optional[int] = None):
count = await repository.delete_logs(group_id, user_id)
return _ok(msg=f"已删除 {count} 条语料")
@app.delete("/groups/{group_id}/data", dependencies=[API])
async def clear_group(group_id: int):
await repository.clear_group_data(group_id)
return _ok(msg="已清空该群全部数据")
@app.delete("/groups/{group_id}", dependencies=[API])
async def remove_group(group_id: int):
await repository.remove_group(group_id)
return _ok(msg="已移出管理列表(数据保留,重新加入可续上)")
@app.get("/settings", dependencies=[API])
async def settings():
from . import processor
return _ok(
{
"thresholds": {
"max_content_len": processor.MAX_CONTENT_LEN,
"repeat_window": processor.REPEAT_WINDOW,
"throttle_seconds": processor.THROTTLE_SECONDS,
"rolling_window": repository.ROLLING_WINDOW,
},
}
)
return app