MinimalTelegramBot.StateMachine 0.0.11

dotnet add package MinimalTelegramBot.StateMachine --version 0.0.11                
NuGet\Install-Package MinimalTelegramBot.StateMachine -Version 0.0.11                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MinimalTelegramBot.StateMachine" Version="0.0.11" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add MinimalTelegramBot.StateMachine --version 0.0.11                
#r "nuget: MinimalTelegramBot.StateMachine, 0.0.11"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install MinimalTelegramBot.StateMachine as a Cake Addin
#addin nuget:?package=MinimalTelegramBot.StateMachine&version=0.0.11

// Install MinimalTelegramBot.StateMachine as a Cake Tool
#tool nuget:?package=MinimalTelegramBot.StateMachine&version=0.0.11                

Welcome to the Minimal Telegram Bot project

Minimal Telegram Bot is a modern .NET framework for building Telegram Bots using simple and concise syntax inspired by ASP.NET Core Minimal APIs.

Intentions

The development of a Minimal Telegram Bot project was started as an attempt to solve the problem with the lack of a single convenient solution for Telegram bot development on the .NET platform.

A great community-approved Telegram Bot API wrapper already exists in .NET. However, creating bots for more complex cases using only a bare-bones wrapper soon becomes cumbersome and unsupportable. Existing solutions that solve such a problem were made very focused on the specific author's problem and not fit well to wider scope of demands, and some of the solutions are abandoned as of today.

With some experience in development and other languages, I developed a proof of concept where writing bots was made way easier, hiding unnecessary boilerplate details. The developer and consumer of this project now only need to focus on their tasks, write the code that is needed here and now to solve the business tasks and not worry about the rest.

Considerations

Detailed documentation is still in progress with framework's public APIs so some changes are possible in the future. The project roadmap includes considerations for changing the policy of support and updates as well as the communication practices with the audience if it gains enough audience.

At the moment, this framework already has enough features and customization mechanisms to use in real projects. Future plans are to create more public API methods for user convenience, tailor the API and provide full documentation.

Features

  • Support for the latest Telegram Bot API version, receiving fast updates thanks to Telegram.Bot, the most popular .NET Client for Telegram Bot API

Framework features

Extensions

  • Support for the Finite State Machine concept for Telegram bots using StateMachine extension (WIP)
  • Built-in bot localization features using Localization extension (WIP)

Getting updates from Telegram

  • Support getting updates via both webhook and polling
  • Provides direct responses to webhook requests

Getting started

Prerequisites

This project is using .NET 8 for now, so be sure to install the appropriate version of .NET, and that's all you need to start developing.

New project

Start by creating an "Empty" ASP.NET Core application using the command line:

dotnet new web -n MyBot -f net8.0

Or create a new ASP.NET Core web application in your IDE, and choose "Empty" for the project template.

Installation

Install the library using NuGet package manager in your IDE:

dotnet add package MinimalTelegramBot

In addition, you can add some extension packages that provide Localization and State Machine features:

dotnet add package MinimalTelegramBot.Localization
dotnet add package MinimalTelegramBot.StateMachine

Usage

Basic setup

In your Program.cs you can use following basic setup:

using MinimalTelegramBot.Builder;
using MinimalTelegramBot.Handling;

var builder = BotApplication.CreateBuilder(args);
var bot = builder.Build();

bot.HandleCommand("/start", () => "Hello, World!");

bot.Run();

In the code above we are creating BotApplication using builder just like in web applications. When the bot instance are set up and ready for handling configuration, we call HandleCommand() with specified /start command and return Hello, World! message as a result.

By default, polling method for getting updates from Telegram is used because it's faster to start development with polling method and polling requires less effort to configure. Bot token is specified in configuration with "BotToken" key. For local development you can set your bot token using dotnet user-secrets tool:

dotnet user-secrets init
dotnet user-secrets set "BotToken" "your bot token"

In other environments you can use .env files or just set bot token in appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "BotToken": "your bot token"
}

Ensure that your bot token is not included in version control to prevent unauthorized access to your bot.

If you have any trouble with telegram bot token or @BotFather, read the official tutorial.

When all configuration is complete, just run program with dotnet run or via IDE's interface and test your bot.

Advanced handling examples

Localizer usage

Localization is essential when creating bots for users across multiple regions. Here, the localizer service allows you to retrieve messages dynamically based on the user's locale. How to add localizer in bot app with single locale before building the bot app:

builder.Services.AddSingleLocale(
    new Locale("en"),
    locale => locale.EnrichFromFile("Localization/en.yaml"));

How to use localizer services after building bot app:

bot.UseLocalization();

