fix(monitor): 策略4改为贪婪匹配,不管bot回复在哪都摘掉

原策略4是头部剥离(只处理bot回复在消息前面的情况),
用户反馈:OCR不确定哪天会把小帽子扣在哪,需要用贪婪匹配兜底。

新策略4:
1. 归一化文本(去空格标点),抵抗OCR噪声
2. 遍历已知bot回复,检查是否包含在文本中
3. 如果包含,定位并移除匹配部分
4. 保留剩余的用户消息

覆盖场景:
- 头部:'官方下载 bbsmc分流 那我知道公告信息' → '那我知道公告信息'
- 尾部:'那个登录插件在哪 见github' → '那个登录插件在哪'
- 中部:'你好 [√] GitHub(稳定版) 请问怎么下载' → '你好 请问怎么下载'

commit a3f7c2e (2026-08-16)
This commit is contained in:
youpaishidifu
2026-08-16 18:37:18 +08:00
parent 21a2890480
commit 78a4bd3ed8
+51 -37
View File
@@ -4794,44 +4794,58 @@ class MessageMonitor:
)
stripped_bot = True
break
# 策略4:头部剥离——OCR把bot回复拼在用户消息前面("小帽子"问题)
# 例:"官方下载 bbsmc分流 那我知道咱们服务器有哪些公告信息呀?"
# → "那我知道咱们服务器有哪些公告信息呀?"
# 修复:定位中文疑问词(那/请问/想知道等),剥离问句前的bot回复头部
# 策略4:贪婪匹配剥离——不管bot回复拼在哪(头/尾/中),都摘掉
# 例:
# "官方下载 bbsmc分流 那我知道公告信息" → "那我知道公告信息"
# "问问那个登录插件在哪儿能下载 见github" → "问问那个登录插件在哪儿能下载"
# "[√] GitHub(稳定版) 那个登录插件在哪?" → "那个登录插件在哪?"
if not stripped_bot:
# 常见中文疑问词/短语,用于定位用户消息的起始位置
_question_markers = [
'那我知道', '那请问', '请问', '想知道', '想问',
'怎么', '哪里', '什么', '哪些', '哪个',
]
_q_start = len(ts)
for _marker in _question_markers:
_pos = ts.find(_marker)
if _pos >= 0 and _pos < _q_start:
_q_start = _pos
# 如果没找到疑问词,回退到找问号
if _q_start == len(ts):
_q_mark = max(ts.find('?'), ts.find('?'))
if _q_mark >= 0:
_q_start = _q_mark
_head = ts[:_q_start].strip()
_tail = ts[_q_start:]
if _head and len(_head) >= 3 and _tail:
# 检查头部是否匹配已知的bot回复
for _r in replied:
if len(_r) < 5:
continue
_head_norm = re.sub(r'[\s\W_]+', '', _head)
_r_norm = re.sub(r'[\s\W_]+', '', _r)
if _head_norm and _r_norm and (_head_norm == _r_norm or _head_norm in _r_norm):
ts = _tail
stripped_bot = True
logger.debug(
f"[qq] 头部匹配剥离bot回复: {ts!r} <- "
f"{_head!r} (匹配bot reply: {_r[:30]!r})"
)
break
for _r in replied:
if len(_r) < 5:
continue
# 归一化比较:去空格标点,抵抗OCR噪声
_r_norm = self._norm_frag(_r)
if not _r_norm:
continue
_ts_norm = self._norm_frag(ts)
if not _ts_norm:
continue
# 贪婪匹配:如果文本包含bot回复,移除匹配部分
if _r_norm in _ts_norm:
# 定位匹配位置
_r_idx = _ts_norm.find(_r_norm)
if _r_idx >= 0:
# 计算原始文本中的位置(考虑归一化可能改变字符数)
# 使用简化的近似定位:从开头跳过非匹配部分
_skip_count = 0
_norm_count = 0
for _i, _c in enumerate(ts):
if _norm_count == _r_idx:
_skip_count = _i
break
if re.sub(r'[\s\W_]+', '', _c):
_norm_count += 1
# 计算bot回复的归一化长度
_r_norm_len = len(_r_norm)
_end_norm = _r_idx + _r_norm_len
_end_count = 0
_end_pos = len(ts)
for _i, _c in enumerate(ts[_skip_count:]):
if re.sub(r'[\s\W_]+', '', _c):
_end_count += 1
if _end_count == _r_norm_len:
_end_pos = _skip_count + _i + 1
break
# 移除匹配的bot回复部分
_cleaned = ts[:_skip_count] + ts[_end_pos:]
if _cleaned.strip():
logger.debug(
f"[qq] 贪婪匹配剥离bot回复: {ts!r} -> { _cleaned!r} "
f"(移除: {ts[_skip_count:_end_pos]!r})"
)
ts = _cleaned.strip()
stripped_bot = True
break
if stripped_bot:
ts = re.sub(r'\s+', ' ', ts).strip().strip(',').strip()
return ts