BotFoundry.Telegram 1.0.2

dotnet add package BotFoundry.Telegram --version 1.0.2
                    
NuGet\Install-Package BotFoundry.Telegram -Version 1.0.2
                    
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="BotFoundry.Telegram" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BotFoundry.Telegram" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="BotFoundry.Telegram" />
                    
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 BotFoundry.Telegram --version 1.0.2
                    
#r "nuget: BotFoundry.Telegram, 1.0.2"
                    
#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.
#:package BotFoundry.Telegram@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BotFoundry.Telegram&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=BotFoundry.Telegram&version=1.0.2
                    
Install as a Cake Tool

๐Ÿค– BotFoundry.Telegram

Library for building Telegram bots in C# without reimplementing infrastructure for every project.

You write the handlers and steps. The library takes care of the pipeline, session, routing, and connection to Telegram.

Repository status: This repository is intended to be public once the CI/CD setup is complete.


๐Ÿ“ฆ Installation

dotnet add package BotFoundry.Telegram

โšก Minimal setup

// Program.cs
var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddBotFoundry("YOUR_TOKEN_HERE");

var app = builder.Build();
await app.RunAsync();

The bot is already connected to Telegram via long polling. No additional configuration is required to get started.


๐Ÿงฉ Creating a simple handler

A handler responds to a command such as /command. Implement ITopicHandler and register it with DI.

// Responds to the /hello command
public sealed class HelloHandler : ITopicHandler
{
    public string Command => "hello";

    public async Task<FlowResult> HandleAsync(BotContext ctx, CancellationToken ct)
    {
        await ctx.SendAsync("๐Ÿ‘‹ Hello! How can I help you?", ct);
        return FlowResult.Complete();
    }
}
// Program.cs
builder.Services.AddSingleton<ITopicHandler, HelloHandler>();
builder.Services.AddBotFoundry("YOUR_TOKEN_HERE");

The user sends /hello โ†’ the bot replies and the flow ends.


๐Ÿ’ฌ Multi-step flow with a session

Richer handlers guide the user through multiple steps. Each step can read and write session data, which persists between messages.

Example: expense registration

The user types /expense. The bot guides them through three consecutive steps, accumulating data in the session until it displays the final summary.

/expense
  โ†’ What is the category?   [Food] [Transport] [Leisure] [Other]
  โ†’ What is the amount? (e.g. 45.90)
  โ†’ Any notes? (optional โ€” type "skip" to ignore)
  โ†’ โœ… Expense registered: Food ยท $45.90 ยท "lunch"
Handler โ€” entry point
public sealed class ExpenseHandler : ITopicHandler
{
    public string Command => "expense";

    public async Task<FlowResult> HandleAsync(BotContext ctx, CancellationToken ct)
    {
        await ctx.SendAsync(
            "๐Ÿ“‚ What is the expense category?",
            quickReplies: ["Food", "Transport", "Leisure", "Other"],
            ct);

        ctx.Session.ActiveStepId = "expense_category";
        return FlowResult.Continue(); // indicates that there are steps to follow
    }
}
Steps
// Step 1 โ€” captures the category (already sent by the handler)
public sealed class ExpenseCategoryStep : IConversationStep
{
    public string StepId => "expense_category";

    public Task AskAsync(BotContext ctx, CancellationToken ct)
        => ctx.SendAsync(
            "๐Ÿ“‚ What is the expense category?",
            quickReplies: ["Food", "Transport", "Leisure", "Other"],
            ct);

    public Task<bool> ValidateAsync(BotContext ctx, CancellationToken ct)
    {
        string[] validCategories = ["Food", "Transport", "Leisure", "Other"];
        return Task.FromResult(validCategories.Contains(ctx.Text));
    }

    public Task<StepResult> ProcessAsync(BotContext ctx, CancellationToken ct)
    {
        ctx.Session.Data["expense_category"] = ctx.Text; // saves to the session
        return Task.FromResult(StepResult.JumpTo("expense_amount"));
    }
}

// Step 2 โ€” captures the amount
public sealed class ExpenseAmountStep : IConversationStep
{
    public string StepId => "expense_amount";

