Telegram.Bot.UI 0.0.3

dotnet add package Telegram.Bot.UI --version 0.0.3
                    
NuGet\Install-Package Telegram.Bot.UI -Version 0.0.3
                    
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="Telegram.Bot.UI" Version="0.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Telegram.Bot.UI" Version="0.0.3" />
                    
Directory.Packages.props
<PackageReference Include="Telegram.Bot.UI" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Telegram.Bot.UI --version 0.0.3
                    
#r "nuget: Telegram.Bot.UI, 0.0.3"
                    
#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.
#addin nuget:?package=Telegram.Bot.UI&version=0.0.3
                    
Install Telegram.Bot.UI as a Cake Addin
#tool nuget:?package=Telegram.Bot.UI&version=0.0.3
                    
Install Telegram.Bot.UI as a Cake Tool

Telegram Bot UI πŸ€–

Library for creating Telegram bot interfaces based on Telegram.Bot

πŸ“‘ Table of Contents

✨ Features

  • πŸ”„ Different bot operation modes:
    • Long Polling
    • WebHook via controller
    • Built-in WebHook server
  • πŸ–ΌοΈ Text templating system
  • πŸ“¦ Resource loader (texts, images, etc.)
  • πŸ“„ Nested interface pages
  • ⌨️ Built-in command parser
  • πŸ›‘οΈ User permissions management system (useful for bans)
  • ⚠️ Safe bot shutdown mechanism (waits for all critical operations to complete)
  • πŸ–ŒοΈ Page wallpaper support (via web preview)
  • πŸ“ Built-in license agreement acceptance mechanism
  • 🧰 Rich library of interactive menu components

🧰 Interface Components

The library provides numerous interactive components:

  • MenuCheckbox - Checkboxes for enabling/disabling options
  • MenuCheckboxGroup - Group of checkboxes for multiple selection
  • MenuCheckboxModal - Modal window with checkboxes (separate page)
  • MenuCommand - Button for triggering custom actions
  • MenuLink - Link to external resources, channels, chats
  • MenuNavigatePanel - Navigation between menu pages (in development)
  • MenuOpenPege - Opening other interface pages
  • MenuRadio - Radio buttons for selecting one of several options
  • MenuRadioModal - Modal window with radio buttons
  • MenuSplit - Element separator (line break)
  • MenuSwitch - Carousel option switch (one button)

πŸš€ Getting Started

Creating a Bot User Class

A separate instance of the user class is created for each user, where you can store state, work with the database, configure localization and interface:

public class MyBotUser : BaseBotUser 
{
    public LanguageView languageView { get; private set; }
    public UserAgreementView userAgreementView { get; private set; }
    public InformationView informationView { get; private set; }

    public MyBotUser(IBotWorker worker, long chatId, ITelegramBotClient client, CancellationToken token) :
        base(worker, chatId, client, token) 
    {
        // Setting up pages
        languageView = new(this);
        userAgreementView = new(this);
        informationView = new(this);

        parseMode = ParseMode.Html;
    }

    public override void Begin() {
        // These values can be retrieved from the database
        localization.code = "en";
        acceptLicense = false;
    }

    public override async Task HandleCommandAsync(string cmd, string[] arguments, Message message) {
        switch (cmd) {
            case "hello":
            case "info":
            case "start": {
                await informationView.SendPageAsync();
            }
            break;
            case "lang": {
                await languageView.SendPageAsync();
            }
            break;
            case "ping": {
                await SendTextMessageAsync("`pong`", mode: ParseMode.MarkdownV2);
            }
            break;
        }
    }

    public override Task<bool> HandlePermissiveAsync(Message message) {
        // Prohibit private chats
        return Task.FromResult(message.Chat.Type != ChatType.Private);
    }

    public override async Task HandleAcceptLicense(Message message) {
        // License must be accepted first
        await userAgreementView.SendPageAsync();
    }

    public override async Task HandleErrorAsync(Exception exception) {
        // Log the error and send it in response
        Console.WriteLine(exception.ToString());
        await SendTextMessageAsync($"<pre>{EscapeText(exception.ToString(), ParseMode.Html)}</pre>", mode: ParseMode.Html);
    }
}

πŸ”„ Bot Operation Modes

Long Polling

A simple way for a quick start:

