В современном мире телеграм-боты стали неотъемлемой частью нашей повседневной жизни. Они стали незаменимыми помощниками в самых разнообразных задачах – от автоматизации повседневных операций до обеспечения клиентов высококачественным сервисом.

Иногда бывает так, что возникает необходимость создать собственного телеграм-бота. Меня тоже коснулся этот момент. Начав искать готовые решения, столкнулся с небольшим разочарованием: оказалось, что подходящих фреймворков нет, и придется разрабатывать бота с нуля.

Не страшно! В этой статье я поделюсь с вами всеми этапами создания собственного фреймворка для телеграм-бота с использованием C#.

Для начал я расскажу, что я хотел от телеграм бота. Из-за особенностей проекта пришлось выбрать long-polling модель вместо webhooks. Бот должен быть разработан на платформе asp.net. Нужны были возможности легко настраивать, добавления метрик или кэширования данных. Бот будет выполнять задачи обновления и чтения информации.

Для начала пойдем по пути ничего не подозревающего разработчика и попытаемся найти готовые решения.

Спойлер: написание ботов на c# почему-то не самое популярное и благоприятное направление.

Как опорную точку возьмем библиотеку Telegram.Bot, как саму популярную и развитую библиотеку для взаимодействия с API телеграм ботов. Зайдем на github и поищем кто и как ее использует.

Сюрприз, но c# почему-то не самый оптимальный выбор для написания телеграм ботов. Единственный серьезный проект, который я нашел, это какая-то странная игра для телеграма tgwerewolf github, но активно развивающаяся. Данный проект использует чистый Telegram.Bot, что-то для себя конечно можно вынести, но переносить будет тяжело.

C фрейморками дела обстоят чуть лучше. Я нашел аж 2 готовых решения.
Начнем с TelegramBotFramework — начали за здравие, а закончили за упокой.

Проект в вялотекущем состоянии, хоть и начинался бодро: сейчас поддерживается одним человеком. Из плюсов, если строго следовать указаниям и не писать что-то сложное с дальнейшим расширением возможностей самого фреймворка, и вы готовы смириться с отсутствием внедрения зависимостей, то должно очень неплохо зайти. Даже есть набор "ui" элементов, что забавно.

На самом деле внедрение зависимостей есть, но оно костыльное и лучше даже палкой не тыкать, только если осторожно.

Ну и из минусов: тяжело было адаптировать в существующую экосистему asp.net; отсутствие внедрение зависимостей; тяжело кастомизировать под себя. В общем, точно не мой выбор, так что идем дальше.

И напоследок самое вкусное — TgBotFramework — небольшой, но интересный фреймворк от контрибьютера Telegram.Bot. Фреймворк хорошо написан, дружит с внедрением зависимостей, есть удобное описание команд для телеграм бота. Ну просто сказка, берем и идем писать телеграм бота.

Увы, для моего случая уже был готовый код, который и кэширует, и метрики берет, и все это на медиаторе, а дублировать логику не хотелось. Также не совсем было понятно, как контролировать цикл жизни обработчика команды от телеграм бота, может как-то через реализацию своего Processor, но я не пробовал.

Обидно, что c# и экосистема .NET никак не используются для написания телеграм ботов. Если глянуть в другие языки — python-telegram-bot и Telethon, go — telegram-bot-api и самая интересная реализация на rust— teloxide

Хотя может я плохо искал. Буду рад, если подскажете существуют ли еще какие-нибудь реализации фрейморков для телеграм бота на c#.

Ну, раз я такой притязательный к выбору, то не отчаиваемся — руки в ноги и делаем из костылей свое решение. Как вы могли уже догадаться, за основу будет взят медиатор. Самое главное при регистрации обработчиков команд объявить их как scoped.

Для начала напишем фоновую службу, которая будет запускать обработчик сообщений от бота:

public class PollingServiceBase : BackgroundService
{
    private readonly ITelegramBotClient botClient;
    private readonly IUpdateHandler handler;

    private readonly ReceiverOptions receiverOptions = new()
    {
        AllowedUpdates = [UpdateType.Message, UpdateType.CallbackQuery],
        ThrowPendingUpdates = true,
    };

