Files
wx-win-agent/node-agent/WxAgent.Windows/WechatChatClient.Search.cs
T

92 lines
5.5 KiB
C#

using FlaUI.Core.AutomationElements;
using FlaUI.Core.Definitions;
using WxAgent.Core;
namespace WxAgent.Windows;
public static partial class WechatChatClient
{
private static FlaUI.Core.AutomationElements.TextBox GetSearchBox(AutomationElement main) =>
(main.FindFirstDescendant(cf => cf.ByControlType(ControlType.Edit).And(cf.ByName(WechatLocators.Search)))
?? throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Search input was not found.")).AsTextBox();
private static AutomationElement[] GetSearchRows(AutomationElement main) =>
(FindByAutomationId(main, WechatLocators.SearchList)
?? throw new WxAgentException(WxAgentErrorCode.UiStructureChanged, "Local search list was not found."))
.FindAllChildren(cf => cf.ByControlType(ControlType.ListItem));
private static WechatSearchPage ReadSearchPage(AutomationElement[] rows, string query, string? category = null) =>
WechatSearchRules.Parse(rows.Select(row => (SafeAutomationId(row), SafeName(row))).ToArray(), query, category);
private static async Task<WechatSearchPage> ResetLocalSearchAsync(AutomationElement main, string query, CancellationToken cancellationToken)
{
GetSearchBox(main).Text = string.Empty;
GetSearchBox(main).Text = query;
if (!string.Equals(GetSearchBox(main).Text, query, StringComparison.Ordinal))
throw new WxAgentException(WxAgentErrorCode.ResultUnconfirmed, "Search input did not retain the exact query.");
await Task.Delay(500, cancellationToken).ConfigureAwait(false);
return ReadSearchPage(GetSearchRows(main), query);
}
private static async Task<WechatSearchPage> ExpandLocalCategoryAsync(
AutomationElement main, string query, WechatSearchExpansion expansion, CancellationToken cancellationToken)
{
var rows = GetSearchRows(main);
var current = ReadSearchPage(rows, query).Expansions.SingleOrDefault(item => item.Category == expansion.Category)
?? throw new WxAgentException(WxAgentErrorCode.UiStructureChanged, "Local expansion control changed before activation.");
// This row was classified under a local header, not found by a global keyword/name search.
rows[current.RowIndex].Click();
for (var attempt = 0; attempt < 25; attempt++)
{
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
var page = ReadSearchPage(GetSearchRows(main), query, current.Category);
if (page.Results.Count(result => result.Category == current.Category) >= current.ExpectedCount
&& !page.Expansions.Any(item => item.Category == current.Category)) return page;
}
throw new WxAgentException(WxAgentErrorCode.UiStructureChanged, "Not all local search results became available; refusing a partial selection.");
}
private static async Task<IReadOnlyList<WechatSessionSearchResult>> SearchLocalSessionsCoreAsync(
AutomationElement main, string query, CancellationToken cancellationToken)
{
var overview = await ResetLocalSearchAsync(main, query, cancellationToken).ConfigureAwait(false);
var results = overview.Results.ToList();
foreach (var expansion in overview.Expansions)
{
await ResetLocalSearchAsync(main, query, cancellationToken).ConfigureAwait(false);
var full = await ExpandLocalCategoryAsync(main, query, expansion, cancellationToken).ConfigureAwait(false);
results.RemoveAll(result => result.Category == expansion.Category);
results.AddRange(full.Results.Where(result => result.Category == expansion.Category));
}
return results;
}
private static async Task<WechatSessionSearchResult> OpenLocalSearchSessionAsync(AutomationElement main, string name, CancellationToken cancellationToken, string? query = null)
{
query ??= name;
var results = await SearchLocalSessionsCoreAsync(main, query, cancellationToken).ConfigureAwait(false);
var selected = WechatSearchRules.RequireExact(results, name);
var page = await ResetLocalSearchAsync(main, query, cancellationToken).ConfigureAwait(false);
var expansion = page.Expansions.SingleOrDefault(item => item.Category == selected.Category);
if (expansion is not null)
await ExpandLocalCategoryAsync(main, query, expansion, cancellationToken).ConfigureAwait(false);
var rows = GetSearchRows(main);
var current = ReadSearchPage(rows, query, expansion?.Category);
var fresh = WechatSearchRules.RequireExact(current.Results, name);
if (fresh.Category != selected.Category)
throw new WxAgentException(WxAgentErrorCode.InvalidOperationState, "Local search category changed before selection.");
var matches = rows.Where(row => SafeAutomationId(row) == fresh.AutomationId && SessionNameMatches(SafeName(row), name)).ToArray();
if (matches.Length != 1)
throw new WxAgentException(WxAgentErrorCode.InvalidOperationState, "Local search target is no longer unique.");
if (matches[0].IsOffscreen)
{
var scroll = matches[0].Patterns.ScrollItem.PatternOrDefault
?? throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "Local result is offscreen and cannot be scrolled into view.");
scroll.ScrollIntoView();
}
matches[0].Click();
await WaitForSessionPageAsync(main, name, cancellationToken).ConfigureAwait(false);
return fresh;
}
}