Добавлены новые методы отправки сообщений
This commit is contained in:
@@ -16,15 +16,12 @@ using Telegram.Bot.Types.ReplyMarkups;
|
||||
|
||||
namespace BotPages.Telegram;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Адаптер для Telegram на базе Telegram.Bot.
|
||||
/// Реализует отправку текста, кнопок, файлов, альбомов и прогресса.
|
||||
/// </summary>
|
||||
public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
public sealed class TelegramAdapter : MessengerAdapterBase
|
||||
{
|
||||
internal static readonly string AdapterType = typeof(TelegramAdapter).FullName;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private TelegramBotClient? _client;
|
||||
private string _token;
|
||||
@@ -45,29 +42,35 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
_logger = logger;
|
||||
_token = token;
|
||||
_options = options ?? new TelegramOptions();
|
||||
|
||||
// Устанавливаем имя для отображения
|
||||
DisplayName = $"Telegram Bot";
|
||||
}
|
||||
|
||||
/// <summary>Тип адаптера.</summary>
|
||||
public override string AdapterType => "Telegram";
|
||||
|
||||
/// <summary>
|
||||
///Идентификатор мессенджера / адаптера
|
||||
/// Идентификатор мессенджера / адаптера
|
||||
/// </summary>
|
||||
public string MessengerType { get; set; } = "Telegram: " + Guid.NewGuid().ToString();
|
||||
public string MessengerType => "Telegram";
|
||||
|
||||
/// <summary>
|
||||
/// Доступные возможности адаптера.
|
||||
/// </summary>
|
||||
public Capabilities Capabilities => _capabilities;
|
||||
public override Capabilities Capabilities => _capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Запустить polling для приема обновлений от Telegram.
|
||||
/// </summary>
|
||||
public async Task StartAdapterAsync(Func<UpdateContext, Task> onUpdate, List<BotPages.Core.Routing.Command> commands, CancellationToken ct)
|
||||
public override async Task StartAdapterAsync(Func<UpdateContext, Task> onUpdate, List<BotPages.Core.Routing.Command> commands, CancellationToken ct)
|
||||
{
|
||||
_client = new TelegramBotClient(_token);
|
||||
|
||||
_client.StartReceiving(
|
||||
updateHandler: async (_, update, ct2) =>
|
||||
{
|
||||
var mapped = TelegramUpdateMapper.Map(MessengerType, update, _client);
|
||||
var mapped = TelegramUpdateMapper.Map(this, update, _client);
|
||||
if (mapped is not null)
|
||||
await onUpdate(mapped);
|
||||
if (update.CallbackQuery is not null)
|
||||
@@ -78,18 +81,18 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
|
||||
errorHandler: async (_, ex, ct2) =>
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"{MessengerType} error.", ex);
|
||||
_logger.Log(LogLevel.Warn, $"{AdapterType} ({AdapterId}) error.", ex);
|
||||
await Task.CompletedTask;
|
||||
},
|
||||
|
||||
cancellationToken: ct
|
||||
);
|
||||
|
||||
|
||||
await _client.SetMyCommands(commands.Where(t => t.Publish).Select(t => new BotCommand(t.Name, t.Description ?? t.Name.TrimStart('/'))), cancellationToken: ct);
|
||||
|
||||
var me = await _client.GetMe();
|
||||
_logger.Log(LogLevel.Info, $"{MessengerType} started: @{me.Username}");
|
||||
DisplayName = $"Telegram: @{me.Username}";
|
||||
_logger.Log(LogLevel.Info, $"{AdapterType} ({AdapterId}) started: {DisplayName}");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -98,7 +101,7 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
/// Универсальный внутренний метод отправки — определяет, нужно ли отправлять текст или файл по параметрам.
|
||||
/// Возвращает id сообщения (или null).
|
||||
/// </summary>
|
||||
public async Task<string?> SendAsync(SendRequest req, CancellationToken ct)
|
||||
public override async Task<string?> SendAsync(SendRequest req, CancellationToken ct)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
@@ -112,33 +115,80 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
{
|
||||
telegramOptions = opt;
|
||||
}
|
||||
var disableNotification = !telegramOptions.NotifyOnSend;
|
||||
var disableNotification = !telegramOptions.NotifyOnSend || req.DisableNotification;
|
||||
|
||||
// Build markup
|
||||
var inlineMarkup = BuildInlineMarkup(req.Inline);
|
||||
ReplyMarkup? markup = BuildReplyMarkup(req.Reply);
|
||||
|
||||
// Ответ на сообщение
|
||||
int? replyToMessageId = null;
|
||||
if (!string.IsNullOrEmpty(req.ReplyToMessageId) && int.TryParse(req.ReplyToMessageId, out var replyId))
|
||||
{
|
||||
replyToMessageId = replyId;
|
||||
}
|
||||
|
||||
// Файлы: сейчас поддерживается один файл через SendRequest.File.
|
||||
// При необходимости для нескольких файлов следует использовать альбомы (CreateAlbumBuilder)
|
||||
if (req.File is not null)
|
||||
{
|
||||
if (inlineMarkup is not null) markup = inlineMarkup;
|
||||
|
||||
var sent = await SendFileAsync(req.File, req.ChatId, markup, disableNotification, req.Caption, req.CaptionFormat, ct);
|
||||
var sent = await SendFileAsync(req.File, req.ChatId, markup, disableNotification,
|
||||
req.Caption, req.CaptionFormat, replyToMessageId, req.ProtectContent, ct);
|
||||
return sent?.MessageId.ToString();
|
||||
}
|
||||
|
||||
// Текст
|
||||
if (!string.IsNullOrWhiteSpace(req.Text))
|
||||
{
|
||||
return await SendTextAsync(req, inlineMarkup, markup, disableNotification, ct);
|
||||
return await SendTextAsync(req, inlineMarkup, markup, disableNotification,
|
||||
replyToMessageId, req.DisableWebPagePreview, req.ProtectContent, ct);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task DeleteAsync(string chatId, string messageId, CancellationToken ct = default)
|
||||
public override Task DeleteAsync(string chatId, string messageId, CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return _client.DeleteMessage(chatId, Convert.ToInt32(messageId), ct);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<bool> DeleteMultipleAsync(string chatId, IEnumerable<string> messageIds, CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Telegram не поддерживает массовое удаление, удаляем по одному
|
||||
foreach (var messageId in messageIds)
|
||||
{
|
||||
await DeleteAsync(chatId, messageId, ct);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"Failed to delete multiple messages: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<string?> EditTextAsync(string chatId, string messageId, string text,
|
||||
MessageFormat? format = null, CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
@@ -146,7 +196,171 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
return null;
|
||||
}
|
||||
|
||||
return _client.DeleteMessage(chatId, Convert.ToInt32(messageId), ct);
|
||||
try
|
||||
{
|
||||
var parseMode = GetParseMode(format);
|
||||
await _client.EditMessageText(
|
||||
chatId: long.Parse(chatId),
|
||||
messageId: int.Parse(messageId),
|
||||
text: text,
|
||||
parseMode: parseMode,
|
||||
cancellationToken: ct
|
||||
);
|
||||
return messageId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"Failed to edit text: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<string?> EditButtonsAsync(string chatId, string messageId,
|
||||
IEnumerable<IEnumerable<InlineButton>>? inlineButtons = null, CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var inlineMarkup = BuildInlineMarkup(inlineButtons);
|
||||
await _client.EditMessageReplyMarkup(
|
||||
chatId: long.Parse(chatId),
|
||||
messageId: int.Parse(messageId),
|
||||
replyMarkup: inlineMarkup,
|
||||
cancellationToken: ct
|
||||
);
|
||||
return messageId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"Failed to edit buttons: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<bool> PinMessageAsync(string chatId, string messageId, bool disableNotification = false,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _client.PinChatMessage(
|
||||
chatId: long.Parse(chatId),
|
||||
messageId: int.Parse(messageId),
|
||||
disableNotification: disableNotification,
|
||||
cancellationToken: ct
|
||||
);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"Failed to pin message: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<bool> UnpinMessageAsync(string chatId, string messageId, CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _client.UnpinChatMessage(
|
||||
chatId: long.Parse(chatId),
|
||||
messageId: int.Parse(messageId),
|
||||
cancellationToken: ct
|
||||
);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"Failed to unpin message: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<MessageInfo?> GetMessageInfoAsync(string chatId, string messageId, CancellationToken ct = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<string?> ForwardMessageAsync(string fromChatId, string messageId, string toChatId,
|
||||
bool disableNotification = false, CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var message = await _client.ForwardMessage(
|
||||
chatId: long.Parse(toChatId),
|
||||
fromChatId: long.Parse(fromChatId),
|
||||
messageId: int.Parse(messageId),
|
||||
disableNotification: disableNotification,
|
||||
cancellationToken: ct
|
||||
);
|
||||
|
||||
return message.MessageId.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"Failed to forward message: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<string?> CopyMessageAsync(string fromChatId, string messageId, string toChatId,
|
||||
string? caption = null, MessageFormat? captionFormat = null,
|
||||
bool disableNotification = false, CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var parseMode = GetParseMode(captionFormat);
|
||||
var message = await _client.CopyMessage(
|
||||
chatId: long.Parse(toChatId),
|
||||
fromChatId: long.Parse(fromChatId),
|
||||
messageId: int.Parse(messageId),
|
||||
caption: caption,
|
||||
parseMode: parseMode,
|
||||
disableNotification: disableNotification,
|
||||
cancellationToken: ct
|
||||
);
|
||||
|
||||
return message.Id.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"Failed to copy message: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
@@ -209,21 +423,64 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Message?> SendFileAsync(FileDescriptor file, string chatId, ReplyMarkup? markup, bool disableNotification, string? caption, MessageFormat? captionFormat, CancellationToken ct)
|
||||
private async Task<Message?> SendFileAsync(FileDescriptor file, string chatId, ReplyMarkup? markup,
|
||||
bool disableNotification, string? caption, MessageFormat? captionFormat, int? replyToMessageId,
|
||||
bool protectContent, CancellationToken ct)
|
||||
{
|
||||
var inputFile = await CreateInputFileAsync(file, ct);
|
||||
var parseMode = GetParseMode(captionFormat);
|
||||
|
||||
return file.Kind switch
|
||||
{
|
||||
FileKind.Photo => await _client.SendPhoto(long.Parse(chatId), inputFile, caption ?? "", parseMode, replyMarkup: markup, disableNotification: disableNotification, cancellationToken: ct),
|
||||
FileKind.Video => await _client.SendVideo(long.Parse(chatId), inputFile, caption: caption ?? "", parseMode, replyMarkup: markup, disableNotification: disableNotification, cancellationToken: ct),
|
||||
FileKind.Audio => await _client.SendAudio(long.Parse(chatId), inputFile, caption ?? "", parseMode, replyMarkup: markup, disableNotification: disableNotification, cancellationToken: ct),
|
||||
_ => await _client.SendDocument(long.Parse(chatId), inputFile, caption ?? "", parseMode, replyMarkup: markup, disableNotification: disableNotification, cancellationToken: ct),
|
||||
FileKind.Photo => await _client.SendPhoto(
|
||||
long.Parse(chatId),
|
||||
inputFile,
|
||||
caption ?? "",
|
||||
parseMode,
|
||||
replyParameters: replyToMessageId,
|
||||
replyMarkup: markup,
|
||||
disableNotification: disableNotification,
|
||||
protectContent: protectContent,
|
||||
cancellationToken: ct),
|
||||
|
||||
FileKind.Video => await _client.SendVideo(
|
||||
long.Parse(chatId),
|
||||
inputFile,
|
||||
caption: caption ?? "",
|
||||
parseMode,
|
||||
replyParameters: replyToMessageId,
|
||||
replyMarkup: markup,
|
||||
disableNotification: disableNotification,
|
||||
protectContent: protectContent,
|
||||
cancellationToken: ct),
|
||||
|
||||
FileKind.Audio => await _client.SendAudio(
|
||||
long.Parse(chatId),
|
||||
inputFile,
|
||||
caption ?? "",
|
||||
parseMode,
|
||||
replyParameters: replyToMessageId,
|
||||
replyMarkup: markup,
|
||||
disableNotification: disableNotification,
|
||||
protectContent: protectContent,
|
||||
cancellationToken: ct),
|
||||
|
||||
_ => await _client.SendDocument(
|
||||
long.Parse(chatId),
|
||||
inputFile,
|
||||
caption ?? "",
|
||||
parseMode,
|
||||
replyParameters: replyToMessageId,
|
||||
replyMarkup: markup,
|
||||
disableNotification: disableNotification,
|
||||
protectContent: protectContent,
|
||||
cancellationToken: ct),
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<string?> SendTextAsync(SendRequest req, InlineKeyboardMarkup? inlineMarkup, ReplyMarkup? markup, bool disableNotification, CancellationToken ct)
|
||||
private async Task<string?> SendTextAsync(SendRequest req, InlineKeyboardMarkup? inlineMarkup,
|
||||
ReplyMarkup? markup, bool disableNotification, int? replyToMessageId,
|
||||
bool disableWebPagePreview, bool protectContent, CancellationToken ct)
|
||||
{
|
||||
var format = req.TextFormat ?? MessageFormat.Plain;
|
||||
var parseMode = GetParseMode(format);
|
||||
@@ -242,6 +499,10 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
chatId: long.Parse(req.ChatId),
|
||||
text: text,
|
||||
parseMode: parseMode,
|
||||
linkPreviewOptions: new()
|
||||
{
|
||||
IsDisabled = disableWebPagePreview,
|
||||
},
|
||||
replyMarkup: inlineMarkup,
|
||||
cancellationToken: ct
|
||||
);
|
||||
@@ -256,8 +517,14 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
chatId: long.Parse(req.ChatId),
|
||||
text: text,
|
||||
parseMode: parseMode,
|
||||
linkPreviewOptions: new()
|
||||
{
|
||||
IsDisabled = disableWebPagePreview,
|
||||
},
|
||||
replyMarkup: markup,
|
||||
replyParameters: replyToMessageId,
|
||||
disableNotification: disableNotification,
|
||||
protectContent: protectContent,
|
||||
cancellationToken: ct
|
||||
);
|
||||
|
||||
@@ -266,8 +533,8 @@ public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAlbumBuilder CreateAlbumBuilder(PageContext ctx) => new TelegramAlbumBuilder(this, ctx, _logger, _client);
|
||||
public override IAlbumBuilder CreateAlbumBuilder(PageContext ctx) => new TelegramAlbumBuilder(this, ctx, _logger, _client);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task OnLeaveAsync(PageContext ctx, CancellationToken ct) => Task.CompletedTask;
|
||||
public override Task OnLeaveAsync(PageContext ctx, CancellationToken ct) => Task.CompletedTask;
|
||||
}
|
||||
Reference in New Issue
Block a user