From dac243dcc040e5cb90889e7d9ec8cf7b7420be66 Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Fri, 21 Aug 2026 12:34:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BC=96=E7=A8=8B=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=9D=97=E5=89=A5=E7=A6=BB=E5=87=BD=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E5=A4=B4=E9=83=A8=E6=95=B4=E6=AE=B5=E8=B7=B3=E8=BF=87=EF=BC=8C?= =?UTF-8?q?=E5=B0=BE=E9=83=A8=E5=88=87=E6=8E=89=EF=BC=88=E5=90=AB=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E6=A0=BC=E5=BC=8F=E5=8C=B9=E9=85=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 _strip_programming_format_blocks 函数,检测消息头部/尾部的JSON、YAML、Markdown表格等编程格式块 - 头部检测:JSON对象、Markdown表格、YAML块 → 整段丢弃 - 尾部检测:JSON对象、Markdown表格、YAML块 → 切掉尾部保留前段 - 兼容格式匹配:检测不完整JSON片段(中英文双引号包裹键值对,以逗号/括号结尾),即使OCR识别不完整也能拦截 - 在过滤管道中位于 _strip_json_yaml_blocks 之后调用 - 修复Markdown表格和JSON配置通过过滤器触发自动回复的问题 --- monitor.py | 173 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/monitor.py b/monitor.py index d6be7bc..e53949f 100644 --- a/monitor.py +++ b/monitor.py @@ -6501,6 +6501,179 @@ class MessageMonitor: return '\n'.join(cleaned) + @staticmethod + def _strip_programming_format_blocks(raw_text: str, platform: str = "") -> str: + """剥离消息头部/尾部的编程格式块(JSON、YAML、Markdown表格等)。 + + 用户要求:所有JSON、YAML、Markdown等编程语言格式,如果在消息头部,则整段跳过不处理; + 如果在尾部,则一刀切掉尾部。 + + 策略: + 1. 检测消息头部(去除空行后)是否为JSON对象、YAML块、Markdown表格。 + 如果是,整段丢弃(返回空字符串)。 + 2. 如果不是头部,则检测消息尾部是否包含这些格式。 + 如果是,切掉尾部(从格式开始到末尾),返回前段。 + 3.否则原样返回。 + + 安全边界: + - 用户聊天不会以 { 开头(JSON),也不会以 | 表格行开头(Markdown表格)。 + - YAML 块以 key: value 开头,用户聊天不会这样开头。 + - 因此头部检测误杀风险低。 + """ + if not raw_text or not raw_text.strip(): + return raw_text + + lines = raw_text.split('\n') + # 去除空行,得到非空行列表 + non_empty_lines = [line.strip() for line in lines if line.strip()] + if not non_empty_lines: + return raw_text + + # —— 兼容格式:检测不完整JSON片段(中英文双引号包裹键值对,以逗号/括号结尾)—— + def _is_json_fragment(line: str) -> bool: + """检测单行是否为JSON片段(键值对模式),即使不完整。""" + # 匹配 "key": "value", 或 "key": value} 或 "key": "value"] + # 支持中英文双引号,键和值都可能被引号包裹,也可能没有 + pattern = r'["\u201c\u201d].*?["\u201c\u201d]\s*:\s*["\u201c\u201d].*?["\u201c\u201d]\s*[,}\])]' + if re.search(pattern, line): + return True + # 也匹配不完整情况: "key": "value" 后缺少闭合引号,但有逗号/括号 + # 例如:"text":"test", 或 "text":"test} + pattern2 = r'["\u201c\u201d].*?["\u201c\u201d]\s*:\s*["\u201c\u201d].*?[,}\])]' + if re.search(pattern2, line): + return True + # 匹配键没有引号的情况: key: "value", 或 key: value} + pattern3 = r'^[a-zA-Z_][\w]*\s*:\s*["\u201c\u201d].*?["\u201c\u201d]\s*[,}\])]' + if re.search(pattern3, line): + return True + return False + + # —— 头部检测 —— + # 如果非空行首行以 { 开头,可能为JSON对象 + first_line = non_empty_lines[0] + if first_line.startswith('{'): + # 尝试检测完整的JSON对象(大括号深度归零) + depth = 0 + json_end_index = -1 + for idx, line in enumerate(non_empty_lines): + depth += line.count('{') - line.count('}') + if depth <= 0: + json_end_index = idx + break + if json_end_index >= 0: + # 找到完整JSON对象,整段丢弃 + logger.debug(f"[{platform}] 编程格式剥离: 头部JSON对象,整段丢弃: {first_line[:60]!r}") + return '' + + # 兼容格式:如果首行是JSON片段(即使不完整),整段丢弃 + if _is_json_fragment(first_line): + logger.debug(f"[{platform}] 编程格式剥离: 头部JSON片段(兼容),整段丢弃: {first_line[:60]!r}") + return '' + + # 如果非空行首行以 | 开头,可能为Markdown表格 + if first_line.startswith('|'): + # 检测至少两行以 | 结尾(表格特征) + table_lines = 0 + for line in non_empty_lines[:5]: # 只检查前5行 + if line.endswith('|'): + table_lines += 1 + if table_lines >= 2: + logger.debug(f"[{platform}] 编程格式剥离: 头部Markdown表格,整段丢弃: {first_line[:60]!r}") + return '' + + # 如果非空行首行匹配YAML键值对模式(key: value) + if re.match(r'^[a-zA-Z_][\w]*\s*:\s*', first_line) and \ + not re.match(r'^(https?|ftp)://', first_line) and \ + not re.match(r'^[a-zA-Z_][\w]*\s*:\s*/', first_line): + # 检测后续行是否有缩进或 - 列表项(YAML块特征) + yaml_block_lines = 1 + for line in non_empty_lines[1:10]: # 检查后续9行 + if re.match(r'^\s+', line) or re.match(r'^-\s+\S', line): + yaml_block_lines += 1 + else: + break + if yaml_block_lines >= 2: # 至少2行符合YAML特征 + logger.debug(f"[{platform}] 编程格式剥离: 头部YAML块,整段丢弃: {first_line[:60]!r}") + return '' + + # —— 尾部检测 —— + # 从尾部向前扫描,找到编程格式开始的位置 + last_line = non_empty_lines[-1] + # 尾部JSON对象:以 } 结尾,且前面有对应的 { + if last_line.endswith('}'): + #向前找到匹配的 { + depth = 0 + json_start_index = -1 + for idx in range(len(non_empty_lines)-1, -1, -1): + line = non_empty_lines[idx] + depth += line.count('}') - line.count('{') + if depth <= 0: + json_start_index = idx + break + if json_start_index >= 0 and json_start_index < len(non_empty_lines)-1: + # 切掉从json_start_index开始到末尾的所有行 + cut_lines = lines[:json_start_index] + result = '\n'.join(cut_lines).strip() + if result: + logger.debug(f"[{platform}] 编程格式剥离: 尾部JSON对象,切掉尾部: {last_line[:60]!r}") + return result + else: + # 切完为空,说明整段都是JSON,丢弃 + return '' + + # 兼容格式:如果尾行是JSON片段(即使不完整),切掉尾部 + if _is_json_fragment(last_line): + # 找到JSON片段开始的行(从尾部向上找第一个不是JSON片段的行) + json_start = len(non_empty_lines) - 1 + while json_start > 0 and _is_json_fragment(non_empty_lines[json_start-1]): + json_start -= 1 + # 切掉从json_start开始到末尾的所有行 + cut_lines = lines[:json_start] + result = '\n'.join(cut_lines).strip() + if result: + logger.debug(f"[{platform}] 编程格式剥离: 尾部JSON片段(兼容),切掉尾部: {last_line[:60]!r}") + return result + else: + return '' + + # 尾部Markdown表格:以 | 结尾,且前一行也以 | 结尾 + if last_line.endswith('|') and len(non_empty_lines) >= 2 and non_empty_lines[-2].endswith('|'): + # 找到表格开始的行(从尾部向上找第一个不以 | 结尾的行) + table_start = len(non_empty_lines) - 1 + while table_start > 0 and non_empty_lines[table_start-1].endswith('|'): + table_start -= 1 + # 切掉从table_start开始到末尾的所有行 + cut_lines = lines[:table_start] + result = '\n'.join(cut_lines).strip() + if result: + logger.debug(f"[{platform}] 编程格式剥离: 尾部Markdown表格,切掉尾部: {last_line[:60]!r}") + return result + else: + return '' + + # 尾部YAML块:以 key: value 或 - item 结尾,且前面有缩进 + if (re.match(r'^[a-zA-Z_][\w]*\s*:\s*', last_line) or re.match(r'^-\s+\S', last_line)) and \ + len(non_empty_lines) >= 2: + # 向前找到YAML块开始(第一个不缩进且不匹配YAML特征的行) + yaml_start = len(non_empty_lines) - 1 + while yaml_start > 0: + prev_line = non_empty_lines[yaml_start-1] + if re.match(r'^\s+', prev_line) or re.match(r'^[a-zA-Z_][\w]*\s*:\s*', prev_line) or re.match(r'^-\s+\S', prev_line): + yaml_start -= 1 + else: + break + if yaml_start < len(non_empty_lines)-1: + cut_lines = lines[:yaml_start] + result = '\n'.join(cut_lines).strip() + if result: + logger.debug(f"[{platform}] 编程格式剥离: 尾部YAML块,切掉尾部: {last_line[:60]!r}") + return result + else: + return '' + + # 无编程格式,原样返回 + return raw_text + # —— md 尾巴剥离模式(一刀切)—— # ⭐ 2026-08-17 原则:用户聊天从不手打 markdown(- [ ] 列表、# 标题、**加粗**、 # [x](url) 链接、| 表格、` 代码),任何 md 特有结构都可安全一刀切。