diff --git a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs
index 8f42142f..145961e7 100644
--- a/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs
+++ b/BetterGenshinImpact/GameTask/AutoTrackPath/TpTask.cs
@@ -57,6 +57,8 @@ public class TpTask(CancellationToken ct)
public async Task TpToStatueOfTheSeven()
{
await CheckInBigMapUi();
+
+ // 提前调整至恰当的缩放以更快的传送
if (_tpConfig.MapZoomEnabled)
{
double currentZoomLevel = GetBigMapZoomLevel(CaptureToRectArea());
@@ -73,22 +75,23 @@ public class TpTask(CancellationToken ct)
string? area = _tpConfig.ReviveStatueOfTheSevenArea;
double x = _tpConfig.ReviveStatueOfTheSevenPointX;
double y = _tpConfig.ReviveStatueOfTheSevenPointY;
+ GiTpPosition revivePoint = _tpConfig.ReviveStatueOfTheSeven ?? GetNearestGoddess(x, y);
if (_tpConfig.IsReviveInNearestStatueOfTheSeven)
{
var center = GetBigMapCenterPoint();
var giTpPoint = GetNearestGoddess(center.X, center.Y);
- if (giTpPoint != null)
- {
- country = giTpPoint.Country;
- area = giTpPoint.Area;
- x = giTpPoint.X;
- y = giTpPoint.Y;
- }
+ country = giTpPoint.Country;
+ area = giTpPoint.Area;
+ x = giTpPoint.X;
+ y = giTpPoint.Y;
+ revivePoint = giTpPoint;
}
+
Logger.LogInformation("将传送至 {country} {area} 七天神像", country, area);
await Tp(x, y);
if (_tpConfig.ShouldMove || _tpConfig.IsReviveInNearestStatueOfTheSeven)
{
+ (x, y) = GetClosestPoint(revivePoint.TranX, revivePoint.TranY, x, y, 5);
var waypoint = new Waypoint
{
X = x,
@@ -98,18 +101,45 @@ public class TpTask(CancellationToken ct)
};
var waypointForTrack = new WaypointForTrack(waypoint);
await new PathExecutor(ct).MoveTo(waypointForTrack);
+ Simulation.SendInput.SimulateAction(GIActions.Drop);
}
await Delay((int)(_tpConfig.HpRestoreDuration * 1000), ct);
}
+ ///
+ ///
+ ///
+ /// 传送后实际到达的点X坐标
+ /// 传送后实际到达的点Y坐标
+ /// 传送点 X 坐标
+ /// 传送点 Y 坐标
+ /// 期望最终离传送点的距离
+ ///
+ private static (double X, double Y) GetClosestPoint(double tranX, double tranY, double x, double y, double d)
+ {
+ double dx = x - tranX;
+ double dy = y - tranY;
+ double distanceSquared = dx * dx + dy * dy;
+ double distance = Math.Sqrt(distanceSquared);
+ d = d > 0 ? d : 0;
+ if (distance < d)
+ {
+ return (tranX, tranY);
+ }
+ double ratio = d / distance;
+ double px = (x - dx * ratio);
+ double py = (y - dy * ratio);
+ return (px, py);
+ }
+
///
/// 获取离 x,y 最近的七天神像
///
///
///
///
- private GiTpPosition? GetNearestGoddess(double x, double y)
+ private GiTpPosition GetNearestGoddess(double x, double y)
{
GiTpPosition? nearestGiTpPosition = null;
double minDistance = double.MaxValue;
@@ -123,7 +153,7 @@ public class TpTask(CancellationToken ct)
}
}
// 获取最近的神像位置
- return nearestGiTpPosition;
+ return nearestGiTpPosition ?? throw new InvalidOperationException("没找到最近的七天神像");;
}
///
diff --git a/BetterGenshinImpact/GameTask/LogParse/LogParse.cs b/BetterGenshinImpact/GameTask/LogParse/LogParse.cs
index c381b369..bb9c2289 100644
--- a/BetterGenshinImpact/GameTask/LogParse/LogParse.cs
+++ b/BetterGenshinImpact/GameTask/LogParse/LogParse.cs
@@ -108,6 +108,7 @@ namespace LogParse
{
configTask.Fault.ReviveCount++;
}
+
//传送失败,重试 n 次
result = parseBgiLine($@"传送失败,重试 (\d+) 次", logstr);
if (result.Item1)
@@ -115,19 +116,31 @@ namespace LogParse
configTask.Fault.TeleportFailCount = int.Parse(result.Item2[1]);
}
+
//战斗超时结束
if (logstr == "战斗超时结束")
{
configTask.Fault.BattleTimeoutCount ++;
- }
+ }
//重试一次路线或放弃此路线!
if (logstr.EndsWith("重试一次路线或放弃此路线!"))
{
- configTask.Fault.RetryCount++;
+ configTask.Fault.RetryCount ++;
}
+ //疑似卡死,尝试脱离...
+ if (logstr == "疑似卡死,尝试脱离...")
+ {
+ configTask.Fault.StuckCount ++;
+ }
+ //One or more errors occurred
+ result = parseBgiLine(@"执行脚本时发生异常: ""(.+?)""", logstr);
+ if (result.Item1)
+ {
+ configTask.Fault.ErrCount ++;
+ }
if (logstr.StartsWith("→ 脚本执行结束: \"" + configTask.Name + "\""))
{
@@ -241,11 +254,14 @@ namespace LogParse
public int ReviveCount { get; set; } = 0;
//传送失败次数
public int TeleportFailCount { get; set; } = 0;
+ //疑似卡死次数
+ public int StuckCount { get; set; } = 0;
//重试次数
public int RetryCount { get; set; } = 0;
//战斗超时
public int BattleTimeoutCount { get; set; } = 0;
-
+ //异常发生次数
+ public int ErrCount { get; set; } = 0;
}
}
@@ -401,8 +417,10 @@ namespace LogParse
{
colConfigList.Add((name: "复活次数", value: task => FormatNumberWithStyle(task.Fault.ReviveCount)));
colConfigList.Add((name: "重试次数", value: task => FormatNumberWithStyle(task.Fault.RetryCount)));
+ colConfigList.Add((name: "疑似卡死次数", value: task => FormatNumberWithStyle(task.Fault.StuckCount)));
colConfigList.Add((name: "战斗超时次数", value: task => FormatNumberWithStyle(task.Fault.BattleTimeoutCount)));
colConfigList.Add((name: "传送失败次数", value: task => FormatNumberWithStyle(task.Fault.TeleportFailCount)));
+ colConfigList.Add((name: "异常发生次数", value: task => FormatNumberWithStyle(task.Fault.ErrCount)));
}
diff --git a/BetterGenshinImpact/Service/Notification/NotificationConfig.cs b/BetterGenshinImpact/Service/Notification/NotificationConfig.cs
index 4ea96fd2..e4b30dea 100644
--- a/BetterGenshinImpact/Service/Notification/NotificationConfig.cs
+++ b/BetterGenshinImpact/Service/Notification/NotificationConfig.cs
@@ -1,6 +1,5 @@
-using CommunityToolkit.Mvvm.ComponentModel;
using System;
-using System.DirectoryServices.ActiveDirectory;
+using CommunityToolkit.Mvvm.ComponentModel;
namespace BetterGenshinImpact.Service.Notification;
@@ -10,187 +9,174 @@ namespace BetterGenshinImpact.Service.Notification;
[Serializable]
public partial class NotificationConfig : ObservableObject
{
-
-
- [ObservableProperty]
- private string _notificationEventSubscribe = string.Empty;
-
- ///
- ///
- ///
- [ObservableProperty]
- private bool _webhookEnabled;
-
- ///
- ///
- ///
- [ObservableProperty]
- private string _webhookEndpoint = string.Empty;
-
-
-
- ///
- /// 是否包含截图
- ///
- [ObservableProperty]
- private bool _includeScreenShot = true;
-
- ///
- /// windows uwp 通知是否启用
- ///
- [ObservableProperty]
- private bool _windowsUwpNotificationEnabled = false;
-
-
- // 飞书通知
- ///
- /// 飞书通知是否启用
- ///
- [ObservableProperty]
- private bool _feishuNotificationEnabled = false;
-
-
- ///
- /// 飞书通知地址
- ///
- [ObservableProperty]
- private string _feishuWebhookUrl = string.Empty;
-
-
- // 企业微信通知
- ///
- /// 企业微信通知是否启用
- ///
- [ObservableProperty]
- private bool _workweixinNotificationEnabled = false;
-
-
- ///
- /// 企业微信通知通知地址
- ///
- [ObservableProperty]
- private string _workweixinWebhookUrl = string.Empty;
-
- [ObservableProperty]
- bool _webSocketNotificationEnabled = false;
-
- [ObservableProperty]
- private string _webSocketEndpoint = string.Empty;
-
- // Email 通知配置
- [ObservableProperty]
- private bool _emailNotificationEnabled = false;
-
- [ObservableProperty]
- private string _smtpServer = string.Empty;
-
- [ObservableProperty]
- private int _smtpPort;
-
- [ObservableProperty]
- private string _smtpUsername = string.Empty;
-
- [ObservableProperty]
- private string _smtpPassword = string.Empty;
-
- [ObservableProperty]
- private string _fromEmail = string.Empty;
-
- [ObservableProperty]
- private string _fromName = string.Empty;
-
- [ObservableProperty]
- private string _toEmail = string.Empty;
-
- ///
- /// Bark移动推送通知配置
- ///
- [ObservableProperty]
- private bool _barkNotificationEnabled = false;
-
- [ObservableProperty]
- private string _barkApiEndpoint = string.Empty;
-
- [ObservableProperty]
- private string _barkDeviceKeys = string.Empty;
-
- // Bark 通知附加参数配置
-
- ///
- /// 推送副标题
- ///
- [ObservableProperty]
- private string _barkSubtitle = string.Empty;
-
- ///
- /// 推送中断级别:critical(重要警告), active(默认值), timeSensitive(时效性通知), passive(仅添加到通知列表)
- ///
- [ObservableProperty]
- private string _barkLevel = "active";
-
- ///
- /// 通知声音
- ///
- [ObservableProperty]
- private string _barkSound = "minuet";
-
- ///
- /// 重要警告的通知音量,取值范围: 0-10
- ///
- [ObservableProperty]
- private int _barkVolume = 5;
-
- ///
- /// 推送角标,可以是任意数字
- ///
- [ObservableProperty]
- private int _barkBadge = 1;
-
- ///
- /// 通知铃声重复播放,1为开启
- ///
- [ObservableProperty]
- private string _barkCall = string.Empty;
-
- ///
- /// iOS14.5以下自动复制推送内容,1为开启
- ///
- [ObservableProperty]
- private string _barkAutoCopy = string.Empty;
-
- ///
- /// 复制推送时指定复制的内容
- ///
- [ObservableProperty]
- private string _barkCopy = string.Empty;
-
- ///
- /// 为推送设置自定义图标URL
- ///
- [ObservableProperty]
- private string _barkIcon = string.Empty;
-
- ///
- /// 对消息进行分组,推送将按group分组显示在通知中心中
- ///
- [ObservableProperty]
- private string _barkGroup = "default";
-
- ///
- /// 传1保存推送,传其他的不保存推送
- ///
- [ObservableProperty]
- private string _barkIsArchive = "1";
-
- ///
- /// 点击推送时跳转的URL
- ///
- [ObservableProperty]
- private string _barkUrl = string.Empty;
-
///
/// 传"none"时,点击推送不会弹窗
///
- [ObservableProperty]
- private string _barkAction = string.Empty;
+ [ObservableProperty] private string _barkAction = string.Empty;
+
+ [ObservableProperty] private string _barkApiEndpoint = string.Empty;
+
+ ///
+ /// iOS14.5以下自动复制推送内容,1为开启
+ ///
+ [ObservableProperty] private string _barkAutoCopy = string.Empty;
+
+ ///
+ /// 推送角标,可以是任意数字
+ ///
+ [ObservableProperty] private int _barkBadge = 1;
+
+ ///
+ /// 通知铃声重复播放,1为开启
+ ///
+ [ObservableProperty] private string _barkCall = string.Empty;
+
+ [ObservableProperty] private string _barkCiphertext = string.Empty;
+
+ ///
+ /// 复制推送时指定复制的内容
+ ///
+ [ObservableProperty] private string _barkCopy = string.Empty;
+
+ [ObservableProperty] private string _barkDeviceKeys = string.Empty;
+
+ ///
+ /// 对消息进行分组,推送将按group分组显示在通知中心中
+ ///
+ [ObservableProperty] private string _barkGroup = "default";
+
+ ///
+ /// 为推送设置自定义图标URL
+ ///
+ [ObservableProperty] private string _barkIcon = string.Empty;
+
+ ///
+ /// 传1保存推送,传其他的不保存推送
+ ///
+ [ObservableProperty] private string _barkIsArchive = "1";
+
+ ///
+ /// 推送中断级别:critical(重要警告), active(默认值), timeSensitive(时效性通知), passive(仅添加到通知列表)
+ ///
+ [ObservableProperty] private string _barkLevel = "active";
+
+ ///
+ /// Bark移动推送通知配置
+ ///
+ [ObservableProperty] private bool _barkNotificationEnabled;
+
+ ///
+ /// 通知声音
+ ///
+ [ObservableProperty] private string _barkSound = "minuet";
+
+ // Bark 通知附加参数配置
+
+ ///
+ /// 推送副标题
+ ///
+ [ObservableProperty] private string _barkSubtitle = string.Empty;
+
+ ///
+ /// 点击推送时跳转的URL
+ ///
+ [ObservableProperty] private string _barkUrl = string.Empty;
+
+ ///
+ /// 重要警告的通知音量,取值范围: 0-10
+ ///
+ [ObservableProperty] private int _barkVolume = 5;
+
+ // Email 通知配置
+ [ObservableProperty] private bool _emailNotificationEnabled;
+
+
+ // 飞书通知
+ ///
+ /// 飞书通知是否启用
+ ///
+ [ObservableProperty] private bool _feishuNotificationEnabled;
+
+
+ ///
+ /// 飞书通知地址
+ ///
+ [ObservableProperty] private string _feishuWebhookUrl = string.Empty;
+
+ [ObservableProperty] private string _fromEmail = string.Empty;
+
+ [ObservableProperty] private string _fromName = string.Empty;
+
+
+ ///
+ /// 是否包含截图
+ ///
+ [ObservableProperty] private bool _includeScreenShot = true;
+
+
+ [ObservableProperty] private string _notificationEventSubscribe = string.Empty;
+
+ [ObservableProperty] private string _smtpPassword = string.Empty;
+
+ [ObservableProperty] private int _smtpPort;
+
+ [ObservableProperty] private string _smtpServer = string.Empty;
+
+ [ObservableProperty] private string _smtpUsername = string.Empty;
+
+ ///
+ /// Telegram API基础URL(可选,留空使用官方API)
+ ///
+ [ObservableProperty] private string _telegramApiBaseUrl = string.Empty;
+
+ ///
+ /// Telegram机器人Token
+ ///
+ [ObservableProperty] private string _telegramBotToken = string.Empty;
+
+ ///
+ /// Telegram聊天ID
+ ///
+ [ObservableProperty] private string _telegramChatId = string.Empty;
+
+ // Telegram通知
+ ///
+ /// Telegram通知是否启用
+ ///
+ [ObservableProperty] private bool _telegramNotificationEnabled;
+
+ [ObservableProperty] private string _toEmail = string.Empty;
+
+ ///
+ ///
+ [ObservableProperty] private bool _webhookEnabled;
+
+ ///
+ ///
+ [ObservableProperty] private string _webhookEndpoint = string.Empty;
+
+
+ [ObservableProperty] private string _webhookSendTo = string.Empty; // 修改属性名
+
+ [ObservableProperty] private string _webSocketEndpoint = string.Empty;
+
+ [ObservableProperty] private bool _webSocketNotificationEnabled;
+
+ ///
+ /// windows uwp 通知是否启用
+ ///
+ [ObservableProperty] private bool _windowsUwpNotificationEnabled;
+
+
+ // 企业微信通知
+ ///
+ /// 企业微信通知是否启用
+ ///
+ [ObservableProperty] private bool _workweixinNotificationEnabled;
+
+
+ ///
+ /// 企业微信通知通知地址
+ ///
+ [ObservableProperty] private string _workweixinWebhookUrl = string.Empty;
}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Service/Notification/NotificationService.cs b/BetterGenshinImpact/Service/Notification/NotificationService.cs
index 22c2025e..d5329966 100644
--- a/BetterGenshinImpact/Service/Notification/NotificationService.cs
+++ b/BetterGenshinImpact/Service/Notification/NotificationService.cs
@@ -6,11 +6,17 @@ using Microsoft.Extensions.Hosting;
using System;
using System.Drawing;
using System.Net.Http;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.GameTask.Common;
+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;
using System.Text.Json;
using OpenCvSharp.Extensions;
@@ -31,16 +37,6 @@ public class NotificationService : IHostedService
InitializeNotifiers();
}
- public static NotificationService Instance()
- {
- if (_instance == null)
- {
- throw new Exception("Not instantiated");
- }
-
- return _instance;
- }
-
public Task StartAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
@@ -51,12 +47,20 @@ public class NotificationService : IHostedService
return Task.CompletedTask;
}
+ public static NotificationService Instance()
+ {
+ if (_instance == null) throw new Exception("Not instantiated");
+
+ return _instance;
+ }
+
private void InitializeNotifiers()
{
if (TaskContext.Instance().Config.NotificationConfig.WebhookEnabled)
{
- _notifierManager.RegisterNotifier(new WebhookNotifier(NotifyHttpClient, TaskContext.Instance().Config.NotificationConfig.WebhookEndpoint));
+ _notifierManager.RegisterNotifier(new WebhookNotifier(NotifyHttpClient,
+ TaskContext.Instance().Config.NotificationConfig));
}
if (TaskContext.Instance().Config.NotificationConfig.WindowsUwpNotificationEnabled)
@@ -66,37 +70,37 @@ public class NotificationService : IHostedService
if (TaskContext.Instance().Config.NotificationConfig.FeishuNotificationEnabled)
{
- _notifierManager.RegisterNotifier(new FeishuNotifier(NotifyHttpClient, TaskContext.Instance().Config.NotificationConfig.FeishuWebhookUrl));
+ _notifierManager.RegisterNotifier(new FeishuNotifier(NotifyHttpClient,
+ TaskContext.Instance().Config.NotificationConfig.FeishuWebhookUrl));
}
if (TaskContext.Instance().Config.NotificationConfig.WorkweixinNotificationEnabled)
{
- _notifierManager.RegisterNotifier(new WorkWeixinNotifier(NotifyHttpClient, TaskContext.Instance().Config.NotificationConfig.WorkweixinWebhookUrl));
+ _notifierManager.RegisterNotifier(new WorkWeixinNotifier(NotifyHttpClient,
+ TaskContext.Instance().Config.NotificationConfig.WorkweixinWebhookUrl));
}
- // WebSocket通知初始化
- if (TaskContext.Instance().Config.NotificationConfig.WebSocketNotificationEnabled)
+ // WebSocket通知初始化
+ if (TaskContext.Instance().Config.NotificationConfig.WebSocketNotificationEnabled)
+ {
+ var jsonSerializerOptions = new JsonSerializerOptions
{
- var jsonSerializerOptions = new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
- };
- var cts = new CancellationTokenSource();
- _notifierManager.RegisterNotifier(new WebSocketNotifier(
- TaskContext.Instance().Config.NotificationConfig.WebSocketEndpoint,
- jsonSerializerOptions,
- cts
- ));
- }
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
+ };
+ var cts = new CancellationTokenSource();
+ _notifierManager.RegisterNotifier(new WebSocketNotifier(
+ TaskContext.Instance().Config.NotificationConfig.WebSocketEndpoint,
+ jsonSerializerOptions,
+ cts
+ ));
+ }
- // Bark通知初始化
- if (TaskContext.Instance().Config.NotificationConfig.BarkNotificationEnabled)
- {
- _notifierManager.RegisterNotifier(new BarkNotifier(
- TaskContext.Instance().Config.NotificationConfig.BarkDeviceKeys,
- TaskContext.Instance().Config.NotificationConfig.BarkApiEndpoint
- ));
- }
+ // Bark通知初始化
+ if (TaskContext.Instance().Config.NotificationConfig.BarkNotificationEnabled)
+ _notifierManager.RegisterNotifier(new BarkNotifier(
+ TaskContext.Instance().Config.NotificationConfig.BarkDeviceKeys,
+ TaskContext.Instance().Config.NotificationConfig.BarkApiEndpoint
+ ));
// 邮件通知初始化
if (TaskContext.Instance().Config.NotificationConfig.EmailNotificationEnabled)
@@ -111,6 +115,15 @@ public class NotificationService : IHostedService
TaskContext.Instance().Config.NotificationConfig.ToEmail
));
}
+
+ // Telegram通知初始化
+ if (TaskContext.Instance().Config.NotificationConfig.TelegramNotificationEnabled)
+ _notifierManager.RegisterNotifier(new TelegramNotifier(
+ NotifyHttpClient, // 使用共享的HttpClient
+ TaskContext.Instance().Config.NotificationConfig.TelegramBotToken,
+ TaskContext.Instance().Config.NotificationConfig.TelegramChatId,
+ TaskContext.Instance().Config.NotificationConfig.TelegramApiBaseUrl
+ ));
}
public void RefreshNotifiers()
@@ -191,4 +204,4 @@ public class NotificationService : IHostedService
{
NotifyHttpClient.Dispose();
}
-}
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Service/Notifier/EmailNotifier.cs b/BetterGenshinImpact/Service/Notifier/EmailNotifier.cs
index b9b694df..e43d7493 100644
--- a/BetterGenshinImpact/Service/Notifier/EmailNotifier.cs
+++ b/BetterGenshinImpact/Service/Notifier/EmailNotifier.cs
@@ -1,3 +1,6 @@
+using System.Drawing.Imaging;
+using System.IO;
+using System.Net;
using System.Net.Mail;
using System.Text;
using System.Threading.Tasks;
@@ -9,23 +12,15 @@ namespace BetterGenshinImpact.Service.Notifier
{
public class EmailNotifier : INotifier
{
- public string Name { get; set; } = "Email";
-
- // SMTP服务器配置
- private readonly string _smtpServer;
- private readonly int _smtpPort;
- private readonly string _smtpUsername;
- private readonly string _smtpPassword;
-
// 发件人配置
private readonly string _fromEmail;
private readonly string _fromName;
-
- // 收件人邮箱
- public string ToEmail { get; set; }
+ private readonly string _smtpPassword;
+ private readonly int _smtpPort;
- // 提升 SmtpClient 为类的成员变量
- private readonly SmtpClient _smtpClient;
+ // SMTP服务器配置
+ private readonly string _smtpServer;
+ private readonly string _smtpUsername;
public EmailNotifier(
string smtpServer,
@@ -44,14 +39,15 @@ namespace BetterGenshinImpact.Service.Notifier
_fromName = fromName;
ToEmail = toEmail;
- // 在构造函数中初始化 SmtpClient
- _smtpClient = new SmtpClient(_smtpServer, _smtpPort)
- {
- Credentials = new System.Net.NetworkCredential(_smtpUsername, _smtpPassword),
- EnableSsl = true
- };
+ // 忽略SSL证书验证错误
+ ServicePointManager.ServerCertificateValidationCallback =
+ delegate { return true; };
}
+ // 收件人邮箱
+ public string ToEmail { get; set; }
+ public string Name { get; set; } = "Email";
+
public async Task SendAsync(BaseNotificationData content)
{
if (string.IsNullOrEmpty(ToEmail))
@@ -59,24 +55,78 @@ namespace BetterGenshinImpact.Service.Notifier
throw new NotifierException("收件人邮箱地址为空");
}
- try
+ // 创建一个新的SmtpClient实例(不复用)
+ using (var smtpClient = new SmtpClient())
{
- using var mailMessage = new MailMessage
+ try
{
- From = new MailAddress(_fromEmail, _fromName),
- Subject = FormatEmailSubject(content),
- Body = FormatEmailBody(content),
- IsBodyHtml = true
- };
-
- mailMessage.To.Add(ToEmail);
+ // 配置SMTP客户端
+ smtpClient.Host = _smtpServer;
+ smtpClient.Port = _smtpPort;
+ smtpClient.EnableSsl = true;
+ smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
+ smtpClient.UseDefaultCredentials = false;
+ smtpClient.Credentials = new NetworkCredential(_smtpUsername, _smtpPassword);
+ smtpClient.Timeout = 30000; // 30秒超时
- // 使用成员变量 _smtpClient 发送邮件
- await _smtpClient.SendMailAsync(mailMessage);
- }
- catch (System.Exception ex)
- {
- throw new NotifierException($"发送邮件失败: {ex.Message}");
+ // 创建邮件
+ using (var mailMessage = new MailMessage())
+ {
+ mailMessage.From = new MailAddress(_fromEmail, _fromName);
+ mailMessage.To.Add(ToEmail);
+ mailMessage.Subject = FormatEmailSubject(content);
+ mailMessage.Body = FormatEmailBody(content);
+ mailMessage.IsBodyHtml = true;
+ mailMessage.BodyEncoding = Encoding.UTF8;
+ mailMessage.SubjectEncoding = Encoding.UTF8;
+
+ // 添加图片附件(如果存在)
+ if (content.Screenshot != null)
+ {
+ var tempPath = Path.GetTempFileName() + ".jpg";
+ try
+ {
+ // 保存图片到临时文件
+ content.Screenshot.Save(tempPath, ImageFormat.Jpeg);
+
+ // 从文件添加附件
+ var attachment = new Attachment(tempPath);
+ mailMessage.Attachments.Add(attachment);
+
+ // 发送邮件
+ await smtpClient.SendMailAsync(mailMessage);
+
+ // 清理附件和临时文件
+ attachment.Dispose();
+ if (File.Exists(tempPath)) File.Delete(tempPath);
+ }
+ catch (System.Exception ex)
+ {
+ // 尝试清理临时文件
+ try
+ {
+ if (File.Exists(tempPath)) File.Delete(tempPath);
+ }
+ catch
+ {
+ /* 忽略清理错误 */
+ }
+
+ throw new NotifierException($"发送邮件失败: {ex.Message}");
+ }
+ }
+ else
+ {
+ // 没有图片时直接发送
+ await smtpClient.SendMailAsync(mailMessage);
+ }
+ }
+ }
+ catch (System.Exception ex)
+ {
+ var errorMessage = $"发送邮件失败: {ex.Message}";
+ throw new NotifierException(errorMessage);
+ }
}
}
@@ -89,23 +139,29 @@ namespace BetterGenshinImpact.Service.Notifier
private string FormatEmailBody(BaseNotificationData content)
{
var builder = new StringBuilder();
- builder.AppendLine("");
-
+ builder.AppendLine("");
+
// 添加通知标题
- builder.AppendLine("通知详情
");
-
+ builder.AppendLine("通知详情
");
+
// 添加通知内容
foreach (var prop in content.GetType().GetProperties())
{
+ // 跳过Screenshot属性,它会单独处理
+ if (prop.Name == "Screenshot")
+ continue;
+
var value = prop.GetValue(content);
if (value != null)
{
- builder.AppendLine($"{prop.Name}: {value}
");
+ builder.AppendFormat("{0}: {1}
", prop.Name, value);
}
}
-
+
+ // 添加提示信息
+ builder.AppendLine("如有截图,请查看附件。
");
builder.AppendLine("");
return builder.ToString();
}
}
-}
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Service/Notifier/TelegramNotifier.cs b/BetterGenshinImpact/Service/Notifier/TelegramNotifier.cs
new file mode 100644
index 00000000..21bc68cb
--- /dev/null
+++ b/BetterGenshinImpact/Service/Notifier/TelegramNotifier.cs
@@ -0,0 +1,226 @@
+using System;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using BetterGenshinImpact.Service.Notification.Model;
+using BetterGenshinImpact.Service.Notifier.Exception;
+using BetterGenshinImpact.Service.Notifier.Interface;
+
+namespace BetterGenshinImpact.Service.Notifier;
+
+public class TelegramNotifier : INotifier, IDisposable
+{
+ // 永远不更改此URL常量,这是Telegram API的标准URL前缀
+ private const string TELEGRAM_API_URL = "https://api.telegram.org/bot";
+ private readonly bool _createdHttpClient;
+ private readonly HttpClient _httpClient;
+
+ private readonly JsonSerializerOptions _jsonSerializerOptions = new()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
+ };
+
+ ///
+ /// 创建一个新的Telegram通知器实例
+ ///
+ /// 可选的HttpClient,如果不提供则创建新的
+ /// Telegram机器人Token
+ /// Telegram聊天ID
+ /// 不再使用,保留参数仅为兼容性
+ public TelegramNotifier(HttpClient httpClient = null, string telegramBotToken = "", string telegramChatId = "",
+ string telegramApiBaseUrl = "")
+ {
+ TelegramBotToken = telegramBotToken;
+ TelegramChatId = telegramChatId;
+
+ if (httpClient != null)
+ {
+ _httpClient = httpClient;
+ _createdHttpClient = false;
+ }
+ else
+ {
+ _httpClient = new HttpClient();
+ _createdHttpClient = true;
+ _httpClient.Timeout = TimeSpan.FromSeconds(30);
+ }
+
+ // 忽略自定义API URL,始终使用标准Telegram API URL
+ TelegramApiBaseUrl = TELEGRAM_API_URL;
+ }
+
+ ///
+ /// Telegram机器人Token
+ ///
+ public string TelegramBotToken { get; set; }
+
+ ///
+ /// Telegram聊天ID
+ ///
+ public string TelegramChatId { get; set; }
+
+ ///
+ /// Telegram API基础URL - 内部使用
+ ///
+ private string TelegramApiBaseUrl { get; set; }
+
+ public void Dispose()
+ {
+ if (_createdHttpClient)
+ {
+ _httpClient?.Dispose();
+ }
+ }
+
+ ///
+ /// 通知器名称
+ ///
+ public string Name { get; set; } = "Telegram";
+
+ public async Task SendAsync(BaseNotificationData content)
+ {
+ if (string.IsNullOrEmpty(TelegramBotToken))
+ {
+ throw new NotifierException("Telegram bot token is not set");
+ }
+
+ if (string.IsNullOrEmpty(TelegramChatId))
+ {
+ throw new NotifierException("Telegram chat ID is not set");
+ }
+
+ try
+ {
+ var message = content.Message;
+ var fullMessage = !string.IsNullOrEmpty(message) ? message : "";
+
+ if (!string.IsNullOrEmpty(fullMessage))
+ {
+ await SendTextMessageAsync(fullMessage);
+ }
+ else
+ {
+ throw new NotifierException("No message content to send");
+ }
+ }
+ catch (HttpRequestException ex)
+ {
+ throw new NotifierException("Network error sending Telegram notification: " + ex.Message);
+ }
+ catch (TaskCanceledException)
+ {
+ throw new NotifierException("Telegram API request timed out. Check your internet connection.");
+ }
+ catch (NotifierException)
+ {
+ throw;
+ }
+ catch (System.Exception ex)
+ {
+ throw new NotifierException("Error sending Telegram notification: " + ex.Message);
+ }
+ }
+
+ private async Task SendTextMessageAsync(string message)
+ {
+ // 构建Telegram API URL - 固定格式:https://api.telegram.org/bot{token}/sendMessage
+ var endpoint = $"{TELEGRAM_API_URL}{TelegramBotToken}/sendMessage";
+
+ try
+ {
+ var jsonContent = new
+ {
+ chat_id = TelegramChatId,
+ text = message,
+ disable_web_page_preview = true
+ };
+
+ var json = JsonSerializer.Serialize(jsonContent, _jsonSerializerOptions);
+ var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
+ {
+ Content = content
+ };
+
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ request.Headers.UserAgent.Add(new ProductInfoHeaderValue("BetterGenshinImpact", "1.0"));
+
+ var response = await _httpClient.SendAsync(request);
+ var responseContent = await response.Content.ReadAsStringAsync();
+
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new NotifierException(
+ $"Telegram message failed with code: {response.StatusCode}, Error: {responseContent}");
+ }
+
+ // Check for API errors in the response
+ var (isSuccess, errorCode, errorDescription) = ValidateApiResponse(responseContent);
+
+ if (!isSuccess)
+ {
+ if (errorCode == 400)
+ {
+ throw new NotifierException(
+ "Please send a message to the bot first and check that the chat ID is correct.");
+ }
+
+ if (errorCode == 401)
+ {
+ throw new NotifierException("Telegram bot token is incorrect.");
+ }
+
+ if (errorCode == 404)
+ throw new NotifierException(
+ $"Telegram API not found (404). Please verify your bot token is correct. URL: {endpoint}");
+
+ throw new NotifierException($"Telegram API error: {errorDescription} (Code: {errorCode})");
+ }
+ }
+ catch (System.Exception ex) when (!(ex is NotifierException))
+ {
+ throw new NotifierException("Error sending Telegram notification: " + ex.Message);
+ }
+ }
+
+ private (bool isSuccess, int errorCode, string errorDescription) ValidateApiResponse(string responseJson)
+ {
+ try
+ {
+ using (var doc = JsonDocument.Parse(responseJson))
+ {
+ var root = doc.RootElement;
+
+ // Telegram API returns "ok": true for success
+ if (root.TryGetProperty("ok", out var okElement))
+ {
+ var isOk = okElement.GetBoolean();
+
+ if (!isOk)
+ {
+ var errorDescription = "Unknown Telegram API error";
+ if (root.TryGetProperty("description", out var descriptionElement))
+ errorDescription = descriptionElement.GetString();
+
+ var errorCode = 0;
+ if (root.TryGetProperty("error_code", out var errorCodeElement))
+ errorCode = errorCodeElement.GetInt32();
+
+ return (false, errorCode, errorDescription);
+ }
+
+ return (true, 0, string.Empty);
+ }
+
+ return (false, 0, "Invalid API response: 'ok' field missing");
+ }
+ }
+ catch (JsonException ex)
+ {
+ return (false, 0, "Failed to parse API response: " + ex.Message);
+ }
+ }
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Service/Notifier/WebSocketNotifier.cs b/BetterGenshinImpact/Service/Notifier/WebSocketNotifier.cs
index 55a2d512..347e5f5a 100644
--- a/BetterGenshinImpact/Service/Notifier/WebSocketNotifier.cs
+++ b/BetterGenshinImpact/Service/Notifier/WebSocketNotifier.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Net.WebSockets;
using System.Text.Json;
using System.Threading;
@@ -53,6 +53,7 @@ namespace BetterGenshinImpact.Service.Notifier
var json = JsonSerializer.Serialize(notificationData, _jsonSerializerOptions);
var buffer = System.Text.Encoding.UTF8.GetBytes(json);
await _webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, _cts.Token);
+ await CloseAsync(); // 添加关闭连接的代码
}
catch (WebSocketException ex)
{
@@ -82,4 +83,4 @@ namespace BetterGenshinImpact.Service.Notifier
await SendAsync(notificationData);
}
}
-}
+}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Service/Notifier/WebhookNotifier.cs b/BetterGenshinImpact/Service/Notifier/WebhookNotifier.cs
index e6b6483f..d8e702ac 100644
--- a/BetterGenshinImpact/Service/Notifier/WebhookNotifier.cs
+++ b/BetterGenshinImpact/Service/Notifier/WebhookNotifier.cs
@@ -1,10 +1,13 @@
-using BetterGenshinImpact.Service.Notifier.Exception;
+using BetterGenshinImpact.Service.Notifier.Exception;
using BetterGenshinImpact.Service.Notifier.Interface;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using BetterGenshinImpact.Service.Notification.Model;
+using System.Collections.Generic;
+using BetterGenshinImpact.Service.Notification; // 添加对 System.Collections.Generic 命名空间的引用
+using BetterGenshinImpact.Service.Notification; // 添加对 NotificationConfig 类型的引用
namespace BetterGenshinImpact.Service.Notifier;
@@ -14,6 +17,9 @@ public class WebhookNotifier : INotifier
public string Endpoint { get; set; }
+ // 添加 send_to 属性
+ private string SendTo { get; set; }
+
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonSerializerOptions = new()
@@ -21,10 +27,12 @@ public class WebhookNotifier : INotifier
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
};
- public WebhookNotifier(HttpClient httpClient, string endpoint = "")
+ public WebhookNotifier(HttpClient httpClient, NotificationConfig config)
{
+
_httpClient = httpClient;
- Endpoint = endpoint;
+ Endpoint = config.WebhookEndpoint;
+ SendTo = config.WebhookSendTo; // 初始化 send_to 属性
}
public async Task SendAsync(BaseNotificationData content)
@@ -53,11 +61,21 @@ public class WebhookNotifier : INotifier
}
}
-
private StringContent TransformData(BaseNotificationData notificationData)
{
- // using object type here so it serializes the interface correctly
- var serializedData = JsonSerializer.Serialize