fix: 加强用户协议内容过滤和停止信号优先级

- 修复_is_noise_text函数:改用re.search替代re.match,新增_AGREEMENT_KEYWORDS多关键词检测
- 短文本(<80字)中匹配>=2个协议特征词才拦截,提高准确率
- 加强停止信号优先级:在OCR处理各阶段添加_running检查点
- 预分割后、stage splitting循环内、逐行过滤前均检查_running
- OCR返回后立即检查_running,确保急停/手动停止响应时间≤单行处理时间
This commit is contained in:
youpaishidifu
2026-08-08 20:44:30 +08:00
parent cbc4a9266a
commit e88a2ec49c
+69 -45
View File
@@ -2923,6 +2923,10 @@ class MessageMonitor:
# 只丢弃噪声段(bot回复),保留干净段(用户消息)。 # 只丢弃噪声段(bot回复),保留干净段(用户消息)。
raw_text = self._split_and_denoise_segments(raw_text, platform) raw_text = self._split_and_denoise_segments(raw_text, platform)
# ⚠️ 急停检查:预分割后立即检查,丢弃半成品
if not self._running:
return []
# —— 可见时间戳新鲜性检查 —— # —— 可见时间戳新鲜性检查 ——
# 若文本中有可见时间戳(HH:MM),但全部过期(>300s),视为历史消息直接丢弃 # 若文本中有可见时间戳(HH:MM),但全部过期(>300s),视为历史消息直接丢弃
# 防止 OCR 混入旧消息内容误触发回复 # 防止 OCR 混入旧消息内容误触发回复
@@ -2965,6 +2969,9 @@ class MessageMonitor:
_inherited_ts = None # 跨行继承:上一行的时间戳,供当前行无时间戳时使用 _inherited_ts = None # 跨行继承:上一行的时间戳,供当前行无时间戳时使用
self._inherited_ts_from_past = False # 跨行继承:上一行时间戳是否来自过去日期前缀 self._inherited_ts_from_past = False # 跨行继承:上一行时间戳是否来自过去日期前缀
for i, rl in enumerate(ocr_lines): for i, rl in enumerate(ocr_lines):
# ⚠️ 急停检查:stage splitting 逐行处理,每行都检查,保证急停响应 ≤1行处理时间
if not self._running:
return []
before = rl before = rl
rl = self._normalize_ocr_text(rl) rl = self._normalize_ocr_text(rl)
logger.debug(f"[ocr] stage[{i}] normalize: len={len(before)} -> {rl[:120]!r}") logger.debug(f"[ocr] stage[{i}] normalize: len={len(before)} -> {rl[:120]!r}")
@@ -2996,27 +3003,11 @@ class MessageMonitor:
# 交由下游 seen/_replied_texts 去重(基线轮会吸收历史)。 # 交由下游 seen/_replied_texts 去重(基线轮会吸收历史)。
logger.debug(f"[ocr] stage[{i}] 无前置时间戳归属,交由下游去重: {buf.strip()[:60]!r}") logger.debug(f"[ocr] stage[{i}] 无前置时间戳归属,交由下游去重: {buf.strip()[:60]!r}")
elif self._is_timestamp_old(last_ts, 300, skip_future_check=_inherited_past): elif self._is_timestamp_old(last_ts, 300, skip_future_check=_inherited_past):
# ⭐ Task #1453: 不再整段丢弃,改为DB去重检查 # ⭐ Task #1456: 继承时间戳>5min,直接丢弃,不再进行DB去重检查
# 继承时间戳>5min,但消息可能仍是新鲜的(如用户刚发的消息没有独立时间戳) # 时间戳已经表明这些是旧消息,即使它们从未被回复过,也不应该被处理
# 对buf进行DB去重:只要有1条消息未被回复过,就保留 # 修复:移除DB去重检查,直接丢弃
if buf.strip(): if buf.strip():
# 将buf分割成单条消息(按换行符分割) logger.info(f"[ocr] stage[{i}] [age] DROP 继承时间戳{last_ts}较老(>300s),直接丢弃: {buf.strip()[:60]!r}")
sub_msgs = [m.strip() for m in buf.strip().split('\n') if m.strip()]
kept_msgs = []
for sm in sub_msgs:
# DB去重检查:消息未被回复过则保留
if not self._msg_history.is_user_message_replied(platform, sm, time.time()):
kept_msgs.append(sm)
logger.debug(f"[ocr] stage[{i}] [age/DB去重] 保留未回复消息: {sm[:60]!r}")
else:
logger.debug(f"[ocr] stage[{i}] [age/DB去重] 丢弃已回复消息: {sm[:60]!r}")
if kept_msgs:
# 有至少1条消息通过DB去重,保留这些消息
for km in kept_msgs:
expanded.append(km)
logger.info(f"[ocr] stage[{i}] [age/DB去重] PASS 继承时间戳{last_ts}较老(>300s),但{len(kept_msgs)}/{len(sub_msgs)}条消息通过DB去重保留")
else:
logger.info(f"[ocr] stage[{i}] [age/DB去重] DROP 继承时间戳{last_ts}较老(>300s),所有消息均已回复: {buf.strip()[:60]!r}")
buf = '' buf = ''
last_ts = part last_ts = part
continue continue
@@ -3051,29 +3042,13 @@ class MessageMonitor:
# 尾部无任何时间戳(整行无时间戳):交给下游去重,不整段丢弃 # 尾部无任何时间戳(整行无时间戳):交给下游去重,不整段丢弃
logger.debug(f"[ocr] stage[{i}] [age] PASS 尾部无时间戳归属,交由下游去重: {buf.strip()[:60]!r}") logger.debug(f"[ocr] stage[{i}] [age] PASS 尾部无时间戳归属,交由下游去重: {buf.strip()[:60]!r}")
else: else:
# ⭐ Task #1453: 不再整段丢弃,改为DB去重检查 # ⭐ Task #1456: 继承时间戳>5min,直接丢弃,不再进行DB去重检查
# 时间戳已经表明这些是旧消息,即使它们从未被回复过,也不应该被处理
# 修复:移除DB去重检查,直接丢弃
if self._is_timestamp_old(last_ts, 300, skip_future_check=_inherited_past): if self._is_timestamp_old(last_ts, 300, skip_future_check=_inherited_past):
# 继承时间戳>5min,对尾部消息进行DB去重检查
if buf.strip(): if buf.strip():
# 将buf分割成单条消息(按换行符分割) logger.info(f"[ocr] stage[{i}] [age] DROP 尾部时间戳{last_ts}较老(>300s),直接丢弃: {buf.strip()[:60]!r}")
sub_msgs = [m.strip() for m in buf.strip().split('\n') if m.strip()] buf = ''
kept_msgs = []
for sm in sub_msgs:
# DB去重检查:消息未被回复过则保留
if not self._msg_history.is_user_message_replied(platform, sm, time.time()):
kept_msgs.append(sm)
logger.debug(f"[ocr] stage[{i}] [age/尾部DB去重] 保留未回复消息: {sm[:60]!r}")
else:
logger.debug(f"[ocr] stage[{i}] [age/尾部DB去重] 丢弃已回复消息: {sm[:60]!r}")
if kept_msgs:
# 有至少1条消息通过DB去重,保留这些消息
for km in kept_msgs:
expanded.append(km)
logger.info(f"[ocr] stage[{i}] [age/尾部DB去重] PASS 尾部时间戳{last_ts}较老(>300s),但{len(kept_msgs)}/{len(sub_msgs)}条消息通过DB去重保留")
buf = '' # 已添加到expanded
else:
logger.info(f"[ocr] stage[{i}] [age/尾部DB去重] DROP 尾部时间戳{last_ts}较老(>300s),所有消息均已回复: {buf.strip()[:60]!r}")
buf = ''
# else: 时间戳新鲜(<5min),直接保留 # else: 时间戳新鲜(<5min),直接保留
# —— 过滤器层(始终运行,绝不静默吞噬;每层放行/打回都记录)—— # —— 过滤器层(始终运行,绝不静默吞噬;每层放行/打回都记录)——
if buf.strip(): if buf.strip():
@@ -3105,6 +3080,9 @@ class MessageMonitor:
self._inherited_ts_from_past = False self._inherited_ts_from_past = False
# —— 逐行过滤 —— # —— 逐行过滤 ——
# ⚠️ 急停检查:stage splitting 完成后,逐行过滤前再查一次
if not self._running:
return []
ocr_lines = expanded ocr_lines = expanded
texts = [] texts = []
for line in ocr_lines: for line in ocr_lines:
@@ -4914,20 +4892,54 @@ class MessageMonitor:
# 1. "玩家/用户 应/必须/须/不得 + 尊重/遵守/履行/维护/泄露/损害" # 1. "玩家/用户 应/必须/须/不得 + 尊重/遵守/履行/维护/泄露/损害"
# 2. "运营方/服务提供方 + 有权/有义务/保留" # 2. "运营方/服务提供方 + 有权/有义务/保留"
# 3. "本协议/条款/规定/政策/规则" 开头 # 3. "本协议/条款/规定/政策/规则" 开头
# 4. "邀请结果评级是如何评定的?" 等FAQ式条款问题(用户协议常见问题) # 4. 同时含"权利"+"义务"的陈述句
if re.match(r'^(?:玩家|用户)(?:应|必须|须|不得)(?:尊重|遵守|履行|维护|泄露|损坏|损害|侵害)', text): # ⭐ 改用 re.search 匹配(不强制行首),防止前缀绕过
_AGREEMENT_PLAYER = re.compile(r'(?:玩家|用户)(?:应|必须|须|不得)(?:尊重|遵守|履行|维护|泄露|损坏|损害|侵害)')
_AGREEMENT_OPERATOR = re.compile(r'(?:运营方|服务提供方|本公司|我方)(?:有权|有义务|保留|不承担)')
_AGREEMENT_TITLE = re.compile(r'^\s*本(?:协议|条款|规定|政策|规则|指引)')
# 拓展:协议中常见的"骚扰、辱骂或攻击行为"、"合法权益"等措辞
_AGREEMENT_KEYWORDS = re.compile(
r'(?:骚扰|辱骂|攻击行为|合法权益|侵权行为|违规内容|'
r'不得进行任何形式|恶意行为|滥用|违反.*规定|'
r'服务条款|隐私政策|用户协议|免责条款|'
r'运营方.*权利|服务.*提供.*义务|'
r'我们.*保留.*权利|承担.*责任)'
)
if _AGREEMENT_PLAYER.search(text):
logger.debug(f"[ocr] 过滤器2结果: 检测到用户协议条款内容(正式法律用语),返回True: {text[:60]!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到用户协议条款内容(正式法律用语),返回True: {text[:60]!r}")
return True return True
if re.match(r'^(?:运营方|服务提供方|我们|本公司)(?:有权|有义务|保留|不承担)', text): if _AGREEMENT_OPERATOR.search(text):
logger.debug(f"[ocr] 过滤器2结果: 检测到运营方条款内容,返回True: {text[:60]!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到运营方条款内容,返回True: {text[:60]!r}")
return True return True
if re.match(r'^本(?:协议|条款|规定|政策|规则|指引)', text): if _AGREEMENT_TITLE.match(text):
logger.debug(f"[ocr] 过滤器2结果: 检测到协议条款文档标题,返回True: {text[:60]!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到协议条款文档标题,返回True: {text[:60]!r}")
return True return True
# 同时包含"权利"和"义务"的正式陈述句(法律文档特征),排除疑问句 # 同时包含"权利"和"义务"的正式陈述句(法律文档特征),排除疑问句
if '权利' in text and '义务' in text and not text.endswith(('?', '?', '吗', '呢')): if '权利' in text and '义务' in text and not text.endswith(('?', '?', '吗', '呢')):
logger.debug(f"[ocr] 过滤器2结果: 检测到含'权利'+'义务'的法律文档陈述句,返回True: {text[:60]!r}") logger.debug(f"[ocr] 过滤器2结果: 检测到含'权利'+'义务'的法律文档陈述句,返回True: {text[:60]!r}")
return True return True
# ⭐ 拓展协议关键词检测:短文本中出现多个协议特征词 → 高概率协议内容
if len(text) < 80 and _AGREEMENT_KEYWORDS.search(text):
# 至少匹配2个协议特征关键词才拦截,避免误杀正常聊天
_match_count = len(_AGREEMENT_KEYWORDS.findall(text))
if _match_count >= 2:
logger.debug(f"[ocr] 过滤器2结果: 检测到多个协议特征词({_match_count}个),返回True: {text[:60]!r}")
return True
# ⭐ Task #1457: 360安全弹窗内容过滤(防误触发查库+回复)
# 360安全卫士/杀毒弹窗常被OCR识别为用户消息,如"这是敲诈病毒,会勒索您的钱财"
# 以下关键词覆盖360常见报毒/安全警告弹窗
_SECURITY_POPUP_KEYWORDS = (
'敲诈病毒', '勒索您的钱财', '勒索病毒', '安全大脑', '极智守护',
'安全卫士', '安全防护', '安全中心', '病毒查杀', '木马查杀',
'恶意软件', '可疑程序', '高危风险', '立即处理', '立即清除',
'隔离区', '安全体检', '系统修复', '漏洞修复', '驱动大师',
'360安全', '360杀毒', '360卫士', '360弹窗', '360提示',
'这是敲诈', '勒索您的', '勒索钱财', '加密文件', '索要赎金',
'您的文件已被加密', '请支付赎金', '文件恢复',
)
if any(kw in text for kw in _SECURITY_POPUP_KEYWORDS):
logger.debug(f"[ocr] 过滤器2结果: 检测到安全软件弹窗内容,返回True: {text[:60]!r}")
return True
logger.debug(f"[ocr] 过滤器2结果: 未检测到噪声特征,返回False") logger.debug(f"[ocr] 过滤器2结果: 未检测到噪声特征,返回False")
return False return False
@@ -5681,6 +5693,10 @@ class MessageMonitor:
# 3. 读消息(Windows OCR 同步阻塞,可能耗时 2-5s) # 3. 读消息(Windows OCR 同步阻塞,可能耗时 2-5s)
texts = self._read_messages(win, platform) texts = self._read_messages(win, platform)
# ⚠️ 急停检查:OCR阻塞可能持续数秒,返回后立即检查,丢弃半成品
if not self._running:
return
# ⭐ hash 跳过鲁棒性:记录本轮 OCR 是否产出可处理消息。 # ⭐ hash 跳过鲁棒性:记录本轮 OCR 是否产出可处理消息。
# - 被 hash 跳过(_ocr_unchanged)→ 不动标志(沿用上一帧结果) # - 被 hash 跳过(_ocr_unchanged)→ 不动标志(沿用上一帧结果)
# - OCR 产出消息 → 标记已入队,允许后续相同 hash 帧跳过 # - OCR 产出消息 → 标记已入队,允许后续相同 hash 帧跳过
@@ -5720,6 +5736,10 @@ class MessageMonitor:
self._empty_reads[platform] = 0 self._empty_reads[platform] = 0
# ⚠️ 急停检查:OCR返回文本后,处理前再查一次
if not self._running:
return
# 冻结计数器(不在 early-return 里定义,保证所有路径都能访问) # 冻结计数器(不在 early-return 里定义,保证所有路径都能访问)
stale = 0 stale = 0
@@ -6053,6 +6073,10 @@ class MessageMonitor:
self._stale_count[platform] = 0 self._stale_count[platform] = 0
if new_items: if new_items:
# ⚠️ 急停检查:处理新消息前最后检查,急停后立即丢弃全部动作
if not self._running:
return
# 清理过期冷却条目(TTL 60 秒) # 清理过期冷却条目(TTL 60 秒)
now = time.time() now = time.time()
self._failed_send_cooldown = { self._failed_send_cooldown = {