    public PollingServiceBase(ITelegramBotClient botClient, IUpdateHandler handler, IOptions<AcadeMarketConfiguration> options, ILogger<PollingServiceBase> logger)
    {
        this.botClient = botClient;
        this.handler = handler;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
	        try
	        {
		        await botClient.ReceiveAsync(updateHandler: handler, receiverOptions: receiverOptions, cancellationToken: stoppingToken);
	        }
	        catch
	        {
	        }
        }
    }

Тут все просто. Крутим в фоне в цикле обработку сообщений. В ReceiveAsync, а точнее в DefaultUpdateReceiver, есть собственный цикл по отправке сообщений, но собственный while пригодится позже для восстановления после ошибок в нашем коде, как и сама обработка ошибок. Идем дальше к UpdateHandler.

Вот тут начинается вся магия!

public class UpdateHandler(IServiceProvider serviceProvider) : IUpdateHandler, IDisposable
{
    internal readonly Dictionary<long, (string command, IServiceScope scope)> UsersCommand = [];

    public UpdateHandler(
        Dictionary<long, (string command, IServiceScope scope)> dictionary,
        IServiceProvider serviceProvider) : 
        this(serviceProvider)
    {
        UsersCommand = dictionary;
    }

    public async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
    {
        await using var scope = serviceProvider.CreateAsyncScope();
        var publisher = scope.ServiceProvider.GetRequiredService<IPublisher>();

        var chat = update.GetChat();
        ArgumentNullException.ThrowIfNull(chat);

        var getCommand = new GetBotCommandNotification { Update = update };
        await publisher.Publish(getCommand, cancellationToken);

        var tuple = ChooseCommand(chat.Id, getCommand.Command);
        if (!tuple.HasValue) return;

        var provider = tuple.Value.scope;
        var command = tuple.Value.command;

        var scopeMediator = provider.ServiceProvider.GetRequiredKeyedService<IMediator>(nameof(CustomTelegramMediator));
        IBotCommandContext updateReceived = new BotCommandContext(update, chat, command);

        try
        {
            await scopeMediator.Publish(updateReceived, cancellationToken);            		
            ArgumentException.ThrowIfNullOrWhiteSpace(updateReceived.Command);
        }
        catch (Exception e)
        {
            var asyncServiceScope = new AsyncServiceScope(provider);
            await asyncServiceScope.DisposeAsync();

            UsersCommand.Remove(chat.Id);

            throw;
        }

        if (updateReceived.NeedRefresh)
        {
            provider.Dispose();
            UsersCommand.Remove(chat.Id);
        }
    }

    private (IServiceScope scope, string command)? ChooseCommand(long chatId, string botCommand)
    {
        var exist = UsersCommand.TryGetValue(chatId, out var tuple);

        IServiceScope provider;
        var command = botCommand;

        if (string.IsNullOrWhiteSpace(command) && !exist) return null;
        if (!string.IsNullOrWhiteSpace(command) && !exist)
        {
            provider = serviceProvider.CreateScope();
            UsersCommand.Add(chatId, (command, provider));
        }
        else if (string.IsNullOrWhiteSpace(command) && exist)
        {
            provider = tuple.scope;
            command = tuple.command;
        }
        else
        {
            if (tuple.command != command)
            {
                tuple.scope.Dispose();

                provider = serviceProvider.CreateScope();
                UsersCommand[chatId] = (command, provider);
            }
            else
            {
                provider = tuple.scope;
                command = tuple.command;
            }
        }

        return (provider, command);
    }
}

Ничего удивительного нет: для каждого пользователя заводим scope и выбранную им команду. Дальше смотрим, есть ли какие-нибудь изменения: пользователь поменял команду; команда вернула NeedRefresh и так далее. Единственно, что может смутить — var scopeMediator = provider.ServiceProvider.GetRequiredKeyedService<IMediator>(nameof(CustomTelegramMediator)), но об этом позже.

Получение команды происходит через GetBotCommandNotification, не знаю, насколько это правильно, что notification возвращает результат, записывая его в GetBotCommandNotification. Сам GetBotCommandHandler очень простой:

public abstract class GetBotCommandHandler : INotificationHandler<GetBotCommandNotification>
{
    public abstract UpdateType Type { get; }

    public ValueTask Handle(GetBotCommandNotification command, CancellationToken cancellationToken)
    {
        if (command.Update.Type != Type) return ValueTask.CompletedTask;
        if (!CanHandle(command)) return ValueTask.CompletedTask;

        SetCommand(command);
        return ValueTask.CompletedTask;
    }

