Приветствую всех в своем уроке по написанию Telegram бота на языке программирования C#. В этом уроке я постараюсь максимально просто и понятно объяснить как написать своего бота с нуля. Конечно, здесь не будет какого-то трудного проекта, я дам вам необходимую базу, с помощью которой вы сможете писать своих ботов.
Начало
Для начала нам требуется зарегистрировать нашего бота. Делается это через "Ботобатю" (кликабельно) в самом Telegram.
Переходим по ссылке и попадаем в диалог с BotFather и жмем "Запустить". BotFather - это официальный бот Telegram`а, через которого проходит регистрация и настройка ваших ботов.
От вашего лица должна отправиться команда /start , на которую бот ответит большим списком команд с описаниями. Подробно изучить каждую Вы можете сами, а мы двигаемся к шагу 3.
Используем команду /newbot. На этом шаге нужно отправить боту имя Вашего будущего бота. Например, пусть это будет Александр Николаевич.
Далее нас попросят ввести username бота, учтите, что он должен быть написан строго на латинице, а также содержать bot или _bot в конце. Username - это уникальный идентификатор аккаунта (вашего или бота), по которому люди могут найти аккаунт в поиске. В случае, если username уже занят, вас попросят использовать другой.
После всех выполнений действий, Ботобатя присылает нам небольшое сообщение, в котором говорится об успешном создании бота. Из этого сообщения нам нужен token (уникальный ключ), с помощью которого мы будем авторизировать нашего бота в дальнейшем, он имеет примерно такой вид: 13523457:AFAgd_SDFG6sd7f6asdf67asdf78. Учтите, что абсолютно никому нельзя присылать этот token! Это ваш секретный ключ для доступа к боту.
Создание проекта в IDE
После того, как мы создали бота, перейдем к созданию проекта в вашей IDE. У меня это Rider от компании JetBrains. Вы можете использовать эту же IDE, либо Visual Studio от компании Microsoft, либо все от той же компании Visual Studio Code.
По сути, вы можете создать любой тип проекта, будь то консольное приложение или же какой-нибудь WinForms. Я всегда создаю консольное приложение, так как в будущем делаю деплой на Linux, да и как-то не вижу смысла в создании бота с программным интерфейсом.
Если же вы до сих пор не знаете, что такое IDE, как создавать проекты, то вам явно рано писать ботов, займитесь для начала изучением языка!
После того, как мы создали проект, нам нужно установить библиотеку Telegram.Bot (GitHub библиотеки, Nuget пакет). Сделать это можно либо через терминал в IDE, написав команду
dotnet add Telegram.Bot
либо же использовать графический интерфейс. На момент написания статьи была установлена самая последняя и самая актуальная версия пакета (19.0.0).
У библиотеки есть своя документация, можете посмотреть ее здесь.
Написание бота
Теперь приступим к написанию бота, для начала напишем стандартный класс Program. Добавим туда объект интерфейса ITelegramBotClient и в методе Main создадим стандартные переменные и присвоим им соответствующие значения.
class Program
{
// Это клиент для работы с Telegram Bot API, который позволяет отправлять сообщения, управлять ботом, подписываться на обновления и многое другое.
private static ITelegramBotClient _botClient;
// Это объект с настройками работы бота. Здесь мы будем указывать, какие типы Update мы будем получать, Timeout бота и так далее.
private static ReceiverOptions _receiverOptions;
static async Task Main()
{
_botClient = new TelegramBotClient("<token>"); // Присваиваем нашей переменной значение, в параметре передаем Token, полученный от BotFather
_receiverOptions = new ReceiverOptions // Также присваем значение настройкам бота
{
AllowedUpdates = new[] // Тут указываем типы получаемых Update`ов, о них подробнее расказано тут https://core.telegram.org/bots/api#update
{
UpdateType.Message, // Сообщения (текст, фото/видео, голосовые/видео сообщения и т.д.)
},
// Параметр, отвечающий за обработку сообщений, пришедших за то время, когда ваш бот был оффлайн
// True - не обрабатывать, False (стоит по умолчанию) - обрабаывать
ThrowPendingUpdates = true,
};
using var cts = new CancellationTokenSource();
// UpdateHander - обработчик приходящих Update`ов
// ErrorHandler - обработчик ошибок, связанных с Bot API
_botClient.StartReceiving(UpdateHandler, ErrorHandler, _receiverOptions, cts.Token); // Запускаем бота
var me = await _botClient.GetMeAsync(); // Создаем переменную, в которую помещаем информацию о нашем боте.
Console.WriteLine($"{me.FirstName} запущен!");
await Task.Delay(-1); // Устанавливаем бесконечную задержку, чтобы наш бот работал постоянно
}
}
Теперь давайте в этом же классе (можно и в другом) напишем методы UpdateHandler и ErrorHandler.
private static async Task UpdateHandler(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
// Обязательно ставим блок try-catch, чтобы наш бот не "падал" в случае каких-либо ошибок
try
{
// Сразу же ставим конструкцию switch, чтобы обрабатывать приходящие Update
switch (update.Type)
{
case UpdateType.Message:
{
Console.WriteLine("Пришло сообщение!");
return;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private static Task ErrorHandler(ITelegramBotClient botClient, Exception error, CancellationToken cancellationToken)
{
// Тут создадим переменную, в которую поместим код ошибки и её сообщение
var ErrorMessage = error switch
{
ApiRequestException apiRequestException
=> $"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}",
_ => error.ToString()
};
Console.WriteLine(ErrorMessage);
return Task.CompletedTask;
}
Теперь давайте посмотрим, работает ли наш код ? Жмем на кнопку Debug и проверяем :)
Как мы видим, бот успешно запустился, теперь давайте напишем пару сообщений, чтобы понять, получает ли он сообщения
И тут тоже все прекрасно, теперь давайте дополним наш метод UpdateHandler и напишем эхо бота. Также чуть-чуть попозже расскажу немного о типах Update.
private static async Task UpdateHandler(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
// Обязательно ставим блок try-catch, чтобы наш бот не "падал" в случае каких-либо ошибок
try
{
// Сразу же ставим конструкцию switch, чтобы обрабатывать приходящие Update
switch (update.Type)
{
case UpdateType.Message:
{
// эта переменная будет содержать в себе все связанное с сообщениями
var message = update.Message;
// From - это от кого пришло сообщение (или любой другой Update)
var user = message.From;
// Выводим на экран то, что пишут нашему боту, а также небольшую информацию об отправителе
Console.WriteLine($"{user.FirstName} ({user.Id}) написал сообщение: {message.Text}");
// Chat - содержит всю информацию о чате
var chat = message.Chat;
await botClient.SendTextMessageAsync(
chat.Id,
message.Text, // отправляем то, что написал пользователь
replyToMessageId: message.MessageId // по желанию можем поставить этот параметр, отвечающий за "ответ" на сообщение
);
return;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Теперь проверяем.
Как мы видим, все работает идеально. Теперь расскажу о типах Update, так как дальше мы напишем кое-что посложнее. Итак, типы Update:
Message - принимает в себя все сообщения. Обычные текстовые, фото или видео, аудио или видео сообщения (кружочки), стикеры, контакты, геопозицию, голосование и так далее. Всё, что мы отправляем в чат - это все Message.
EditedMessage - тут все просто: этот тип принимает в себя любое обновление сообщения. Схож с Message.
ChannelPost - как и Message, но направлен на каналы.
EditedChannelPost - аналогичен EditedMessage, но также направлен на каналы.
CallbackQuery - отвечает за Inline кнопки, они висят под сообщением, возможно, вы их уже видели в других ботах.
Poll - получает все связанное с голосованием.
PollAnswner - а этот тип работает только тогда, когда пользователь изменил свой ответ в голосовании.
ChatMember - всё, что касается людей в чате/канале: зашел, вышел, повысили, понизили, замьютили и т.д.
MyChatMember - всё, что касается бота в диалоге между пользователем и ботом, т.е. изменения в личных сообщениях.
ChatJoinRequest - получение информации о поданной заявки на вступление в чат/канал.
InlineQuery - получение входящих inline запросов. Inline запрос - это, когда вы в чате используете @ и username бота и вводите какой-то запрос, а результат выполнения отправляется в чат от вашего лица с надписью "сделано с помощью....".
ChosenInlineResult - а это уже то, что как раз таки выбрал пользователь. Т.е. InlineQuery это просто как разрешение использовать эту функцию, а ChosenInlineResult получает выбор пользователя и обрабатывает его. Знаю, что вы думаете "Они что, совсем идиоты ? Не могли сделать нормально ?", но привыкайте, такого будет полно)
PreCheckoutQuery - сюда приходит информация о платеже, который начал оплачивать пользователь.
ShippingQuery - а это срабатывает тогда, когда успешно сработал PreCheckoutQuery , т.е. этот update уже подтверждает успешную оплату пользователем.
Фух, ну вроде все, если желаете посмотреть оригинал, то он находится здесь.
Теперь давайте напишем что-нибудь посложнее. Добавим в AllowedUpdates тип CallbackQuery:
AllowedUpdates = new[] // Тут указываем типы получаемых Update`ов, о них подробнее расказано тут https://core.telegram.org/bots/api#update
{
UpdateType.Message, // Сообщения (текст, фото/видео, голосовые/видео сообщения и т.д.)
UpdateType.CallbackQuery // Inline кнопки
},
Теперь в нашем UpdateHandler добавим обработку команды /start и сделаем там несколько клавиатур, чтобы вы поняли, как работать с разными типами update, а также увидели еще одну клавиатуру, которая называется Reply клавиатура.
private static async Task UpdateHandler(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
// Обязательно ставим блок try-catch, чтобы наш бот не "падал" в случае каких-либо ошибок
try
{
// Сразу же ставим конструкцию switch, чтобы обрабатывать приходящие Update
switch (update.Type)
{
case UpdateType.Message:
{
// эта переменная будет содержать в себе все связанное с сообщениями
var message = update.Message;
// From - это от кого пришло сообщение
var user = message.From;
// Выводим на экран то, что пишут нашему боту, а также небольшую информацию об отправителе
Console.WriteLine($"{user.FirstName} ({user.Id}) написал сообщение: {message.Text}");
// Chat - содержит всю информацию о чате
var chat = message.Chat;
// Добавляем проверку на тип Message
switch (message.Type)
{
// Тут понятно, текстовый тип
case MessageType.Text:
{
// тут обрабатываем команду /start, остальные аналогично
if (message.Text == "/start")
{
await botClient.SendTextMessageAsync(
chat.Id,
"Выбери клавиатуру:\n" +
"/inline\n" +
"/reply\n");
return;
}
if (message.Text == "/inline")
{
// Тут создаем нашу клавиатуру
var inlineKeyboard = new InlineKeyboardMarkup(
new List<InlineKeyboardButton[]>() // здесь создаем лист (массив), который содрежит в себе массив из класса кнопок
{
// Каждый новый массив - это дополнительные строки,
// а каждая дополнительная строка (кнопка) в массиве - это добавление ряда
new InlineKeyboardButton[] // тут создаем массив кнопок
{
InlineKeyboardButton.WithUrl("Это кнопка с сайтом", "https://habr.com/"),
InlineKeyboardButton.WithCallbackData("А это просто кнопка", "button1"),
},
new InlineKeyboardButton[]
{
InlineKeyboardButton.WithCallbackData("Тут еще одна", "button2"),
InlineKeyboardButton.WithCallbackData("И здесь", "button3"),
},
});
await botClient.SendTextMessageAsync(
chat.Id,
"Это inline клавиатура!",
replyMarkup: inlineKeyboard); // Все клавиатуры передаются в параметр replyMarkup
return;
}
if (message.Text == "/reply")
{
// Тут все аналогично Inline клавиатуре, только меняются классы
// НО! Тут потребуется дополнительно указать один параметр, чтобы
// клавиатура выглядела нормально, а не как абы что
var replyKeyboard = new ReplyKeyboardMarkup(
new List<KeyboardButton[]>()
{
new KeyboardButton[]
{
new KeyboardButton("Привет!"),
new KeyboardButton("Пока!"),
},
new KeyboardButton[]
{
new KeyboardButton("Позвони мне!")
},
new KeyboardButton[]
{
new KeyboardButton("Напиши моему соседу!")
}
})
{
// автоматическое изменение размера клавиатуры, если не стоит true,
// тогда клавиатура растягивается чуть ли не до луны,
// проверить можете сами
ResizeKeyboard = true,
};
await botClient.SendTextMessageAsync(
chat.Id,
"Это reply клавиатура!",
replyMarkup: replyKeyboard); // опять передаем клавиатуру в параметр replyMarkup
return;
}
return;
}
// Добавил default , чтобы показать вам разницу типов Message
default:
{
await botClient.SendTextMessageAsync(
chat.Id,
"Используй только текст!");
return;
}
}
return;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Как мы видим, бот теперь реагирует на /start:
Вот так выглядит inline клавиатура:
А вот так reply клавиатура:
Причем, если вы понажимаете на кнопки, то как вы уже поняли, ничего не произойдет:)
Конечно, это логично, ведь мы не добавили обработчики этих кнопок. Как вы могли заметить, reply клавиатура - это просто как заготовленный текст для пользователя, поэтому с обработкой этих кнопок у вас не должно возникнуть проблем. Так как это просто дополнительные if в блоке case MessageType.Text. Но я все же покажу, как это сделать, после перейдем к кейсу с Inline клавиатурой.
private static async Task UpdateHandler(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
// Обязательно ставим блок try-catch, чтобы наш бот не "падал" в случае каких-либо ошибок
try
{
// Сразу же ставим конструкцию switch, чтобы обрабатывать приходящие Update
switch (update.Type)
{
case UpdateType.Message:
{
// тут все переменные
// Добавляем проверку на тип Message
switch (message.Type)
{
// Тут понятно, текстовый тип
case MessageType.Text:
{
// а тут обработчики команд
if (message.Text == "Позвони мне!")
{
await botClient.SendTextMessageAsync(
chat.Id,
"Хорошо, присылай номер!",
replyToMessageId: message.MessageId);
return;
}
if (message.Text == "Напиши моему соседу!")
{
await botClient.SendTextMessageAsync(
chat.Id,
"А самому что, трудно что-ли ?",
replyToMessageId: message.MessageId);
return;
}
return;
}
// тут остальной код
}
return;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Ну, вот так как-то вышло:
Теперь перейдем к блоку с Inline клавиатурами. Для обработки этой клавиатуры нам потребуется добавить в
switch(update.Type)
{
case UpdateType.Message:
{
// тут весь код из примеров выше
}
}
следующий код:
case UpdateType.CallbackQuery:
{
// Переменная, которая будет содержать в себе всю информацию о кнопке, которую нажали
var callbackQuery = update.CallbackQuery;
// Аналогично и с Message мы можем получить информацию о чате, о пользователе и т.д.
var user = callbackQuery.From;
// Выводим на экран нажатие кнопки
Console.WriteLine($"{user.FirstName} ({user.Id}) нажал на кнопку: {callbackQuery.Data}");
// Вот тут нужно уже быть немножко внимательным и не путаться!
// Мы пишем не callbackQuery.Chat , а callbackQuery.Message.Chat , так как
// кнопка привязана к сообщению, то мы берем информацию от сообщения.
var chat = callbackQuery.Message.Chat;
// Добавляем блок switch для проверки кнопок
switch (callbackQuery.Data)
{
// Data - это придуманный нами id кнопки, мы его указывали в параметре
// callbackData при создании кнопок. У меня это button1, button2 и button3
case "button1":
{
// В этом типе клавиатуры обязательно нужно использовать следующий метод
await botClient.AnswerCallbackQueryAsync(callbackQuery.Id);
// Для того, чтобы отправить телеграмму запрос, что мы нажали на кнопку
await botClient.SendTextMessageAsync(
chat.Id,
$"Вы нажали на {callbackQuery.Data}");
return;
}
case "button2":
{
// А здесь мы добавляем наш сообственный текст, который заменит слово "загрузка", когда мы нажмем на кнопку
await botClient.AnswerCallbackQueryAsync(callbackQuery.Id, "Тут может быть ваш текст!");
await botClient.SendTextMessageAsync(
chat.Id,
$"Вы нажали на {callbackQuery.Data}");
return;
}
case "button3":
{
// А тут мы добавили еще showAlert, чтобы отобразить пользователю полноценное окно
await botClient.AnswerCallbackQueryAsync(callbackQuery.Id, "А это полноэкранный текст!", showAlert: true);
await botClient.SendTextMessageAsync(
chat.Id,
$"Вы нажали на {callbackQuery.Data}");
return;
}
}
return;
}
В конечном счете UpdateHandler должен выглядеть вот так:
private static async Task UpdateHandler(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
// Обязательно ставим блок try-catch, чтобы наш бот не "падал" в случае каких-либо ошибок
try
{
// Сразу же ставим конструкцию switch, чтобы обрабатывать приходящие Update
switch (update.Type)
{
case UpdateType.Message:
{
// Эта переменная будет содержать в себе все связанное с сообщениями
var message = update.Message;
// From - это от кого пришло сообщение (или любой другой Update)
var user = message.From;
// Выводим на экран то, что пишут нашему боту, а также небольшую информацию об отправителе
Console.WriteLine($"{user.FirstName} ({user.Id}) написал сообщение: {message.Text}");
// Chat - содержит всю информацию о чате
var chat = message.Chat;
// Добавляем проверку на тип Message
switch (message.Type)
{
// Тут понятно, текстовый тип
case MessageType.Text:
{
// тут обрабатываем команду /start, остальные аналогично
if (message.Text == "/start")
{
await botClient.SendTextMessageAsync(
chat.Id,
"Выбери клавиатуру:\n" +
"/inline\n" +
"/reply\n");
return;
}
if (message.Text == "/inline")
{
// Тут создаем нашу клавиатуру
var inlineKeyboard = new InlineKeyboardMarkup(
new List<InlineKeyboardButton[]>() // здесь создаем лист (массив), который содрежит в себе массив из класса кнопок
{
// Каждый новый массив - это дополнительные строки,
// а каждая дополнительная кнопка в массиве - это добавление ряда
new InlineKeyboardButton[] // тут создаем массив кнопок
{
InlineKeyboardButton.WithUrl("Это кнопка с сайтом", "https://habr.com/"),
InlineKeyboardButton.WithCallbackData("А это просто кнопка", "button1"),
},
new InlineKeyboardButton[]
{
InlineKeyboardButton.WithCallbackData("Тут еще одна", "button2"),
InlineKeyboardButton.WithCallbackData("И здесь", "button3"),
},
});
await botClient.SendTextMessageAsync(
chat.Id,
"Это inline клавиатура!",
replyMarkup: inlineKeyboard); // Все клавиатуры передаются в параметр replyMarkup
return;
}
if (message.Text == "/reply")
{
// Тут все аналогично Inline клавиатуре, только меняются классы
// НО! Тут потребуется дополнительно указать один параметр, чтобы
// клавиатура выглядела нормально, а не как абы что
var replyKeyboard = new ReplyKeyboardMarkup(
new List<KeyboardButton[]>()
{
new KeyboardButton[]
{
new KeyboardButton("Привет!"),
new KeyboardButton("Пока!"),
},
new KeyboardButton[]
{
new KeyboardButton("Позвони мне!")
},
new KeyboardButton[]
{
new KeyboardButton("Напиши моему соседу!")
}
})
{
// автоматическое изменение размера клавиатуры, если не стоит true,
// тогда клавиатура растягивается чуть ли не до луны,
// проверить можете сами
ResizeKeyboard = true,
};
await botClient.SendTextMessageAsync(
chat.Id,
"Это reply клавиатура!",
replyMarkup: replyKeyboard); // опять передаем клавиатуру в параметр replyMarkup
return;
}
if (message.Text == "Позвони мне!")
{
await botClient.SendTextMessageAsync(
chat.Id,
"Хорошо, присылай номер!",
replyToMessageId: message.MessageId);
return;
}
if (message.Text == "Напиши моему соседу!")
{
await botClient.SendTextMessageAsync(
chat.Id,
"А самому что, трудно что-ли ?",
replyToMessageId: message.MessageId);
return;
}
return;
}
// Добавил default , чтобы показать вам разницу типов Message
default:
{
await botClient.SendTextMessageAsync(
chat.Id,
"Используй только текст!");
return;
}
}
return;
}
case UpdateType.CallbackQuery:
{
// Переменная, которая будет содержать в себе всю информацию о кнопке, которую нажали
var callbackQuery = update.CallbackQuery;
// Аналогично и с Message мы можем получить информацию о чате, о пользователе и т.д.
var user = callbackQuery.From;
// Выводим на экран нажатие кнопки
Console.WriteLine($"{user.FirstName} ({user.Id}) нажал на кнопку: {callbackQuery.Data}");
// Вот тут нужно уже быть немножко внимательным и не путаться!
// Мы пишем не callbackQuery.Chat , а callbackQuery.Message.Chat , так как
// кнопка привязана к сообщению, то мы берем информацию от сообщения.
var chat = callbackQuery.Message.Chat;
// Добавляем блок switch для проверки кнопок
switch (callbackQuery.Data)
{
// Data - это придуманный нами id кнопки, мы его указывали в параметре
// callbackData при создании кнопок. У меня это button1, button2 и button3
case "button1":
{
// В этом типе клавиатуры обязательно нужно использовать следующий метод
await botClient.AnswerCallbackQueryAsync(callbackQuery.Id);
// Для того, чтобы отправить телеграмму запрос, что мы нажали на кнопку
await botClient.SendTextMessageAsync(
chat.Id,
$"Вы нажали на {callbackQuery.Data}");
return;
}
case "button2":
{
// А здесь мы добавляем наш сообственный текст, который заменит слово "загрузка", когда мы нажмем на кнопку
await botClient.AnswerCallbackQueryAsync(callbackQuery.Id, "Тут может быть ваш текст!");
await botClient.SendTextMessageAsync(
chat.Id,
$"Вы нажали на {callbackQuery.Data}");
return;
}
case "button3":
{
// А тут мы добавили еще showAlert, чтобы отобразить пользователю полноценное окно
await botClient.AnswerCallbackQueryAsync(callbackQuery.Id, "А это полноэкранный текст!", showAlert: true);
await botClient.SendTextMessageAsync(
chat.Id,
$"Вы нажали на {callbackQuery.Data}");
return;
}
}
return;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
Теперь запускаем проект и проверяем кнопки!
Заключение
К сожалению, на этом пока всё. Скорее всего будет вторая часть этой статьи или полноценный видеоурок на Youtube, но пока вот так.
Прошу оценить мою статью и оставить комментарий, так как это первая моя подобная работа, до этого мне не доводилось писать статьи или что-нибудь подобное.