From 58cbc8d8ca1383053bd08ef622d0fed5aa1ed35c Mon Sep 17 00:00:00 2001 From: Takaranoao Date: Tue, 1 Jul 2025 22:40:59 -0500 Subject: [PATCH] =?UTF-8?q?telegram=20=E9=80=9A=E7=9F=A5=E7=8E=B0=E5=9C=A8?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E6=94=AF=E6=8C=81=E5=9B=BE=E7=89=87=E5=95=A6?= =?UTF-8?q?=20(#1816)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Notifier/TelegramNotifier.cs | 270 +++++++----------- 1 file changed, 105 insertions(+), 165 deletions(-) diff --git a/BetterGenshinImpact/Service/Notifier/TelegramNotifier.cs b/BetterGenshinImpact/Service/Notifier/TelegramNotifier.cs index a014e420..b9d7b370 100644 --- a/BetterGenshinImpact/Service/Notifier/TelegramNotifier.cs +++ b/BetterGenshinImpact/Service/Notifier/TelegramNotifier.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Net.Http; using System.Net.Http.Headers; using System.Text; @@ -7,69 +8,21 @@ using System.Threading.Tasks; using BetterGenshinImpact.Service.Notification.Model; using BetterGenshinImpact.Service.Notifier.Exception; using BetterGenshinImpact.Service.Notifier.Interface; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; namespace BetterGenshinImpact.Service.Notifier; public class TelegramNotifier : INotifier, IDisposable { // 默认Telegram API的标准URL前缀 - private const string DEFAULT_TELEGRAM_API_URL = "https://api.telegram.org/bot"; - private readonly bool _createdHttpClient; + private const string DefaultApiUrl = "https://api.telegram.org/bot"; + + private static readonly JsonSerializerOptions JsonOptions = new() + { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }; + private readonly HttpClient _httpClient; - - private readonly JsonSerializerOptions _jsonSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower - }; - - /// - /// 创建一个新的Telegram通知器实例 - /// - /// 可选的HttpClient,如果不提供则创建新的 - /// Telegram机器人Token - /// Telegram聊天ID - /// 自定义Telegram API基础URL(可以只填写域名,如"xxx.xxx.xxx"),为空则使用默认URL - 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 - if (string.IsNullOrEmpty(telegramApiBaseUrl)) - { - TelegramApiBaseUrl = DEFAULT_TELEGRAM_API_URL; - } - else - { - // 格式化用户提供的API URL - var formattedUrl = telegramApiBaseUrl.Trim(); - - // 添加协议前缀(如果没有) - if (!formattedUrl.StartsWith("http://") && !formattedUrl.StartsWith("https://")) - formattedUrl = "https://" + formattedUrl; - - // 确保URL以斜杠结尾 - if (!formattedUrl.EndsWith("/")) formattedUrl += "/"; - - // 添加bot路径(如果需要) - if (!formattedUrl.EndsWith("/bot")) formattedUrl += "bot"; - - TelegramApiBaseUrl = formattedUrl; - } - } + private readonly bool _ownsHttpClient; /// /// Telegram机器人Token @@ -86,44 +39,48 @@ public class TelegramNotifier : INotifier, IDisposable /// public string TelegramApiBaseUrl { get; set; } - public void Dispose() - { - if (_createdHttpClient) - { - _httpClient?.Dispose(); - } - } - /// /// 通知器名称 /// public string Name { get; set; } = "Telegram"; + /// + /// 创建一个新的Telegram通知器实例 + /// + /// 可选的HttpClient,如果不提供则创建新的 + /// Telegram机器人Token + /// Telegram聊天ID + /// 自定义Telegram API基础URL(可以只填写域名,如"xxx.xxx.xxx"),为空则使用默认URL + public TelegramNotifier(HttpClient? httpClient = null, string telegramBotToken = "", string telegramChatId = "", + string telegramApiBaseUrl = "") + { + TelegramBotToken = telegramBotToken; + TelegramChatId = telegramChatId; + _httpClient = httpClient ?? new HttpClient { Timeout = TimeSpan.FromSeconds(30) }; + _ownsHttpClient = httpClient == null; + TelegramApiBaseUrl = FormatApiBaseUrl(telegramApiBaseUrl); + } + + public void Dispose() + { + if (_ownsHttpClient) _httpClient.Dispose(); + } + 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"); - } + 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"); + if (string.IsNullOrEmpty(content.Message)) throw new NotifierException("No message content to send"); try { - var message = content.Message; - var fullMessage = !string.IsNullOrEmpty(message) ? message : ""; + if (content.Screenshot != null) + { + await SendImageMessageAsync(content.Screenshot, content.Message.Length < 1024 ? content.Message : null); + if (content.Message.Length < 1024) return; + } - if (!string.IsNullOrEmpty(fullMessage)) - { - await SendTextMessageAsync(fullMessage); - } - else - { - throw new NotifierException("No message content to send"); - } + await SendTextMessageAsync(content.Message); } catch (HttpRequestException ex) { @@ -133,110 +90,93 @@ public class TelegramNotifier : INotifier, IDisposable { throw new NotifierException("Telegram API request timed out. Check your internet connection."); } - catch (NotifierException) - { - throw; - } - catch (System.Exception ex) + catch (System.Exception ex) when (ex is not NotifierException) { throw new NotifierException("Error sending Telegram notification: " + ex.Message); } } + private async Task SendImageMessageAsync(Image image, string? caption) + { + var endpoint = $"{TelegramApiBaseUrl}{TelegramBotToken}/sendPhoto"; + using var memoryStream = new MemoryStream(); + await image.SaveAsPngAsync(memoryStream); + memoryStream.Position = 0; + + var content = new MultipartFormDataContent + { + { new StreamContent(memoryStream), "photo", "image.png" }, + { new StringContent(TelegramChatId), "chat_id" } + }; + if (!string.IsNullOrEmpty(caption)) content.Add(new StringContent(caption), "caption"); + + await SendRequestAsync(endpoint, content, "image"); + } + private async Task SendTextMessageAsync(string message) { - // 构建Telegram API URL - 使用自定义或默认API基础URL var endpoint = $"{TelegramApiBaseUrl}{TelegramBotToken}/sendMessage"; - - try + var json = JsonSerializer.Serialize(new { - var jsonContent = new - { - chat_id = TelegramChatId, - text = message, - disable_web_page_preview = true - }; + chat_id = TelegramChatId, + text = message, + disable_web_page_preview = true + }, JsonOptions); - var json = JsonSerializer.Serialize(jsonContent, _jsonSerializerOptions); - var content = new StringContent(json, Encoding.UTF8, "application/json"); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + await SendRequestAsync(endpoint, content, "text"); + } - var request = new HttpRequestMessage(HttpMethod.Post, endpoint) - { - Content = content - }; + private async Task SendRequestAsync(string endpoint, HttpContent content, string type) + { + 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")); - 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(); - var response = await _httpClient.SendAsync(request); - var responseContent = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + throw new NotifierException($"Telegram {type} message failed: {response.StatusCode}, {responseContent}"); - 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)) + var (isSuccess, errorCode, errorDescription) = ValidateApiResponse(responseContent); + if (!isSuccess) { - throw new NotifierException("Error sending Telegram notification: " + ex.Message); + var msg = errorCode switch + { + 400 => "Please send a message to the bot first and check that the chat ID is correct.", + 401 => "Telegram bot token is incorrect.", + 404 => $"Telegram API not found (404). Please verify your bot token is correct. URL: {endpoint}", + _ => $"Telegram API error: {errorDescription} (Code: {errorCode})" + }; + throw new NotifierException(msg); } } - private (bool isSuccess, int errorCode, string errorDescription) ValidateApiResponse(string responseJson) + private static string FormatApiBaseUrl(string apiBaseUrl) + { + if (string.IsNullOrEmpty(apiBaseUrl)) return DefaultApiUrl; + var url = apiBaseUrl.Trim(); + if (!url.StartsWith("http://") && !url.StartsWith("https://")) url = "https://" + url; + if (!url.EndsWith("/")) url += "/"; + if (!url.EndsWith("/bot")) url += "bot"; + return url; + } + + private static (bool isSuccess, int errorCode, string errorDescription) ValidateApiResponse(string responseJson) { try { - using (var doc = JsonDocument.Parse(responseJson)) - { - var root = doc.RootElement; + using var doc = JsonDocument.Parse(responseJson); + var root = doc.RootElement; + if (root.TryGetProperty("ok", out var okElement) && okElement.GetBoolean()) + return (true, 0, string.Empty); - // 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"); - } + var errorDescription = root.TryGetProperty("description", out var desc) + ? desc.GetString() + : "Unknown Telegram API error"; + var errorCode = root.TryGetProperty("error_code", out var code) ? code.GetInt32() : 0; + return (false, errorCode, errorDescription ?? ""); } catch (JsonException ex) {