using BotPages.Core; using BotPages.Core.Abstractions; using BotPages.Core.Context; using BotPages.Core.Logging; using BotPages.Core.Messaging; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Telegram.Bot; using Telegram.Bot.Types; using Telegram.Bot.Types.Enums; using Telegram.Bot.Types.ReplyMarkups; namespace BotPages.Telegram; /// /// Адаптер для Telegram на базе Telegram.Bot. /// Реализует отправку текста, кнопок, файлов, альбомов и прогресса. /// public sealed class TelegramAdapter : IMessengerAdapter { private readonly ILogger _logger; private TelegramBotClient? _client; /// Создать адаптер Telegram. public TelegramAdapter(ILogger logger) => _logger = logger; /// /// Запустить polling для приема обновлений от Telegram. /// public async Task StartPollingAsync(string token, Func onUpdate, CancellationToken ct) { _client = new TelegramBotClient(token); _client.StartReceiving( updateHandler: async (_, update, ct2) => { var mapped = TelegramUpdateMapper.Map(update, _client); if (mapped is not null) await onUpdate(mapped); }, errorHandler: async (_, ex, ct2) => { _logger.Log(LogLevel.Warn, "Telegram error.", ex); await Task.CompletedTask; }, cancellationToken: ct ); var me = await _client.GetMe(); _logger.Log(LogLevel.Info, $"Telegram started: @{me.Username}"); return; } /// public async Task SendTextAsync(PageContext ctx, string text, MessageFormat format, IEnumerable>? inline, IEnumerable>? reply, CancellationToken ct) { if (_client is null) { _logger.Log(LogLevel.Critical, "Telegram client is not initialized."); return; } ReplyMarkup? markup = null; if (inline is not null && inline.Any()) { markup = new InlineKeyboardMarkup( inline.Select(row => row.Select(b => new InlineKeyboardButton(b.Label, b.Value)).ToArray()) .ToArray() ); } else if (reply is not null && reply.Any()) { markup = new ReplyKeyboardMarkup( reply.Select(row => row.Select(b => new KeyboardButton(b.Label)).ToArray()).ToArray() ) { ResizeKeyboard = true }; } var parseMode = ParseMode.None; switch (format) { case MessageFormat.Html: { parseMode = ParseMode.Html; break; } case MessageFormat.Plain: { parseMode = ParseMode.None; break; } case MessageFormat.Markdown: { parseMode = ParseMode.MarkdownV2; break; } default: { _logger.Log(LogLevel.Warn, $"MessageFormat '{format}' not supported. Degraded to plain text."); break; } } // Длина сообщения if (text.Length > ctx.Update.Chat.Capabilities.MaxMessageLength) { _logger.Log(LogLevel.Warn, $"Message too long ({text.Length}). Truncated to {ctx.Update.Chat.Capabilities.MaxMessageLength}."); text = text.Substring(0, ctx.Update.Chat.Capabilities.MaxMessageLength); } await _client.SendMessage( chatId: long.Parse(ctx.Update.Chat.Id), text: text, parseMode: parseMode, replyMarkup: markup, cancellationToken: ct ); } /// public async Task SendFileAsync(PageContext ctx, FileDescriptor file, string? caption, CancellationToken ct) { if (_client is null) { _logger.Log(LogLevel.Critical, "Telegram client is not initialized."); return; } var chatId = long.Parse(ctx.Update.Chat.Id); // Получаем поток, если он задан Stream? stream = null; if (file.GetStreamAsync is not null) { stream = await file.GetStreamAsync(ct); stream.Position = 0; } InputFile inputFile; if (stream is not null && stream != Stream.Null) { inputFile = new InputFileStream(stream, file.Name); } else if (file.Id.StartsWith("http://") || file.Id.StartsWith("https://")) { inputFile = new InputFileUrl(file.Id); } else { inputFile = new InputFileId(file.Id); } // В зависимости от FileKind выбираем подходящий метод switch (file.Kind) { case FileKind.Photo: await _client.SendPhoto(chatId, inputFile, caption ?? "", cancellationToken: ct); break; case FileKind.Video: await _client.SendVideo(chatId, inputFile, caption: caption ?? "", cancellationToken: ct); break; case FileKind.Audio: await _client.SendAudio(chatId, inputFile, caption ?? "", cancellationToken: ct); break; default: await _client.SendDocument(chatId, inputFile, caption ?? "", cancellationToken: ct); break; } } /// public IAlbumBuilder CreateAlbumBuilder(PageContext ctx) => new TelegramAlbumBuilder(this, ctx, _logger, _client); /// public async Task StartProgressAsync(PageContext ctx, string title, CancellationToken ct) { if (_client is null) { _logger.Log(LogLevel.Critical, "Telegram client is not initialized."); return null; } string text = "0%"; if (!string.IsNullOrEmpty(title)) { text = title + Environment.NewLine + text; } var message = await _client.SendMessage( chatId: long.Parse(ctx.Update.Chat.Id), text: text, cancellationToken: ct ); return message.Id.ToString(); } /// public async Task UpdateProgressAsync(PageContext ctx, string messageId, string title, int percent, CancellationToken ct) { if (_client is null) { _logger.Log(LogLevel.Critical, "Telegram client is not initialized."); return; } percent = Math.Clamp(percent, 0, 100); string text = $"{percent}%"; if (!string.IsNullOrEmpty(title)) { text = title + Environment.NewLine + text; } try { await _client.EditMessageText( messageId: int.Parse(messageId), chatId: long.Parse(ctx.Update.Chat.Id), text: text, cancellationToken: ct ); } catch (Exception ex) { _logger.Log(LogLevel.Critical, ex.Message, ex); } } /// public Task OnLeaveAsync(PageContext ctx, CancellationToken ct) => Task.CompletedTask; }