Files

75 lines
4.4 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text.RegularExpressions;
namespace WxAgent.Core;
public sealed record WechatSearchExpansion(string Category, int RowIndex, int ExpectedCount);
public sealed record WechatSearchPage(
IReadOnlyList<WechatSessionSearchResult> Results, IReadOnlyList<WechatSearchExpansion> Expansions);
public static class WechatSearchRules
{
private static readonly Regex More = new(@"^查看全部\s*[(](\d+)[)]$", RegexOptions.CultureInvariant);
public static bool IsLocalCategory(string? name) => name is "联系人" or "群聊" or "功能" or "最常使用";
public static bool IsExpansionLabel(string name) => More.IsMatch(name.Trim());
public static WechatSearchPage Parse(
IReadOnlyList<(string AutomationId, string AccessibleName)> rows, string query, string? expandedCategory = null)
{
if (expandedCategory is not null && expandedCategory is not ("联系人" or "群聊"))
throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "Only local contacts and group chats may be expanded.");
var results = new List<WechatSessionSearchResult>();
var expansions = new List<WechatSearchExpansion>();
var category = expandedCategory;
for (var index = 0; index < rows.Count; index++)
{
var row = rows[index];
var name = row.AccessibleName.Trim();
if (!row.AutomationId.StartsWith("search_item_", StringComparison.Ordinal))
{
if (IsLocalCategory(name)) { category = name; continue; }
var more = More.Match(name);
if (more.Success && category is "联系人" or "群聊")
{
if (!int.TryParse(more.Groups[1].Value, out var count) || count is < 1 or > 10000)
throw new WxAgentException(WxAgentErrorCode.UiStructureChanged, "Invalid local search result count.");
expansions.Add(new WechatSearchExpansion(category, index, count));
continue;
}
// An unrecognized section must not inherit local trust from an expanded category.
category = null;
continue;
}
if (category is null || row.AutomationId.StartsWith("search_item_web_", StringComparison.Ordinal)
|| row.AutomationId.StartsWith("search_item_online_", StringComparison.Ordinal)) continue;
var parsed = WechatSessionParser.ParseSearchResults([row], query).SingleOrDefault();
if (parsed is null) continue;
var lines = row.AccessibleName.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var idName = row.AutomationId["search_item_".Length..];
var displayName = lines.Contains(idName, StringComparer.Ordinal) ? idName
: parsed.Name == category ? lines.LastOrDefault(line => line != category) ?? string.Empty : parsed.Name;
if (displayName.Length == 0 || (category == "功能" && displayName != WechatLocators.FileTransferAssistant)) continue;
// The promoted local row uses the same name-based ID as contacts/groups.
// Do not admit arbitrary function, online, or future result families under this mixed header.
if (category == "最常使用" && row.AutomationId != "search_item_" + displayName
&& row.AutomationId != WechatLocators.FileTransferAssistantSearchResult) continue;
// Preserve duplicates: two same-named local conversations must be reported as ambiguous, not silently merged.
results.Add(parsed with { Name = displayName, Category = category, IsExactMatch = string.Equals(displayName, query, StringComparison.Ordinal) });
}
return new WechatSearchPage(results, expansions);
}
public static WechatSessionSearchResult RequireExact(IReadOnlyList<WechatSessionSearchResult> results, string name)
{
var matches = results.Where(result => IsLocalCategory(result.Category)
&& string.Equals(result.Name, name, StringComparison.Ordinal)).ToArray();
return matches.Length switch
{
1 => matches[0],
0 => throw new WxAgentException(WxAgentErrorCode.ControlNotFound, "The exact local conversation was not found."),
_ => throw new WxAgentException(WxAgentErrorCode.InvalidOperationState, "Multiple local conversations have the same name; refusing an ambiguous selection.")
};
}
}