Добрый день, уважаемые друзья!

В этой статье мы рассмотрим заготовку для создания телеграм бота на C#. В связи с последними обновлениями TelegramBotAPI, большая часть удачных с моей точки зрения публикаций на эту тему несколько устарело. Потому я принял решение написать статью на эту тему.

Чуть подробнее

Мы будем пользоваться библиотеками Telegram.Bot и Telegram.Bot.Extentions.Polling, обновления будем получать периодически опрашивая Telegram сервер на наличие новых обновлений. Webhook'и мы использовать не будем...Тпру, подождите меня забрасывать гнилыми помидорами, матерые кодеры! Да, метод получения обновлений основанный на Webhook'ах лучше, но Polling проще в реализации поскольку не нужно получать SSL-сертификат и бот можно запустить сразу после написания кода без дополнительных заморочек. На этом новичок может застопориться. К тому же есть ряд нюансов при использовании Webhook'ов на моем сервере. Если они есть у меня, значит они могут быть и у Вас. Потому используем метод периодического опроса сервера Телеграма на наличие новых обновлений. Ладно, уважаемый читатель, если Вы все еще со мной не согласны и желаете получить сертификат и работать на Webhook'ах, можешь почитать о получении сертификата в этой статье.

Существующие схемы работы telegram бота

Мне нравится схема работы telegram бота на C#, описанная в этой статье. Считаю ее хорошим примером. Вот код:

using Telegram.Bot;
using Telegram.Bot.Args;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
class Program
{
	private static TelegramBotClient client;	
	static void Main(string[] args)
	{
// token, который вернул BotFather
		client = new TelegramBotClient(token);
		client.OnMessage += BotOnMessageReceived;
		client.OnMessageEdited += BotOnMessageReceived;
		client.StartReceiving();
		Console.ReadLine();
		client.StopReceiving(); 
	} 
private async void BotOnMessageReceived(object sender, MessageEventArgs messageEventArgs)
{
var message = messageEventArgs.Message;         
if (message?.Type == MessageType.TextMessage)
{
await client.SendTextMessageAsync(message.Chat.Id, message.Text);
}
} 
}

Там ничего лишнего. Создаем объект TelegramBotClient чтобы взаимодействовать с нашим ботом с помощью библиотеки, прописываем ему токен, который выдал нам BotFather. Далее создаем событие OnMessage, обрабатываем его методом BotOnMessageReceived и запускаем клиент.

Однако с выходом более новых версий TelegramBotAPI оказалось, что такая схема больше не работает. Более того, боты, написанные на более ранних версиях Telegram.Bot перестают работать после обновления библиотеки.

Нужно использовать другую схему. Давайте попробуем в этом разобраться.

Пошаговая инструкция

Итак ниже я набросал следующий список шагов, которые приведут вас к рабочему telegram боту.

1. Запускаем Visual Studio Community, создаем консольное приложение.

Если у Вас отсутствует Visual Studio Community, Вы можете установить ее используя статью или несколько устаревшее видео. При этом желательно выбирать установку Visual Studio Community 2022 как наиболее актуальную версию на текущий момент.

Создаем проект
Создаем проект
Называем проект как нам удобно
Называем проект как нам удобно
Выбираем платформу .NET 3.1
Выбираем платформу .NET 3.1
Проект создан!
Проект создан!

2. Добавляем в консольное приложение библиотеку Telegram.Bot, Telegram.Bot.Extentions.Polling и Netonsoft.Json

Открытие NuGet
Открытие NuGet
Поиск библиотеки
Поиск библиотеки
Установка пакета
Установка пакета

По завершению у Вас должны быть установлены и отображены названия пакетов, которые мы установили.

Netonsoft.Json применимо к нашей заготовке нужна для того, чтобы у нас была возможность вывести на консоль сообщение от пользователя.

3. Создаем telegram бот в BotFather. Копируем его api key для работы.

Находим в telegram BotFather, отправляем ему /newbot, название и логин бота

BotFather должен нам предоставить API key, который мы должны вставить в код-каркас в следующем шаге в строке

static ITelegramBotClient bot = new TelegramBotClient("TOKEN");

4. В файл Program.cs вставляем следующий код-каркас:

