refactor: split node agent and Go control plane
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace WxAgent.Core;
|
||||
|
||||
public sealed record WechatMomentSnapshot(
|
||||
string Author,
|
||||
string Text,
|
||||
string Fingerprint,
|
||||
string? Time = null,
|
||||
int ImageCount = 0,
|
||||
bool IsLiked = false);
|
||||
|
||||
public sealed record WechatMessageContent(
|
||||
ChatMessageType Type,
|
||||
string Text,
|
||||
Uri? Url = null,
|
||||
QuotedMessageSnapshot? Quote = null);
|
||||
|
||||
public sealed record WechatNoteContentPart(string Kind, string Value);
|
||||
|
||||
public sealed record WechatNoteContent(IReadOnlyList<WechatNoteContentPart> Parts)
|
||||
{
|
||||
public string ToMarkdown() => string.Join(
|
||||
Environment.NewLine + Environment.NewLine,
|
||||
Parts.Select(part => part.Kind == "file" ? $"[{Path.GetFileName(part.Value)}]({part.Value})" : part.Value));
|
||||
}
|
||||
|
||||
public enum WechatMomentPrivacy
|
||||
{
|
||||
Public,
|
||||
TagWhitelist,
|
||||
TagBlacklist
|
||||
}
|
||||
|
||||
public sealed record WechatMomentPublishOptions(
|
||||
WechatMomentPrivacy Privacy = WechatMomentPrivacy.Public,
|
||||
IReadOnlyList<string>? Tags = null)
|
||||
{
|
||||
public void Validate()
|
||||
{
|
||||
if (!Enum.IsDefined(Privacy))
|
||||
throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "Unknown moment privacy mode.");
|
||||
if (Privacy != WechatMomentPrivacy.Public)
|
||||
WechatOperationPolicy.ValidateNames(Tags ?? [], nameof(Tags));
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class WechatMessageContentParser
|
||||
{
|
||||
[GeneratedRegex(@"https?://[^\s<>\]\[\))]+", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
|
||||
private static partial Regex UrlRegex();
|
||||
|
||||
public static Uri? GetUrl(ChatMessageSnapshot message)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
var match = UrlRegex().Match(message.Text);
|
||||
return match.Success && Uri.TryCreate(match.Value.TrimEnd('.', ',', '。', ','), UriKind.Absolute, out var uri) ? uri : null;
|
||||
}
|
||||
|
||||
public static WechatMessageContent GetContent(ChatMessageSnapshot message)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
return new WechatMessageContent(message.Type, message.Text, GetUrl(message), message.Quote);
|
||||
}
|
||||
|
||||
public static string ToMarkdown(ChatMessageSnapshot message)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
var content = GetContent(message);
|
||||
var body = content.Type switch
|
||||
{
|
||||
ChatMessageType.Image => $"",
|
||||
ChatMessageType.Video => $"[视频]({content.Url?.AbsoluteUri ?? string.Empty})",
|
||||
ChatMessageType.File => $"[文件]({content.Url?.AbsoluteUri ?? string.Empty})",
|
||||
ChatMessageType.Voice => $"> 语音:{content.Text}",
|
||||
ChatMessageType.Link when content.Url is not null => $"[{LinkTitle(content.Text, content.Url)}]({content.Url.AbsoluteUri})",
|
||||
_ => content.Text
|
||||
};
|
||||
if (content.Quote is null) return body;
|
||||
var quote = string.Join(Environment.NewLine, content.Quote.Text.Split('\n').Select(line => $"> {line}"));
|
||||
return $"{quote}{Environment.NewLine}{Environment.NewLine}{body}";
|
||||
}
|
||||
|
||||
public static string MomentFingerprint(string author, string text, string? time)
|
||||
{
|
||||
var bytes = SHA256.HashData(Encoding.UTF8.GetBytes($"{author}\n{text}\n{time}"));
|
||||
return Convert.ToHexString(bytes).ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string LinkTitle(string text, Uri url)
|
||||
{
|
||||
var title = UrlRegex().Replace(text, string.Empty, 1).Trim();
|
||||
return string.IsNullOrWhiteSpace(title) ? url.Host : title;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user