from pathlib import Path import hashlib import json import shutil audit=Path(__file__).resolve().parent changes=[] for root in (Path('C:/wechat_rpa'),Path('C:/kefu/wechat_rpa')): path=root/'wxwork_db.py' before=path.read_bytes() text=path.read_text(encoding='utf-8') old=''' try: send_time = float(send_time) except (TypeError, ValueError): return None if send_time > 10 ** 12: send_time /= 1000.0 if send_time <= 0: return None ''' new=''' try: send_time = float(send_time) if send_time > 10 ** 12: send_time /= 1000.0 if not math.isfinite(send_time) or send_time <= 0: return None # Context rendering uses local datetime conversion. Reject corrupt # rows here so neither polling order nor later formatting can crash. datetime.fromtimestamp(send_time) except (TypeError, ValueError, OverflowError, OSError): return None ''' assert text.count(old)==1 assert 'import math\n' not in text text=text.replace('import json\n','import json\nimport math\n',1).replace(old,new) backup=root/'backups/recognition-audit-20260916'/path.name backup.parent.mkdir(parents=True,exist_ok=True) assert not backup.exists() backup.write_bytes(before) path.write_text(text,encoding='utf-8') shutil.copy2(audit/'test_database_timestamp_audit.py',root/'test_database_timestamp_audit.py') changes.append({'path':str(path),'backup':str(backup),'before_sha256':hashlib.sha256(before).hexdigest(),'after_sha256':hashlib.sha256(path.read_bytes()).hexdigest()}) path=Path('C:/wechat_rpa/protocol_engine.py') before=path.read_bytes() text=path.read_text(encoding='utf-8') old='''def _unanswered_requires_manual(context): # A text caption after an image/voice is still a media question. Only inspect # the unanswered batch: media already handled by a human must not block later text. for message in reversed((context or {}).get('messages') or []): if message.get('is_self'):break if not _is_text_message(message):return True return False ''' new='''def _unanswered_manual_reason(context): # A text caption after media is still a media question. Human replies and # restored-contact notices separate the current batch from old history. count=0 for message in reversed((context or {}).get('messages') or []): if message.get('is_self') or system_contact_status(message)=='restored':break count+=1 if not _is_text_message(message):return '当前未回复消息包含图片、语音或其他非文本内容,协议版需人工处理' if count>100:return '连续未回复消息超过 100 条,无法保证问题完整,请人工处理后继续' return '' ''' assert text.count(old)==1 text=text.replace(old,new) old=''' context=self.db.get_conversation_context_by_id(self.account,state['conv_id'],limit=100) if not _is_text_message(state) or _unanswered_requires_manual(context): with self._lock:state.update(reply_text='',staged_reply_text='',approved=False,awaiting_review=False) self._stage(key,'error','此消息类型需要人工处理','当前未回复消息包含图片、语音或其他非文本内容,协议版需人工处理');return ''' new=''' # One extra row detects a current customer batch cut by the 100-message window. context=self.db.get_conversation_context_by_id(self.account,state['conv_id'],limit=101) manual_reason=_unanswered_manual_reason(context) if not _is_text_message(state):manual_reason=manual_reason or '当前协议版只自动处理文本消息' if manual_reason: with self._lock:state.update(reply_text='',staged_reply_text='',approved=False,awaiting_review=False) self._stage(key,'error','当前消息需要人工处理',manual_reason);return ''' assert text.count(old)==1 text=text.replace(old,new) backup=path.parent/'backups/recognition-audit-20260916/protocol_engine.media-fix.py' assert not backup.exists() backup.write_bytes(before) path.write_text(text,encoding='utf-8') changes.append({'path':str(path),'backup':str(backup),'before_sha256':hashlib.sha256(before).hexdigest(),'after_sha256':hashlib.sha256(path.read_bytes()).hexdigest()}) (audit/'protocol-database-fix-manifest.json').write_text(json.dumps(changes,indent=2),encoding='utf-8') print(json.dumps(changes))