Making a Telegram bot with BotticelliBots
Tags: bot, telegram, botticellibots, csharp
Published: 2024-04-10 12:00:35


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ddj3d4ygi8tfk96b3m6w.jpg)

Let's imagine, that we need to create a .NET core - based chatbot, that shows us current time in UTC.

So, we need .NET Core 8.0 and very basic knowledge of C#.

**Before you start**

Please, check your .NET Core and Visual Studio version. You should have .NET Core 8 or higher and VS 2022 or Rider v. 2023.x (any edition). If no - check  [this](https://visualstudio.microsoft.com/ru/vs/) and [this](https://dotnet.microsoft.com/en-us/download/dotnet/8.0). As a framework and chatbot-management system, we'll use [BotticelliBots](https://botticellibots.com) v. 0.3: [see here](https://github.com/devgopher/botticelli/releases/tag/v0.3). You may load it as a submodule with your git repository. 


**Docs**
- [Botticelli common documentation](http://botticellibots.com/documentation)
- [Bot adding/registration in admin pane](http://botticellibots.com/documentation/management/registering-a-new-bot)
- [Admin pane deployment scripts for Linux and Windows](https://github.com/devgopher/botticelli/tree/release/0.3/deploy)

**What do we want**

We need to create a chat bot, that will show you a UTC time on command

And also we need to administrate this bot.

**Let's register our Telegram bot**
Please, check a Telegram [BotFather bot](https://t.me/BotFather) and follow the instructions

**Some basics for BotticelliBots**
Botticelli is an open-source .NET Core framework for building your own universal bots integrated with databases, queue brokers, speech engines and AI engines (such as ChatGPT, DeepSeek and YaGPT). 

In our case, we need several entities to use:
- Commands - represents a messenger command (such as /test)
- Command Processors. Each command needs it's own processor to encapsulate any business logic
- IBot - interface, that represents main bot functions (such as sending message to a chat)
- Message - general message representation, that consists of ChatId, Id and contents

**Let's start**

Let's open our Visual Studio/Rider and create a WebApi project for .Net Core.

First of all, we need  to make some injections in Program.cs:

```csharp
var builder = WebApplication.CreateBuilder(args);

var settings = builder.Configuration
    .GetSection(nameof(VeryBasicBotSettings))
    .Get<VeryBasicBotSettings>();

// Adds telegram bot injections
builder.Services.AddTelegramBot(builder.Configuration,
    new BotOptionsBuilder<TelegramBotSettings>()
        .Set(s => s.SecureStorageSettings = new SecureStorageSettings
        {
            ConnectionString = settings?.SecureStorageConnectionString
        })
        .Set(s => s.Name = settings?.BotName));

// Adds a hosted service, intended for keeping bot working during all
// the lifecycle of a bot
builder.Services.AddHostedService<VeryBasicBotService>();
// Adds a command processor, being used for proceeding a business logic for
// '/GetUtc' command and also adds some validation (in this case - pass validator, that tells OK to any command args)
builder.Services.AddBotCommand<GetUtcCommand, GetUtcCommandProcessor<ReplyKeyboardMarkup>, PassValidator<GetUtcCommand>>();

var app = builder.Build();
app.Services.RegisterBotCommand<GetUtcCommand, GetUtcCommandProcessor<ReplyKeyboardMarkup>, TelegramBot>();

app.Run();
```

**Command Processors**

Each command needs to be processed in it's own processor. Please note, that we should follow a calling convention for a processor and a command: 
```csharp
class <*YourCommandName*>CommandProcessor<TReplyMarkupBase> : CommandProcessor<*YourCommandName*Command> 

public class *YourCommandName*Command : ICommand
```

So, our command processor should be look like:
```csharp
/// <summary>
/// A processor for "/GetUtc" command 
/// </summary>
/// <typeparam name="TReplyMarkupBase"></typeparam>
public class GetUtcCommandProcessor<TReplyMarkupBase> : CommandProcessor<GetUtcCommand> 
    where TReplyMarkupBase : class
{   
    public GetUtcCommandProcessor(ILogger<GetUtcCommandProcessor<TReplyMarkupBase>> logger,
        ICommandValidator<GetUtcCommand> validator,
        MetricsProcessor metricsProcessor)
        : base(logger, validator, metricsProcessor)
    {
    }

    protected override Task InnerProcessContact(Message message, string argsString, CancellationToken token)
    {
        return Task.CompletedTask;
    }

    protected override Task InnerProcessPoll(Message message, string argsString, CancellationToken token)
    {
        return Task.CompletedTask;
    }

    protected override Task InnerProcessLocation(Message message, string argsString, CancellationToken token)
    {
        return Task.CompletedTask;
    }

    /// <summary>
    /// All business logic is being called here...
    /// </summary>
    /// <param name="message"></param>
    /// <param name="args"></param>
    /// <param name="token"></param>
    protected override async Task InnerProcess(Message message, string args, CancellationToken token)
    {
        // Creates a message for sending
        var utcMessageRequest = new SendMessageRequest(Guid.NewGuid().ToString())
        {
            Message = new Message
            {
                Uid = Guid.NewGuid().ToString(),
                ChatIds = message.ChatIds,
                Body = $"Current UTC Time is: {DateTime.UtcNow.ToString(CultureInfo.InvariantCulture)}",
            }
        };

        // Tries to send a message using a concrete implementation of Bot (TelegramBot, for example)
        await _bot.SendMessageAsync(request: utcMessageRequest, optionsBuilder: (ISendOptionsBuilder<TReplyMarkupBase>)null!, token: token);
    }
}

```

**Hosted service**
Also, we should run a hosted service in order to keep our bot onair:

```csharp
namespace VeryBasicBot.Telegram;

/// <summary>
///     This hosted service intended keeping an application alive till the termination
/// </summary>
public class VeryBasicBotService : IHostedService
{
    private readonly IOptionsMonitor<VeryBasicBotSettings> _settings;
    private readonly IBot<TelegramBot> _telegramBot;

    public VeryBasicBotService(IBot<TelegramBot> telegramBot, IOptionsMonitor<VeryBasicBotSettings> settings)
    {
        _telegramBot = telegramBot;
        _settings = settings;
    }
    
    public Task StartAsync(CancellationToken token)
    {
        Console.WriteLine("Start serving...");
 
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("Stop serving...");

        return Task.CompletedTask;
    }
}
```

you can download a whole code here: [github](https://github.com/devgopher/very_basic_bot)

**First run**
So, we need to run our bot now. We need to get a freshly-generated a bot Id:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gwyv6bpq6xpoxsm9kqmh.PNG)

**Deploying an admin-side**
[Admin pane deployment scripts for Linux](https://github.com/devgopher/botticelli/tree/release/0.3/deploy/linux)

First of all, you need to choose where to deploy and admin pane.
We've 2 parts of admin:

- Backend
- Frontend

After deploying using proposed scripts, we need to register. So, after setting up frontend and backend, you should go to: https://<your_front_url>:<your_front_port> and press **Registration** button. On a registration form, please, put in your email and press **Register**. If your backend email server settings are correct, you'll receive a registration letter with username and password.

**_NOTE_**
If you experiencing some problems with untrusted certificate for backend, you may go to https://<backend_url>:<backend_port> in your browser and allow using untrusted certificate. 

So, after registration, you may refresh a page and log in. 

**Adding a bot to admin pane**
After registering and logging in, we need to add own freshly developed bot onto a "Your bots" pane and activate it as it's shown on pictures:

1. Go to 'Your bots' pane: ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvfl6juczo3ytpbbv6je.PNG)
2. Fill parameters and a description: ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/disq6mavwxw7qsk4xoil.PNG)
3. Press 'Unlock' button: 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xqezkilg6i91gradlhq2.PNG)

** Some additional settings **
In a bot solution, please, find `appsettings.json` and make some changes:

```json

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "VeryBasicBotSettings": {
    "SecureStorageConnectionString": "Filename=database.db;Password=123;ReadOnly=false",
    "BotName": "TimeTellingBot"
  },
  "ServerSettings": {
    "ServerUri": "http://<backend_url>:<backend_http_port>/v1/"
  },
  "AnalyticsSettings": {
    "TargetUrl": "http://<analytics_url>:<analytics_http_port>/v1/"
  },
  "AllowedHosts": "*"
}

```
Since, we didn't deploy analytics component, we may set some mocking **TargetUrl**

**Let's check the result**
Now, all we need is to run a VeryBasicBot.Telegram project.

{% embed https://youtu.be/StDd0uqzzCo %}

sources: [github](https://github.com/devgopher/very_basic_bot)
based on BotticelliBots: https://botticellibots.com
email: botticellibots@gmail.com
telegram group: https://t.me/botticelli_bots
Air quality telegram bot, based on BotticelliBots: https://t.me/air_quality_info_bot