PolyBot 1.4.0
See the version list below for details.
dotnet add package PolyBot --version 1.4.0
NuGet\Install-Package PolyBot -Version 1.4.0
<PackageReference Include="PolyBot" Version="1.4.0" />
<PackageVersion Include="PolyBot" Version="1.4.0" />
<PackageReference Include="PolyBot" />
paket add PolyBot --version 1.4.0
#r "nuget: PolyBot, 1.4.0"
#:package PolyBot@1.4.0
#addin nuget:?package=PolyBot&version=1.4.0
#tool nuget:?package=PolyBot&version=1.4.0
PolyBot - The most magicky Telegram.Bot framework
PolyBot is a generative, DI-friendly routing framework for Telegram.Bot. Instead of hand-written switch statements over Update.Type, you write minimal-API-style handler methods decorated with attributes; a Roslyn source generator compiles them into a hard-coded, optimized BotRouter implementing Telegram.Bot.Polling.IUpdateHandler.
Learn and Docs
Learn PolybBot on Official documentation site.
Why PolyBot?
- PolyBot has no reflection after generation and no overhead infrastructure.
switchoverUpdateTypewith inlined handling guards.- Builti-in throttle gates are
static readonlywith lock-protected ring buffers. - Await branches compare against static constant arrays; matching allocates nothing.
- Filter resolution inside the handler branch, not for non-matching handlers.
- The routing code is as close to hand-optimized C# as a generator can produce.
Quick start
[MessageHandler, Command(Aliases = ["start"])]
public static async ValueTask<Result> StartHandler(Message msg, ITelegramBotClient client, CancellationToken ct)
{
client.SendMessage(msg.Chat, "Hello, " + msg.From!.UserName + "!", cancellationToken: ct);
return Result.StopRouting;
}
public static async void Main()
{
await using PolyBotClient client = new PolyBotClient();
client.Services.AddPolyBotRouter();
await client.RunPollingAsync();
}
Features
Attribute routing
One handler attribute per UpdateType: [MessageHandler], [CallbackQueryHandler], [InlineQueryHandler], etc. Handlers return Task<Result> / ValueTask<Result>; Result.ContinueRouting falls through, Result.StopRouting ends routing. Priority or source order arranges handlers within an update type.
Dependency injection
Parameters resolve automatically: ITelegramBotClient, CancellationToken, Update, the update payload (Message, CallbackQuery, ...), [Key("x")] T keyed services, and anything else via GetRequiredService<T>().
Commands
[Command(Aliases = ["start"])] matches messages whose first entity is a / command. The @botname suffix is verified against the current bot's username. Optional metadata (Description, LanguageCode, Scope, IsHidden) is synchronized with BotFather automatically.
Filters
[XxxFilter] attributes gate handlers with custom IUpdateFilter implementations. All filters must pass in attribute order. Generated bases such as MessageFilter let filters work directly with the extracted payload.
public class PremiumOnlyFilter : MessageFilter
{
protected override bool CanPass(Message message)
=> message.From?.IsPremium is true;
}
Rich messages
InputRichMessage rich = new RichMessageBuilder()
.Paragraph(b => b.Plain("Hello, ").Bold("World!"))
.Build();
Keyboards
InlineKeyboardMarkup keyboard = new InlineKeyboardBuilder()
.WithCallbackButton("Open", "open")
.WithCallbackButton("Close", "close")
.Adjust(2);
Or generate keyboards from [CallbackButton] attributes on a partial method returning InlineKeyboardMarkup.
[CallbackButton("First", "first:{val}"), CallbackButton("Second", "second:{val}")]
[CallbackButton("Cancel", "cancel"), CallbackButton("Apply", "apply:{val}")]
private static partial InlineKeyboardMarkup ActionsKeyboard(int val);
Finite state machines
[MessageHandler, State<RegistrationStep>(RegistrationStep.AwaitingAge)]
public static async Task<Result> AskAge(IStateMachine fsm)
{
await fsm.SetAsync(RegistrationStep.Completed);
return Result.Handled();
}
Inject IStateMachine to transition state; register your own IStateStorage before AddPolyBotRouter() for custom persistence.
Inline awaits
Suspend a handler and resume on a later update without blocking the polling loop:
[MessageHandler, Command(Aliases = ["register"])]
public static async Task<Result> Register(IUpdateAwaiter awaiter, CancellationToken ct)
{
Message? age = await awaiter.WaitForMessageAsync()
.TextMatches(@"my age is \d+")
.ByUserId(ct);
return age is null ? Result.Handled() : Result.ContinueRouting;
}
Rate limiting
[MessageHandler, Command(Aliases = ["search"])]
[Throttled(Limit = 3, PeriodMilliseconds = 60_000, Scope = ThrottleScope.User)]
public static Task<Result> Search(Message msg, [Rest] string query) { ... }
Callback-data patterns
[CallbackQueryHandler, Pattern("item:{id}:{action}")]
public static Task<Result> OnItem(CallbackQuery query, [Arg] int id, [Arg] string action)
{
return Result.Handled();
}
Exception handling
[ExceptionHandler]
public static Task OnError(Exception exception, HandleErrorSource source, ILogger log)
{
log.LogError("{Source}: {Message}", source, exception.Message);
return Task.CompletedTask;
}
Hosting and webhooks
Integration is emitted only when the matching package is referenced:
- Generic host —
services.AddPolyBotHostedPolling(...). - ASP.NET Core —
services.AddPolyBotWebhook()andapp.MapPolyBotWebhook("/bot").
Testing
Test without network or tokens:
PolyBotClient client = new PolyBotClient();
client.Services.AddPolyBotRouter();
UpdateMocker mock = client.RunTest();
await mock.Message("hello");
await mock.Callback("item:1:open");
PolyTests is also available as an in-memory ITelegramBotClient for driving the host or polling service manually.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. net9.0 was computed. 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. 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.12)
- Microsoft.Extensions.Configuration (>= 10.0.12)
- Microsoft.Extensions.DependencyInjection (>= 10.0.12)
- Microsoft.Extensions.Http (>= 10.0.12)
- System.Text.Json (>= 10.0.12)
- System.Threading.Channels (>= 10.0.12)
- System.Threading.Tasks.Extensions (>= 4.6.3)
- Telegram.Bot (>= 22.10.3.2)
-
net10.0
- Microsoft.Bcl.AsyncInterfaces (>= 10.0.12)
- Microsoft.Extensions.Configuration (>= 10.0.12)
- Microsoft.Extensions.DependencyInjection (>= 10.0.12)
- Microsoft.Extensions.Http (>= 10.0.12)
- Telegram.Bot (>= 22.10.3.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.