using System;
using System.Threading;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Extensions.Polling;
using Telegram.Bot.Types;
using Telegram.Bot.Exceptions;

namespace TelegramBotExperiments
{

    class Program
    {
        static ITelegramBotClient bot = new TelegramBotClient("TOKEN");
        public static async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
        {
            // Некоторые действия
            Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(update));
            if(update.Type == Telegram.Bot.Types.Enums.UpdateType.Message)
            {
                var message = update.Message;
                if (message.Text.ToLower() == "/start")
                {
                    await botClient.SendTextMessageAsync(message.Chat, "Добро пожаловать на борт, добрый путник!");
                    return;
                }
                await botClient.SendTextMessageAsync(message.Chat, "Привет-привет!!");
            }
        }

        public static async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken)
        {
            // Некоторые действия
            Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(exception));
        }


        static void Main(string[] args)
        {
            Console.WriteLine("Запущен бот " + bot.GetMeAsync().Result.FirstName);

            var cts = new CancellationTokenSource();
            var cancellationToken = cts.Token;
            var receiverOptions = new ReceiverOptions
            {
                AllowedUpdates = { }, // receive all update types
            };
            bot.StartReceiving(
                HandleUpdateAsync,
                HandleErrorAsync,
                receiverOptions,
                cancellationToken
            );
            Console.ReadLine();
        }
    }
}

5. Редактируем код под свои нужды и задачи.

Главное задачей, которую должен выполнять бот - это реагировать на сообщения, которые отправляет ему пользователь. Конечно, разработчики telegram'a заложили возможность отслеживания и реагирования на много других событий.

К важным я бы отнес еще нажатие кнопки inline клавиатуры пользователем, inline mode - когда пользователь вводит логин бота и поисковой запрос в текстовое поле и бот предоставляет список найденных объектов по этому запросу. Также интересно было бы рассмотреть событие публикации нового поста на канале. Получение от пользователя его номера телефона, файла или геолокации. Но это материал для следующих видеороликов и статей. Если Вам интересно увидеть этот материал на YouTube канале или в статье, ставьте лайки, делитесь статьей с друзьями. При достижении 200 лайков и 20 комментариев я буду знать, что вам нравится данная тема и напишу продолжение.

Итак что мы можем сделать когда пользователь отправил нашему боту сообщение?
Во-первых мы можем вывести ее на консоль. Для этого в Nuget установим либу Newtonsoft и пропишем в методе HandleUpdateAsync.

Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(update));

Конечно, мы можем проверить что он нам прислал. И если текст сообщения будет тем, который мы ждем, выполнить определенные действия.Например если пользователь нажал кнопку Start и тем самым отправил боту текст "/start", мы можем отправить ему в ответ "Добро пожаловать на борт, добрый путник!".

if (update.Type == Telegram.Bot.Types.Enums.UpdateType.Message)
	            {
	                var message = update.Message;
	                if (message.Text.ToLower() == "/start")
	                {
	                    await botClient.SendTextMessageAsync(message.Chat, "Добро пожаловать на борт, добрый путник!");
	                    return;
	                }
	                await botClient.SendTextMessageAsync(message.Chat, "Здоров, братан! И тебе не хворать!");
            }

if (update.Type == Telegram.Bot.Types.Enums.UpdateType.Message)
Здесь мы проверяем тип обновления. Если пользователь отправил нам сообщение, выполняем ниже описанные действия.

var message = update.Message;
Создаем новую переменную для удобства и записываем в нее всю информацию о пришедшем сообщении.

if (message.Text.ToLower() == "/start")
Проверяем какой текст отправил пользователь. Если текст сообщения в нижнем регистре (.ToLower()) является словом "/start", то пишем ему сообщение "Добро пожаловать на борт, добрый путник!". И останавливаем выполнение метода командой return.

Целесообразно также данные этого пользователя записать в какую-нибудь базу данных. Например MySQL, PostgreSQL или еще какую-то. Или просто в файл. Напишите в комментариях если Вам это интересно.

А если пользователь отправит боту другое сообщение, например "Здравствуй", мы можем написать боту, например, "Здоров, братан! И тебе не хворать"

await botClient.SendTextMessageAsync(message.Chat, "Здоров, братан! И тебе не хворать!");