    protected internal abstract bool CanHandle(GetBotCommandNotification notification);

    protected internal abstract void SetCommand(GetBotCommandNotification notification);
}

Пришло время обработки команд. Процесс следующий: мы формируем уведомление с необходимой информацией для обработки команды из Telegram. Затем отправляем это уведомление слушателям. Если находим конкретного слушателя для отправленной команды, приступаем к обработке. В противном случае пропускаем уведомление, поскольку не имеем слушателя для его обработки.

Однако здесь возникает проблема: концептуально уведомление нужно для обработки множеством слушателей, а еще мы не можем применить IPipelineBehavior к уведомлениям.

Но на самом деле это не так. В Mediatr добавлена возможность создания своего медиатора и доступ к уведомлениям при их публикации. К тому же, недавно была добавлена возможность получения объекта по ключу (AddKeyed и его друзья), как все удачно сложилось. По-этому идем и пишем собственный медиатр для обработки сообщений.

public class CustomTelegramMediator : MediatR.Mediator
{
    private readonly IServiceProvider serviceProvider;

    public CustomTelegramMediator(IServiceProvider serviceProvider) : base(serviceProvider)
    {
        this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
    }

    public CustomTelegramMediator(IServiceProvider serviceProvider, INotificationPublisher publisher) : base(serviceProvider, publisher)
    {
        this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
    }

    protected override async Task PublishCore(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification, CancellationToken cancellationToken)
    {
        if (notification is not IBotCommandContext botCommandContext) return;

        var handler = handlerExecutors.Single(handlerExecutor =>
            handlerExecutor.HandlerInstance is TelegramCommandHandler commandHandler &&
            commandHandler.Command.Equals(botCommandContext.Command));

        var wrapperType = typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notification.GetType());
        var wrapper = Activator.CreateInstance(wrapperType, [handler.HandlerCallback]) ?? throw new InvalidOperationException($"Could not create wrapper type for {notification.GetType()}");
        var handlerBase = (NotificationHandlerBase)wrapper;

        await handlerBase.Handle(notification, serviceProvider, cancellationToken);
    }
}

И да, это почти все, остался NotificationHandlerWrapperImpl для обработки уведомления и оборачивания ее в IPipelineBehavior

public class NotificationHandlerWrapperImpl<TRequest>(Func<TRequest, CancellationToken, Task> handlerCallback) : NotificationHandlerBase
    where TRequest : INotification
{
    public Task Handle(TRequest request, IServiceProvider serviceProvider, CancellationToken cancellationToken)
    {
        return serviceProvider
            .GetServices<IPipelineBehavior<TRequest, Unit>>()
            .Reverse()
            .Aggregate((RequestHandlerDelegate<Unit>)Handler,
                (next, pipeline) => () => pipeline.Handle(request, next, cancellationToken))();

        async Task<Unit> Handler()
        {
            await handlerCallback(request, cancellationToken);
            return Unit.Value;
        }
    }

    public override Task
        Handle(object request, IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
        Handle((TRequest)request, serviceProvider, cancellationToken);
}

Теперь опишем контракт для уведомления об обработке комманды из телеграм бота

public interface IBotCommandContext : INotification
{
    public Update Update { get; }

    public Chat Chat { get; }

    public string Command { get; }

    public Message? Message { get; }

    public IReadOnlyCollection<string>? Args { get; }

    public bool NeedRefresh { get;  }

    public void AddMessage(Message? message);

    public void AddArgs(IEnumerable<string> args);

    public void SetNeedRefresh();
}

А теперь если нам нужен IPipelineBehavior непосредственно для обработки комманды, то просто указываем where TRequest : IBotCommandContext. К примеру реализуем сохранение подробной информации о пользователях, которые пользуются ботом.

public class AddUserBehaviour<TRequest, TResponse>(IDistributedCache cache) : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IBotCommandContext
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        var value = await cache.GetStringAsync(request.Chat.Id.ToString(), cancellationToken);
        if (!string.IsNullOrWhiteSpace(value)) return await next();

        await cache.SetAsync(request.Chat.Id.ToString(), Encoding.ASCII.GetBytes(request.Chat.Id.ToString()), cancellationToken);

        return await next();
    }
}

А если надо подвязаться к существующей IPipelineBehavior, добавляем к реализации контракта IBotCommandContext маркер. К примеру есть следующий RequestPerformanceBehaviour с маркером IRequestPerformance

