from pathlib import Path import shutil ARTIFACT = Path(__file__).parent def replace_once(text, old, new): if text.count(old) != 1: raise RuntimeError(f'Expected one occurrence ({text.count(old)}): {old[:100]}') return text.replace(old, new, 1) def edit_gui(text): start = text.index('class ReviewAssistantPicker(QDialog):') end = text.index('\nclass ', start + 1) picker = text[start:end] picker = picker.replace('self._filter = "sendable"', 'self._filter = "all"') picker = picker.replace('选择医疗助理私人微信', '从我的客户选择医疗助理') picker = picker.replace('点击联系人查看详情,再确认设为医疗助理。找不到时,先刷新「微信消息库」,再刷新此列表。', '来源:当前托管企业微信「通讯录 → 我的客户」。请仅选择你指定的医疗助理,核对详情后确认。找不到时,先刷新「微信消息库」,再刷新此列表。') picker = picker.replace('(("sendable", "可发送"), ("directory", "通讯录未关联"), ("all", "全部"))', '(("all", "我的客户"), ("sendable", "可发送"), ("directory", "待核验"))') picker = replace_once(picker, 'state = "可设为助理" if row.get("selectable", True) else "通讯录 · 尚未关联可发送会话"', 'state = "可设为助理" if row.get("selectable", True) else "待核验 · 暂不可选"') picker = replace_once(picker, 'suffix = identity.rsplit(":", 1)[-1][-6:]\n return self._display_name(row) + "\\n" + state + (" · 尾号 " + suffix if suffix else "")', 'from review_assistant_contacts import recipient_peer\n peer = str(row.get("peer_id") or recipient_peer(self.account, identity) or identity.rsplit(":", 1)[-1])\n suffix = peer[-6:]\n kind = str(row.get("kind") or ("企业微信客户" if identity.startswith("S:") else "微信客户" if identity.startswith("M:") else "我的客户"))\n return self._display_name(row) + "\\n" + kind + " · " + state + (" · 尾号 " + suffix if suffix else "")') picker = replace_once(picker, 'identity = conversation_id.split(":", 1)[-1] or "—"\n self.details_identity.setText(("联系编号:" if row.get("selectable", True) else "微信标识:") + identity)', 'from review_assistant_contacts import recipient_peer\n identity = str(row.get("peer_id") or recipient_peer(self.account, conversation_id) or conversation_id.split(":", 1)[-1] or "—")\n kind = str(row.get("kind") or ("企业微信客户" if conversation_id.startswith("S:") else "微信客户" if conversation_id.startswith("M:") else "我的客户"))\n self.details_identity.setText(kind + " · 客户编号:" + identity + ("\\n会话编号:" + conversation_id if row.get("selectable", True) else ""))') picker = picker.replace('已关联可发送的私人微信单聊。核对后,点击「设为医疗助理」。', '已核验为当前账号「我的客户」中的单聊联系人。确认此人是指定医疗助理后,点击「设为医疗助理」。') picker = picker.replace('尚无可发送的私人微信会话,请建立单聊并刷新消息库。', '尚无可发送的客户单聊,请建立单聊并刷新消息库。') picker = picker.replace('没有可发送的匹配联系人。可切换「通讯录未关联」查看,再建立单聊并刷新消息库。', '没有可发送的匹配客户。可切换「待核验」查看原因,建立单聊或刷新消息库后重试。') picker = picker.replace('没有匹配联系人。可尝试「全部」、搜索备注或编号,或刷新微信消息库。', '没有匹配客户。可尝试「我的客户」、搜索备注或编号,或刷新微信消息库。') text = text[:start] + picker + text[end:] text = replace_once(text, 'from review_assistant_contacts import notification_preview, recipient_display', 'from review_assistant_contacts import notification_preview, recipient_display, recipient_peer') text = replace_once(text, 'recipient_id = str(config.get("recipient_conv_id") or "")\n return {"supported": available', 'recipient_id = str(config.get("recipient_conv_id") or "")\n peer = recipient_peer(account, recipient_id)\n kind = "企业微信客户" if peer and recipient_id.startswith("S:") else "微信客户" if peer else "待核验联系人"\n check = state.get("recipient_check") or {}\n warning = str(check.get("reason") or "请从我的客户重新选择助理") if check.get("id") == recipient_id and check.get("ok") is False else ""\n return {"supported": available') text = text.replace('点击读取当前账号后选择助理私人微信', '点击读取当前账号后,从我的客户选择医疗助理') text = replace_once(text, '"notification": dict(state.get("notification") or {}),\n "enabled": config.get("enabled") is True,', '"notification": dict(state.get("notification") or {}),\n "recipientWarning": warning,\n "enabled": config.get("enabled") is True,') text = replace_once(text, '"label": f"私人微信 · 联系编号 {recipient_id[2:]}" if recipient_id else ""},', '"kind": kind, "peerId": peer,\n "label": f"{kind} · 联系编号 {peer or recipient_id}" if recipient_id else ""},') # Snapshot settings on the UI thread; all native/DB checks remain in the worker. start = text.index(' def _refresh_review_assistant(self)') end = text.index(' def _choose_review_assistant(self)', start) refresh = text[start:end] refresh = replace_once(refresh, ' def work():', ' accounts = self.runtime_settings.get("review_assistant_accounts") or {}\n saved = {str(key): str(value.get("recipient_conv_id") or "") for key, value in accounts.items() if isinstance(value, dict)} if isinstance(accounts, dict) else {}\n def work():') refresh = refresh.replace('from review_assistant_contacts import active_context, list_recipients', 'from review_assistant_contacts import active_context, list_recipients, validate_recipient') refresh = replace_once(refresh, ' result["notification"] = notification_summary(SCRIPT_DIR, result["account_id"])', ' result["notification"] = notification_summary(SCRIPT_DIR, result["account_id"])\n target = saved.get(str(result["account_id"]), "")\n if target:\n check = validate_recipient(result["account_id"], target)\n result["recipient_check"] = {"id": target, "ok": bool(check.get("ok")), "reason": str(check.get("reason") or "")}') text = text[:start] + refresh + text[end:] text = text.replace('请先选择助理私人微信联系人', '请先从我的客户选择医疗助理联系人') text = replace_once(text, '("account_id", "account_name", "ready", "reason", "notification")', '("account_id", "account_name", "ready", "reason", "notification", "recipient_check")') text = replace_once(text, 'from review_assistant_contacts import recipient_display\n identity = recipient_display', 'from review_assistant_contacts import recipient_display, recipient_peer\n peer = recipient_peer(expected, recipient["id"])\n kind = "企业微信客户" if recipient["id"].startswith("S:") else "微信客户"\n identity = recipient_display') text = replace_once(text, 'recipient_name_known=identity["nameKnown"], recipient_kind="微信客户")', 'recipient_name_known=identity["nameKnown"], recipient_kind=kind, recipient_peer_id=peer)') text = replace_once(text, ' else:\n self._review_assistant_state["message"] = (f"已选择:', ' else:\n if recipient:\n self._review_assistant_state["recipient_check"] = {"id": recipient["id"], "ok": True, "reason": ""}\n self._review_assistant_state["message"] = (f"已选择:') return text def edit_js(text): text = text.replace('尚未选择助理私人微信', '尚未从我的客户选择医疗助理') text = text.replace('向指定助理的私人微信发送客户咨询、待发送回复和审核原因。', '向你从「通讯录 → 我的客户」中指定的医疗助理发送客户咨询、待发送回复和审核原因。') text = text.replace('>助理私人微信', '>${esc(recipient.kind || "我的客户联系人")}') text = text.replace('${esc(recipientId)}', '${esc(recipient.peerId || recipientId)}') text = text.replace('在助理私人微信回复提醒,不会解除客户审核。', '在助理联系人会话中回复提醒,不会解除客户审核。') text = replace_once(text, ' ${r.message ? `

${esc(r.message)}

` : ""}', ' ${r.recipientWarning ? `` : ""}\n ${r.message ? `

${esc(r.message)}

` : ""}') return text for name, root in [('source', Path('C:/kefu/wechat_rpa')), ('production', Path('C:/wechat_rpa'))]: for relative, edit in [('wechat_gui_qt.py', edit_gui), ('assets/ui/console-app.js', edit_js)]: path = root / relative old = path.read_text(encoding='utf-8-sig') new = edit(old) backup = ARTIFACT / 'before-gui' / name / relative backup.parent.mkdir(parents=True, exist_ok=True) if backup.exists(): raise RuntimeError(f'Backup already exists: {backup}') shutil.copy2(path, backup) path.write_text(new, encoding='utf-8', newline='\n') print(name, relative, 'updated')