mirror of
https://github.com/Nezumi-2711/astrbot_plugin_qq_group_daily_analysis.git
synced 2026-09-23 04:09:59 +00:00
191 lines
7.0 KiB
Python
191 lines
7.0 KiB
Python
"""
|
|
PDF工具模块
|
|
负责PDF相关的安装和管理功能
|
|
"""
|
|
|
|
import asyncio
|
|
import sys
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .logger import logger
|
|
|
|
|
|
class PDFInstaller:
|
|
"""
|
|
工具组件:PDF 渲染引擎 (Playwright) 安装器
|
|
|
|
该组件负责管理 Playwright 及其对应浏览器内核 (Chromium) 的安装生命周期。
|
|
由于内核下载耗时较长且受网络波动影响,采用非阻塞的后台任务模式执行。
|
|
"""
|
|
|
|
# 类级别的线程池,专用于隔离耗时的 IO/Shell 操作
|
|
_executor = ThreadPoolExecutor(
|
|
max_workers=1, thread_name_prefix="playwright_install"
|
|
)
|
|
|
|
# 静态安装状态追踪
|
|
_install_status: dict[str, Any] = {
|
|
"in_progress": False,
|
|
"completed": False,
|
|
"failed": False,
|
|
"error_message": None,
|
|
}
|
|
|
|
@staticmethod
|
|
async def install_playwright(config_manager: Any) -> str:
|
|
"""
|
|
异步入口:安装 Playwright 环境。
|
|
|
|
流程:
|
|
1. 调用 pip 安装 `playwright` Python 包。
|
|
2. 验证自定义浏览器路径配置。
|
|
3. 若无自定义路径,则触发浏览器内核安装。
|
|
|
|
Args:
|
|
config_manager (Any): 配置管理实例,用于读取/设置安装状态。
|
|
|
|
Returns:
|
|
str: 安装阶段提示信息
|
|
"""
|
|
try:
|
|
logger.info("正在初始化 Playwright 安装流程...")
|
|
|
|
# 1. 下载并安装库文件
|
|
logger.info("第一步:正在运行 pip install playwright...")
|
|
process = await asyncio.create_subprocess_exec(
|
|
sys.executable,
|
|
"-m",
|
|
"pip",
|
|
"install",
|
|
"playwright>=1.40.0",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
|
|
stdout, stderr = await process.communicate()
|
|
|
|
if process.returncode != 0:
|
|
error_msg = stderr.decode().strip()
|
|
logger.error(f"Playwright 库安装失败: {error_msg}")
|
|
return f"❌ pip install playwright 失败: {error_msg}"
|
|
|
|
logger.info("第一步完成。正在检查浏览器内核...")
|
|
|
|
custom_path = config_manager.get_browser_path()
|
|
if custom_path and Path(custom_path).exists():
|
|
logger.info(f"检测到自定义浏览器路径: {custom_path}。跳过内核下载。")
|
|
return f"✅ Playwright 库已就绪。已检测到自定义浏览器 `{custom_path}`,无需额外安装内核。您可以直接开始生成 PDF。"
|
|
|
|
# 3. 部署浏览器内核
|
|
return await PDFInstaller.install_system_deps()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Playwright 设置过程中出错: {e}")
|
|
return f"❌ 安装过程中出错: {str(e)}"
|
|
|
|
@staticmethod
|
|
async def install_system_deps() -> str:
|
|
"""
|
|
触发浏览器内核的后台异步安装流程。
|
|
|
|
该方法检查防重入状态,并立即返回任务启动信息,不会阻塞主线程。
|
|
|
|
Returns:
|
|
str: 任务排队状态提示
|
|
"""
|
|
try:
|
|
if PDFInstaller._install_status["in_progress"]:
|
|
return "⏳ 浏览器内核正在后台部署中,请稍后检查日志或状态。"
|
|
|
|
PDFInstaller._install_status.update(
|
|
{
|
|
"in_progress": True,
|
|
"completed": False,
|
|
"failed": False,
|
|
"error_message": None,
|
|
}
|
|
)
|
|
|
|
logger.info("正在启动后台线程以部署 Chromium 内核...")
|
|
asyncio.create_task(PDFInstaller._background_playwright_install())
|
|
|
|
return (
|
|
"🚀 浏览器内核安装任务已成功在后台启动。\n\n"
|
|
"程序正在执行 `playwright install chromium`,由于体积较大,通常需花费 2-5 分钟。\n"
|
|
"此过程不会影响机器人正常响应。安装完成后,系统日志将进行通知。"
|
|
)
|
|
|
|
except Exception as e:
|
|
PDFInstaller._install_status["in_progress"] = False
|
|
logger.error(f"启动安装任务失败: {e}")
|
|
return f"❌ 启动安装任务失败: {e}"
|
|
|
|
@staticmethod
|
|
async def _background_playwright_install() -> None:
|
|
"""
|
|
底层宿主任务:驱动 system shell 执行浏览器二进制文件部署。
|
|
"""
|
|
try:
|
|
logger.info("正在执行二进制文件:playwright install chromium")
|
|
|
|
# 通过当前 Python 解释器环境调用子模块,确保环境隔离
|
|
process = await asyncio.create_subprocess_exec(
|
|
sys.executable,
|
|
"-m",
|
|
"playwright",
|
|
"install",
|
|
"chromium",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
|
|
stdout, stderr = await process.communicate()
|
|
|
|
if process.returncode == 0:
|
|
PDFInstaller._install_status["completed"] = True
|
|
logger.info("✅ Chromium 内核安装成功。")
|
|
|
|
# Linux 特殊处理:提示用户补充系统依赖
|
|
if sys.platform.startswith("linux"):
|
|
logger.info(
|
|
"提示:在 Linux 上,如果 PDF 生成仍然失败,请尝试运行 'sudo playwright install-deps'。"
|
|
)
|
|
else:
|
|
PDFInstaller._install_status["failed"] = True
|
|
PDFInstaller._install_status["error_message"] = stderr.decode().strip()
|
|
logger.error(f"❌ Chromium 安装二进制文件执行失败: {stderr.decode()}")
|
|
|
|
except Exception as e:
|
|
PDFInstaller._install_status.update(
|
|
{"failed": True, "error_message": str(e)}
|
|
)
|
|
logger.error(f"Playwright 后台任务遇到异常: {e}")
|
|
finally:
|
|
PDFInstaller._install_status["in_progress"] = False
|
|
|
|
@staticmethod
|
|
def get_pdf_status(config_manager: Any) -> str:
|
|
"""
|
|
查询当前系统的 PDF 功能可用性状态描述。
|
|
|
|
Args:
|
|
config_manager (Any): 配置管理器,用于读取核心探测开关。
|
|
|
|
Returns:
|
|
str: 用户友好的状态文本
|
|
"""
|
|
if config_manager.playwright_available:
|
|
version = config_manager.playwright_version or "Unknown"
|
|
status = f"✅ PDF 功能可用 (核心版本: {version})"
|
|
|
|
if PDFInstaller._install_status["in_progress"]:
|
|
status += "\n⏳ 警告:浏览器内核仍在后台下载/部署中..."
|
|
elif PDFInstaller._install_status["failed"]:
|
|
status += f"\n⚠️ 上次内核安装异常: {PDFInstaller._install_status.get('error_message')}"
|
|
|
|
return status
|
|
else:
|
|
return "❌ PDF 渲染核心未安装 - 请发送管理员指令 `/安装PDF`。"
|