public class RequestPerformanceBehaviour<TRequest, TResponse>(
    TimeProvider timeProvider,
    ILogger<RequestPerformanceBehaviour<TRequest, TResponse>> logger)
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequestPerformance
{
    private readonly ILogger<RequestPerformanceBehaviour<TRequest, TResponse>> logger = logger ?? throw new ArgumentNullException(nameof(logger));
    private readonly TimeProvider timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        var start = timeProvider.GetTimestamp();

        var response = await next();

        var diff = timeProvider.GetElapsedTime(start);

        if (diff.TotalMilliseconds < 500) return response;

        var name = typeof(TRequest).Name;

        logger.LongRunningRequest(name, diff, request);

        return response;
    }
}

internal static partial class LoggerExtensions
{
    [LoggerMessage(EventId = 1, EventName = nameof(LongRunningRequest), Level = LogLevel.Warning, Message = "Long Running Request: {Name} ({Elapsed} milliseconds) {Request}")]
    public static partial void LongRunningRequest(this ILogger logger, string name, TimeSpan elapsed, object request);
}

public interface IRequestPerformance;

Добавляем IRequestPerformance к реализации IBotCommandContext и все.

Дальше обработка комманд, для этого реализуем общий TelegramCommandHandler

public abstract class TelegramCommandHandler : INotificationHandler<IBotCommandContext>
{
    public abstract string Description { get; }

    public abstract string Command { get; }

    public virtual int Calls { get; protected set; }

    public virtual async Task Handle(IBotCommandContext notification, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(notification.Command)) return;
        if (string.IsNullOrWhiteSpace(Command)) return;
        if (!string.Equals(notification.Command, Command)) return;

        Calls++;

        await Core(notification, cancellationToken).ConfigureAwait(false);

        if (await IsLastStage())
        {
            notification.SetNeedRefresh();
        }
    }

    protected abstract Task Core(IBotCommandContext notification, CancellationToken cancellationToken);

    public virtual Task<bool> IsLastStage() => new(true);
}

Дальше остается только клепать команды пока горячо.

Добавим простую команду:

public class TestCommandHandler : TelegramCommandHandler
{
    public override string Description { get; }
    public override string Command => "/hi";

    private readonly ITelegramBotClient botClient;

    public TestCommandHandler(ITelegramBotClient botClient)
    {
        this.botClient = botClient;
    }

    protected override async Task Core(IBotCommandContext notification, CancellationToken cancellationToken)
    {
        await botClient.SendTextMessageAsync(notification.Chat, "Hi", cancellationToken: cancellationToken);
    }
}

Добавим команду с несколькими этапами, эмитируем простой конечный автомат:

public class SignupCommandHandler(IMediator mediator, ITelegramBotClient botClient) : TelegramCommandHandler
{
    public const string CommandValue = "/signup";
    public const string DescriptionValue = "Зарегистрироваться";

    public override string Description => DescriptionValue;
    public override string Command => CommandValue;

    private readonly IMediator mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
    private readonly ITelegramBotClient botClient = botClient ?? throw new ArgumentNullException(nameof(botClient));

    private string Firstname;
    private string Surname;
    private string Age;

    protected override Task Core(IBotCommandContext notification, CancellationToken cancellationToken)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(notification.Message?.Text);

        switch (Calls)
        {
            case 2:
                Firstname = notification.Message.Text;
                break;
            case 3:
                Surname = notification.Message.Text;
                break;
            case 4:
                Age = notification.Message.Text;
                break;
        }

