telegram 通知现在可以支持图片啦 (#1816)

This commit is contained in:
Takaranoao
2025-07-01 22:40:59 -05:00
committed by GitHub
parent d2b389f0b1
commit 58cbc8d8ca

View File

@@ -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
};
/// <summary>
/// 创建一个新的Telegram通知器实例
/// </summary>
/// <param name="httpClient">可选的HttpClient如果不提供则创建新的</param>
/// <param name="telegramBotToken">Telegram机器人Token</param>
/// <param name="telegramChatId">Telegram聊天ID</param>
/// <param name="telegramApiBaseUrl">自定义Telegram API基础URL可以只填写域名如"xxx.xxx.xxx"为空则使用默认URL</param>
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;
/// <summary>
/// Telegram机器人Token
@@ -86,44 +39,48 @@ public class TelegramNotifier : INotifier, IDisposable
/// </summary>
public string TelegramApiBaseUrl { get; set; }
public void Dispose()
{
if (_createdHttpClient)
{
_httpClient?.Dispose();
}
}
/// <summary>
/// 通知器名称
/// </summary>
public string Name { get; set; } = "Telegram";
/// <summary>
/// 创建一个新的Telegram通知器实例
/// </summary>
/// <param name="httpClient">可选的HttpClient如果不提供则创建新的</param>
/// <param name="telegramBotToken">Telegram机器人Token</param>
/// <param name="telegramChatId">Telegram聊天ID</param>
/// <param name="telegramApiBaseUrl">自定义Telegram API基础URL可以只填写域名如"xxx.xxx.xxx"为空则使用默认URL</param>
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<Rgb24> 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)
{