var bot = new BotWorkerPulling<MyBotUser>((worker, chatId, client, token) => {
    return new MyBotUser(worker, chatId, client, token);
}) {
    botToken = "TELEGRAM_BOT_TOKEN",
    resourcePath = Path.Combine("Resources", "View"),
    localizationPack = LocalizationPack.FromJson(new FileInfo(Path.Combine("Resources", "Lang", "Lang.json")))
};

await bot.StartAsync();

WebHook with ASP.NET Controller

  • Wait! But the polling mode is slow! I want a webhook!
  • No problem! This can be implemented like this!
var bot = new BotWorkerWebHook<MyBotUser>((worker, chatId, client, token) => {
    return new MyBotUser(worker, chatId, client, token);
}) {
    botToken = "TELEGRAM_BOT_TOKEN",
    botSecretToken = "WEBHOOK_SECRET_TOKEN",
    botHostAddress = "https://mybot.com",
    botRoute = "TelegramBot/webhook",
    resourcePath = Path.Combine("Resources", "View"),
    localizationPack = LocalizationPack.FromJson(new FileInfo(Path.Combine("Resources", "Lang", "Lang.json")))
};

await bot.StartAsync();
builder.Services.AddSingleton(bot);

Controller for handling requests:

[ApiController]
[Route("[controller]")]
public class TelegramBotController : ControllerBase {
    private readonly BotWorkerWebHook<MyBotUser> bot;

    public TelegramBotController(BotWorkerWebHook<MyBotUser> bot) {
        this.bot = bot;
    }

    [HttpPost("webhook")]
    public async Task<IActionResult> Post([FromBody] Update update) {
        await bot.UpdateHandlerAsync(update);
        return Ok();
    }
}

Built-in WebHook Server

For console applications or when integration with ASP.NET is not possible:

  • Damn! I hate WebApi and all that DI! I want a simple console application with webhook!
  • Don't worry! This is also possible!
var bot = new BotWorkerWebHookServer<MyBotUser>((worker, chatId, client, token) => {
    return new MyBotUser(worker, chatId, client, token);
}) {
    botToken = "TELEGRAM_BOT_TOKEN",
    botSecretToken = "WEBHOOK_SECRET_TOKEN",
    botHostAddress = "https://mybot.com",
    port = 80,
    botRoute = "webhook",
    resourcePath = Path.Combine("Resources", "View"),
    localizationPack = LocalizationPack.FromJson(new FileInfo(Path.Combine("Resources", "Lang", "Lang.json")))
};

await bot.StartAsync();

πŸ“„ Creating Interface Pages

The library uses the concept of pages (classes inheriting from MessagePage) to represent bot interface elements.

Language Selection Page Example

public class LanguageView : MessagePage {
    public override string pageResource => "Language"; // There should be a folder with pageResource name in resourcePath (Resources/View/Language)
    public override string title => $"{flags[botUser.localization.code]} " + "{{ 'Language select' | L }}"; // | L - Built-in localization method
    private MenuRadio languageRadio;
    private Dictionary<string, string> flags { get; init; } = new() {
        ["ru"] = "πŸ‡·πŸ‡Ί",
        ["en"] = "πŸ‡ΊπŸ‡Έ"
    };

    public LanguageView(BaseBotUser botUser) : base(botUser) {
        languageRadio = MenuRadio(MenuSelector.FromArray(new[] {
            ("English", "en"),
            ("Русский", "ru")
        }));

        using var context = ((MyBotUser)botUser).Context();
        var userTable = ((MyBotUser)botUser).GetUserTable(context);

        languageRadio.Select(userTable.language);

        languageRadio.onSelect += select => {
            using var context = ((MyBotUser)botUser).Context();
            var userTable = ((MyBotUser)botUser).GetUserTable(context);

            ((MyBotUser)botUser).localization.code = select.id;
            userTable.language = select.id;
            context.SaveChanges();
        };
    }

    public override string? RequestMessageResource() => $"description-{botUser.localization.code}";

    public override List<ButtonsPage> RequestPageComponents() {
        return ButtonsPage.Page([
            [languageRadio]
        ]);
    }
}

User Agreement Page Example