        return Calls switch
        {
            1 => EnterFirstname(notification, cancellationToken),
            2 => EnterSurname(notification, cancellationToken),
            3 => EnterAge(notification, cancellationToken),
            4 => AddNewUser(notification, cancellationToken),
            _ => Task.CompletedTask,
        };
    }

    private async Task EnterFirstname(IBotCommandContext notification, CancellationToken cancellationToken)
    {
        const string text = "Пожалуйста, введите Ваше имя";
        await botClient.SendTextMessageAsync(chatId: notification.Chat, text: text, cancellationToken: cancellationToken);
    }

    private async Task EnterSurname(IBotCommandContext notification, CancellationToken cancellationToken)
    {
        const string text = "Пожалуйста, введите Вашу фамилию";
        await botClient.SendTextMessageAsync(chatId: notification.Chat, text: text, cancellationToken: cancellationToken);
    }

    private async Task EnterAge(IBotCommandContext notification, CancellationToken cancellationToken)
    {
        const string text = "Пожалуйста, введите Ваш возраст";
        await botClient.SendTextMessageAsync(chatId: notification.Chat, text: text, cancellationToken: cancellationToken);
    }

    private async Task AddNewUser(IBotCommandContext notification, CancellationToken cancellationToken)
    {
        var text = "Регистрирую.";
        await botClient.SendTextMessageAsync(chatId: notification.Chat, text: text, cancellationToken: cancellationToken);

        text = $"Добро пожаловать! {Firstname} {Surname}";
        await botClient.SendTextMessageAsync(chatId: notification.Chat, text: text, cancellationToken: cancellationToken);
    }

    public override ValueTask<bool> IsLastStage()
    {
        return ValueTask.FromResult(Calls >= 4);
    }
}

Это только начало, дальше нас останавливают только фантазия и хотелки бизнеса. К примеру, можно накрутить TransactionBehavior на команды, которые посылаются из TelegramCommandHandler, так мы точно будем знать, что все прошло успешно и можно выдавать результат пользователю. Так же с кэшированием, метриками и так далее. Ну, конечно, если Ваша инфраструктура построена на медиаторе, хех.

В конце обработка ошибок. Единой точкой поимки ошибок является PollingServiceBase, так как внутри ReceiveAsync у себя при внутренних ошибках вызовет HandlePollingErrorAsync у IUpdateHandler. Так как IUpdateHandler располагает достаточной информацией, чтобы понять, у какого пользователя произошла ошибка, и ему же написать о ней.

Или можно можно обработчики комманд оборачивать в IPipelineBehavior для обработки ошибок, ведь теперь мы так умеем.

А еще подобную реализацию легко и приятно поддерживать и тестировать, так как каждый обработчик имеет свой класс с логикой.

На этом все! Конечно, вышеописанная реализация только поверхностно покрывает обработку команд от телеграм бота. К примеру, неплохо бы прикрутить получение id сообщений, и удалять их, если пользователь выбрал другую команду.

Или же имееть возможность восстанавливать контекст пользователя, при критических ситуациях. Но все это легко реализацуется благодаря добавленной возможности втиснуться в процесс обработки сообщений и кастомному медиатору.

Пример данной реализации можно найти на моем github. Если у Вас есть предложения, вопросы или улучшения — смело говорите, рад буду почитать и ответить.