Думаю принцип вы поняли.

Полный код ниже:

using System;
using System.Threading;
using System.Threading.Tasks;
using Telegram.Bot;
using Telegram.Bot.Extensions.Polling;
using Telegram.Bot.Types;
using Telegram.Bot.Exceptions;

namespace TelegramBotExperiments
{

    class Program
    {
        static ITelegramBotClient bot = new TelegramBotClient("TOKEN");
        public static async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
        {
            // Некоторые действия
            Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(update));
            if(update.Type == Telegram.Bot.Types.Enums.UpdateType.Message)
            {
                var message = update.Message;
                if (message.Text.ToLower() == "/start")
                {
                    await botClient.SendTextMessageAsync(message.Chat, "Добро пожаловать на борт, добрый путник!");
                    return;
                }
                await botClient.SendTextMessageAsync(message.Chat, "Привет-привет!!");
            }
        }

        public static async Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken)
        {
            // Некоторые действия
            Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(exception));
        }


        static void Main(string[] args)
        {
            Console.WriteLine("Запущен бот " + bot.GetMeAsync().Result.FirstName);

            var cts = new CancellationTokenSource();
            var cancellationToken = cts.Token;
            var receiverOptions = new ReceiverOptions
            {
                AllowedUpdates = { }, // receive all update types
            };
            bot.StartReceiving(
                HandleUpdateAsync,
                HandleErrorAsync,
                receiverOptions,
                cancellationToken
            );
            Console.ReadLine();
        }
    }
}

Не забудьте вставить в код API key от Вашего бота там, где написано TOKEN.

Для удобства записал видео.

Заключение

Итак в этой статье мы с вами создали telegram бот с нуля и протестировали его на работоспособность. Созданную заготовку можно будет использовать в дальнейшем для создания полномасштабных коммерческих проектов.