public class UserAgreementView : MessagePage {
    public override string pageResource => "UserAgreement";
    public override string title => "{{ 'User agreement' | L }}";
    private MenuRadio languageRadio;
    private MenuCommand acceptCommand;

    public UserAgreementView(BaseBotUser botUser) : base(botUser) {
        languageRadio = MenuRadio(MenuSelector.FromArray(new[] {
            ("English", "en"),
            ("Русский", "ru")
        }));

        using var context = ((MyBotUser)botUser).Context();
        var userTable = ((MyBotUser)botUser).GetUserTable(context);

        languageRadio.Select(userTable.language);

        languageRadio.onSelect += select => {
            using var context = ((MyBotUser)botUser).Context();
            var userTable = ((MyBotUser)botUser).GetUserTable(context);

            ((MyBotUser)botUser).localization.code = select.id;
            userTable.language = select.id;
            context.SaveChanges();
        };

        acceptCommand = MenuCommand("{{ 'I agree' | L }}");
        acceptCommand.onClick += async (callbackQueryId, messageId, chatId) => {
            using var context = ((MyBotUser)botUser).Context();
            var userTable = ((MyBotUser)botUser).GetUserTable(context);

            userTable.acceptLicense = true;
            ((MyBotUser)botUser).acceptLicense = true;
            context.SaveChanges();

            // Delete current page
            await botUser.DeleteMessageAsync(messageId);

            // Send welcome page after accepting the agreement
            await ((MyBotUser)botUser).informationView.SendPageAsync();
        };
    }

    public override string? RequestMessageResource() => $"description-{botUser.localization.code}";

    public override List<ButtonsPage> RequestPageComponents() {
        return ButtonsPage.Page([
            [languageRadio],
            [acceptCommand]
        ]);
    }
}

Information Page Example

public class InformationView : MessagePage {
    public override string pageResource => "Information";
    public override string title => "{{ 'Information' | L }}";

    public InformationView(BaseBotUser botUser) : base(botUser) { }

    public override string? RequestMessageResource() => $"description-{botUser.localization.code}";

    public override object? RequestModel() => new {
        me = botUser.chatId // Now can be used in the templating engine
    };

    public override List<ButtonsPage> RequestPageComponents() {
        return ButtonsPage.Page([
            [
                MenuLink("https://t.me/MyBotSupport", "πŸ†˜ {{ 'Support' | L }}"),
                MenuOpenSubPege(((MyBotUser)botUser).languageView)
            ]
        ]);
    }
}

πŸ”„ Localization

Localization uses a simple JSON format:

[
  {
    "en": "Support",
    "ru": "ΠŸΠΎΠ΄Π΄Π΅Ρ€ΠΆΠΊΠ°"
  },
  {
    "en": "I agree",
    "ru": "Я согласСн"
  },
  {
    "en": "Language select",
    "ru": "Π’Ρ‹Π±ΠΎΡ€ языка"
  },
  {
    "en": "Information",
    "ru": "Π˜Π½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΡ"
  },
  {
    "en": "User agreement",
    "ru": "ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒΡΠΊΠΎΠ΅ соглашСниС"
  }
]

πŸ“‚ Resource Structure

Resources are organized in folders by page name:

Resources/
β”œβ”€β”€ View/
β”‚   β”œβ”€β”€ Language/             # pageResource = "Language"
β”‚   β”‚   β”œβ”€β”€ text/
β”‚   β”‚   β”‚   β”œβ”€β”€ description-en.md
β”‚   β”‚   β”‚   └── description-ru.md
β”‚   β”‚   └── image/
β”‚   β”‚       └── background.png
β”‚   β”œβ”€β”€ UserAgreement/        # pageResource = "UserAgreement" 
β”‚   β”‚   └── text/
β”‚   β”‚       β”œβ”€β”€ description-en.md
β”‚   β”‚       └── description-ru.md
β”‚   └── Information/          # pageResource = "Information"
β”‚       └── text/
β”‚           β”œβ”€β”€ description-en.md
β”‚           └── description-ru.md
└── Lang/
    └── Lang.json           # File with localizations

Resource File Example (description-en.md)

😎 Hello, {{ me }}!

Resource File Example (description-ru.md)

😎 ΠŸΡ€ΠΈΠ²Π΅Ρ‚, {{ me }}!
Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.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.3 97 3/1/2025