Комментарии (13)


  1. kekekk
    19.04.2024 14:50
    +1

    Есть как минимум две замечательные библиотеки для ТГ под .нет:

    • Telegram.Bot - собственно, для ботов

    • WTelegramClient - для написания альтернативного клиента ТГ. Бывает нужно, если не устраивают ограничения бота (мне например, нужно было пересылать файлы > 20МБ)


    1. Kataanee Автор
      19.04.2024 14:50

      Спасибо за WTelegramClient. Telegram.Bot в статье я уже упомянул. Проблема в том, что обе эти библиотеки для работы с ботами через API требуют написания дополнительного кода для обработки сообщений. Хотелось бы сосредоточиться сразу на обработке команд, а не на этих деталях. Об этой проблеме в .NET я и упомянул в статье.


    1. TheCat174
      19.04.2024 14:50
      +1

      Для пересылки файлов больше 20мб подключается локальный API.
      https://github.com/tdlib/telegram-bot-api


  1. sdramare
    19.04.2024 14:50
    +3

    Зачем столько бойлерплейт кода, если тг поддерживает веб хуки и простейший бот это приложие в 3 строчки плюс модель с вызовом http post в ответ, завернутый в aws/azure лямду


    1. assdestr0yer
      19.04.2024 14:50

      Я вот тоже не понимаю зачем все эти библиотеки непонятные, на том же PHP с нуля всё написать можно без всяких библиотек и запустить это всё почти на любом самом дешёвом хостинге


      1. withkittens
        19.04.2024 14:50
        +1

        Я вот тоже не понимаю зачем все эти библиотеки непонятные

        Затем, что C# - строго-типизированный язык, ему нужны типы данных, и эти непонятные библиотеки уже всё для вас имплементируют, например, Chat или Message. Вы точно хотите это писать руками?

        запустить это всё почти на любом самом дешёвом хостинге

        Так и на шарпе можно.


    1. Kataanee Автор
      19.04.2024 14:50

      Я в самом начале статьи сказал: "Из-за особенностей проекта пришлось выбрать long-polling модель вместо webhooks"


    1. withkittens
      19.04.2024 14:50
      +1

      Вы сравниваете тёплое (вебхуки vs long-polling) с мягким (количество кода).

      Почему не вебхуки? Для них нужно светить задницей HTTP-сервером в интернет, либо завязываться на облако. А зачем мне этот ваш амазон, если с long-polling можно запустить бота хоть на тостере?


      1. sdramare
        19.04.2024 14:50

        Статья называется "Просто, но быстро". Просто и быстро это в три строчки сделать хэндлер веб хуков и залить лямбду в aws на free tier(1 миллион запросов в месяц бесплатно). И желательно на том, что имеет короткое время старта, по-этому и "c# почему-то не самый оптимальный выбор для написания телеграм ботов. " А прикручивание лонг-пуллинга, медиатора и прочее для такой задачи выглядит как overkill. Если уж тут делать "энтерпрайзно", то бот должен быть простым фасадом перед апи бэкэнда, а не крутить логику самостоятельно и по-этому 90% того, что написано в статье, особенно включая трюки с скопом, просто не нужно.


        1. BoBaHPyt
          19.04.2024 14:50

          По-моему это не единственная проблема Шарпа, поражающая отсутствие либ для создания телеграм ботов, и кучи чего ещё.

          На сколько я помню, ещё года 3 назад нужно было очень сильно пострадать, чтобы запустить .net приложение на сервере (если конечно сервер не на окнах, тогда страдать придется с процентами). А зачем страдать, когда можно взять любой кроссплатформенный язык и развернуть бота за пару-тройку минут.

          Поэтому в принципе любая работа с сетью, которая должна выполняться дольше часа, на шарпе довольно затруднительна, ибо в этом направлении он развиваться не мог.


          1. sdramare
            19.04.2024 14:50
            +1

            , поражающая отсутствие либ для создания телеграм ботов

            Автор буквально в статье перечисляет либы для создания ботов, что значит "отсуствие"?

            На сколько я помню, ещё года 3 назад нужно было очень сильно пострадать

            Нет, это не правда, проблемы были до 2016(8 лет назад), потом вышел dotnet core, сейчас в принципе весь дотнет ориентирован на линукс и работу в контейнерах.

            Поэтому в принципе любая работа с сетью, которая должна выполняться дольше часа, на шарпе довольно затруднительна

            В чем она конкретно она затруднительна, если оно работает через через sys/socket как и остальные?  

            этом направлении он развиваться не мог.

            что значит "не мог развивать" если он сейчас по перфомансу обходит тоже самый Go? Это он так "не развился", получается в будущем можно топ-1 ждать, быстрее раста и с++?


        1. Kataanee Автор
          19.04.2024 14:50

          простым фасадом перед апи бэкэнда

          Ничего не мешает поменять логику вызовов команд, которые меняют что‑то в базе, на вызов апи бэкэнда.

          хэндлер веб хуков и залить лямбду в aws на free tier(1 миллион запросов в месяц бесплатно)


          Да, так конечно быстрее. Но опять, не всегда получается делать через хуки и остается вопрос хранения состояния шагов и команд пользователя.

          особенно включая трюки с скопом, просто не нужно.

          Опять же. А как тогда хранить состояние шагов пользователя?

          А прикручивание лонг-пуллинга, медиатора и прочее для такой задачи выглядит как overkill

          Разве для каждой команды выделен собственный обработчик — уже overkill? Так же было бы с хэндлером веб хуков, пришлось бы разделять каждый обработчик в свой класс, из-за чего привело бы опять к медиатору.
          И хотя собственная реализация медиатора может показаться излишней. Но давно требовалась возможность в Mediatr оборачивать публикации уведомлений в PipelineBehavior и уведомлять конкретных подписчиков. В результате просто добавили доступ к уведомлениям при их публикации при публикации Mediatr.


  1. JustLooKeD
    19.04.2024 14:50
    +1

    Начав искать готовые решения, столкнулся с небольшим разочарованием:
    оказалось, что подходящих фреймворков нет, и придется разрабатывать бота
    с нуля.

    aiogram для вас шутка? Или вы в Bing искали ? Одна из самых популярных и мощных библиотек для telegram ботов