From da9f0a4549a609741fb6f48997da3ffc78158d86 Mon Sep 17 00:00:00 2001 From: Rogee Date: Tue, 8 Sep 2026 02:54:32 +0800 Subject: [PATCH] fix: read WeChat identity from account settings --- docs/validation/WebUI-MCP-2026-09-07.md | 9 ++-- .../WechatChatClient.Management.cs | 51 +++++++++++++++---- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/docs/validation/WebUI-MCP-2026-09-07.md b/docs/validation/WebUI-MCP-2026-09-07.md index 7d034f0..99b8d10 100644 --- a/docs/validation/WebUI-MCP-2026-09-07.md +++ b/docs/validation/WebUI-MCP-2026-09-07.md @@ -44,13 +44,14 @@ ## 本轮多账号绑定验证 -- Windows API 实例在锁屏/非交互 SSH 会话下返回 1 个脱敏 PID+HWND 目标和 1 个 `Unbound` 数据库账号;身份字段保持空值,未猜测绑定。 -- 缺少 `accountId` 的 sessions/contacts/messages 请求分别返回 HTTP 400 `AccountIdRequired`。 -- `doctor`/`inspect-ui`/`smoke` 在当前会话返回 `SessionLocked`/`WindowNotFound`;未把锁屏结果当作绑定成功证据。 +- 使用 `tscon 1 /dest:console` 将 Rogee 会话重新接入 console 后,WeChat/UIA 可用;未发送消息。 +- `/api/v1/ui-targets` 成功读取 `i-Rogee` / `Rogee`,并通过临时 manage 权限完成 PID 8912、HWND 590928 绑定;随后已恢复原始只读凭据。 +- `/api/v1/accounts` 返回 `Bound`,带 accountId 的 sessions 返回 HTTP 200;缺少 accountId 的 sessions/contacts/messages 仍返回 HTTP 400 `AccountIdRequired`。 +- 已配置 AC/DC 不息屏、不睡眠、不休眠、屏保关闭,并设置 `NoLockScreen=1`;配置仅禁止自动锁屏,不阻止用户手动锁定。 ## 未完成/不宣称 - 未执行发送、联系人/群管理、朋友圈、语音等写操作;`send-text`、`group-at-all` 和 deferred 项保持禁用。 -- 未完成窗口绑定成功和 60 分钟/20 条消息监听、断线补齐和多客户端慢消费者真机验收;SSE 与服务端监听源已实现,默认配置 `EnableListenerEvents=false`。自动化高频探针已中止,不作为验收证据;后续真机消息验证上限 3 条。 +- 未完成第二个账号/第二个窗口隔离、60 分钟/20 条消息监听、断线补齐和多客户端慢消费者真机验收;SSE 与服务端监听源已实现,默认配置 `EnableListenerEvents=false`。自动化高频探针已中止,不作为验收证据;后续真机消息验证上限 3 条。 - 未完成外部主机直接访问验收(防火墙未开放);不将 HTTP 鉴权描述为网络加密。 - 远程验证任务已停止并清理;不再自动发送测试消息,后续真机消息验证上限为 3 条。 diff --git a/src/WxAgent.Windows/WechatChatClient.Management.cs b/src/WxAgent.Windows/WechatChatClient.Management.cs index 1f74984..db5cc20 100644 --- a/src/WxAgent.Windows/WechatChatClient.Management.cs +++ b/src/WxAgent.Windows/WechatChatClient.Management.cs @@ -98,17 +98,45 @@ public static partial class WechatChatClient } public static Task GetMyInfoAsync(CancellationToken cancellationToken = default) => - WithMainWindowAsync(async (main, _) => + WithMainWindowAsync(async (main, automation) => { - var accountButton = main.FindAllDescendants(cf => cf.ByControlType(ControlType.Button)) - .FirstOrDefault(button => string.IsNullOrWhiteSpace(SafeAutomationId(button)) && !string.IsNullOrWhiteSpace(SafeName(button))) - ?? throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Account button was not found."); - var displayName = SafeName(accountButton); - accountButton.Click(); - await Task.Delay(200, cancellationToken).ConfigureAwait(false); - var values = main.FindAllDescendants(cf => cf.ByControlType(ControlType.Text)) - .Select(SafeName).Where(value => !string.IsNullOrWhiteSpace(value)).Distinct(StringComparer.Ordinal).ToArray(); - return new WechatAccountSnapshot(displayName, ValueAfter(values, "微信号"), ValueAfter(values, "地区")); + var more = FindByAutomationId(main, "MainView.main_tabbar.tabbar_setting") + ?? main.FindAllDescendants(cf => cf.ByControlType(ControlType.Button)) + .FirstOrDefault(button => IsActionable(button) && string.Equals(SafeName(button), "更多", StringComparison.Ordinal)); + more?.Click(); + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + var desktop = automation.GetDesktop(); + var menu = desktop.FindAllChildren(cf => cf.ByControlType(ControlType.Window)) + .FirstOrDefault(window => string.Equals(SafeName(window), "Weixin", StringComparison.Ordinal)); + var settingsButton = (menu ?? desktop).FindAllDescendants(cf => cf.ByControlType(ControlType.Button)) + .FirstOrDefault(button => IsActionable(button) && string.Equals(SafeName(button), "设置", StringComparison.Ordinal)); + settingsButton?.Click(); + + AutomationElement? settings = null; + for (var attempt = 0; attempt < 30 && settings is null; attempt++) + { + settings = desktop.FindAllChildren(cf => cf.ByControlType(ControlType.Window)) + .FirstOrDefault(window => string.Equals(SafeAutomationId(window), "PreferenceWindow", StringComparison.Ordinal) + || string.Equals(SafeName(window), "设置", StringComparison.Ordinal)); + if (settings is null) await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + if (settings is null) throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Account settings window was not found."); + try + { + var values = settings.FindAllDescendants(cf => cf.ByControlType(ControlType.Text)) + .Select(SafeName).Where(value => !string.IsNullOrWhiteSpace(value)).Distinct(StringComparer.Ordinal).ToArray(); + var accountIndex = Array.FindIndex(values, value => string.Equals(value, "账号", StringComparison.Ordinal)); + var accountValues = accountIndex >= 0 + ? values.Skip(accountIndex + 1).TakeWhile(value => value is not ("登录方式" or "存储" or "退出登录")).ToArray() + : values; + var wechatId = ValueAfter(values, "微信号") ?? accountValues.FirstOrDefault(LooksLikeWechatId); + var displayName = ValueAfter(values, "昵称") ?? accountValues.FirstOrDefault(value => !string.Equals(value, wechatId, StringComparison.Ordinal) + && value is not ("退出登录" or "自动")); + if (string.IsNullOrWhiteSpace(wechatId) && string.IsNullOrWhiteSpace(displayName)) + throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Account identity was not found in settings."); + return new WechatAccountSnapshot(displayName ?? wechatId ?? string.Empty, wechatId, ValueAfter(values, "地区")); + } + finally { settings.AsWindow().Close(); } }, cancellationToken); /// Reads non-group contact rows, up to maxCount. Use the paged API for completeness and account selection. @@ -630,6 +658,9 @@ public static partial class WechatChatClient return values.Length == 0 ? null : new WechatNewFriendSnapshot(values[0], values.Length > 1 ? values[^1] : "unknown"); } + private static bool LooksLikeWechatId(string value) => + value.Length is >= 6 and <= 20 && char.IsLetter(value[0]) && value.All(character => char.IsLetterOrDigit(character) || character is '_' or '-'); + private static string? ValueAfter(IReadOnlyList values, string label) { for (var index = 0; index < values.Count - 1; index++)