| | | 1 | | using Microsoft.Extensions.Logging; |
| | | 2 | | using Services.ApiServices.TelegramBotApi.Models; |
| | | 3 | | |
| | | 4 | | namespace Services.ApiServices.TelegramBotApi; |
| | | 5 | | |
| | | 6 | | public sealed class TelegramNotifier : ITelegramNotifier |
| | | 7 | | { |
| | | 8 | | private readonly TelegramBotApiClient _client; |
| | | 9 | | private readonly TelegramBotConfig _config; |
| | | 10 | | private readonly ILogger<TelegramNotifier>? _logger; |
| | | 11 | | |
| | 13 | 12 | | public TelegramNotifier(TelegramBotApiClient client, TelegramBotConfig config, ILogger<TelegramNotifier>? logger = n |
| | | 13 | | { |
| | 13 | 14 | | _client = client; |
| | 13 | 15 | | _config = config; |
| | 13 | 16 | | _logger = logger; |
| | 13 | 17 | | } |
| | | 18 | | |
| | | 19 | | public IReadOnlyList<TelegramRoute> RoutesFor(TelegramChannel channel, bool critical = false) => |
| | 0 | 20 | | _config.RoutesFor(channel, critical); |
| | | 21 | | |
| | 2 | 22 | | public bool IsConfigured => _config.IsConfigured; |
| | | 23 | | |
| | | 24 | | public async Task<bool> SendAsync(TelegramMessage message, CancellationToken ct = default) |
| | | 25 | | { |
| | 13 | 26 | | if (!_config.IsConfigured) return false; |
| | | 27 | | |
| | | 28 | | // Явный чат — только ему; иначе маршруты категории (+ критичные). Недоступный получатель не мешает остальным. |
| | 11 | 29 | | IReadOnlyList<TelegramRoute> routes = message.ChatId is { } explicitChat and not 0 |
| | 11 | 30 | | ? [new TelegramRoute { ChatId = explicitChat }] |
| | 11 | 31 | | : _config.RoutesFor(message.Channel, message.Critical); |
| | 11 | 32 | | var anySent = false; |
| | 52 | 33 | | foreach (var route in routes) |
| | 15 | 34 | | anySent |= await SendToRouteAsync(message, route, ct) != null; |
| | 11 | 35 | | return anySent; |
| | 12 | 36 | | } |
| | | 37 | | |
| | | 38 | | public async Task<long?> SendToRouteAsync(TelegramMessage message, TelegramRoute route, CancellationToken ct = defau |
| | | 39 | | { |
| | 15 | 40 | | var chatId = route.ChatId; |
| | 15 | 41 | | if (!_config.IsConfigured || chatId == 0) return null; |
| | | 42 | | try |
| | | 43 | | { |
| | 15 | 44 | | if (!string.IsNullOrWhiteSpace(message.ImageUrl)) |
| | | 45 | | { |
| | 1 | 46 | | var photo = await _client.SendPhotoAsync(new SendPhotoRequest |
| | 1 | 47 | | { |
| | 1 | 48 | | ChatId = chatId, |
| | 1 | 49 | | MessageThreadId = route.ThreadId, |
| | 1 | 50 | | Photo = message.ImageUrl, |
| | 1 | 51 | | Caption = message.Html.Length > TelegramMessageBuilder.MaxCaptionLength |
| | 1 | 52 | | ? message.Html[..TelegramMessageBuilder.MaxCaptionLength] |
| | 1 | 53 | | : message.Html, |
| | 1 | 54 | | ParseMode = "HTML", |
| | 1 | 55 | | DisableNotification = message.Silent |
| | 1 | 56 | | }, ct); |
| | 1 | 57 | | if (photo.Ok) return photo.Value?.MessageId ?? 0; |
| | | 58 | | // Картинка недоступна Telegram — не теряем само сообщение. |
| | | 59 | | } |
| | | 60 | | |
| | 15 | 61 | | var result = await _client.SendMessageAsync(new SendMessageRequest |
| | 15 | 62 | | { |
| | 15 | 63 | | ChatId = chatId, |
| | 15 | 64 | | MessageThreadId = route.ThreadId, |
| | 15 | 65 | | Text = message.Html, |
| | 15 | 66 | | ParseMode = "HTML", |
| | 15 | 67 | | DisableNotification = message.Silent |
| | 15 | 68 | | }, ct); |
| | 15 | 69 | | return result.Ok ? result.Value?.MessageId ?? 0 : null; |
| | | 70 | | } |
| | 0 | 71 | | catch (OperationCanceledException) when (ct.IsCancellationRequested) |
| | | 72 | | { |
| | 0 | 73 | | throw; |
| | | 74 | | } |
| | 0 | 75 | | catch (Exception ex) |
| | | 76 | | { |
| | 0 | 77 | | _logger?.LogWarning("Не удалось отправить сообщение в Telegram (чат {ChatId}): {Type}", chatId, ex.GetType() |
| | 0 | 78 | | return null; |
| | | 79 | | } |
| | 15 | 80 | | } |
| | | 81 | | |
| | | 82 | | public async Task<bool> EditAsync(long chatId, long messageId, string html, CancellationToken ct = default) |
| | | 83 | | { |
| | 2 | 84 | | if (!_config.IsConfigured) return false; |
| | 2 | 85 | | var result = await _client.EditMessageTextAsync(new EditMessageTextRequest |
| | 2 | 86 | | { |
| | 2 | 87 | | ChatId = chatId, MessageId = messageId, Text = html, ParseMode = "HTML" |
| | 2 | 88 | | }, ct); |
| | 2 | 89 | | return result.Ok || (result.Error?.Contains("message is not modified", StringComparison.OrdinalIgnoreCase) ?? fa |
| | 2 | 90 | | } |
| | | 91 | | |
| | | 92 | | public async Task<bool> PinAsync(long chatId, long messageId, bool silent = true, CancellationToken ct = default) => |
| | 0 | 93 | | _config.IsConfigured && (await _client.PinChatMessageAsync( |
| | 0 | 94 | | new PinChatMessageRequest { ChatId = chatId, MessageId = messageId, DisableNotification = silent }, ct)).Ok; |
| | | 95 | | |
| | | 96 | | public async Task<bool> UnpinAsync(long chatId, long messageId, CancellationToken ct = default) => |
| | 0 | 97 | | _config.IsConfigured && (await _client.UnpinChatMessageAsync( |
| | 0 | 98 | | new UnpinChatMessageRequest { ChatId = chatId, MessageId = messageId }, ct)).Ok; |
| | | 99 | | |
| | | 100 | | public async Task<TelegramPinnedMessage?> GetPinnedAsync(long chatId, CancellationToken ct = default) |
| | | 101 | | { |
| | 0 | 102 | | if (!_config.IsConfigured) return null; |
| | 0 | 103 | | var chat = await _client.GetChatAsync(chatId, ct); |
| | 0 | 104 | | var pinned = chat.Value?.PinnedMessage; |
| | 0 | 105 | | return chat.Ok && pinned != null |
| | 0 | 106 | | ? new TelegramPinnedMessage(pinned.MessageId, pinned.Text, pinned.From?.IsBot == true) |
| | 0 | 107 | | : null; |
| | 0 | 108 | | } |
| | | 109 | | } |