Files
astrbot_plugin_qq_group_dai…/scripts/debug_render.py
T

372 lines
12 KiB
Python

import argparse
import asyncio
import os
import sys
import types
from pathlib import Path
# ==========================================
# 1. Environment Setup
# ==========================================
# Add src to path so we can import our modules
# Assuming we are in scripts/
current_dir = os.path.dirname(os.path.abspath(__file__))
plugin_root = os.path.abspath(os.path.join(current_dir, ".."))
sys.path.insert(0, plugin_root)
# Mock astrbot.api before importing our modules
astrbot_api = types.ModuleType("astrbot.api")
class MockLogger:
def info(self, msg, *args, **kwargs):
print(f"[INFO] {msg}")
def error(self, msg, *args, **kwargs):
print(f"[ERROR] {msg}")
def warning(self, msg, *args, **kwargs):
print(f"[WARN] {msg}")
def debug(self, msg, *args, **kwargs):
print(f"[DEBUG] {msg}")
def log(self, level, msg, *args, **kwargs):
print(f"[LOG {level}] {msg}")
def isEnabledFor(self, level):
return True
astrbot_api.logger = MockLogger()
astrbot_api.AstrBotConfig = dict
sys.modules["astrbot.api"] = astrbot_api
# Mock astrbot.core.utils.astrbot_path
astrbot_core_utils = types.ModuleType("astrbot.core.utils")
astrbot_path = types.ModuleType("astrbot.core.utils.astrbot_path")
astrbot_path.get_astrbot_data_path = lambda: Path(".")
sys.modules["astrbot.core.utils"] = astrbot_core_utils
sys.modules["astrbot.core.utils.astrbot_path"] = astrbot_path
from src.domain.models.data_models import ( # noqa: E402
ActivityVisualization,
EmojiStatistics,
GoldenQuote,
GroupStatistics,
QualityDimension,
QualityReview,
SummaryTopic,
TokenUsage,
UserTitle,
)
from src.infrastructure.reporting.generators import ReportGenerator # noqa: E402
class MockConfigManager:
def __init__(self, template_name: str = "scrapbook") -> None:
self.template_name = template_name
def get_report_template(self) -> str:
return self.template_name
def get_max_topics(self) -> int:
return 8
def get_max_user_titles(self) -> int:
return 8
def get_max_golden_quotes(self) -> int:
return 8
def get_pdf_output_dir(self) -> str:
return "data/pdf"
def get_pdf_filename_format(self) -> str:
return "report_{group_id}_{date}.pdf"
def get_enable_user_card(self) -> bool:
return True
@property
def playwright_available(self) -> bool:
return True
def get_browser_path(self) -> str:
return ""
def get_t2i_max_concurrent(self) -> int:
return 4
def get_llm_max_concurrent(self) -> int:
return 2
async def mock_get_user_avatar(user_id: str) -> str:
# Return a known avatar URL for testing
return f"https://q4.qlogo.cn/headimg_dl?dst_uin={user_id}&spec=640"
async def debug_render(
template_name: str, output_file: str = "debug_output.html"
) -> None:
# 1. Setup Mock Data
config_manager = MockConfigManager(template_name)
# 2. Mock Analysis Result using Data Models
stats = GroupStatistics(
message_count=1250,
total_characters=45000,
participant_count=42,
most_active_period="20:00 - 22:00",
golden_quotes=[], # Will be filled later
emoji_count=156,
emoji_statistics=EmojiStatistics(face_count=100, mface_count=56),
activity_visualization=ActivityVisualization(
hourly_activity={
i: (10 + i * 5 if i < 12 else 100 - i * 2) for i in range(24)
}
),
token_usage=TokenUsage(
prompt_tokens=1500, completion_tokens=800, total_tokens=2300
),
chat_quality_review=QualityReview(
title="互联网难民收容所",
subtitle="只要不工作,我们就是最好的朋友",
dimensions=[
QualityDimension(
"水群闲聊",
44.0,
"这里的群友不生产代码,只生产各种表情包和废话,建议送去加个班。",
"#607d8b",
),
QualityDimension(
"技术探讨",
25.5,
"偶尔冒出的技术术语像是在荒漠里发现绿洲,虽然很快就被废话淹没了。",
"#2196f3",
),
QualityDimension(
"深夜发情",
15.0,
"凌晨三点的群聊内容需要打上 R18 标签,建议各位群友早点休息。",
"#f44336",
),
QualityDimension(
"就业焦虑",
10.5,
"谈到工作时群里笼罩着一股淡淡的忧伤,大家都在比谁的工位更像牢房。",
"#ff9800",
),
],
summary="今天也是充满活力(或者说充满废话)的一天,继续保持这份不求上进的快乐吧。",
),
)
topics = [
SummaryTopic(
topic="关于AstrBot插件开发的讨论",
contributors=["张三", "李四", "王五"],
detail="大家深入探讨了专家 [123456789] 提到的如何利用Jinja2模板渲染出精美的分析报告,[987654321] 也分享了调试技巧。",
),
SummaryTopic(
topic="午餐吃什么的终极哲学问题",
contributors=["赵六", "孙七"],
detail="[112233445] 提议去吃黄焖鸡,但群友对螺蛳粉的优劣进行了长达一小时的辩论,最终未能达成共识。",
),
SummaryTopic(
topic="新出的3A大作测评",
contributors=["周八", "吴九"],
detail="[123456789] 分享了最新游戏的通关体验,讨论了画面表现和剧情走向。",
),
SummaryTopic(
topic="新出的3A大作测评",
contributors=["周八", "吴九"],
detail="[123456789] 分享了最新游戏的通关体验,讨论了画面表现和剧情走向。",
),
SummaryTopic(
topic="新出的3A大作测评",
contributors=["周八", "吴九"],
detail="[123456789] 分享了最新游戏的通关体验,讨论了画面表现和剧情走向。",
),
SummaryTopic(
topic="新出的3A大作测评",
contributors=["周八", "吴九"],
detail="[123456789] 分享了最新游戏的通关体验,讨论了画面表现和剧情走向。",
),
SummaryTopic(
topic="新出的3A大作测评",
contributors=["周八", "吴九"],
detail="[123456789] 分享了最新游戏的通关体验,讨论了画面表现和剧情走向。",
),
]
user_titles = [
UserTitle(
name="张三",
user_id="123456789",
title="代码收割机",
mbti="INTJ",
reason="在短短一小时内提交了10个PR,效率惊人。",
),
UserTitle(
name="李四",
user_id="987654321",
title="群聊气氛组",
mbti="ENFP",
reason="总能精准接住每一个冷笑话,让群里充满快活的气息。",
),
UserTitle(
name="潜水员",
user_id="112233445",
title="深夜潜水员",
mbti="INFP",
reason="总是在凌晨三点出没,留下几句深奥的话语后消失。",
),
UserTitle(
name="潜水员",
user_id="112233445",
title="深夜潜水员",
mbti="INFP",
reason="总是在凌晨三点出没,留下几句深奥的话语后消失。",
),
UserTitle(
name="潜水员",
user_id="112233445",
title="深夜潜水员",
mbti="INFP",
reason="总是在凌晨三点出没,留下几句深奥的话语后消失。",
),
UserTitle(
name="潜水员",
user_id="112233445",
title="深夜潜水员",
mbti="INFP",
reason="总是在凌晨三点出没,留下几句深奥的话语后消失。",
),
UserTitle(
name="潜水员",
user_id="112233445",
title="深夜潜水员",
mbti="INFP",
reason="总是在凌晨三点出没,留下几句深奥的话语后消失。",
),
]
golden_quotes = [
GoldenQuote(
content="代码写得好,下班走得早。",
sender="张三",
reason="深刻揭示了程序员的生存法则",
user_id="123456789",
),
GoldenQuote(
content="这个Bug我不修,它就是个Feature。",
sender="李四",
reason="经典的开发辩解",
user_id="987654321",
),
GoldenQuote(
content="PHP是世界上最好的语言!",
sender="王五",
reason="引发了长达3小时的群聊大讨论",
user_id="112233445",
),
GoldenQuote(
content="PHP是世界上最好的语言!",
sender="王五",
reason="引发了长达3小时的群聊大讨论",
user_id="112233445",
),
GoldenQuote(
content="PHP是世界上最好的语言!",
sender="王五",
reason="引发了长达3小时的群聊大讨论",
user_id="112233445",
),
GoldenQuote(
content="PHP是世界上最好的语言!",
sender="王五",
reason="引发了长达3小时的群聊大讨论",
user_id="112233445",
),
GoldenQuote(
content="PHP是世界上最好的语言!",
sender="王五",
reason="引发了长达3小时的群聊大讨论",
user_id="112233445",
),
]
stats.golden_quotes = golden_quotes
# token_usage already set in constructor
analysis_result = {
"statistics": stats,
"topics": topics,
"user_titles": user_titles,
"user_analysis": {
"123456789": {"nickname": "张三"},
"987654321": {"nickname": "李四"},
"112233445": {"nickname": "潜水员"},
},
"chat_quality_review": stats.chat_quality_review,
"analysis_date": "2026年02月11日",
"group_id": "123456",
"group_name": "测试群组",
}
# 3. Initialize Generator
generator = ReportGenerator(config_manager)
# Override internal methods to facilitate debugging without real dependencies
generator._get_user_avatar_data = mock_get_user_avatar
# 4. Prepare Render Data
# Note: _prepare_render_data handles converting Entities to template-friendly dicts
render_payload = await generator._prepare_render_data(analysis_result)
# Use Jinja2 renderer
final_html = generator.html_templates.render_template(
"image_template.html", **render_payload
)
# 6. Save to file
output_path = Path(output_file)
output_path.write_text(final_html, encoding="utf-8")
# 7. Close generator
await generator.close()
print(
f"Successfully rendered template '{template_name}' to {output_path.absolute()}"
)
print("You can now open this file with your browser to debug your HTML/CSS.")
def main() -> None:
parser = argparse.ArgumentParser(
description="Debug render tool for astrbot_plugin_qq_group_daily_analysis report templates."
)
parser.add_argument(
"-t",
"--template",
type=str,
default="scrapbook",
help="Template name to render (default: scrapbook)",
)
parser.add_argument(
"-o",
"--output",
type=str,
default="debug_output.html",
help="Output HTML file path (default: debug_output.html)",
)
args = parser.parse_args()
asyncio.run(debug_render(args.template, args.output))
if __name__ == "__main__":
main()