"""otto 活字印刷 —— 入口(元数据 + 依赖)。""" from __future__ import annotations from pathlib import Path from nonebot import get_driver, require from nonebot.plugin import PluginMetadata require("nonebot_plugin_alconna") __plugin_meta__ = PluginMetadata( name="otto活字印刷", description="鬼畜音源活字印刷(otto语音合成)", usage="otto 一些内容 生成otto语音\notto 一些内容 -reverse 生成并倒放", type="application", ) _PLUGIN_ROOT = Path(__file__).resolve().parent # 生成音频统一落到 hexi/data/temp/otto(与 video_analysis 的临时目录约定一致)。 # 协议端(NapCat)通常与 bot 后端不在同一台机器,无法直接访问本地文件, # 所以先落盘再通过 FastAPI 路由以 http URL 提供给 NapCat 下载。 _TEMP_ROOT = _PLUGIN_ROOT.parent.parent / "data" / "temp" / "otto" _driver = get_driver() @_driver.on_startup async def _register_media_route() -> None: """挂载 /api/otto/{filename},供协议端跨机器下载生成的语音。""" from fastapi import HTTPException from fastapi.responses import FileResponse from nonebot import get_app app = get_app() def serve_otto_file(filename: str): root = _TEMP_ROOT.resolve() target = (root / filename).resolve() if root != target.parent and root not in target.parents: raise HTTPException(status_code=403, detail="Forbidden") if not target.is_file(): raise HTTPException(status_code=404, detail="Not Found") return FileResponse(target, media_type="audio/mpeg") if not any(getattr(r, "path", None) == "/api/otto/{filename}" for r in app.routes): app.add_api_route( "/api/otto/{filename}", serve_otto_file, methods=["GET"], name="otto_media", ) from .handlers import otto # noqa: E402,F401