feat: add remote control plane and whitelist reads
Build web service image / build (push) Successful in 1m53s
Build web service image / build (push) Successful in 1m53s
This commit is contained in:
@@ -100,29 +100,40 @@ public static partial class WechatChatClient
|
||||
public static Task<WechatAccountSnapshot> GetMyInfoAsync(CancellationToken cancellationToken = default) =>
|
||||
WithMainWindowAsync(async (main, automation) =>
|
||||
{
|
||||
ClickProfileAvatar(main);
|
||||
var desktop = automation.GetDesktop();
|
||||
AutomationElement? profile = null;
|
||||
for (var attempt = 0; attempt < 30 && profile is null; attempt++)
|
||||
{
|
||||
profile = desktop.FindAllChildren(cf => cf.ByControlType(ControlType.Window))
|
||||
.FirstOrDefault(window => IsActionable(window)
|
||||
&& string.Equals(SafeName(window), WechatLocators.ProfileWindowTitle, StringComparison.Ordinal)
|
||||
&& FindByAutomationId(window, WechatLocators.ProfileDisplayName) is not null);
|
||||
if (profile is null) await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
if (profile is null) throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Account profile window was not found.");
|
||||
var settings = FindPreferenceWindow(desktop);
|
||||
var closeSettings = settings is null;
|
||||
settings ??= await OpenPreferenceWindowAsync(main, automation, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var displayName = SafeName(FindByAutomationId(profile, WechatLocators.ProfileDisplayName));
|
||||
var wechatId = SafeName(FindByAutomationId(profile, WechatLocators.ProfileWechatId));
|
||||
if (string.IsNullOrWhiteSpace(displayName) && string.IsNullOrWhiteSpace(wechatId))
|
||||
throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Account identity was not found in the profile window.");
|
||||
return new WechatAccountSnapshot(
|
||||
string.IsNullOrWhiteSpace(displayName) ? wechatId : displayName,
|
||||
string.IsNullOrWhiteSpace(wechatId) ? null : wechatId);
|
||||
var accountLabel = settings.FindAllDescendants(cf => cf.ByControlType(ControlType.Text))
|
||||
.FirstOrDefault(element => SafeName(element) == "账号");
|
||||
if (accountLabel is null)
|
||||
throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Account section was not found in WeChat settings.");
|
||||
|
||||
var labelBounds = accountLabel.BoundingRectangle;
|
||||
var values = settings.FindAllDescendants(cf => cf.ByControlType(ControlType.Text))
|
||||
.Select(element => (Name: SafeName(element), Bounds: element.BoundingRectangle))
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value.Name)
|
||||
&& value.Bounds.Top >= labelBounds.Bottom
|
||||
&& value.Bounds.Bottom <= labelBounds.Bottom + 90
|
||||
&& value.Bounds.Left >= labelBounds.Left
|
||||
&& value.Bounds.Right <= labelBounds.Right - 80)
|
||||
.OrderBy(value => value.Bounds.Top)
|
||||
.Select(value => value.Name)
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
if (values.Length < 2)
|
||||
throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Account identity was not found in WeChat settings.");
|
||||
return new WechatAccountSnapshot(values[0], values[1]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (closeSettings)
|
||||
{
|
||||
try { settings.AsWindow().Close(); } catch { }
|
||||
}
|
||||
}
|
||||
finally { profile.AsWindow().Close(); }
|
||||
}, cancellationToken);
|
||||
|
||||
/// <summary>Reads non-group contact rows, up to maxCount. Use the paged API for completeness and account selection.</summary>
|
||||
@@ -581,18 +592,47 @@ public static partial class WechatChatClient
|
||||
throw new WxAgentException(WxAgentErrorCode.ControlNotFound, $"Person checkbox '{name}' was not exposed.");
|
||||
}
|
||||
|
||||
// ponytail: WeChat exposes the avatar as a nameless custom widget; use the first rail slot until it exposes a stable UIA locator.
|
||||
private static void ClickProfileAvatar(AutomationElement main)
|
||||
private static async Task<AutomationElement> OpenPreferenceWindowAsync(
|
||||
AutomationElement main, UIA3Automation automation, CancellationToken cancellationToken)
|
||||
{
|
||||
var tabBar = FindByAutomationId(main, WechatLocators.MainTabBar)
|
||||
?? throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "WeChat navigation bar was not found.");
|
||||
var bounds = tabBar.BoundingRectangle;
|
||||
if (bounds.Width <= 0 || bounds.Height <= bounds.Width)
|
||||
throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "WeChat navigation bar has no usable bounds.");
|
||||
ExecuteInputStep("open-profile", () => Mouse.Click(
|
||||
new System.Drawing.Point(bounds.Left + bounds.Width / 2, bounds.Top + bounds.Width), MouseButton.Left));
|
||||
var desktop = automation.GetDesktop();
|
||||
var menu = FindSettingsMenu(desktop);
|
||||
if (menu is null)
|
||||
{
|
||||
var settingsButton = FindByAutomationId(main, WechatLocators.SettingsMenuButton)
|
||||
?? throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "WeChat settings button was not found.");
|
||||
ExecuteInputStep("open-settings-menu", () => settingsButton.Click());
|
||||
}
|
||||
|
||||
var clickedSettings = false;
|
||||
for (var attempt = 0; attempt < 40; attempt++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var settings = FindPreferenceWindow(desktop);
|
||||
if (settings is not null) return settings;
|
||||
menu ??= FindSettingsMenu(desktop);
|
||||
if (menu is not null && !clickedSettings)
|
||||
{
|
||||
ClickNamed(menu, "设置", ControlType.Button);
|
||||
clickedSettings = true;
|
||||
}
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "WeChat settings window was not found.");
|
||||
}
|
||||
|
||||
private static AutomationElement? FindPreferenceWindow(AutomationElement desktop) =>
|
||||
desktop.FindAllChildren(cf => cf.ByControlType(ControlType.Window))
|
||||
.FirstOrDefault(window => IsActionable(window)
|
||||
&& (SafeAutomationId(window) == WechatLocators.PreferenceWindow
|
||||
|| SafeName(window) == "设置"));
|
||||
|
||||
private static AutomationElement? FindSettingsMenu(AutomationElement desktop) =>
|
||||
desktop.FindAllChildren(cf => cf.ByControlType(ControlType.Window))
|
||||
.FirstOrDefault(window => IsActionable(window)
|
||||
&& window.FindAllDescendants(cf => cf.ByControlType(ControlType.Button))
|
||||
.Any(button => IsActionable(button) && SafeName(button) == "设置"));
|
||||
|
||||
private static bool IsActionable(AutomationElement element) =>
|
||||
!element.Properties.IsOffscreen.ValueOrDefault && element.Properties.IsEnabled.ValueOrDefault && !element.BoundingRectangle.IsEmpty;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user