    public Task AskAsync(BotContext ctx, CancellationToken ct)
        => ctx.SendAsync("๐Ÿ’ฐ What is the amount? (e.g. 45.90)", ct);

    public Task<bool> ValidateAsync(BotContext ctx, CancellationToken ct)
        => Task.FromResult(decimal.TryParse(
            ctx.Text,
            System.Globalization.NumberStyles.Number,
            System.Globalization.CultureInfo.InvariantCulture,
            out var value) && value > 0);

    public Task<StepResult> ProcessAsync(BotContext ctx, CancellationToken ct)
    {
        ctx.Session.Data["expense_amount"] = ctx.Text;
        return Task.FromResult(StepResult.JumpTo("expense_note"));
    }
}

// Step 3 โ€” optional note and final summary
public sealed class ExpenseNoteStep : IConversationStep
{
    public string StepId => "expense_note";

    public Task AskAsync(BotContext ctx, CancellationToken ct)
        => ctx.SendAsync(
            "๐Ÿ“ Any notes? (or type \"skip\")", ct);

    public Task<bool> ValidateAsync(BotContext ctx, CancellationToken ct)
        => Task.FromResult(true); // always valid

    public async Task<StepResult> ProcessAsync(BotContext ctx, CancellationToken ct)
    {
        var category = ctx.Session.Data["expense_category"];
        var amount   = ctx.Session.Data["expense_amount"];
        var note     = ctx.Text == "skip" ? "โ€”" : ctx.Text;

        await ctx.SendAsync(
            $"โœ… Expense registered!\n\n" +
            $"๐Ÿ“‚ Category: {category}\n" +
            $"๐Ÿ’ฐ Amount: ${amount}\n" +
            $"๐Ÿ“ Note: {note}",
            ct);

        return StepResult.Complete();
    }
}
DI registration
// Program.cs
builder.Services.AddSingleton<ITopicHandler, ExpenseHandler>();
builder.Services.AddSingleton<IConversationStep, ExpenseCategoryStep>();
builder.Services.AddSingleton<IConversationStep, ExpenseAmountStep>();
builder.Services.AddSingleton<IConversationStep, ExpenseNoteStep>();

builder.Services.AddBotFoundry("YOUR_TOKEN_HERE");

How the session works: each step reads and writes to ctx.Session, a per-user dictionary that persists between messages. Step 3 reads the values saved by steps 1 and 2 without requiring the user to send anything again.


๐Ÿ”Œ Optional extensions

All of the contracts below are optional. Register only the ones your bot needs.

Contract When to use
ISessionStore Persist sessions beyond memory (Redis, SQL). Default: in-memory
IAuthMiddleware Restrict access by chatId or any other rule
ILlmBridge Forward messages without an active command to a language agent

Example โ€” restrict access with an allowlist

public sealed class AllowListAuth : IAuthMiddleware
{
    private readonly HashSet<long> _allowed = [123456789, 987654321];

    public async Task<bool> IsAllowedAsync(BotContext ctx, CancellationToken ct)
    {
        if (_allowed.Contains(ctx.ChatId)) return true;

        await ctx.SendAsync("โ›” Access not authorized.", ct);
        return false;
    }
}
// Register before AddBotFoundry
builder.Services.AddSingleton<IAuthMiddleware, AllowListAuth>();
builder.Services.AddBotFoundry("YOUR_TOKEN_HERE");

๐Ÿ“ Repository structure

src/
โ”œโ”€โ”€ BotFoundry.Telegram.Abstractions/   # Public contracts (ITopicHandler, IConversationStep, โ€ฆ)
โ”œโ”€โ”€ BotFoundry.Telegram.Core/           # Pipeline, router, step runner
โ””โ”€โ”€ BotFoundry.Telegram/                # Telegram.Bot adapters, DI registration
tests/
โ””โ”€โ”€ BotFoundry.Telegram.Tests/          # xUnit + NSubstitute + FluentAssertions
docs/
โ””โ”€โ”€ architecture/                       # Architecture docs, design docs, ADRs

๐Ÿ“„ License

MIT

Product 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. 
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
1.0.2 97 9/2/2026
1.0.1 108 8/23/2026