"""阴阳师等长公告的安全 HTML 截图渲染。"""
import base64
import html
import re
from typing import Optional
from pyppeteer import launch
try:
import bleach
except ImportError:
bleach = None
ARTICLE_WIDTH = 900
ARTICLE_PADDING = 48
_ALLOWED_TAGS = [
"p", "br", "strong", "b", "em", "i", "u", "span", "div",
"h1", "h2", "h3", "ul", "ol", "li", "blockquote", "hr",
]
def _sanitize_article_html(content_html: str) -> str:
"""净化外部公告 HTML;缺少 bleach 时退化为安全的纯文本段落。"""
if bleach is not None:
return bleach.clean(content_html, tags=_ALLOWED_TAGS, attributes={}, strip=True)
# 生产环境缺少可选依赖时,宁可丢失样式也不把不可信标签交给浏览器执行。
text = re.sub(r"(?is)<(script|style).*?>.*?\1>", "", content_html)
text = re.sub(r"(?i)
", "\n", text)
text = re.sub(r"(?i)(p|div|h[1-6]|li|blockquote)>", "\n", text)
text = re.sub(r"<[^>]+>", "", text)
return "
" + html.escape(text).replace("\n", "
") + "
"
def build_article_document(content_html: str, title: str, published_at: Optional[str]) -> str:
"""净化上游公告 HTML,并包装为固定版式的截图页面。"""
safe_content = _sanitize_article_html(content_html)
safe_title = html.escape(title)
safe_time = html.escape(published_at or "")
return f"""
阴阳师维护公告
{safe_title}
发布时间:{safe_time}
"""
async def render_article_to_base64(content_html: str, title: str, published_at: Optional[str]) -> str:
"""使用无头 Chromium 渲染完整公告为一张长图。"""
browser = None
page = None
try:
browser = await launch(
headless=True,
args=["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu"],
)
page = await browser.newPage()
await page.setViewport({"width": ARTICLE_WIDTH, "height": 1200, "deviceScaleFactor": 1})
await page.setContent(build_article_document(content_html, title, published_at))
height = await page.evaluate("document.documentElement.scrollHeight")
if height <= 0:
raise ValueError("公告截图高度无效")
image = await page.screenshot({"fullPage": True, "type": "png"})
return f"base64://{base64.b64encode(image).decode('ascii')}"
finally:
if page is not None:
await page.close()
if browser is not None:
await browser.close()