Add HeXi bot codebase: custom plugins, web frontends, tests

- hexi core: message handling, rate limiting, cooldown, plugin manager
- Custom plugins: BF stats, daily check-in, quotes, persona cards, etc.
- Community plugins vendored under hexi/plugins with local fixes
- Web admin frontends (learning-chat, persona-admin), unified hexi/web
- Tests for rate_limit/cooldown/memes/persona; poetry.lock

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-01 13:13:40 +08:00
co-authored by Claude
parent 1783c60afa
commit b61d09f09f
3201 changed files with 160436 additions and 171 deletions
@@ -0,0 +1,62 @@
"""Playwright 渲染器:将报告 HTML 渲染为图片字节。
签名对应原插件 html_render(html, data, return_url, options)。
"""
from __future__ import annotations
from typing import Any
from .core.utils.logger import logger
_SCALE_MAP = {
"ultra": 3,
"high": 2,
"normal": 1,
"low": 0.5,
}
async def html_render(
html_content: str,
data: dict | None = None,
return_url: bool = False,
options: dict | None = None,
) -> bytes | str:
"""渲染 HTML 为图片。返回 bytes(PNG/JPEG)。"""
options = options or {}
viewport_width = int(options.get("viewport_width", 1440))
viewport_height = int(options.get("viewport_height", 900))
full_page = bool(options.get("full_page", True))
img_type = str(options.get("type", "png")).lower()
if img_type in ("jpg", "jpeg"):
img_type = "jpeg"
quality = int(options.get("quality", 90))
timeout = int(options.get("timeout", 30000))
scale_level = str(options.get("device_scale_factor_level", "normal")).lower()
scale = _SCALE_MAP.get(scale_level, 1)
try:
from playwright.async_api import async_playwright
except Exception as e:
logger.error(f"[T2I] 无法导入 playwright: {e}")
return ""
try:
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page(
viewport={"width": viewport_width, "height": viewport_height},
device_scale_factor=scale,
)
await page.set_content(html_content, wait_until="networkidle", timeout=timeout)
shot = await page.screenshot(
type=img_type,
quality=quality if img_type == "jpeg" else None,
full_page=full_page,
)
await browser.close()
return shot
except Exception as e:
logger.error(f"[T2I] Playwright 渲染失败: {e}", exc_info=True)
return ""