To use different languages, you need to implement IUserLocaleProvider interface, in that way you provide the context for resolving user's locale. You can find more details about adding localization and different languages in the code samples.

The command below uses localizer to respond to the /start command with a "HelloText" message retrieved and a dynamically generated keyboard based on the user's locale from method MenuKeyboard(). See more on keyboards.

bot.HandleCommand("/start", (ILocalizer localizer) =>
{
    var helloText = localizer["HelloText"];
    var keyboard = MenuKeyboard(localizer);
    return Results.Message(helloText, keyboard);
});

Callback data handling

This handler handles callback data "Photo" and responses with photo and description.

Here, general Handle() method is used with FilterCallbackData() filter.

Note, wwwroot folder comes from ASP.NET Core static file serving.

bot.Handle(() =>
        Results.Photo("wwwroot/cat.jpeg", "Little cat"))
    .FilterCallbackData(x => x == "Photo");

Parameter parsing

This example handles the /add command by parsing user input into AddCommandModel and responding with a sum of two parsed numbers or error message, depending on the input's validity. Example: /add 1.3 5.7 outputs 7. Attribute [UseFormatProvider] allows culture-specific number parsing like comma- or point-based decimal separator.

bot.HandleCommand("/add", (
    [UseFormatProvider] AddCommandModel model,
    ILocalizer localizer) =>
{
    return model.IsError
        ? localizer["ParsingError"]
        : localizer["AddCommandTemplate", model.Result];
});

Here example of AddCommandModel class, note that implementing an interface ICommandParser. This interface is essential for ensuring that command will be parsed.

public class AddCommandModel : ICommandParser<AddCommandModel>
{
    public double A { get; set; }
    public double B { get; set; }
    public bool IsError { get; set; }
    public double Result => A + B;

    public static AddCommandModel Parse(
        string command,
        IFormatProvider? formatProvider = null)
    {
        var parts = command.Split(' ');

        if (parts.Length < 3 ||
            !double.TryParse(parts[1], formatProvider, out var a) ||
            !double.TryParse(parts[2], formatProvider, out var b))
        {
            return new AddCommandModel { IsError = true, };
        }

        return new AddCommandModel { A = a, B = b, };
    }
}

Callback data parsing is also supported using a similar API.

Dependency injection and keyed services

In this example, we handle commands that retrieve data from a specific service registered in DI container with a key. The NameService is used here to provide different names depending on the command. By using the FromKeyedServices attribute, we dynamically inject the appropriate service into the handler based on the command.

FromKeyedServices attribute allows you to dynamically inject services registered with specific keys, such as FirstName, or LastName in this example.

Standard dependency injection techniques are also supported, handling context is a scope.

bot.HandleCommand("/firstname",
    ([FromKeyedServices("FirstName")] NameService nameService) =>
        nameService.Name);

bot.HandleCommand("/lastname",
    ([FromKeyedServices("LastName")] NameService nameService) =>
        nameService.Name);

Async weather service example

This example demonstrates how to handle complex interactions asynchronously, using a weather service to fetch real-time data.

Here, general Handle() method is used with FilterCallbackData() filter.

Note, here string messageText directly injected from incoming message, and handler catches all messages by using FilterUpdateType(UpdateType.Message) filter.

bot.Handle(async (string messageText, WeatherService weatherService) =>
{
    var weather = await weatherService.GetWeather();
    var keyboard = new InlineKeyboardMarkup(InlineKeyboardButton.WithCallbackData("Hello", "Hello"));
    return ($"Hello, {messageText}, weather is {weather}", keyboard);
}).FilterUpdateType(UpdateType.Message);

Error handling

This example shows default error handling. Command /throw randomly decides to throw an exception and default error handling mechanism gracefully caches an exception and logs it preventing bot app from unexpected shutdown. If no error occurs, the bot responds with message 'Ok'.

bot.HandleCommand("/throw", () =>
{
    if (Random.Shared.Next(0, 2) == 0)
    {
        throw new Exception("Error");
    }

    return Results.Message("Ok");
});

If you need custom error handling, you can wrap bot app execution pipeline (middleware) in your own middleware like this:

bot.Use(async (context, next) =>
{
    try
    {
        await next(context);
    }
    catch (Exception ex)
    {
        // Custom exception handling.
    }
});

Ensure that this middleware is placed before other handlers in the pipeline.

More advanced use-cases

If you need examples of more complex use-cases and features, you can check code example here or ask any question in Telegram group.

Additional resources

Check out some resources that can help you develop good bots:

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.0.11 102 9/17/2024
0.0.10 60 9/17/2024
0.0.9 68 9/16/2024
0.0.8 124 9/4/2024