using System; using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using BetterGenshinImpact.GameTask; using BetterGenshinImpact.GameTask.Common; using BetterGenshinImpact.GameTask.Model.Area; using BetterGenshinImpact.Service.Notification.Model; using BetterGenshinImpact.Service.Notification.Model.Enum; using BetterGenshinImpact.Service.Notifier; using BetterGenshinImpact.Service.Notifier.Exception; using BetterGenshinImpact.Service.Notifier.Interface; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace BetterGenshinImpact.Service.Notification; /// /// 通知服务类,管理和分发各种通知 /// public class NotificationService : IHostedService, IDisposable { private static readonly object InstanceLock = new(); private static NotificationService? _instance; private readonly NotifierManager _notifierManager; private readonly HttpClient _notifyHttpClient; private readonly CancellationTokenSource? _webSocketCts; // 缓存配置对象引用 private NotificationConfig? _notificationConfig; /// /// 构造函数 /// public NotificationService(NotifierManager notifierManager) { _notifierManager = notifierManager ?? throw new ArgumentNullException(nameof(notifierManager)); _notifyHttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; _webSocketCts = new CancellationTokenSource(); lock (InstanceLock) { _instance = this; } } /// /// 释放资源 /// public void Dispose() { // _webSocketCts?.Cancel(); _webSocketCts?.Dispose(); _notifyHttpClient?.Dispose(); GC.SuppressFinalize(this); } /// /// 服务启动时初始化所有通知器 /// public Task StartAsync(CancellationToken cancellationToken) { _notificationConfig = TaskContext.Instance().Config.NotificationConfig; InitializeNotifiers(); return Task.CompletedTask; } /// /// 服务停止时取消WebSocket连接 /// public Task StopAsync(CancellationToken cancellationToken) { _webSocketCts?.Cancel(); return Task.CompletedTask; } /// /// 获取NotificationService单例实例 /// public static NotificationService Instance() { if (_instance == null) throw new InvalidOperationException("NotificationService 未初始化"); return _instance; } /// /// 初始化所有通知器 /// 注意:添加新的通知渠道时,需在此处添加相应的初始化方法调用 /// private void InitializeNotifiers() { if (_notificationConfig == null) _notificationConfig = TaskContext.Instance().Config.NotificationConfig; // 初始化各类通知器 InitializeWebhookNotifier(); InitializeWindowsUwpNotifier(); InitializeFeishuNotifier(); InitializeOneBotNotifier(); InitializeWorkWeixinNotifier(); InitializeWebSocketNotifier(); InitializeBarkNotifier(); InitializeEmailNotifier(); InitializeDingDingNotifier(); InitializeTelegramNotifier(); InitializeXxtuiNotifier(); InitializeDiscordWebhookNotifier(); InitializeServerChanNotifier(); // 添加新通知渠道时,在此处添加对应的初始化方法调用 } // =================== 各类通知器初始化方法 =================== // 添加新的通知渠道时,参考以下模板创建新的初始化方法 /// /// 初始化Webhook通知器 /// private void InitializeWebhookNotifier() { if (_notificationConfig?.WebhookEnabled == true) _notifierManager.RegisterNotifier(new WebhookNotifier(_notifyHttpClient, _notificationConfig)); } /// /// 初始化Windows UWP通知器 /// private void InitializeWindowsUwpNotifier() { if (_notificationConfig?.WindowsUwpNotificationEnabled == true) { _notifierManager.RegisterNotifier(new WindowsUwpNotifier()); } } /// /// 初始化飞书通知器 /// private void InitializeFeishuNotifier() { if (_notificationConfig?.FeishuNotificationEnabled == true) _notifierManager.RegisterNotifier(new FeishuNotifier( _notifyHttpClient, _notificationConfig.FeishuWebhookUrl, _notificationConfig.FeishuAppId, _notificationConfig.FeishuAppSecret )); } /// /// 初始化OneBot通知器 /// private void InitializeOneBotNotifier() { if (_notificationConfig?.OneBotNotificationEnabled == true) _notifierManager.RegisterNotifier(new OneBotNotifier( _notifyHttpClient, _notificationConfig.OneBotEndpoint, _notificationConfig.OneBotUserId, _notificationConfig.OneBotGroupId, _notificationConfig.OneBotToken )); } /// /// 初始化企业微信通知器 /// private void InitializeWorkWeixinNotifier() { if (_notificationConfig?.WorkweixinNotificationEnabled == true) { _notifierManager.RegisterNotifier(new WorkWeixinNotifier( _notifyHttpClient, _notificationConfig.WorkweixinWebhookUrl )); } } /// /// 初始化WebSocket通知器 /// private void InitializeWebSocketNotifier() { if (_notificationConfig?.WebSocketNotificationEnabled == true) { var jsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; _notifierManager.RegisterNotifier(new WebSocketNotifier( _notificationConfig.WebSocketEndpoint, jsonSerializerOptions, _webSocketCts ?? new CancellationTokenSource() )); } } /// /// 初始化Bark通知器 /// private void InitializeBarkNotifier() { if (_notificationConfig?.BarkNotificationEnabled == true) { _notifierManager.RegisterNotifier(new BarkNotifier( _notificationConfig.BarkDeviceKeys, _notificationConfig.BarkApiEndpoint, _notificationConfig.BarkGroup, _notificationConfig.BarkSound, _notificationConfig.BarkIcon )); } } /// /// 初始化邮件通知器 /// private void InitializeEmailNotifier() { if (_notificationConfig?.EmailNotificationEnabled == true) { _notifierManager.RegisterNotifier(new EmailNotifier( _notificationConfig.SmtpServer, _notificationConfig.SmtpPort, _notificationConfig.SmtpUsername, _notificationConfig.SmtpPassword, _notificationConfig.FromEmail, _notificationConfig.FromName, _notificationConfig.ToEmail )); } } /// /// 初始化钉钉通知器 /// private void InitializeDingDingNotifier() { if (_notificationConfig?.DingDingwebhookNotificationEnabled == true && !string.IsNullOrEmpty(_notificationConfig.DingdingWebhookUrl) && !string.IsNullOrEmpty(_notificationConfig.DingDingSecret)) { _notifierManager.RegisterNotifier(new DingDingWebhook( _notifyHttpClient, _notificationConfig.DingdingWebhookUrl, _notificationConfig.DingDingSecret )); } } /// /// 初始化Telegram通知器 /// private void InitializeTelegramNotifier() { if (_notificationConfig?.TelegramNotificationEnabled == true) { _notifierManager.RegisterNotifier(new TelegramNotifier( null, _notificationConfig.TelegramBotToken, _notificationConfig.TelegramChatId, _notificationConfig.TelegramApiBaseUrl, _notificationConfig.TelegramProxyUrl, _notificationConfig.TelegramProxyEnabled )); } } /// /// 初始化信息推送通知器 /// private void InitializeXxtuiNotifier() { if (_notificationConfig?.XxtuiNotificationEnabled != true) return; var channels = ParseXxtuiChannels(_notificationConfig.XxtuiChannels); _notifierManager.RegisterNotifier(new XxtuiNotifier( _notificationConfig.XxtuiApiKey, _notificationConfig.XxtuiFrom, channels )); } /// /// 初始化 Discord 通知器 /// private void InitializeDiscordWebhookNotifier() { if (_notificationConfig?.DiscordWebhookNotificationEnabled != true) return; _notifierManager.RegisterNotifier(new DiscordWebhookNotifier( _notifyHttpClient, _notificationConfig.DiscordWebhookUrl, _notificationConfig.DiscordWebhookUsername, _notificationConfig.DiscordWebhookAvatarUrl, _notificationConfig.DiscordWebhookImageEncoder )); } /// /// 初始化 ServerChan 通知器 /// private void InitializeServerChanNotifier() { if (_notificationConfig?.ServerChanNotificationEnabled != true) return; _notifierManager.RegisterNotifier(new ServerChanNotifier( _notifyHttpClient, _notificationConfig.ServerChanSendKey )); } /// /// 解析信息推送通知渠道配置 /// private static XxtuiChannel[] ParseXxtuiChannels(string channelsStr) { if (string.IsNullOrEmpty(channelsStr)) return new[] { XxtuiChannel.WX_MP }; var channelStrings = channelsStr.Split(','); var validChannels = new XxtuiChannel[channelStrings.Length]; var validCount = 0; foreach (var channel in channelStrings) if (Enum.TryParse(channel.Trim(), out var parsedChannel)) validChannels[validCount++] = parsedChannel; if (validCount < channelStrings.Length) Array.Resize(ref validChannels, validCount); return validChannels.Length > 0 ? validChannels : new[] { XxtuiChannel.WX_MP }; } /// /// 刷新所有通知器 /// 配置更改后调用此方法应用新配置 /// public void RefreshNotifiers() { _notificationConfig = TaskContext.Instance().Config.NotificationConfig; _notifierManager.RemoveAllNotifiers(); InitializeNotifiers(); } /// /// 测试指定类型的通知器 /// public async Task TestNotifierAsync() where T : INotifier { try { var notifier = _notifierManager.GetNotifier(); if (notifier == null) { return NotificationTestResult.Error("通知类型未启用"); } var testData = CreateTestNotificationData(); await notifier.SendAsync(testData); return NotificationTestResult.Success(); } catch (NotifierException ex) { return NotificationTestResult.Error(ex.Message); } catch (Exception ex) { return NotificationTestResult.Error($"测试通知时发生未知错误: {ex.Message}"); } } /// /// 创建测试用的通知数据 /// private static BaseNotificationData CreateTestNotificationData() { var testData = new BaseNotificationData { Event = NotificationEvent.Test.Code, Result = NotificationEventResult.Success, Message = "这是一条测试通知信息" }; if (TaskContext.Instance().IsInitialized) { try { testData.Screenshot = TaskControl.CaptureToRectArea().CacheImage; } catch (Exception ex) { TaskControl.Logger.LogWarning(ex, "获取测试通知截图失败"); } } return testData; } /// /// 向所有通知器发送通知(异步方法) /// public async Task NotifyAllNotifiersAsync(BaseNotificationData notificationData) { if (notificationData == null) throw new ArgumentNullException(nameof(notificationData)); if (!ShouldSendNotification(notificationData.Event)) return; try { await AddScreenshotIfNeededAsync(notificationData); await _notifierManager.SendNotificationToAllAsync(notificationData); } catch (Exception ex) { TaskControl.Logger.LogError(ex, "发送通知时发生错误"); } } /// /// 判断是否应该发送此类型的通知 /// private bool ShouldSendNotification(string eventCode) { var subscribeEventStr = _notificationConfig?.NotificationEventSubscribe; if (string.IsNullOrEmpty(subscribeEventStr)) return true; return subscribeEventStr.Contains(eventCode); } /// /// 如果需要,为通知添加截图 /// private async Task AddScreenshotIfNeededAsync(BaseNotificationData notificationData) { if (_notificationConfig?.IncludeScreenShot != true) { return; } try { var mat = TaskControl.CaptureGameImageNoRetry(TaskTriggerDispatcher.GlobalGameCapture); if (mat != null) { var imageRegion = new ImageRegion(mat, 0, 0); notificationData.Screenshot = imageRegion.CacheImage; } } catch (Exception ex) { TaskControl.Logger.LogDebug(ex, "补充通知截图失败"); } await Task.CompletedTask; } /// /// 向所有通知器发送通知(非阻塞方法) /// public void NotifyAllNotifiers(BaseNotificationData notificationData) { if (notificationData == null) throw new ArgumentNullException(nameof(notificationData)); Task.Run(async () => { try { await NotifyAllNotifiersAsync(notificationData); } catch (Exception ex) { TaskControl.Logger.LogError(ex, "后台发送通知时发生错误"); } }); } }