群总结模板升级:新增Gemini风格卡片并优化Markdown富标签渲染
变更项: 1. 新增 templates/gemini_summary_card.html,按 Gemini 风格实现移动卡片化总结模板。 2. message_summary 渲染链路升级:支持更完整的 Markdown 富标签转 HTML(标题/列表/表格/代码块/引用等)。 3. 增加渲染后 HTML 安全过滤,清理 script/iframe/on* 事件与 javascript: 链接。 4. 增加 markdown 依赖缺失时的轻量回退解析,保证插件在最小环境下可用。 5. 默认输出配置切换为 template,并指向新 Gemini 风格模板。
This commit is contained in:
@@ -9,6 +9,11 @@ from typing import Dict, Any, Tuple, Optional, List
|
||||
|
||||
from loguru import logger
|
||||
from markupsafe import Markup
|
||||
try:
|
||||
# 优先使用 markdown 库做完整渲染(支持表格、代码块等)。
|
||||
import markdown as markdown_lib
|
||||
except Exception:
|
||||
markdown_lib = None
|
||||
|
||||
from base.plugin_common.message_plugin_interface import MessagePluginInterface
|
||||
from base.plugin_common.plugin_interface import PluginStatus
|
||||
@@ -410,11 +415,76 @@ class MessageSummaryPlugin(MessagePluginInterface):
|
||||
return cleaned
|
||||
|
||||
@staticmethod
|
||||
def _summary_markdown_to_html(summary_text: str) -> str:
|
||||
"""把总结 Markdown 转为基础 HTML 片段(模板内部展示用)。"""
|
||||
# 这里不依赖第三方 markdown 库,保证在最小运行环境也能稳定渲染。
|
||||
# 规则按“标题/列表/段落”三类做轻量转换,足够覆盖总结文本场景。
|
||||
lines = str(summary_text or "").splitlines()
|
||||
def _sanitize_rendered_html(rendered_html: str) -> str:
|
||||
"""对渲染后的 HTML 做最小安全过滤。
|
||||
|
||||
安全策略:
|
||||
1. 移除 script/style/iframe 等高风险标签,避免模板渲染执行脚本;
|
||||
2. 清除行内事件属性(onload/onerror/onclick...);
|
||||
3. 禁止 javascript: 协议链接。
|
||||
|
||||
说明:
|
||||
- 这里是“轻量过滤”,目标是平衡安全与展示完整度;
|
||||
- 若后续需要更严格过滤,可接入专门的 HTML Sanitizer。
|
||||
"""
|
||||
safe_html = str(rendered_html or "")
|
||||
# 删除高风险标签及其内容。
|
||||
safe_html = re.sub(
|
||||
r"<\s*(script|style|iframe|object|embed|form|link|meta)\b[^>]*>.*?<\s*/\s*\1\s*>",
|
||||
"",
|
||||
safe_html,
|
||||
flags=re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
# 删除自闭合高风险标签。
|
||||
safe_html = re.sub(
|
||||
r"<\s*(script|style|iframe|object|embed|form|link|meta)\b[^>]*/\s*>",
|
||||
"",
|
||||
safe_html,
|
||||
flags=re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
# 删除行内事件处理器属性。
|
||||
safe_html = re.sub(r"\son[a-zA-Z]+\s*=\s*(['\"]).*?\1", "", safe_html, flags=re.IGNORECASE | re.DOTALL)
|
||||
# 阻断 javascript: 链接。
|
||||
safe_html = re.sub(
|
||||
r"""(href|src)\s*=\s*(['"])\s*javascript:[^'"]*\2""",
|
||||
r'\1=\2#\2',
|
||||
safe_html,
|
||||
flags=re.IGNORECASE,
|
||||
)
|
||||
return safe_html
|
||||
|
||||
@classmethod
|
||||
def _summary_markdown_to_html(cls, summary_text: str) -> str:
|
||||
"""把总结 Markdown 转为 HTML 片段(模板内部展示用)。
|
||||
|
||||
升级点:
|
||||
1. 使用 markdown 库完整支持标题、列表、粗斜体、引用、代码块、表格等结构;
|
||||
2. 对 LLM 输出里常见的富标签 markdown(例如 ```、|表格|、> 引用)效果更好;
|
||||
3. 渲染后做一次轻量安全过滤,避免模板内注入脚本。
|
||||
"""
|
||||
text = str(summary_text or "").strip()
|
||||
if not text:
|
||||
return "<p>暂无总结内容。</p>"
|
||||
|
||||
# 兼容处理:
|
||||
# 1. 环境安装了 markdown 库时,走完整渲染;
|
||||
# 2. 未安装时自动降级到内置轻量转换,避免插件启动失败。
|
||||
if markdown_lib is not None:
|
||||
rendered = markdown_lib.markdown(
|
||||
text,
|
||||
extensions=[
|
||||
"extra", # 综合扩展:支持表格、定义列表等
|
||||
"fenced_code", # 支持 ``` 代码块
|
||||
"tables", # 支持 Markdown 表格
|
||||
"sane_lists", # 更稳定的列表解析
|
||||
"nl2br", # 保留换行,提升聊天总结可读性
|
||||
],
|
||||
output_format="html5",
|
||||
)
|
||||
return cls._sanitize_rendered_html(rendered)
|
||||
|
||||
# 轻量回退实现(兼容无 markdown 三方包的运行环境)。
|
||||
lines = text.splitlines()
|
||||
html_parts: List[str] = []
|
||||
list_items: List[str] = []
|
||||
|
||||
@@ -443,7 +513,8 @@ class MessageSummaryPlugin(MessagePluginInterface):
|
||||
flush_list()
|
||||
html_parts.append(f"<p>{html.escape(line)}</p>")
|
||||
flush_list()
|
||||
return "".join(html_parts)
|
||||
rendered = "".join(html_parts)
|
||||
return cls._sanitize_rendered_html(rendered)
|
||||
|
||||
def _render_summary_template_html(self, group_name: str, summary_text: str) -> str:
|
||||
"""根据模板路径渲染总结图片 HTML。"""
|
||||
|
||||
Reference in New Issue
Block a user