This commit is contained in:
@@ -1,10 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageId>BotPages.Telegram</PackageId>
|
||||
<Product>BotPages</Product>
|
||||
<Version>0.1.0</Version>
|
||||
<Authors>FrigaT</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Description>Адаптер для Telegram Bot API. Работает поверх BotPages.Core.</Description>
|
||||
<Copyright>Copyright © 2025 FrigaT</Copyright>
|
||||
<RepositoryUrl>https://git.frigat.duckdns.org/FrigaT/BotPages</RepositoryUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Telegram.Bot" Version="22.7.5" />
|
||||
|
||||
248
BotPages.Telegram/TelegramAdapter.cs
Normal file
248
BotPages.Telegram/TelegramAdapter.cs
Normal file
@@ -0,0 +1,248 @@
|
||||
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 : IMessengerAdapter
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private TelegramBotClient? _client;
|
||||
|
||||
/// <summary>Создать адаптер Telegram.</summary>
|
||||
public TelegramAdapter(ILogger logger) => _logger = logger;
|
||||
|
||||
/// <summary>
|
||||
/// Запустить polling для приема обновлений от Telegram.
|
||||
/// </summary>
|
||||
public async Task StartPollingAsync(string token, Func<UpdateContext, Task> 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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SendTextAsync(PageContext ctx, string text, MessageFormat format,
|
||||
IEnumerable<IEnumerable<InlineButton>>? inline,
|
||||
IEnumerable<IEnumerable<ReplyButton>>? 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
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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, "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();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task OnLeaveAsync(PageContext ctx, CancellationToken ct) => Task.CompletedTask;
|
||||
}
|
||||
105
BotPages.Telegram/TelegramAlbumBuilder.cs
Normal file
105
BotPages.Telegram/TelegramAlbumBuilder.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Abstractions;
|
||||
using BotPages.Core.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace BotPages.Telegram;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Внутренний билдер альбомов (медиагрупп) для Telegram.
|
||||
/// </summary>
|
||||
public sealed class TelegramAlbumBuilder : IAlbumBuilder
|
||||
{
|
||||
private readonly TelegramAdapter _adapter;
|
||||
private readonly PageContext _ctx;
|
||||
private readonly ILogger _logger;
|
||||
private readonly TelegramBotClient? _client;
|
||||
private readonly List<(FileDescriptor file, string? caption)> _items = new();
|
||||
|
||||
/// <summary>Создать билдер альбома.</summary>
|
||||
public TelegramAlbumBuilder(TelegramAdapter adapter, PageContext ctx, ILogger logger, TelegramBotClient? client)
|
||||
{
|
||||
_adapter = adapter;
|
||||
_ctx = ctx;
|
||||
_logger = logger;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAlbumBuilder Add(FileDescriptor file, string? caption = null)
|
||||
{
|
||||
_items.Add((file, caption));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SendAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
_logger.Log(LogLevel.Critical, "Telegram client is not initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
var chatId = long.Parse(_ctx.Update.Chat.Id);
|
||||
|
||||
if (!_ctx.Update.Chat.Capabilities.SupportsAlbums)
|
||||
{
|
||||
_logger.Log(LogLevel.Warn, "Albums not supported. Degraded to sequential sends.");
|
||||
foreach (var (file, caption) in _items)
|
||||
await _adapter.SendFileAsync(_ctx, file, caption, ct);
|
||||
return;
|
||||
}
|
||||
|
||||
var media = new List<IAlbumInputMedia>();
|
||||
foreach (var (file, caption) in _items)
|
||||
{
|
||||
Stream? stream = null;
|
||||
if (file.GetStreamAsync is not null)
|
||||
{
|
||||
stream = await file.GetStreamAsync(ct);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
IAlbumInputMedia? m = file.Kind switch
|
||||
{
|
||||
FileKind.Audio => new InputMediaAudio(inputFile) { Caption = caption ?? "" },
|
||||
FileKind.Document => new InputMediaDocument(inputFile) { Caption = caption ?? "" },
|
||||
FileKind.Photo => new InputMediaPhoto(inputFile) { Caption = caption ?? "" },
|
||||
FileKind.Video => new InputMediaVideo(inputFile) { Caption = caption ?? "" },
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (m is not null) media.Add(m);
|
||||
else
|
||||
{
|
||||
// Telegram не поддерживает document в альбомах — деградация
|
||||
_logger.Log(LogLevel.Warn, $"Document '{file.Kind}' in album not supported. Sending document separately.");
|
||||
await _adapter.SendFileAsync(_ctx, file, caption, ct);
|
||||
}
|
||||
}
|
||||
|
||||
if (media.Count > 0)
|
||||
await _client.SendMediaGroup(chatId, media, cancellationToken: ct);
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
using BotPages.Core;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
using Telegram.Bot.Types.Enums;
|
||||
using Telegram.Bot.Types.ReplyMarkups;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace BotPages.Telegram
|
||||
{
|
||||
/// <summary>
|
||||
/// Адаптер клиента для Telegram Bot API.
|
||||
/// </summary>
|
||||
public sealed class TelegramClientAdapter : IChatClient
|
||||
{
|
||||
private readonly ITelegramBotClient _bot;
|
||||
|
||||
public string Id { get; init; } = nameof(TelegramClientAdapter);
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт адаптер на основе ITelegramBotClient.
|
||||
/// </summary>
|
||||
public TelegramClientAdapter(ITelegramBotClient bot) => _bot = bot;
|
||||
|
||||
/// <summary>
|
||||
/// Отправляет текстовое сообщение с опциональной клавиатурой.
|
||||
/// </summary>
|
||||
public Task SendTextAsync(long chatId, PageMessage message, IEnumerable<PageAction>? actions, CancellationToken ct)
|
||||
{
|
||||
ReplyMarkup? replyMarkup = null;
|
||||
|
||||
if (actions is { })
|
||||
{
|
||||
var inlineGroups = actions
|
||||
.Where(a => a.Placement == ActionPlacement.Inline)
|
||||
.GroupBy(a => a.Row)
|
||||
.OrderBy(g => g.Key)
|
||||
.Select(g => g.Select(a => InlineKeyboardButton.WithCallbackData(a.Label, a.Value)).ToArray())
|
||||
.ToArray();
|
||||
|
||||
var replyGroups = actions
|
||||
.Where(a => a.Placement == ActionPlacement.Reply)
|
||||
.GroupBy(a => a.Row)
|
||||
.OrderBy(g => g.Key)
|
||||
.Select(g => g.Select(a => new KeyboardButton(a.Label)).ToArray())
|
||||
.ToArray();
|
||||
|
||||
if (inlineGroups.Any())
|
||||
replyMarkup = new InlineKeyboardMarkup(inlineGroups);
|
||||
else if (replyGroups.Any())
|
||||
replyMarkup = new ReplyKeyboardMarkup(replyGroups) { ResizeKeyboard = true };
|
||||
}
|
||||
|
||||
var parseMode = message.Format switch
|
||||
{
|
||||
MessageFormat.Markdown => ParseMode.MarkdownV2,
|
||||
MessageFormat.Html => ParseMode.Html,
|
||||
_ => ParseMode.None,
|
||||
};
|
||||
|
||||
|
||||
return _bot.SendMessage(new ChatId(chatId),
|
||||
message.Text,
|
||||
parseMode: parseMode,
|
||||
replyMarkup: replyMarkup,
|
||||
disableNotification: message.IsSilent,
|
||||
cancellationToken: ct
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Отправляет файлы как документы (по одному или пачкой).
|
||||
/// </summary>
|
||||
public async Task SendFilesAsync(long chatId, IEnumerable<FileDescriptor> files, CancellationToken ct)
|
||||
{
|
||||
foreach (var f in files)
|
||||
{
|
||||
if (f.Content is not null)
|
||||
{
|
||||
var input = new InputFileStream(f.Content, f.Name);
|
||||
await _bot.SendDocument(chatId, input, cancellationToken: ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Если контент не загружен, и есть FileId — отправляем по Id
|
||||
await _bot.SendDocument(chatId, new InputFileId(f.Id), cancellationToken: ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using BotPages.Core;
|
||||
using Telegram.Bot;
|
||||
|
||||
namespace BotPages.Telegram
|
||||
{
|
||||
/// <summary>
|
||||
/// FileService для Telegram: загрузка по FileId.
|
||||
/// </summary>
|
||||
public sealed class TelegramFileService : IFileService
|
||||
{
|
||||
private readonly ITelegramBotClient _bot;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт файловый сервис для Telegram.
|
||||
/// </summary>
|
||||
public TelegramFileService(ITelegramBotClient bot) => _bot = bot;
|
||||
|
||||
/// <summary>
|
||||
/// Загружает файл по идентификатору Telegram FileId.
|
||||
/// </summary>
|
||||
public async Task<FileDescriptor> DownloadAsync(string fileId, CancellationToken ct)
|
||||
{
|
||||
var file = await _bot.GetFile(fileId, ct);
|
||||
var ms = new MemoryStream();
|
||||
await _bot.DownloadFile(file.FilePath!, ms, ct);
|
||||
ms.Position = 0;
|
||||
var name = System.IO.Path.GetFileName(file.FilePath!);
|
||||
return new FileDescriptor(file.FileId, name, "application/octet-stream", ms);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Загружает несколько файлов по их идентификаторам.
|
||||
/// </summary>
|
||||
public async Task<IReadOnlyList<FileDescriptor>> DownloadManyAsync(IEnumerable<string> fileIds, CancellationToken ct)
|
||||
{
|
||||
var res = new List<FileDescriptor>();
|
||||
foreach (var id in fileIds)
|
||||
res.Add(await DownloadAsync(id, ct));
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отправляет один файл в чат.
|
||||
/// </summary>
|
||||
public async Task SendAsync(IChatClient client, long chatId, FileDescriptor file, CancellationToken ct)
|
||||
{
|
||||
await client.SendFilesAsync(chatId, new[] { file }, ct);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Отправляет несколько файлов в чат.
|
||||
/// </summary>
|
||||
public Task SendManyAsync(IChatClient client, long chatId, IEnumerable<FileDescriptor> files, CancellationToken ct)
|
||||
{
|
||||
return client.SendFilesAsync(chatId, files, ct);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +1,157 @@
|
||||
using BotPages.Core;
|
||||
using BotPages.Core.Abstractions;
|
||||
using BotPages.Core.Context;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Telegram.Bot;
|
||||
using Telegram.Bot.Types;
|
||||
|
||||
namespace BotPages.Telegram
|
||||
namespace BotPages.Telegram;
|
||||
|
||||
/// <summary>
|
||||
/// Утилита для маппинга Telegram Update → UpdateContext.
|
||||
/// </summary>
|
||||
public static class TelegramUpdateMapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Утилиты для извлечения контекста из Telegram Update.
|
||||
/// Маппинг Telegram Update в UpdateContext BotPages.
|
||||
/// </summary>
|
||||
public static class TelegramUpdateMapper
|
||||
public static UpdateContext Map(Update update, TelegramBotClient client)
|
||||
{
|
||||
/// <summary>
|
||||
/// Преобразует Telegram Update в универсальный UpdateContext.
|
||||
/// </summary>
|
||||
public static UpdateContext Map(ITelegramBotClient bot, INavigationService nav, IStateStore store, Update update)
|
||||
var chat = update.Message?.Chat ?? update.CallbackQuery?.Message?.Chat;
|
||||
var user = update.Message?.From ?? update.CallbackQuery?.From;
|
||||
|
||||
var userContext = new UserContext
|
||||
{
|
||||
var chat = update.Message?.Chat ?? update.CallbackQuery?.Message?.Chat;
|
||||
var user = update.Message?.From ?? update.CallbackQuery?.From;
|
||||
Id = user?.Id.ToString() ?? "unknown",
|
||||
DisplayName = user?.Username,
|
||||
};
|
||||
|
||||
var text = update.Message?.Text ?? update.CallbackQuery?.Data;
|
||||
|
||||
var files = new List<FileDescriptor>();
|
||||
if (update.Message?.Document is { } doc)
|
||||
var chatContext = new ChatContext
|
||||
{
|
||||
Id = chat?.Id.ToString() ?? "unknown",
|
||||
Title = chat?.Title,
|
||||
Capabilities = new Capabilities
|
||||
{
|
||||
files.Add(new FileDescriptor(doc.FileId, doc.FileName ?? "file", doc.MimeType ?? "application/octet-stream"));
|
||||
SupportsInlineButtons = true,
|
||||
SupportsReplyButtons = true,
|
||||
SupportsAlbums = true,
|
||||
SupportsFormattingMarkdown = true,
|
||||
SupportsFormattingHtml = true,
|
||||
MaxMessageLength = 4096,
|
||||
}
|
||||
if (update.Message?.Photo is { } photos && photos.Count() > 0)
|
||||
};
|
||||
|
||||
string? text = null;
|
||||
UpdateKind kind = UpdateKind.None;
|
||||
var files = new List<FileDescriptor>();
|
||||
|
||||
if (update.Message is { } msg)
|
||||
{
|
||||
|
||||
if (msg.Text is not null)
|
||||
{
|
||||
var largest = photos.OrderBy(p => p.FileSize).Last();
|
||||
files.Add(new FileDescriptor(largest.FileId, "photo.jpg", "image/jpeg"));
|
||||
text = msg.Text;
|
||||
kind |= UpdateKind.Text;
|
||||
}
|
||||
|
||||
return new UpdateContext
|
||||
if (msg.Photo is { Length: > 0 })
|
||||
{
|
||||
Client = new TelegramClientAdapter(bot),
|
||||
Chat = new ChatContext { Id = chat!.Id, Title = chat.Title },
|
||||
User = new UserContext { Id = user!.Id, DisplayName = $"{user.FirstName} {user.LastName}" },
|
||||
Text = text,
|
||||
IncomingFiles = files,
|
||||
RawUpdate = update,
|
||||
Nav = nav,
|
||||
State = store
|
||||
};
|
||||
foreach (var p in msg.Photo)
|
||||
{
|
||||
files.Add(new FileDescriptor
|
||||
{
|
||||
Id = p.FileId,
|
||||
Name = "photo",
|
||||
Extension = "jpg",
|
||||
Size = p.FileSize ?? 0,
|
||||
Kind = FileKind.Photo,
|
||||
GetStreamAsync = GetStreamAsync(client, p.FileId),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.Document is not null)
|
||||
{
|
||||
files.Add(new FileDescriptor
|
||||
{
|
||||
Id = msg.Document.FileId,
|
||||
Name = msg.Document.FileName ?? "document",
|
||||
Extension = System.IO.Path.GetExtension(msg.Document.FileName) ?? "bin",
|
||||
Size = msg.Document.FileSize ?? 0,
|
||||
Kind = FileKind.Document,
|
||||
Mime = msg.Document.MimeType,
|
||||
GetStreamAsync = GetStreamAsync(client, msg.Document.FileId),
|
||||
});
|
||||
}
|
||||
|
||||
if (msg.Audio is not null)
|
||||
{
|
||||
files.Add(new FileDescriptor
|
||||
{
|
||||
Id = msg.Audio.FileId,
|
||||
Name = msg.Audio.FileName ?? "audio",
|
||||
Extension = System.IO.Path.GetExtension(msg.Audio.FileName) ?? "mp3",
|
||||
Size = msg.Audio.FileSize ?? 0,
|
||||
Kind = FileKind.Audio,
|
||||
Mime = msg.Audio.MimeType,
|
||||
GetStreamAsync = GetStreamAsync(client, msg.Audio.FileId),
|
||||
});
|
||||
}
|
||||
|
||||
if (msg.Video is not null)
|
||||
{
|
||||
files.Add(new FileDescriptor
|
||||
{
|
||||
Id = msg.Video.FileId,
|
||||
Name = "video",
|
||||
Extension = "mp4",
|
||||
Size = msg.Video.FileSize ?? 0,
|
||||
Kind = FileKind.Video,
|
||||
Mime = msg.Video.MimeType,
|
||||
GetStreamAsync = GetStreamAsync(client, msg.Video.FileId),
|
||||
});
|
||||
}
|
||||
|
||||
if (files.Count > 0)
|
||||
{
|
||||
kind |= UpdateKind.File;
|
||||
}
|
||||
}
|
||||
|
||||
if (update.CallbackQuery is { } cb)
|
||||
{
|
||||
kind |= UpdateKind.Button;
|
||||
text = cb.Data;
|
||||
}
|
||||
|
||||
|
||||
return new UpdateContext
|
||||
{
|
||||
MessengerType = "Telegram",
|
||||
User = userContext,
|
||||
Chat = chatContext,
|
||||
Text = text,
|
||||
Kind = kind,
|
||||
Files = files
|
||||
};
|
||||
}
|
||||
|
||||
private static Func<CancellationToken, Task<Stream>> GetStreamAsync(TelegramBotClient client, string fileId)
|
||||
{
|
||||
|
||||
Func<CancellationToken, Task<Stream>> getStreamAsync = async _ =>
|
||||
{
|
||||
var file = await client.GetFile(fileId);
|
||||
var stream = new MemoryStream();
|
||||
await client.DownloadFile(file, stream);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
};
|
||||
|
||||
return getStreamAsync;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user