Комментарии (11)


  1. maxgammer
    26.03.2022 18:37
    +4

    Впервые увидел как это делается. Да еще и на c#. Спасибо за статью.


    1. AgentFire
      26.03.2022 21:10
      +3

      За что человеку минусы.


  1. Scrypto
    27.03.2022 09:03

    Тоже делал бота, чтобы разделить ответы, создал уровни пользователя (Пользователь идентифицируется по ID), при том или ином ответе переходит на новый уровень, или назад.
    Проблема встала при работе с inline кнопками, я могу, их послать, принять ответ, но вот не могу поменять на ходу. Не присылает inlineMessageId.
    Не подскажешь, что делаю не так?

    Отправка кнопок:

    public async void SendInline(long chatId, CancellationToken cancellationToken)
        {
            InlineKeyboardMarkup inlineKeyboard = new(new[]
            {
                    // first row
                    new[]
                    {
                        InlineKeyboardButton.WithCallbackData(text: "Кнопка 1", callbackData: "post"),
                        InlineKeyboardButton.WithCallbackData(text: "Кнопка 2", callbackData: "12"),
                    },
     
                });
     
            Message sentMessage = await botClient.SendTextMessageAsync(
                chatId: chatId,
                text: "за что мне это??",
                replyMarkup: inlineKeyboard,
                cancellationToken: cancellationToken);
        }

    Попытка изменить:

    if (update!.CallbackQuery!.InlineMessageId != null)
                    {
                        await botClient.EditMessageReplyMarkupAsync(inlineMessageId: update!.CallbackQuery!.InlineMessageId,
                            replyMarkup: inlineKeyboard);
     
                    }

    Кнопки присылают callbackData, но inlineMessageId всегда null


  1. vabka
    27.03.2022 09:25

    Netonsoft.Json применимо к нашей заготовке нужна для того, чтобы у нас была возможность вывести на консоль сообщение от пользователя.

    Зачем, если есть встроенный System.Text.Json?


    1. vabka
      27.03.2022 10:08
      +1

      Чуть подробнее, чем мне местный код не нравится, и что бы я изменил:


      Код
      using System.Diagnostics;
      using System.Text.Json;
      using Telegram.Bot;
      using Telegram.Bot.Extensions.Polling;
      using Telegram.Bot.Types;
      using Telegram.Bot.Types.Enums;
      
      var botClient = new TelegramBotClient("token");
      
      var cts = new CancellationTokenSource();
      Console.CancelKeyPress += (_, _) => cts.Cancel(); // Чтобы отловить нажатие ctrl+C и всякие sigterm, sigkill, etc
      
      var handler = new UpdateHandler();
      var receiverOptions = new ReceiverOptions();
      botClient.StartReceiving(handler, receiverOptions, cancellationToken: cts.Token);
      
      Console.WriteLine("Bot started. Press ^C to stop");
      await Task.Delay(-1, cancellationToken: cts.Token); // Такой вариант советуют MS: https://github.com/dotnet/runtime/issues/28510#issuecomment-458139641
      Console.WriteLine("Bot stopped");
      
      // Чтобы сильно не захламлять Main - это можно вынести в отдельный файл
      class UpdateHandler : IUpdateHandler
      {
          public async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
          {
              Debug.WriteLine(JsonSerializer.Serialize(update));
              // Вообще, для обработки сообщений лучше подходит паттерн "Цепочка обязанностей", но для примера тут switch-case
              // https://refactoring.guru/ru/design-patterns/chain-of-responsibility
              switch (update)
              {
                  case
                  {
                      Type: UpdateType.Message,
                      Message: { Text: { } text, Chat: { } chat },
                  } when text.Equals("/start", StringComparison.OrdinalIgnoreCase):
                  {
                      await botClient.SendTextMessageAsync(chat!, "Добро пожаловать на борт, добрый путник!", cancellationToken: cancellationToken);
                      break;
                  }
                  case
                  {
                      Type: UpdateType.Message,
                      Message.Chat: { } chat
                  }:
                  {
                      await botClient.SendTextMessageAsync(chat!, "Привет-привет!!", cancellationToken: cancellationToken);
                      break;
                  }
              }
          }
          public Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken)
          {
              Console.Error.WriteLine(exception);
              return Task.CompletedTask;
          }
      }
      


  1. vuidji
    28.03.2022 00:01

    Кажется в начале статьи есть достаточно важная неточность, Вы указываете, что "обновления будем получать периодически опрашивая Telegram сервер на наличие новых обновлений", но ведь указанный Telegram.Bot.Extentions.Polling использует long polling, поддерживаемый API Telegram согласно документации:

    getUpdates

    Use this method to receive incoming updates using long polling

    Таким образом, никакого периодического опроса сервера нет, мы подключаемся к нему и висим до тех пор, пока серверу не будет что отдать, сразу после получения новых данных библиотека делает повторное подключение к API Telegram и опять висит. За счёт этого нет каких - либо задержек при получении обновлений и боты работают моментально.

    По моему скромному мнению, с момента поддержки long polling не стало самого главного преимущества вебхуков (моментальности), а инфраструктурных недостатков и неудобств сильно больше. В наших проектах перешли на long polling и очень довольны, удобно.


  1. Anisim0ff
    28.03.2022 08:59

    Спасибо за гайд ! Жду еще


  1. kipzshady
    28.03.2022 09:14

    Пожалуйста, теперь покажите пример на вебхуках


  1. makar_crypt
    28.03.2022 09:43

    Привет. Спасибо за статью. Пару вопросиков

    1) Есть ли библиотека не для ботов, чтобы зайти под своим логином и паролем и например фильтровать сообщения из всех своих подписок?

    2) Как можно выдернуть урл на медиа ресурсы, например на видео? Мы долго рыскали по API , но не нашли такого функционала. При этом знаем что существуют боты которые формируют эти линки если переслать им сообщение с видео.

    Спасибо.


    1. vabka
      28.03.2022 22:47

      1. Есть вполне стандартное официальное решение — TDLib
      2. Можно пример такого бота?
        В api для ботов можно получить ссылку для скачивания, но она в себя включает токен бота.
        Может, подходят только те видео, которые добавлены по ссылке (на yt, к примеру), а не те видео, которые загружаются в сам телеграм?


  1. Evgeny40
    29.03.2022 12:16

    Привет всем. Новичок совсем в коде.

    Но не понимаю до конца. Как мне сделать, чтобы бот работал всегда ? А то она работает, когда я запускаю программу. А желательно, что бы он работал круглосуточно.

    За ранее Спасибо!