Files
BotPages/BotPages.Telegram/TelegramAdapter.cs
FrigaT fce6e8d013
All checks were successful
CI / build-test (push) Successful in 35s
Release / pack-and-publish (release) Successful in 34s
Добавлено скрытие Reply клавиатуры
2025-12-06 08:36:04 +03:00

308 lines
9.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// Адаптер для Telegram на базе Telegram.Bot.
/// Реализует отправку текста, кнопок, файлов, альбомов и прогресса.
/// </summary>
public sealed class TelegramAdapter : IMessangerAdapterSetup
{
private readonly ILogger _logger;
private TelegramBotClient? _client;
private string _token;
private static Capabilities _capabilities = new()
{
SupportsInlineButtons = true,
SupportsReplyButtons = true,
SupportsAlbums = true,
SupportsFormattingMarkdown = true,
SupportsFormattingHtml = true,
MaxMessageLength = 4096,
};
/// <summary>Создать адаптер Telegram.</summary>
public TelegramAdapter(ILogger logger, string token)
{
_logger = logger;
_token = token;
}
/// <summary>
///Идентификатор мессенджера / адаптера
/// </summary>
public string MessengerType { get; set; } = "Telegram: " + Guid.NewGuid().ToString();
/// <summary>
/// Доступные возможности адаптера.
/// </summary>
public Capabilities Capabilities => _capabilities;
/// <summary>
/// Запустить polling для приема обновлений от Telegram.
/// </summary>
public async Task StartAdapterAsync(Func<UpdateContext, Task> onUpdate, CancellationToken ct)
{
_client = new TelegramBotClient(_token);
_client.StartReceiving(
updateHandler: async (_, update, ct2) =>
{
var mapped = TelegramUpdateMapper.Map(MessengerType, update, _client);
if (mapped is not null)
await onUpdate(mapped);
},
errorHandler: async (_, ex, ct2) =>
{
_logger.Log(LogLevel.Warn, $"{MessengerType} error.", ex);
await Task.CompletedTask;
},
cancellationToken: ct
);
var me = await _client.GetMe();
_logger.Log(LogLevel.Info, $"{MessengerType} started: @{me.Username}");
return;
}
/// <inheritdoc />
public async Task SendTextAsync(string chatId, string text, MessageFormat format,
IEnumerable<IEnumerable<InlineButton>>? inline,
IEnumerable<IEnumerable<ReplyButton>>? reply, CancellationToken ct)
{
if (_client is null)
{
_logger.Log(LogLevel.Critical, $"{MessengerType} 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)
{
if (reply.Any())
{
markup = new ReplyKeyboardMarkup(
reply.Select(row => row.Select(b => new KeyboardButton(b.Label)).ToArray()).ToArray()
)
{
ResizeKeyboard = true
};
}
else
{
markup = new ReplyKeyboardRemove();
}
}
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 > Capabilities.MaxMessageLength)
{
_logger.Log(LogLevel.Warn, $"Message too long ({text.Length}). Truncated to {Capabilities.MaxMessageLength}.");
text = text.Substring(0, Capabilities.MaxMessageLength);
}
await _client.SendMessage(
chatId: long.Parse(chatId),
text: text,
parseMode: parseMode,
replyMarkup: markup,
cancellationToken: ct
);
}
/// <inheritdoc />
public async Task SendFileAsync(string chatId, FileDescriptor file, string? caption, MessageFormat? captionFormat, CancellationToken ct)
{
if (_client is null)
{
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
return;
}
// Получаем поток, если он задан
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);
}
var parseMode = ParseMode.None;
switch (captionFormat)
{
case MessageFormat.Html:
{
parseMode = ParseMode.Html;
break;
}
case MessageFormat.Plain:
{
parseMode = ParseMode.None;
break;
}
case MessageFormat.Markdown:
{
parseMode = ParseMode.MarkdownV2;
break;
}
case null:
{
parseMode = ParseMode.None;
break;
}
default:
{
_logger.Log(LogLevel.Warn, $"MessageFormat '{captionFormat}' not supported. Degraded to plain text.");
break;
}
}
// В зависимости от FileKind выбираем подходящий метод
switch (file.Kind)
{
case FileKind.Photo:
await _client.SendPhoto(long.Parse(chatId), inputFile, caption ?? "", parseMode, cancellationToken: ct);
break;
case FileKind.Video:
await _client.SendVideo(long.Parse(chatId), inputFile, caption: caption ?? "", parseMode, cancellationToken: ct);
break;
case FileKind.Audio:
await _client.SendAudio(long.Parse(chatId), inputFile, caption ?? "", parseMode, cancellationToken: ct);
break;
default:
await _client.SendDocument(long.Parse(chatId), inputFile, caption ?? "", parseMode, cancellationToken: ct);
break;
}
}
/// <inheritdoc />
public IAlbumBuilder CreateAlbumBuilder(PageContext ctx) => new TelegramAlbumBuilder(this, ctx, _logger, _client);
/// <inheritdoc />
public async Task<string?> StartProgressAsync(PageContext ctx, string title, CancellationToken ct)
{
if (_client is null)
{
_logger.Log(LogLevel.Critical, $"{MessengerType} 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();
}
/// <inheritdoc />
public async Task UpdateProgressAsync(PageContext ctx, string messageId, string title, int percent, CancellationToken ct)
{
if (_client is null)
{
_logger.Log(LogLevel.Critical, $"{MessengerType} 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);
}
}
/// <inheritdoc />
public Task OnLeaveAsync(PageContext ctx, CancellationToken ct) => Task.CompletedTask;
}