Новый api отправки сообщений
This commit is contained in:
@@ -21,11 +21,12 @@ namespace BotPages.Telegram;
|
||||
/// Адаптер для Telegram на базе Telegram.Bot.
|
||||
/// Реализует отправку текста, кнопок, файлов, альбомов и прогресса.
|
||||
/// </summary>
|
||||
public sealed class TelegramAdapter : IMessangerAdapterSetup
|
||||
public sealed class TelegramAdapter : IMessengerAdapterSetup
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private TelegramBotClient? _client;
|
||||
private string _token;
|
||||
private readonly TelegramOptions _options;
|
||||
private static Capabilities _capabilities = new()
|
||||
{
|
||||
SupportsInlineButtons = true,
|
||||
@@ -37,10 +38,11 @@ public sealed class TelegramAdapter : IMessangerAdapterSetup
|
||||
};
|
||||
|
||||
/// <summary>Создать адаптер Telegram.</summary>
|
||||
public TelegramAdapter(ILogger logger, string token)
|
||||
public TelegramAdapter(ILogger logger, string token, TelegramOptions? options = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_token = token;
|
||||
_options = options ?? new TelegramOptions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,6 +83,7 @@ public sealed class TelegramAdapter : IMessangerAdapterSetup
|
||||
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();
|
||||
@@ -89,13 +92,11 @@ public sealed class TelegramAdapter : IMessangerAdapterSetup
|
||||
return;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string?> SendTextAsync(string chatId, string text,
|
||||
MessageFormat format = MessageFormat.Plain,
|
||||
IEnumerable<IEnumerable<InlineButton>>? inline = null,
|
||||
IEnumerable<IEnumerable<ReplyButton>>? reply = null,
|
||||
string? messageId = null,
|
||||
CancellationToken ct = default)
|
||||
/// <summary>
|
||||
/// Универсальный внутренний метод отправки — определяет, нужно ли отправлять текст или файл по параметрам.
|
||||
/// Возвращает id сообщения (или null).
|
||||
/// </summary>
|
||||
public async Task<string?> SendAsync(SendRequest req, CancellationToken ct)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
@@ -103,23 +104,29 @@ public sealed class TelegramAdapter : IMessangerAdapterSetup
|
||||
return null;
|
||||
}
|
||||
|
||||
// determine adapter-specific options (TelegramOptions) from bag or use adapter default
|
||||
TelegramOptions telegramOptions = _options;
|
||||
if (req.AdapterOptions is AdapterOptionsBag bag && bag.TryGet("telegram", out TelegramOptions? opt) && opt is not null)
|
||||
{
|
||||
telegramOptions = opt;
|
||||
}
|
||||
var disableNotification = !telegramOptions.NotifyOnSend;
|
||||
|
||||
// Build markup
|
||||
InlineKeyboardMarkup? inlineMarkup = null;
|
||||
ReplyMarkup? markup = null;
|
||||
|
||||
if (inline is not null && inline.Any())
|
||||
if (req.Inline is not null && req.Inline.Any())
|
||||
{
|
||||
inlineMarkup = new InlineKeyboardMarkup(
|
||||
inline.Select(row => row.Select(b => new InlineKeyboardButton(b.Label, b.Value)).ToArray())
|
||||
.ToArray()
|
||||
req.Inline.Select(row => row.Select(b => new InlineKeyboardButton(b.Label, b.Value)).ToArray()).ToArray()
|
||||
);
|
||||
}
|
||||
else if (reply is not null)
|
||||
else if (req.Reply is not null)
|
||||
{
|
||||
if (reply.Any())
|
||||
if (req.Reply.Any())
|
||||
{
|
||||
markup = new ReplyKeyboardMarkup(
|
||||
reply.Select(row => row.Select(b => new KeyboardButton(b.Label)).ToArray()).ToArray()
|
||||
)
|
||||
markup = new ReplyKeyboardMarkup(req.Reply.Select(row => row.Select(b => new KeyboardButton(b.Label)).ToArray()).ToArray())
|
||||
{
|
||||
ResizeKeyboard = true
|
||||
};
|
||||
@@ -130,180 +137,169 @@ public sealed class TelegramAdapter : IMessangerAdapterSetup
|
||||
}
|
||||
}
|
||||
|
||||
var parseMode = ParseMode.None;
|
||||
|
||||
switch (format)
|
||||
// If there is a file — send file
|
||||
if (req.File is not null)
|
||||
{
|
||||
case MessageFormat.Html:
|
||||
{
|
||||
var file = req.File;
|
||||
|
||||
// Получаем поток, если он задан
|
||||
Stream? stream = null;
|
||||
if (file.GetStreamAsync is not null)
|
||||
{
|
||||
stream = await file.GetStreamAsync(ct);
|
||||
if (stream is not null) 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 (req.CaptionFormat)
|
||||
{
|
||||
case MessageFormat.Html:
|
||||
parseMode = ParseMode.Html;
|
||||
break;
|
||||
}
|
||||
case MessageFormat.Plain:
|
||||
{
|
||||
parseMode = ParseMode.None;
|
||||
break;
|
||||
}
|
||||
case MessageFormat.Markdown:
|
||||
{
|
||||
case MessageFormat.Markdown:
|
||||
parseMode = ParseMode.MarkdownV2;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"MessageFormat '{format}' not supported. Degraded to plain text.");
|
||||
case MessageFormat.Plain:
|
||||
case null:
|
||||
parseMode = ParseMode.None;
|
||||
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);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(messageId))
|
||||
{
|
||||
await _client.EditMessageText(
|
||||
messageId: int.Parse(messageId),
|
||||
chatId: long.Parse(chatId),
|
||||
text: text,
|
||||
parseMode: parseMode,
|
||||
replyMarkup: inlineMarkup,
|
||||
cancellationToken: ct
|
||||
);
|
||||
|
||||
return messageId;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inlineMarkup is not null) markup = inlineMarkup;
|
||||
|
||||
var message = await _client.SendMessage(
|
||||
chatId: long.Parse(chatId),
|
||||
text: text,
|
||||
parseMode: parseMode,
|
||||
replyMarkup: markup,
|
||||
cancellationToken: ct
|
||||
);
|
||||
Message? sentMessage = req.File.Kind switch
|
||||
{
|
||||
FileKind.Photo => await _client.SendPhoto(long.Parse(req.ChatId), inputFile, req.Caption ?? "", parseMode, replyMarkup: markup, disableNotification: disableNotification, cancellationToken: ct),
|
||||
FileKind.Video => await _client.SendVideo(long.Parse(req.ChatId), inputFile, caption: req.Caption ?? "", parseMode, replyMarkup: markup, disableNotification: disableNotification, cancellationToken: ct),
|
||||
FileKind.Audio => await _client.SendAudio(long.Parse(req.ChatId), inputFile, req.Caption ?? "", parseMode, replyMarkup: markup, disableNotification: disableNotification, cancellationToken: ct),
|
||||
_ => await _client.SendDocument(long.Parse(req.ChatId), inputFile, req.Caption ?? "", parseMode, replyMarkup: markup, disableNotification: disableNotification, cancellationToken: ct),
|
||||
};
|
||||
|
||||
return message.Id.ToString();
|
||||
return sentMessage?.MessageId.ToString();
|
||||
}
|
||||
|
||||
// Otherwise treat as text
|
||||
if (!string.IsNullOrWhiteSpace(req.Text))
|
||||
{
|
||||
var format = req.TextFormat ?? MessageFormat.Plain;
|
||||
var parseMode = ParseMode.None;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case MessageFormat.Html:
|
||||
parseMode = ParseMode.Html;
|
||||
break;
|
||||
case MessageFormat.Markdown:
|
||||
parseMode = ParseMode.MarkdownV2;
|
||||
break;
|
||||
case MessageFormat.Plain:
|
||||
default:
|
||||
parseMode = ParseMode.None;
|
||||
break;
|
||||
}
|
||||
|
||||
// Длина сообщения
|
||||
var text = req.Text!;
|
||||
if (text.Length > Capabilities.MaxMessageLength)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, $"Message too long ({text.Length}). Truncated to {Capabilities.MaxMessageLength}.");
|
||||
text = text.Substring(0, Capabilities.MaxMessageLength);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(req.MessageId))
|
||||
{
|
||||
await _client.EditMessageText(
|
||||
messageId: int.Parse(req.MessageId),
|
||||
chatId: long.Parse(req.ChatId),
|
||||
text: text,
|
||||
parseMode: parseMode,
|
||||
replyMarkup: inlineMarkup,
|
||||
cancellationToken: ct
|
||||
);
|
||||
|
||||
return req.MessageId;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inlineMarkup is not null) markup = inlineMarkup;
|
||||
|
||||
var message = await _client.SendMessage(
|
||||
chatId: long.Parse(req.ChatId),
|
||||
text: text,
|
||||
parseMode: parseMode,
|
||||
replyMarkup: markup,
|
||||
disableNotification: disableNotification,
|
||||
cancellationToken: ct
|
||||
);
|
||||
|
||||
return message.MessageId.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SendFileAsync(string chatId,
|
||||
public Task<string?> SendTextAsync(string chatId, string text,
|
||||
MessageFormat format = MessageFormat.Plain,
|
||||
IEnumerable<IEnumerable<InlineButton>>? inline = null,
|
||||
IEnumerable<IEnumerable<ReplyButton>>? reply = null,
|
||||
string? messageId = null,
|
||||
object? adapterOptions = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var req = new SendRequest
|
||||
{
|
||||
ChatId = chatId,
|
||||
Text = text,
|
||||
TextFormat = format,
|
||||
Inline = inline,
|
||||
Reply = reply,
|
||||
MessageId = messageId,
|
||||
AdapterOptions = adapterOptions as AdapterOptionsBag
|
||||
};
|
||||
|
||||
return SendAsync(req, ct);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendFileAsync(string chatId,
|
||||
FileDescriptor file,
|
||||
string? caption = null,
|
||||
MessageFormat? captionFormat = null,
|
||||
IEnumerable<IEnumerable<InlineButton>>? inline = null,
|
||||
IEnumerable<IEnumerable<ReplyButton>>? reply = null,
|
||||
object? adapterOptions = null,
|
||||
CancellationToken ct = default
|
||||
)
|
||||
{
|
||||
if (_client is null)
|
||||
var req = new SendRequest
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, $"{MessengerType} client is not initialized.");
|
||||
return;
|
||||
}
|
||||
ChatId = chatId,
|
||||
File = file,
|
||||
Caption = caption,
|
||||
CaptionFormat = captionFormat,
|
||||
Inline = inline,
|
||||
Reply = reply,
|
||||
AdapterOptions = adapterOptions as AdapterOptionsBag
|
||||
};
|
||||
|
||||
// Получаем поток, если он задан
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// В зависимости от FileKind выбираем подходящий метод
|
||||
switch (file.Kind)
|
||||
{
|
||||
case FileKind.Photo:
|
||||
await _client.SendPhoto(long.Parse(chatId), inputFile, caption ?? "", parseMode, replyMarkup: markup, cancellationToken: ct);
|
||||
break;
|
||||
case FileKind.Video:
|
||||
await _client.SendVideo(long.Parse(chatId), inputFile, caption: caption ?? "", parseMode, replyMarkup: markup, cancellationToken: ct);
|
||||
break;
|
||||
case FileKind.Audio:
|
||||
await _client.SendAudio(long.Parse(chatId), inputFile, caption ?? "", parseMode, replyMarkup: markup, cancellationToken: ct);
|
||||
break;
|
||||
default:
|
||||
await _client.SendDocument(long.Parse(chatId), inputFile, caption ?? "", parseMode, replyMarkup: markup, cancellationToken: ct);
|
||||
break;
|
||||
}
|
||||
return SendAsync(req, ct);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
Reference in New Issue
Block a user