Telegram.Bot.UI
0.2.1
dotnet add package Telegram.Bot.UI --version 0.2.1
NuGet\Install-Package Telegram.Bot.UI -Version 0.2.1
<PackageReference Include="Telegram.Bot.UI" Version="0.2.1" />
<PackageVersion Include="Telegram.Bot.UI" Version="0.2.1" />
<PackageReference Include="Telegram.Bot.UI" />
paket add Telegram.Bot.UI --version 0.2.1
#r "nuget: Telegram.Bot.UI, 0.2.1"
#:package Telegram.Bot.UI@0.2.1
#addin nuget:?package=Telegram.Bot.UI&version=0.2.1
#tool nuget:?package=Telegram.Bot.UI&version=0.2.1
Telegram Bot UI 🤖
Library for creating Telegram bot interfaces based on Telegram.Bot
Visit the repository with a demo project of a photo editor bot Telegram.Bot.UI.Demo
✨ Features
- Two API approaches:
- Declarative XML pages (
.pagefiles) - Simple, hot-reloadable, Vue-like syntax - C# MessagePage classes - Full control, programmatic approach
- Declarative XML pages (
- Different bot operation modes:
- Long Polling
- WebHook via controller
- Built-in WebHook server
- Text templating system with
{{ }}expressions - Resource loader (texts, images, etc.) with virtual resource support
- Nested interface pages with navigation
- 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
- JavaScript scripting in pages (powered by Jint)
- ViewModels for C#/JavaScript integration
Interface Components
The library provides numerous interactive components for both declarative and programmatic APIs:
| XML Component | C# Class | Description |
|---|---|---|
<command> |
MenuCommand |
Button for triggering custom actions |
<open> |
MenuOpen |
Opening pages, links, or web apps |
<checkbox> |
MenuCheckbox |
Toggle for enabling/disabling options |
<radio> |
MenuRadio |
Radio buttons for single selection |
<switch> |
MenuSwitch |
Carousel option switch (one button) |
<card> |
MenuCard |
Container with optional pagination |
<navigate> |
MenuNavigatePanel |
Navigation controls for paginated content |
<row> |
- | Groups components on same row |
| - | MenuCheckboxModal |
Modal window with checkboxes |
| - | MenuRadioModal |
Modal window with radio buttons |
| - | MenuSplit |
Element separator (line break) |
See Documentation/Components.md for detailed component reference.
📦 Nuget
The Telegram.Bot.UI package is available via NuGet!
dotnet add package Telegram.Bot.UI
🚀 Quick Start with Declarative Pages
The easiest way to create bot interfaces is using declarative .page files with Vue-like syntax.
1. Create a Page File
Create Resources/Pages/home.page:
<view>
<title>Welcome</title>
<message>Hello! This is a demo bot.<br/><br/>Choose an option below:</message>
<components>
<command title="Counter Demo" @click="UI.navigate('counter')" />
<open title="Settings" target="settings" />
<row>
<open type="link" title="GitHub" target="https://github.com" />
<open type="link" title="Docs" target="https://example.com/docs" />
</row>
</components>
</view>
2. Create a Counter Page with ViewModel
Create Resources/Pages/counter.page:
<view vmodel="CounterViewModel">
<title>Counter</title>
<message>Count: {{ VModel.Count }}<br/>Status: {{ VModel.GetStatus() }}</message>
<components>
<row>
<command title="➖" @click="decrement()" />
<command title="{{ VModel.Count }}" @click="reset()" />
<command title="➕" @click="increment()" />
</row>
<command title="Back" @click="UI.back()" />
</components>
</view>
<script>
function increment() {
VModel.Increment();
UI.refresh();
}
function decrement() {
VModel.Decrement();
UI.refresh();
}
function reset() {
VModel.Reset();
UI.toast('Counter reset!');
UI.refresh();
}
</script>
3. Create the ViewModel
public class CounterViewModel {
public int Count { get; set; } = 0;
public void Increment() => Count++;
public void Decrement() => Count--;
public void Reset() => Count = 0;
public string GetStatus() => Count switch {
0 => "Zero",
> 0 => "Positive",
< 0 => "Negative"
};
}
4. Setup Program.cs
// Load pages
var pagesPath = Path.Combine("Resources", "Pages");
var vmodelAssembly = typeof(CounterViewModel).Assembly;
var pageManager = new PageManager(pagesPath, vmodelAssembly);
pageManager.LoadAll();
// Create bot
var bot = new BotWorkerPulling<MyBotUser>((worker, chatId, client, token) => {
return new MyBotUser(pageManager, worker, chatId, client, token);
}) {
botToken = "YOUR_BOT_TOKEN",
resourceLoader = new ResourceLoader("Resources")
};
await bot.StartAsync();
5. Handle Commands in BotUser
public class MyBotUser : BaseBotUser {
private PageManager pageManager;
private Dictionary<string, ScriptPage> pageCache = new();
public MyBotUser(PageManager pageManager, IBotWorker worker, long chatId,
ITelegramBotClient client, CancellationToken token)
: base(worker, chatId, client, token) {
this.pageManager = pageManager;
}
public override async Task HandleCommandAsync(string cmd, string[] args, Message message) {
switch (cmd) {
case "start":
case "home":
var page = GetOrCreatePage("home");
if (page != null) await page.SendPageAsync();
break;
default:
// Try to open page by command name
var dynamicPage = GetOrCreatePage(cmd);
if (dynamicPage != null) await dynamicPage.SendPageAsync();
break;
}
}
private ScriptPage? GetOrCreatePage(string pageId) {
if (pageCache.TryGetValue(pageId, out var cached)) return cached;
var page = pageManager.GetPage(pageId, this);
if (page != null) pageCache[pageId] = page;
return page;
}
}
Key Concepts
UI Namespace:
All page control functions are in the UI namespace:
UI.navigate('page-id'); // Navigate to page
UI.refresh(); // Refresh current page
UI.toast('Message'); // Show notification
UI.back(); // Go back
User Object: Access to BaseBotUser properties and methods:
User.chatId // User's chat ID
User.localization.code // Current language
await User.SendTextMessageAsync('Hi') // Send message
Base Object: Access to current ScriptPage properties:
Base.pageId // Current page ID
Base.title // Current page title
Base.parent // Parent page reference
Documentation
Start Here:
- Documentation/GettingStarted.md - Complete beginner's guide with examples
Reference:
- Documentation/Components.md - All UI components (command, radio, checkbox, etc.)
- Documentation/JavaScriptAPI.md - JavaScript API (UI namespace, Base object, lifecycle hooks)
- Documentation/ViewModels.md - ViewModel integration with C#
- Documentation/Pages.md - Page structure, attributes, and configuration
🚀 Getting Started (C# API)
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) {
logger.LogError(exception, "Error in bot user {ChatId}", chatId);
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",
resourceLoader = new ResourceLoader("Resources"),
localizationPack = LocalizationPack.FromLPack(new FileInfo(Path.Combine("Resources", "Lang.lpack")))
};
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",
resourceLoader = new ResourceLoader("Resources"),
localizationPack = LocalizationPack.FromLPack(new FileInfo(Path.Combine("Resources", "Lang.lpack")))
};
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",
resourceLoader = new ResourceLoader("Resources"),
localizationPack = LocalizationPack.FromLPack(new FileInfo(Path.Combine("Resources", "Lang.lpack")))
};
await bot.StartAsync();
📝 Logging Configuration
The library supports Microsoft.Extensions.Logging for comprehensive logging throughout the bot lifecycle.
Basic Logging Setup
using Microsoft.Extensions.Logging;
var loggerFactory = LoggerFactory.Create(builder => {
builder
.SetMinimumLevel(LogLevel.Information)
.AddConsole();
});
var bot = new BotWorkerPulling<MyBotUser>((worker, chatId, client, token) => {
return new MyBotUser(worker, chatId, client, token);
}) {
botToken = "TELEGRAM_BOT_TOKEN",
resourceLoader = new ResourceLoader("Resources"),
localizationPack = LocalizationPack.FromLPack(new FileInfo(Path.Combine("Resources", "Lang.lpack"))),
logger = loggerFactory.CreateLogger<BotWorkerPulling<MyBotUser>>()
};
await bot.StartAsync();
Passing Logger to User Instance
To enable logging in your user class, pass the logger in the constructor:
var bot = new BotWorkerPulling<MyBotUser>((worker, chatId, client, token) => {
var user = new MyBotUser(worker, chatId, client, token);
user.logger = loggerFactory.CreateLogger<MyBotUser>();
return user;
}) {
botToken = "TELEGRAM_BOT_TOKEN",
resourceLoader = new ResourceLoader("Resources"),
logger = loggerFactory.CreateLogger<BotWorkerPulling<MyBotUser>>()
};
Integration with ASP.NET Core
When using WebHook mode with ASP.NET Core, you can use dependency injection:
builder.Services.AddSingleton(serviceProvider => {
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
var bot = new BotWorkerWebHook<MyBotUser>((worker, chatId, client, token) => {
var user = new MyBotUser(worker, chatId, client, token);
user.logger = loggerFactory.CreateLogger<MyBotUser>();
return user;
}) {
botToken = "TELEGRAM_BOT_TOKEN",
botSecretToken = "WEBHOOK_SECRET_TOKEN",
botHostAddress = "https://mybot.com",
botRoute = "TelegramBot/webhook",
resourceLoader = new ResourceLoader("Resources"),
localizationPack = LocalizationPack.FromLPack(new FileInfo(Path.Combine("Resources", "Lang.lpack"))),
logger = loggerFactory.CreateLogger<BotWorkerWebHook<MyBotUser>>()
};
return bot;
});
Logged Events
The library logs the following events:
BotWorker (BaseBotWorker):
- Bot startup and shutdown (Information level)
- User cache operations (Debug level)
- New user creation (Information level)
- Errors during update handling (Error/Critical level)
BotUser (BaseBotUser):
- Custom error handling in
HandleErrorAsync(your implementation)
📄 Creating Interface Pages (C# MessagePage - Legacy)
Note: The
MessagePageC# API is a legacy approach. For new projects, use declarative.pagefiles as shown in the Quick Start section.
The MessagePage class provides programmatic control over bot pages. See the source code for implementation details.
Localization
Localization supports two formats: .lpack and .json.
LPack Format
Create Resources/Lang.lpack:
en: Support
ru: Поддержка
en: I agree
ru: Я согласен
en: Language select
ru: Выбор языка
en: Settings
ru: Настройки
Each block is separated by an empty line. Each line is code: text where code is the language code (en, ru, etc.).
JSON Format
Alternatively, use JSON:
[
{ "en": "Support", "ru": "Поддержка" },
{ "en": "I agree", "ru": "Я согласен" },
{ "en": "Language select", "ru": "Выбор языка" },
{ "en": "Settings", "ru": "Настройки" }
]
Load with LocalizationPack.FromJson() instead of FromLPack().
In declarative pages, use the $t() function:
<command :title="$t('Save')" />
<title>{{ $t('Settings') }}</title>
<command :title="'✅ ' + $t('Confirm')" />
📂 Resource Structure
Resources for declarative pages:
Resources/
├── Pages/
│ ├── home.page
│ ├── settings.page
│ └── components/
│ ├── counter.page
│ └── forms.page
├── images/
│ └── banner.png
├── texts/
│ ├── welcome-en.md
│ └── welcome-ru.md
└── Lang.lpack
Message with External Resource
<view>
<title>Welcome</title>
<message md resource="text/welcome-{{ User.localization.code }}"></message>
</view>
Note: User provides access to BaseBotUser properties like localization.code, chatId, etc. Base provides access to the current ScriptPage instance.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- AngleSharp (>= 1.4.0)
- Jint (>= 4.4.2)
- Microsoft.AspNetCore.Mvc.NewtonsoftJson (>= 10.0.1)
- Microsoft.AspNetCore.WebUtilities (>= 10.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.1)
- Mime (>= 3.8.0)
- MimeTypesMap (>= 1.0.9)
- Newtonsoft.Json (>= 13.0.4)
- Telegram.Bot (>= 22.7.6)
- Vey.Localization (>= 0.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.