fix: expand local session results and reject unsafe search targets

This commit is contained in:
2026-09-06 01:24:50 +08:00
parent 93f4583737
commit c35a4a1051
16 changed files with 557 additions and 46 deletions
+24 -32
View File
@@ -40,7 +40,8 @@ public static partial class WechatChatClient
public static async Task<IReadOnlyList<WechatSessionSearchResult>> SearchSessionsAsync(
string query,
bool exactOnly,
CancellationToken cancellationToken)
CancellationToken cancellationToken,
string? uiOutput = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(query);
await CommandQueue.WaitAsync(cancellationToken);
@@ -55,18 +56,9 @@ public static partial class WechatChatClient
var previous = box.Text;
try
{
box.Text = query;
if (!string.Equals(box.Text, query, StringComparison.Ordinal))
{
throw new WxAgentException(WxAgentErrorCode.ResultUnconfirmed, "The search box did not contain the exact query.");
}
await Task.Delay(500, cancellationToken);
var results = WechatSessionParser.ParseSearchResults(
window.FindAllDescendants()
.Where(element => SafeControlType(element) == ControlType.ListItem)
.Select(element => (SafeAutomationId(element), SafeName(element))),
query);
var results = await SearchLocalSessionsCoreAsync(window, query, cancellationToken).ConfigureAwait(false);
if (uiOutput is not null)
await WechatUiInspector.CaptureAsync(uiOutput, cancellationToken).ConfigureAwait(false);
return exactOnly ? results.Where(result => result.IsExactMatch).ToArray() : results;
}
finally
@@ -90,14 +82,20 @@ public static partial class WechatChatClient
: new WechatSessionSnapshot(name, SafeAutomationId(element!), true));
}, cancellationToken);
public static async Task<WechatSessionSnapshot> OpenSessionAsync(string name, CancellationToken cancellationToken)
public static async Task<WechatSessionSnapshot> OpenSessionAsync(string name, CancellationToken cancellationToken, string? query = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
if (query is not null) ArgumentException.ThrowIfNullOrWhiteSpace(query);
await CommandQueue.WaitAsync(cancellationToken);
try
{
using var automation = new UIA3Automation();
var window = AttachWindow(automation, cancellationToken);
if (query is not null)
{
var selected = await OpenLocalSearchSessionAsync(window, name, cancellationToken, query).ConfigureAwait(false);
return new WechatSessionSnapshot(name, selected.AutomationId, true);
}
if (SafeName(FindByAutomationId(window, WechatLocators.CurrentChatName)) == name)
{
return new WechatSessionSnapshot(name, SafeAutomationId(FindByAutomationId(window, WechatLocators.CurrentChatName)!), true);
@@ -106,23 +104,8 @@ public static partial class WechatChatClient
var target = FindByAutomationId(window, "session_item_" + name);
if (target is null)
{
var search = window.FindAllDescendants().FirstOrDefault(element =>
SafeControlType(element) == ControlType.Edit && SafeName(element) == WechatLocators.Search)
?? throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "WeChat search box was not found.");
search.AsTextBox().Text = name;
var deadline = DateTimeOffset.UtcNow + TimeSpan.FromSeconds(5);
while (DateTimeOffset.UtcNow < deadline && target is null)
{
cancellationToken.ThrowIfCancellationRequested();
target = window.FindAllDescendants().FirstOrDefault(element =>
SafeAutomationId(element).StartsWith("search_item_", StringComparison.Ordinal) && SessionNameMatches(SafeName(element), name));
if (target is null) await Task.Delay(150, cancellationToken);
}
}
if (target is null)
{
throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "The requested session was not found.");
var selected = await OpenLocalSearchSessionAsync(window, name, cancellationToken).ConfigureAwait(false);
return new WechatSessionSnapshot(name, selected.AutomationId, true);
}
ClickCenter(target);
@@ -400,6 +383,9 @@ public static partial class WechatChatClient
IDisposable? subscription = null;
var connected = false;
nint subscribedWindow = 0;
var bindingAttempted = false;
var observedSnapshot = false;
Exception? lastReadError = null;
var announceReconnect = recoveredScan;
var elapsed = System.Diagnostics.Stopwatch.StartNew();
using var readDeadline = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
@@ -416,6 +402,7 @@ public static partial class WechatChatClient
await CommandQueue.WaitAsync(readDeadline.Token);
try
{
bindingAttempted = true;
window = await BindListeningWindowAsync(automation, session, independentWindow, readDeadline.Token).ConfigureAwait(false);
var handle = window.Properties.NativeWindowHandle.Value;
if (subscribedWindow != 0 && handle != subscribedWindow)
@@ -440,6 +427,7 @@ public static partial class WechatChatClient
if (!IsListeningSession(window, session, independentWindow))
throw new WxAgentException(WxAgentErrorCode.InvalidOperationState, "Listener session changed during snapshot.");
visible = snapshot;
observedSnapshot = true;
snapshotObserved?.Invoke();
}
finally
@@ -455,8 +443,9 @@ public static partial class WechatChatClient
{
throw;
}
catch
catch (Exception exception)
{
lastReadError = exception;
visible = null;
connected = false;
subscription?.Dispose();
@@ -551,6 +540,9 @@ public static partial class WechatChatClient
{
subscription?.Dispose();
}
if (bindingAttempted && !observedSnapshot)
throw new WxAgentException(lastReadError is WxAgentException error ? error.Code : WxAgentErrorCode.ControlNotFound,
$"Listener never obtained a valid snapshot (stage: {lastReadError?.TargetSite?.Name ?? "initial binding"}).", lastReadError);
}
private static AutomationElement AttachWindow(UIA3Automation automation, CancellationToken cancellationToken)