XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using XFEEDToolBox.Core.Data;

namespace XFEEDToolBox.Core.Notifications;

public sealed record GameAnnouncement(string Id,string Title,string Message,DateTimeOffset At,DateTimeOffset? MaintenanceAt=null)
{
    public string Detail => MaintenanceAt is {} deadline ? $"预计维护时间 {deadline.ToLocalTime():HH:mm} · 以游戏后续公告为准。" : "";
    public string ToastXml()=>new XElement("toast",new XAttribute("launch","action=announcement&page=home"),new XAttribute("duration","long"),
        new XElement("visual",new XElement("binding",new XAttribute("template","ToastGeneric"),
            new XElement("text",Title),new XElement("text",Message),new XElement("text",Detail))),
        new XElement("audio",new XAttribute("src","ms-winsoundevent:Notification.Default"))).ToString(SaveOptions.DisableFormatting);
}

/// <summary>Only fresh server/system announcements, never ordinary commander or NPC chat.</summary>
public sealed partial class GameAnnouncementObserver
{
    private readonly HashSet<string> seen=new(StringComparer.Ordinal);
    [GeneratedRegex(@"(?<minutes>\d{1,3})\s*(?:minutes?\b|mins?\b|分钟|分鐘)",RegexOptions.IgnoreCase|RegexOptions.CultureInvariant)]
    private static partial Regex MinutesPattern();
    public GameAnnouncement? Observe(ObservedJournalEvent observed,DateTimeOffset now)
    {
        var data=observed.Data;if(data.Text("event")!="ReceiveText")return null;
        var sender=data.Text("From");var channel=data.Text("Channel");
        if(channel.Equals("npc",StringComparison.OrdinalIgnoreCase)
            ||!(channel.Equals("admin",StringComparison.OrdinalIgnoreCase)||channel.Equals("system",StringComparison.OrdinalIgnoreCase)
                ||sender.Equals("Server",StringComparison.OrdinalIgnoreCase)||sender.Equals("System",StringComparison.OrdinalIgnoreCase)))return null;
        var at=DateTimeOffset.TryParse(data.Text("timestamp"),CultureInfo.InvariantCulture,DateTimeStyles.AssumeUniversal,out var timestamp)?timestamp:observed.ObservedAt;
        if(now-at>TimeSpan.FromMinutes(2)||at>now.AddSeconds(1))return null;
        var message=data.Text("Message_Localised",data.Text("Message")).Trim();
        if(message.Length==0||message.Length>8000)return null;
        var id=Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes($"{at:O}\n{sender}\n{channel}\n{message}")));
        lock(seen)
        {
            if(!seen.Add(id))return null;
            if(seen.Count>4096){seen.Clear();seen.Add(id);}
        }
        var maintenance=message.Contains("maintenance",StringComparison.OrdinalIgnoreCase)||message.Contains("维护",StringComparison.Ordinal)||message.Contains("維護",StringComparison.Ordinal);
        DateTimeOffset? deadline=null;
        if(maintenance&&MinutesPattern().Match(message) is {Success:true} match&&int.TryParse(match.Groups["minutes"].Value,out var minutes)&&minutes is >0 and <=180)
            deadline=at.AddMinutes(minutes);
        return new(id,maintenance?"服务器维护公告":"游戏公告",message,at,deadline);
    }
}