"""ncatbot-webhook-plugin 入口:组装各模块,启动 HTTP 服务。""" import asyncio import logging import os from aiohttp import web from ncatbot.plugin import NcatBotPlugin from .config import HOST, PORT, UPLOAD_DIR, WEBHOOK_API_KEY from .handlers.health import health_handler from .handlers.message import webhook_handler from .handlers.upload import cleanup_expired_files, upload_handler from .middleware import auth_middleware, request_id_middleware logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) class WebHookPlugin(NcatBotPlugin): """NcatBot 插件:对外暴露 HTTP 接口,接收外部消息转发至 QQ。""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._webhook_runner: web.AppRunner | None = None self._cleanup_task: asyncio.Task | None = None async def on_load(self): self.logger.info("Webhook 插件已加载") self.logger.info("WEBHOOK_API_KEY: %s", "已配置" if os.environ.get("WEBHOOK_API_KEY") else "自动生成") asyncio.create_task(self._start_webhook()) self._cleanup_task = asyncio.create_task(self._cleanup_loop()) async def on_close(self): if self._cleanup_task is not None: self._cleanup_task.cancel() try: await self._cleanup_task except asyncio.CancelledError: pass self._cleanup_task = None await self._stop_webhook() self.logger.info("Webhook 插件已卸载") async def _cleanup_loop(self) -> None: """每小时清理一次过期上传文件。""" while True: await asyncio.sleep(3600) try: await cleanup_expired_files() except Exception as exc: self.logger.error("清理过期文件失败: %s", exc) def _create_app(self) -> web.Application: app = web.Application(middlewares=[request_id_middleware, auth_middleware]) app["qq_api"] = self.api app["logger"] = self.logger app.router.add_get("/healthz", health_handler) app.router.add_post("/webhook", webhook_handler) app.router.add_post("/upload", upload_handler) return app async def _start_webhook(self): await self._stop_webhook() app = self._create_app() self._webhook_runner = web.AppRunner(app) await self._webhook_runner.setup() site = web.TCPSite(self._webhook_runner, HOST, PORT) await site.start() self.logger.info("Webhook 已启动: %s:%d", HOST, PORT) self.logger.info("上传目录: %s", UPLOAD_DIR) async def _stop_webhook(self): if self._webhook_runner is not None: await self._webhook_runner.cleanup() self._webhook_runner = None self.logger.info("Webhook 已停止")