Skip to main content
PolyBot exposes a set of focused interfaces that you interact with at runtime: a gate interface for filtering updates, a typed FSM facade for managing conversation state, a raw storage interface for custom persistence, an awaiter interface for suspending handlers mid-conversation, and a BotFather sync interface for managing command menus. This page documents each interface, its methods, and the concrete implementations registered by default.

IUpdateFilter

Namespace: PolyBot.Routing A synchronous gate over an incoming update, evaluated by the generated router before invoking a handler that carries filter attributes. The source generator discovers every concrete, non-abstract, non-generic class that implements IUpdateFilter and emits a matching [XxxFilterAttribute] in PolyBot.Attributes.
bool
Returns true if the update is allowed through to the handler. Returning false skips the handler entirely.
Usage: Implement the interface directly (for raw Update access) or subclass one of the generated abstract DTO bases (MessageFilter, CallbackQueryFilter, etc.) whose CanPass(Update) extracts the payload and delegates to CanPass(TDto).
At runtime the router resolves each filter from the service container first (GetService<T>()), falling back to ActivatorUtilities.CreateInstance. Parameterless filters work without being registered; filters with constructor dependencies should be registered in DI.
Filter attributes ([NonEmptyTextFilter]) and With* extensions (WithNonEmptyTextFilter()) are emitted by the generator, not written by hand. See the Source Generator page for details.

IStateMachine

Namespace: PolyBot.State A typed facade over IStateStorage scoped to the update currently being routed. Keys are resolved from the current update via StateKeyResolver. Inject into handler parameters directly — the generator maps the IStateMachine parameter to GetRequiredService<IStateMachine>().
ValueTask<TEnum?>
Returns the current state for the resolved key, or null when no current update is active, the key is unresolvable, or no state is stored.
ValueTask
Stores state for the resolved key. Throws InvalidOperationException when the key cannot be resolved — silent state loss is worse than a visible failure.
ValueTask
Removes the stored state for the resolved key, if any. Throws InvalidOperationException when the key cannot be resolved.

StateKey enum

Extension methods: StateMachineExtensions

Namespace: PolyBot.State Two convenience methods walk forward/backward through an ordered enum:
Both methods return the resulting state (or null when cleared). Both throw InvalidOperationException when a write is required but the key cannot be resolved.

IStateStorage

Namespace: PolyBot.State The raw persistence layer for FSM states. IStateMachine wraps this with update-context awareness; use IStateStorage directly when you need to read or modify state outside the routing loop (e.g. admin commands that reset another user’s state).
ValueTask<TEnum?>
Returns the stored state for key, or null when none (or an expired entry) exists.
ValueTask
Stores state for key, replacing any previously stored state.
ValueTask
Removes the state stored for key, if any.
Implementations namespace keys internally by enum type, so states of different enum types never collide for the same raw key string.

MemoryStateStorage (default implementation)

Namespace: PolyBot.State The built-in IStateStorage backed by a ConcurrentDictionary<(Type, string), Entry>. Suitable for development and single-instance bots; not shared across process restarts or multiple app instances.
MemoryStateStorage is process-local. For multi-instance deployments register a custom IStateStorage (backed by Redis, a database, etc.) before calling AddPolyBotRouter(). AddPolyBotDefaults() registers MemoryStateStorage with try-add semantics, so your registration wins if it comes first.

IUpdateAwaiter

Namespace: PolyBot.Awaits Enables conversation-style inline awaiting: a handler builds a typed await registration, the framework suspends the handler and resumes it when a matching update arrives — without blocking the polling loop.
UpdateAwaiterBuilder<TUpdate>
Starts building a single-shot await for a TUpdate payload. Chain marker methods (TextMatches, WithFilter, Where) then call a terminal method (ByUserId, ByChatId, ByUserInChat) to register the await and return a Task<TUpdate?>.
ValueTask<bool>
Router-facing delivery hook called by the generated router after the branch’s static conditions have already passed. You never call this directly. Returns true when the update was claimed by a pending await; false lets it fall through to normal routing.
Usage example:
WaitForMessageAsync() and all other WaitFor*Async() extension methods are generated into PolyBot.Attributes by the source generator. With* extensions are emitted one-per-discovered-filter. These generated methods are marker calls — the generator reads them syntactically to emit the correct branch conditions into the router.

UpdateAwaiterBuilder<TUpdate>

Returned by WaitForAsync<TUpdate>. Fluent builder for configuring the await before registering it.

IBotFatherSync

Namespace: PolyBot.BotFather Holds the commands discovered from [Command] attributes and synchronizes them with BotFather via SetMyCommands. The concrete implementation PolyBotBotFatherSync is generated by the source generator and registered automatically by AddPolyBotRouter().
IReadOnlyList<BotCommand>
All commands found at build time that are not marked IsHidden = true. Read-only.
Task
Groups commands by (Scope, LanguageCode) and sends one SetMyCommands API call per group. Called automatically by PolyBotClient.RunPollingAsync() before polling starts.

DI registration defaults

AddPolyBotDefaults() registers the following with try-add semantics (your earlier registrations win): AddPolyBotRouter() calls AddPolyBotDefaults() first, then additionally registers (without try-add — these always replace): ITelegramBotClient is registered separately — by PolyBotClient.BuildProvider() when using the standalone runner, or by your own DI setup when using the generic host or ASP.NET Core. To override IStateStorage with a custom implementation, register it before AddPolyBotRouter() (which calls AddPolyBotDefaults() internally — try-add semantics mean your earlier registration wins):