92 lines
5.5 KiB
C#
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;
|
|
}
|
|
}
|