diff --git a/_conf_schema.json b/_conf_schema.json index 66e0fff..b524da2 100644 --- a/_conf_schema.json +++ b/_conf_schema.json @@ -66,6 +66,12 @@ "default": "image", "hint": "分析报告的输出格式:image(图片)、text(文本)、pdf(PDF文件)。使用 PDF 需要额外配置,根据文件中的 PDF_功能说明.md 进行配置" }, + "report_template": { + "type": "string", + "description": "报告模板", + "default": "scrapbook", + "hint": "分析报告使用的HTML模板名称,使用 `/设置模板` 查看使用指南" + }, "min_messages_threshold": { "type": "int", diff --git a/main.py b/main.py index f517f6b..b633c5c 100644 --- a/main.py +++ b/main.py @@ -320,6 +320,47 @@ class QQGroupDailyAnalysis(Star): config_manager.set_output_format(format_type) yield event.plain_result(f"✅ 输出格式已设置为: {format_type}") + @filter.command("设置模板") + @filter.permission_type(PermissionType.ADMIN) + async def set_report_template( + self, event: AiocqhttpMessageEvent, template_name: str = "" + ): + """ + 设置分析报告模板 + 用法: /设置模板 [模板名称] + """ + if not isinstance(event, AiocqhttpMessageEvent): + yield event.plain_result("❌ 此功能仅支持QQ群聊") + return + + if not template_name: + current_template = config_manager.get_report_template() + # 列出可用的模板 + import os + template_dir = os.path.join(os.path.dirname(__file__), "src", "reports", "templates") + available_templates = [] + if os.path.exists(template_dir): + available_templates = [d for d in os.listdir(template_dir) if os.path.isdir(os.path.join(template_dir, d)) and not d.startswith("__")] + + template_list_str = "\n".join([f"• {t}" for t in available_templates]) + yield event.plain_result(f"""🎨 当前报告模板: {current_template} + +可用模板: +{template_list_str} + +用法: /设置模板 [模板名称]""") + return + + # 检查模板是否存在 + import os + template_dir = os.path.join(os.path.dirname(__file__), "src", "reports", "templates", template_name) + if not os.path.exists(template_dir): + yield event.plain_result(f"❌ 模板 '{template_name}' 不存在") + return + + config_manager.set_report_template(template_name) + yield event.plain_result(f"✅ 报告模板已设置为: {template_name}") + @filter.command("安装PDF") @filter.permission_type(PermissionType.ADMIN) async def install_pdf_deps(self, event: AiocqhttpMessageEvent): diff --git a/src/core/config.py b/src/core/config.py index 69a4928..3f1b8ea 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -311,6 +311,15 @@ class ConfigManager: self.config["pdf_filename_format"] = format_str self.config.save_config() + def get_report_template(self) -> str: + """获取报告模板名称""" + return self.config.get("report_template", "scrapbook") + + def set_report_template(self, template_name: str): + """设置报告模板名称""" + self.config["report_template"] = template_name + self.config.save_config() + def get_enable_user_card(self) -> bool: """获取是否使用用户群名片""" return self.config.get("enable_user_card", False) diff --git a/src/reports/generators.py b/src/reports/generators.py index d14ee06..16da6a9 100644 --- a/src/reports/generators.py +++ b/src/reports/generators.py @@ -19,7 +19,7 @@ class ReportGenerator: def __init__(self, config_manager): self.config_manager = config_manager self.activity_visualizer = ActivityVisualizer() - self.html_templates = HTMLTemplates() # 实例化HTML模板管理器 + self.html_templates = HTMLTemplates(config_manager) # 实例化HTML模板管理器 async def generate_image_report( self, analysis_result: dict, group_id: str, html_render_func diff --git a/src/reports/templates.py b/src/reports/templates.py index f679226..977505d 100644 --- a/src/reports/templates.py +++ b/src/reports/templates.py @@ -11,43 +11,64 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape class HTMLTemplates: """HTML模板管理类""" - def __init__(self): + def __init__(self, config_manager): """初始化Jinja2环境""" - # 设置模板目录 - template_dir = os.path.join(os.path.dirname(__file__), "templates") + self.config_manager = config_manager + # 设置模板根目录 + self.base_dir = os.path.join(os.path.dirname(__file__), "templates") + # 缓存不同模板的Jinja2环境 + self._envs = {} - # 创建Jinja2环境 - self.jinja_env = Environment( + def _get_env(self) -> Environment: + """获取当前配置的模板环境""" + template_name = self.config_manager.get_report_template() + + # 如果环境已缓存且配置未变(这里简单假设配置变了会重新获取,或者我们可以每次都检查) + # 为了响应配置热更,我们每次都检查一下或者简单地按需创建 + if template_name in self._envs: + return self._envs[template_name] + + template_dir = os.path.join(self.base_dir, template_name) + if not os.path.exists(template_dir): + logger.warning(f"模板目录不存在: {template_dir},回退到 scrapbook") + template_dir = os.path.join(self.base_dir, "scrapbook") + # 如果 scrapbook 也不存在,那就有大问题了,不过这里假设 scrapbook 一定存在 + + env = Environment( loader=FileSystemLoader(template_dir), autoescape=select_autoescape(["html", "xml"]), trim_blocks=True, lstrip_blocks=True, ) + self._envs[template_name] = env + return env def get_image_template(self) -> str: """获取图片报告的HTML模板(返回原始模板字符串)""" try: + env = self._get_env() # 获取模板对象 - template = self.jinja_env.get_template("image_template.html") + template = env.get_template("image_template.html") # 读取原始模板文件内容,而不是渲染它 with open(template.filename, encoding="utf-8") as f: return f.read() - except Exception: + except Exception as e: # 如果加载失败,返回空字符串让调用者处理 - logger.error("加载图片模板失败") + logger.error(f"加载图片模板失败: {e}") return "" def get_pdf_template(self) -> str: """获取PDF报告的HTML模板(返回原始模板字符串)""" try: + env = self._get_env() # 获取模板对象 - template = self.jinja_env.get_template("pdf_template.html") + template = env.get_template("pdf_template.html") # 读取原始模板文件内容,而不是渲染它 with open(template.filename, encoding="utf-8") as f: return f.read() - except Exception: + except Exception as e: # 如果加载失败,返回空字符串让调用者处理 - logger.error("加载PDF模板失败") + logger.error(f"加载PDF模板失败: {e}") return "" def render_template(self, template_name: str, **kwargs) -> str: @@ -61,8 +82,9 @@ class HTMLTemplates: 渲染后的HTML字符串 """ try: - template = self.jinja_env.get_template(template_name) + env = self._get_env() + template = env.get_template(template_name) return template.render(**kwargs) - except Exception: - logger.error(f"渲染模板 {template_name} 失败") + except Exception as e: + logger.error(f"渲染模板 {template_name} 失败: {e}") return "" diff --git a/src/reports/templates/old/image_template.html b/src/reports/templates/old/image_template.html new file mode 100644 index 0000000..cffe817 --- /dev/null +++ b/src/reports/templates/old/image_template.html @@ -0,0 +1,584 @@ + + + + + + 群聊日常分析报告 + + + + +
+
+

📊 群聊日常分析报告

+
{{current_date}}
+
+
+
+

📈 基础统计

+
+
{{message_count}}
消息总数
+
{{participant_count}}
参与人数
+
{{total_characters}}
总字符数
+
{{emoji_count}}
表情数量
+
+
+
{{most_active_period}}
+
最活跃时段
+
+
+ + +
+
+
+
⏱️ 24小时活跃度分布
+
+
+ {{hourly_chart_html}} +
+
+

💬 热门话题

+
{{topics_html}}
+
+
+

🏆 群友称号

+
{{titles_html}}
+
+
+

💬 群圣经

+ {{quotes_html}} +
+
+ +
+ + \ No newline at end of file diff --git a/src/reports/templates/old/pdf_template.html b/src/reports/templates/old/pdf_template.html new file mode 100644 index 0000000..23ebbbf --- /dev/null +++ b/src/reports/templates/old/pdf_template.html @@ -0,0 +1,444 @@ + + + + + + 群聊日常分析报告 + + + + +
+
+

📊 群聊日常分析报告

+
{{current_date}}
+
+
+

📈 基础统计

+
+
+
{{message_count}}
+
消息总数
+
+
+
{{participant_count}}
+
参与人数
+
+
+
{{total_characters}}
+
总字符数
+
+
+
{{emoji_count}}
+
表情数量
+
+
+
+
{{most_active_period}}
+
最活跃时段
+
+ +
+
+
⏱️ 活跃度分布
+
+ {{hourly_chart_html}} +
+
+ +
+

💬 热门话题

+ {{topics_html}} +
+
+

🏆 群友称号

+ {{titles_html}} +
+
+

💬 群圣经

+ {{quotes_html}} +
+ +
+ + + \ No newline at end of file diff --git a/src/reports/templates/old/quote_item.html b/src/reports/templates/old/quote_item.html new file mode 100644 index 0000000..72bb43a --- /dev/null +++ b/src/reports/templates/old/quote_item.html @@ -0,0 +1,7 @@ +{% for quote in quotes %} +
+
"{{ quote.content }}"
+
—— {{ quote.sender }}
+
{{ quote.reason }}
+
+{% endfor %} \ No newline at end of file diff --git a/src/reports/templates/old/topic_item.html b/src/reports/templates/old/topic_item.html new file mode 100644 index 0000000..d8ff7ad --- /dev/null +++ b/src/reports/templates/old/topic_item.html @@ -0,0 +1,10 @@ +{% for topic in topics %} +
+
+ {{ topic.index }} + {{ topic.topic.topic }} +
+
参与者: {{ topic.contributors }}
+
{{ topic.topic.detail }}
+
+{% endfor %} \ No newline at end of file diff --git a/src/reports/templates/old/user_title_item.html b/src/reports/templates/old/user_title_item.html new file mode 100644 index 0000000..0a3a4fe --- /dev/null +++ b/src/reports/templates/old/user_title_item.html @@ -0,0 +1,19 @@ +{% for title in titles %} +
+
+ {% if title.avatar_data %} + 头像 + {% else %} +
👤
+ {% endif %} +
+
{{ title.name }}
+
+
{{ title.title }}
+
{{ title.mbti }}
+
+
+
+
{{ title.reason }}
+
+{% endfor %} \ No newline at end of file diff --git a/src/reports/templates/image_template.html b/src/reports/templates/scrapbook/image_template.html similarity index 100% rename from src/reports/templates/image_template.html rename to src/reports/templates/scrapbook/image_template.html diff --git a/src/reports/templates/pdf_template.html b/src/reports/templates/scrapbook/pdf_template.html similarity index 100% rename from src/reports/templates/pdf_template.html rename to src/reports/templates/scrapbook/pdf_template.html diff --git a/src/reports/templates/quote_item.html b/src/reports/templates/scrapbook/quote_item.html similarity index 100% rename from src/reports/templates/quote_item.html rename to src/reports/templates/scrapbook/quote_item.html diff --git a/src/reports/templates/topic_item.html b/src/reports/templates/scrapbook/topic_item.html similarity index 100% rename from src/reports/templates/topic_item.html rename to src/reports/templates/scrapbook/topic_item.html diff --git a/src/reports/templates/user_title_item.html b/src/reports/templates/scrapbook/user_title_item.html similarity index 100% rename from src/reports/templates/user_title_item.html rename to src/reports/templates/scrapbook/user_title_item.html