Files
kefu/deploy/skip-sessions-20260917/apply-visual-exclusions.py
T
2026-09-21 10:34:06 +08:00

172 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pathlib import Path
from shutil import copy2
import ast
roots=[Path(r'C:\kefu\wechat_rpa'),Path(r'C:\wechat_rpa')]
helpers=''' def _confirmed_open_session_exclusion(self, fp: bytes, name: str = "") -> str:
"""Confirm excluded headers twice against the actual selected identity."""
from reply_session_policy import excluded_session_reason, visual_group_title_reason
try:
title = self._open_chat_display_name()
reason = excluded_session_reason(title) or visual_group_title_reason(title, name=name)
if not reason:
# Extra list OCR is only needed for a possible member suffix.
if name or not re.search(r"[((]\\d+[^))]*[))]$", str(title or "")):
return ""
image = self.capture_session_list()
row = self.detect_selected_row(image)
if row < 0 or not self._session_fp_matches(self._session_fingerprint(image, row, row_center=True), fp):
return ""
name = self._row_display_name(image, row, row_center=True)
reason = visual_group_title_reason(title, name=name)
if not reason:
return ""
def proven(identity):
if not identity or not self._message_workspace_selected(self._capture_full_window()):
return False
current = self._raw_selected_session_fingerprint() or self._selected_session_fingerprint()
if current:
return self._chat_target_matches(fp, identity, current_fp=current,
current_identity=self._chat_identity_signature(), selected_fp_checked=True)
# An opened row can disappear from the unread filter, but a
# separately observed list name must still prove this target.
return bool(name and self._fp_from_name(name) == fp and self._chat_surface_signature())
identity = self._chat_identity_signature()
if not proven(identity):
return ""
time.sleep(0.06)
second_title = self._open_chat_display_name()
second_identity = self._chat_identity_signature()
if (second_identity != identity or not proven(second_identity)
or session_name.normalize_name(second_title) != session_name.normalize_name(title)):
return ""
second_reason = excluded_session_reason(second_title) or visual_group_title_reason(second_title, name=name)
return reason if second_reason == reason else ""
except Exception:
return "" # Missing OCR or geometry cannot prove a group.
def _skip_excluded_reply_session(self, fp: bytes, *, inspect_open=False, display_name="", metadata=None) -> bool:
"""Skip proven excluded sessions, retaining possibly-sent evidence."""
from contextlib import nullcontext
from reply_session_policy import excluded_session_reason
raw = bytes(fp or b"")
if not raw:
return False
key = raw.hex()
state = (getattr(self, "_pending_reply_sessions", {}) or {}).get(key) or {}
name = str(display_name or state.get("display_name") or "")
event = state.get("database_event") if isinstance(state.get("database_event"), dict) else {}
supplied = metadata if isinstance(metadata, dict) else {}
reason = excluded_session_reason(name, metadata=supplied) or excluded_session_reason(name, metadata=state)
cache = getattr(self, "_excluded_session_cache", None)
if cache is None:
cache = self._excluded_session_cache = {}
cached = cache.get(key) or {}
conv_id = str(supplied.get("conv_id") or event.get("conv_id") or state.get("conv_id") or "")
personal = (conv_id.startswith("M:") and len(conv_id) > 2 or re.fullmatch(r"S:\\d+_\\d+", conv_id) is not None)
if not reason and not personal and time.monotonic() < float(cached.get("until") or 0):
reason = str(cached.get("reason") or "")
if not reason and inspect_open and not personal:
reason = self._confirmed_open_session_exclusion(raw, name)
if reason:
cache[key] = {"reason": reason, "until": time.monotonic() + 300}
if len(cache) > 256:
for stale in sorted(cache, key=lambda item: cache[item].get("until", 0))[:-256]:
cache.pop(stale, None)
if not reason:
return False
self._last_session_exclusion_reason = reason
lock = getattr(self, "_pending_lock", None)
with lock if lock is not None else nullcontext():
current = (getattr(self, "_pending_reply_sessions", {}) or {}).get(key)
if isinstance(current, dict):
protected = (str(current.get("stage") or "") in {"sending", "receipt_check", "uncertain", "unknown"}
or str(current.get("send_state") or "") in {"sending", "uncertain", "unknown", "sent_uncommitted"}
or bool(current.get("send_dispatch_started")))
if not protected:
self._clear_reply_pending(raw)
logged = getattr(self, "_excluded_session_logged", None)
if logged is None:
logged = self._excluded_session_logged = set()
marker = (key, reason)
if marker not in logged:
if len(logged) >= 256:
logged.clear()
logged.add(marker)
try:
self.queue_log.append(self.session_id_of(raw), name or "已排除会话", "跳过", reason)
except Exception:
pass
self.report_progress(reason)
print(f" [会话过滤] {reason}")
return True
'''
def editfn(s,name,old,new):
node=next(n for n in ast.walk(ast.parse(s)) if isinstance(n,ast.FunctionDef) and n.name==name)
lines=s.splitlines(True);seg=''.join(lines[node.lineno-1:node.end_lineno])
assert old in seg,(name,old)
return ''.join(lines[:node.lineno-1])+seg.replace(old,new,1)+''.join(lines[node.end_lineno:])
for root in roots:
p=root/'wechat_bot.py';backup=root/'backups/skip-sessions-20260917';backup.mkdir(parents=True,exist_ok=True)
if not (backup/p.name).exists():copy2(p,backup/p.name)
s=p.read_text(encoding='utf-8-sig')
assert ' def _skip_excluded_reply_session(' not in s
s=s.replace(' "企业微信团队", "微信团队", "离职继承",',' "企业微信团队", "微信团队", "离职继承",\n "企小码", "企小码会话", "企小码会话管理", "企小碼", "企小碼會話管理",',1)
s=s.replace(' def _purge_system_pending_tasks(self) -> int:\n',helpers+' def _purge_system_pending_tasks(self) -> int:\n',1)
s=editfn(s,'_purge_system_pending_tasks',''' if self._is_system_entry_name(name):
pending.pop(key, None)''',''' protected = (str(state.get("stage") or "") in {"sending", "receipt_check", "uncertain", "unknown"}
or str(state.get("send_state") or "") in {"sending", "uncertain", "unknown", "sent_uncommitted"}
or bool(state.get("send_dispatch_started")))
if self._is_system_entry_name(name) and not protected:
pending.pop(key, None)''')
s=editfn(s,'enqueue_detected',' now = time.time()',''' if self._skip_excluded_reply_session(fp, display_name=display_name, metadata=database_event):
return False, str(getattr(self, "_last_session_exclusion_reason", "会话不参与自动回复"))
now = time.time()''')
s=editfn(s,'_mark_reply_pending',' lock = getattr(self, "_pending_lock", None)',''' if self._skip_excluded_reply_session(fp, display_name=display_name):
return False
lock = getattr(self, "_pending_lock", None)''')
s=editfn(s,'_session_accepts_replies',' key = bytes(fp or b"").hex()',''' if self._skip_excluded_reply_session(fp, inspect_open=True):
return False
key = bytes(fp or b"").hex()''')
s=editfn(s,'click_session',' self._last_click_failure_reason = "unknown"',''' self._last_click_failure_reason = "unknown"
if expected_fp and self._skip_excluded_reply_session(expected_fp):
self._last_click_failure_reason = "excluded_session"
return False''')
s=editfn(s,'click_session',''' if opened_matches:
self._last_click_failure_reason = ""''',''' if opened_matches:
if self._skip_excluded_reply_session(expected_fp or opened_fp, inspect_open=True):
self._last_click_failure_reason = "excluded_session"
return False
self._last_click_failure_reason = ""''')
s=editfn(s,'_target_from_session_image',' if fp in processed_fp or fp in non_conv_fp:',''' if self._skip_excluded_reply_session(fp, display_name=row_name):
non_conv_fp.add(fp)
continue
if fp in processed_fp or fp in non_conv_fp:''')
s=editfn(s,'_target_from_filtered_unread_image',' if self._flat_session_rejected(fp):',''' if self._skip_excluded_reply_session(fp, display_name=row_name):
non_conv_fp.add(fp)
continue
if self._flat_session_rejected(fp):''')
s=editfn(s,'_resume_orphaned_pending_reply',' if bool(state.get("awaiting_review", False)):',''' if self._skip_excluded_reply_session(target_fp):
continue
if bool(state.get("awaiting_review", False)):''')
s=editfn(s,'_check_selected_session',' pending_state = self._pending_reply_state(fp)',''' if self._skip_excluded_reply_session(fp, inspect_open=True):
return False
pending_state = self._pending_reply_state(fp)''')
s=editfn(s,'_generate_ai_reply',''' self._consume_database_update(fp)
initial_state''',''' if self._skip_excluded_reply_session(fp, inspect_open=True):
self._generation_logged = True
return None
self._consume_database_update(fp)
initial_state''')
s=editfn(s,'_send_reply_inner',' self._last_send_failure_reason = ""',''' self._last_send_failure_reason = ""
exclusion_fp = expected_fp or getattr(self, "_active_session_fp", None)
if exclusion_fp and self._skip_excluded_reply_session(exclusion_fp, inspect_open=True):
return self._deny_send(str(getattr(self, "_last_session_exclusion_reason", "会话不参与自动回复")))''')
s=editfn(s,'_send_reply_inner',' blanket_review = self._review_mode_enabled()',''' if self._skip_excluded_reply_session(expected_fp, inspect_open=True):
return self._deny_send(str(getattr(self, "_last_session_exclusion_reason", "会话不参与自动回复")))
blanket_review = self._review_mode_enabled()''')
ast.parse(s);p.write_text(s,encoding='utf-8',newline='\n')
print(str(root)+': visual exclusion applied')