mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-04-25 22:29:47 +08:00
telegram 通知现在可以支持图片啦 (#1816)
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user