Вступление
Создание Telegram-ботов обычно ассоциируется с Python , но C++ — это мощная альтернатива для тех, кто ценит производительность и контроль над ресурсами. Использовать мы будем библиотеку Boost для работы с https запросами.

Если нужен только проект то он есть на гитхабе https://github.com/sergey00010/telegram_bot_cpp_boost
CMakeList.txt
Я в проекте буду использовать систему сборки cmake, т.к она очень удобна по многим причинам.
для начала укажем версию cmake, название проекта, и стандарт плюсов
cmake_minimum_required(VERSION 3.14)
project(TelegramBot)
set(CMAKE_CXX_STANDARD 17)
Далее укажем какие библиотеки мы будем использовать, Boost для работы с сетью, nlohmann_json для парсинга json и OpenSSL чтобы мы могли работать не только с http, но и с https запросами
find_package(Boost REQUIRED COMPONENTS system)
find_package(nlohmann_json REQUIRED)
find_package(OpenSSL REQUIRED)
target_link_libraries(tgBot
Boost::system
OpenSSL::SSL
OpenSSL::Crypto
nlohmann_json::nlohmann_json
)
Далее мы добавим все наши файлы в проект, все функции для бота будем создавать в отдельном классе, поэтому создаем файлы TelegramBot.cpp и TelegramBot.h
add_executable(tgBot
src/main.cpp
src/TelegramBot.cpp
src/TelegramBot.h
)
теперь cmake выглядит так:
cmake_minimum_required(VERSION 3.14)
project(TelegramBot)
set(CMAKE_CXX_STANDARD 17)
find_package(Boost REQUIRED COMPONENTS system)
find_package(nlohmann_json REQUIRED)
find_package(OpenSSL REQUIRED)
add_executable(tgBot
src/main.cpp
src/TelegramBot.cpp
src/TelegramBot.h
)
target_link_libraries(tgBot
Boost::system
OpenSSL::SSL
OpenSSL::Crypto
nlohmann_json::nlohmann_json
)
TelegramBot.h
Для начала добавим библиотеки, которые будем использовать, далее в классе создаем конструктор в который будем передавать токен бота и функцию, которая нужна для ответа пользователю. Далее добавляем функцию для запуска бота.
//transfer telegram bot token and function which use after get new message
TelegramBot(const std::string& token, const std::function<std::string()> &funcAnswer);
//start bot
void start();
После добавляем переменные,
apiUrl - в нее будем писать адрес телеграм апи + токен
botToken - в нее будем писать токен бота
lastUpdateId - используется для отслеживания последнего обработанного обновления от Telegram API, для того, чтобы бот не обрабатывал одни и те же обновления повторно.
std::string apiUrl;
std::string botToken;
std::string lastUpdateId;
Далее добавляем функции
void handleUpdates(const nlohmann::json& updates) - Обработка полученных обновлений. updates: JSON-объект, содержащий список обновлений.
void sendMessage(const std::string& chatId, const std::string& text) - Отправка сообщения в указанный чат. chatid Идентификатор чата, куда нужно отправить сообщение. text: Текст сообщения.
nlohmann::json makeRequest(const std::string& method, const nlohmann::json& payload = {}) - Выполнение HTTP-запросов к Telegram API. method: Метод Telegram API (например, getUpdates или sendMessage). payload: JSON-объект с параметрами запроса.
//process new messages
void handleUpdates(const nlohmann::json& updates);
//create new json and transfer to make request
void sendMessage(const std::string& chatId, const std::string& text);
//make request to api telegram server
nlohmann::json makeRequest(const std::string& method, const nlohmann::json& payload = {});
В переменную std::function funcAnswer будет писаться функция из конструктора класса, и уже ее мы будем вызывать, чтобы ответить пользователю.
Весь код:
#ifndef TELEGRAMBOT_H
#define TELEGRAMBOT_H
#include <string>
#include <boost/beast.hpp>
#include <nlohmann/json.hpp>
#include <functional>
class TelegramBot {
public:
//transfer telegram bot token and function which use after get new message
TelegramBot(const std::string& token, const std::function<std::string()> &funcAnswer);
//start bot
void start();
private:
std::string apiUrl;
std::string botToken;
std::string lastUpdateId;
//process new messages
void handleUpdates(const nlohmann::json& updates);
//create new json and transfer to make request
void sendMessage(const std::string& chatId, const std::string& text);
//make request to api telegram server
nlohmann::json makeRequest(const std::string& method, const nlohmann::json& payload = {});
std::function<std::string()> funcAnswer;
};
#endif // TELEGRAMBOT_H
TelegramBot.cpp
для начала добавляем все необходимые библеотеки, после создаем псевдоним tcp для типа и псевдоним для пространства имен для более удобной работы.
#include "TelegramBot.h"
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
using tcp = boost::asio::ip::tcp;
namespace http = boost::beast::http;
После создаем конструктор, который настраивает бота, сохраняя токен и функцию для генерации ответов.
TelegramBot::TelegramBot(const std::string& token, const std::function<std::string()> &funcAnswer)
: botToken(token), apiUrl("https://api.telegram.org/bot" + token) , funcAnswer(funcAnswer) {}
Далее создаем функцию start
это основной цикл работы Telegram-бота. Она отвечает за:
Запрос обновлений от Telegram API (новых сообщений, событий)
Обработку обновлений (например, ответ на сообщения пользователя)
Бесконечный цикл, чтобы бот работал постоянно
void TelegramBot::start() {
while (true) {
try {
/*
* Создается JSON-объект payload,
* который будет отправлен в запросе к Telegram API.
*
* Если lastUpdateId не пуст
* (то есть бот уже обработал какие-то обновления ранее),
* в payload добавляется параметр offset.
* Это нужно, чтобы бот получал только новые обновления,
* начиная с последнего обработанного update_id + 1.
*/
nlohmann::json payload;
if (!lastUpdateId.empty()) {
payload["offset"] = std::stoi(lastUpdateId) + 1;
}
/* Вызывается функция makeRequest,
* которая отправляет HTTP-запрос к Telegram API
* с методом getUpdates.
* В ответ приходит JSON-объект response,
* содержащий список обновлений
*/
В ответ приходит JSON-объект response, содержащий список обновлений (новые сообщения, события и т.д.).
auto response = makeRequest("getUpdates", payload);
/* Обработка полученных обновлений
* Функция handleUpdates принимает массив обновлений
* (response["result"])
* и обрабатывает каждое из них.
* Например, если пришло новое сообщение, бот может отправить ответ.
*/
handleUpdates(response["result"]);
} catch (const std::exception& e) {
// Обработка ошибок
std::cerr << "Error: " << e.what() << std::endl;
}
}
}
Далее добавляем функцию handleUpdates
Функция отвечает за обработку обновлений, полученных от Telegram API. Она анализирует каждое обновление, извлекает полезную информацию (текст сообщения и ID чата) и отправляет ответ пользователю
void TelegramBot::handleUpdates(const nlohmann::json& updates) {
for (const auto& update : updates) {
// Сохраняем ID последнего обновления
if (update.contains("update_id")) {
lastUpdateId = std::to_string(update["update_id"].get<int>());
}
// Проверяем, содержит ли обновление сообщение с текстом
if (update.contains("message") && update["message"].contains("text")) {
// Извлекаем ID чата и текст сообщения
std::string chatId = std::to_string(update["message"]["chat"]["id"].get<int>());
// тут нигде я не буду применять эту переменную,
// но добавил ее для информативности
std::string text = update["message"]["text"].get<std::string>();
// Формируем ответное сообщение (бот будет присылать температуру gpu)
std::string respText = "Gpu temp: " + funcAnswer();
// Отправляем ответ пользователю
sendMessage(chatId, respText);
}
}
}
Далее создаем функцию sendMessage
Функция отвечает за отправку сообщения в указанный чат через Telegram API.
void TelegramBot::sendMessage(const std::string& chatId, const std::string& text) {
// Создаем JSON-объект с данными для отправки
nlohmann::json payload;
payload["chat_id"] = chatId;
payload["text"] = text;
// Вызываем makeRequest для отправки сообщения через API Telegram
makeRequest("sendMessage", payload);
}
Далее создаем функцию makeRequest
Функция выполняет HTTP-запрос к Telegram API с использованием библиотек Boost.Asio и Boost.Beast. Она отправляет данные (сообщение) и получает ответ от сервера.
nlohmann::json TelegramBot::makeRequest(const std::string& method, const nlohmann::json& payload) {
try {
// Создаем контекст ввода-вывода
boost::asio::io_context ioc;
// Настраиваем SSL-контекст
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tlsv12_client);
//загружает стандартные сертификаты для проверки подлинности сервера.
ssl_ctx.set_default_verify_paths();
// Создаем резолвер и SSL-поток
// преобразует доменное имя в IP-адрес.
tcp::resolver resolver(ioc);
// это SSL-поток, который поверх обычного TCP-потока добавляет шифрование.
boost::beast::ssl_stream<boost::beast::tcp_stream> stream(ioc, ssl_ctx);
// Разрешаем доменное имя и устанавливаем соединение
auto const results = resolver.resolve("api.telegram.org", "443");
boost::beast::get_lowest_layer(stream).connect(results);
// Выполняется SSL-рукопожатие для установки защищенного соединения.
stream.handshake(boost::asio::ssl::stream_base::client);
// Создаем HTTP-запрос
http::request<http::string_body> req{http::verb::post, "/bot" + botToken + "/" + method, 11};
req.set(http::field::host, "api.telegram.org");
req.set(http::field::content_type, "application/json");
req.body() = payload.dump();
req.prepare_payload();
// Отправляем запрос
http::write(stream, req);
// Получаем ответ
boost::beast::flat_buffer buffer;
http::response<http::string_body> res;
http::read(stream, buffer, res);
// Закрываем SSL-соединение
boost::system::error_code ec;
stream.shutdown(ec);
// 1Игнорируем ошибку "stream truncated"
// (она часто возникает при закрытии SSL)
if (ec == boost::asio::ssl::error::stream_truncated) {
ec.assign(0, ec.category());
} else if (ec) {
throw boost::system::system_error(ec);
}
// Парсим и возвращаем ответ в формате JSON
return nlohmann::json::parse(res.body());
} catch (const std::exception& e) {
// Обрабатываем ошибки
throw std::runtime_error(std::string("Request failed: ") + e.what());
}
}
Весь код:
#include "TelegramBot.h"
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
using tcp = boost::asio::ip::tcp;
namespace http = boost::beast::http;
TelegramBot::TelegramBot(const std::string& token, const std::function<std::string()> &funcAnswer)
: botToken(token), apiUrl("https://api.telegram.org/bot" + token) , funcAnswer(funcAnswer) {}
void TelegramBot::start() {
while (true) {
try {
// Get updates
nlohmann::json payload;
if (!lastUpdateId.empty()) {
payload["offset"] = std::stoi(lastUpdateId) + 1;
}
auto response = makeRequest("getUpdates", payload);
handleUpdates(response["result"]);
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
}
void TelegramBot::handleUpdates(const nlohmann::json& updates) {
for (const auto& update : updates) {
if (update.contains("update_id")) {
lastUpdateId = std::to_string(update["update_id"].get<int>());
}
if (update.contains("message") && update["message"].contains("text")) {
std::string chatId = std::to_string(update["message"]["chat"]["id"].get<int>());
std::string text = update["message"]["text"].get<std::string>();
std::string respText = "Gpu temp: " + funcAnswer();
sendMessage(chatId, respText);
}
}
}
void TelegramBot::sendMessage(const std::string& chatId, const std::string& text) {
nlohmann::json payload;
payload["chat_id"] = chatId;
payload["text"] = text;
makeRequest("sendMessage", payload);
}
nlohmann::json TelegramBot::makeRequest(const std::string& method, const nlohmann::json& payload) {
try {
boost::asio::io_context ioc;
// SSL Context
boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tlsv12_client);
ssl_ctx.set_default_verify_paths();
tcp::resolver resolver(ioc);
boost::beast::ssl_stream<boost::beast::tcp_stream> stream(ioc, ssl_ctx);
// Resolve host and connect
auto const results = resolver.resolve("api.telegram.org", "443");
boost::beast::get_lowest_layer(stream).connect(results);
// Perform SSL handshake
stream.handshake(boost::asio::ssl::stream_base::client);
// Create HTTP request
http::request<http::string_body> req{http::verb::post, "/bot" + botToken + "/" + method, 11};
req.set(http::field::host, "api.telegram.org");
req.set(http::field::content_type, "application/json");
req.body() = payload.dump();
req.prepare_payload();
// Send the request
http::write(stream, req);
// Receive the response
boost::beast::flat_buffer buffer;
http::response<http::string_body> res;
http::read(stream, buffer, res);
// Shut down the SSL stream
boost::system::error_code ec;
stream.shutdown(ec);
// Ignore the "stream truncated" error, as it is common during SSL shutdown
if (ec == boost::asio::ssl::error::stream_truncated) {
ec.assign(0, ec.category());
} else if (ec) {
throw boost::system::system_error(ec);
}
// Parse and return the response body as JSON
return nlohmann::json::parse(res.body());
} catch (const std::exception& e) {
throw std::runtime_error(std::string("Request failed: ") + e.what());
}
}
main.cpp
Теперь функция main, тут просто будем запускать бота, в конструктор можно любую функцию передавать, которая будет возвращать string, это и будет ответным сообщением бота, я передаю температуру gpu через лямбду функции
#include "TelegramBot.h"
#include <iostream>
int main() {
const std::string token = "TOKEN";
TelegramBot bot(token, []() -> std::string {
//get gpu temperature
char buffer[128];
std::string result = "";
FILE* pipe = popen("nvidia-smi --query-gpu=temperature.gpu --format=csv,noheader", "r");
if (!pipe) throw std::runtime_error("popen() failed!");
try {
while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
result += buffer;
}
} catch (...) {
pclose(pipe);
throw;
}
pclose(pipe);
return result;
});
bot.start();
return 0;
}
На этом все.
В этой статье я показал, как создать Telegram-бота на C++, который взаимодействует с Telegram API для отправки и получения сообщений. Если я помог хотя бы 1 человеку то потратил время на написании статьи не зря. Спасибо за внимание.
Комментарии (11)
mxr
01.02.2025 01:02Скрытый текст
Curl был бы более лаконичным решением, для простых http запросов уж точно.
Но раз Вы используете boost, то почему используетсяnlohmann/json.hpp
, а не boost JSON?tolyanski
01.02.2025 01:02У меня тоже такое было... Вкорячил громоздкий буст в проект, позднее оказалось что все самое интересное из буста уже давно перекочевало в std, но раз уже вкорячил, надо как-то оправдать его наличие))
Arenoros
01.02.2025 01:02не видел в std интрузивных контейнеров, очередей с приоритетами, asio, да даже банальные алгоритмы со строками типа split и trim завезли только в 20-ом, а он далеко не у всех в проде есть
Kelbon
01.02.2025 01:02Создание Telegram-ботов обычно ассоциируется с Python
надо же, а мне в моей статье про библиотеку для тг ботов на С++ писали, что это всё ложь и можно использовать какую-то там обёртку на go и это очень популярно...
Кстати, вот сама библиотека, которая сильно бы упростила
создать Telegram-бота на C++, который взаимодействует с Telegram API для отправки и получения сообщений
9241304
01.02.2025 01:02Краткое кодревью.)
Исключения... Да ещё по (...). Просто море исключений. И до кучи ещё и error_code вместо них для отдельных вызовов
Весь проект - странная помесь плюсов и си. Где conan или vcpkg?
Целый asio для простых запросов к апи. Серьёзно? Есть Curl (раз уж автор так любит си), sockpp. nlohmann, когда есть reflect-cpp.
И как будто этого мало, вот вам ещё popen. Давайте, попробуйте на своей винде)
Витринные проекты так не пишут. Хотя если цель была в том, чтобы показать, как не надо, она достигнута.
hiewpoint
01.02.2025 01:02У него не просто asio, у него boost beast, которому не нужен ни Curl, ни тем более sockpp, на котором вообще весь HTTPS протокол пришлось бы ему самому реализовывать. Так что человек вполне разумно взял готовый https(s) инструмент, причём не сишный, а плюсовый.
reflect-cpp - это здоровенный комбайн с кучей форматов (де)сериализации, тянущий для них кучу зависимостей, а автору был нужен только JSON, вот он и выбрал одну из самых простых в освоении реализацию nlohmann.
9241304
01.02.2025 01:02У него не просто asio, у него boost beast, которому не нужен ни Curl, ни тем более sockpp, на котором вообще весь HTTPS протокол пришлось бы ему самому реализовывать. Так что человек вполне разумно взял готовый https(s) инструмент, причём не сишный, а плюсовый.
Возвращаясь к разумности, логично было взять курл, раз уже на плюсах человек не любит писать. Неразумно брать целый буст для простых запросов (как вы это назвали ниже, "здоровенный комбайн"). Насчёт sockpp это конечно неудачный пример был, эта либа для другого предназначена, но вот в чём штука - "легковесные" либы для отправки простых http запросов тоже есть! Не надо палить из пушки по воробьям, особенно в витринном примере.
reflect-cpp - это здоровенный комбайн с кучей форматов (де)сериализации, тянущий для них кучу зависимостей
Иногда не помешало было не просто "искоса низко голову наклоня" по диагонали пробежать документацию, а немного вдумчиво её прочитать (ну раз уж вы спорите и полезли её читать). А там написано, что по умолчанию зависимостей нет. Да, внутри есть yyjson, но это не внешняя зависимость. Давайте глянем поближе на этот "здоровеннейший комбайн":
Rebuild started at 10:44... 1>------ Rebuild All started: Project: reflectcpp, Configuration: Release x64 ------ 1>Building Custom Rule D:/proj/installer/build/_deps/rfl-src/CMakeLists.txt 1>reflectcpp.cpp 1>yyjson.c 1>Generating Code... 1>reflectcpp.vcxproj -> D:\proj\installer\build\lib\Release\reflectcpp.lib ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ========== ========== Rebuild completed at 10:44 and took 12,176 seconds ==========
140кб оверхеда со статическими зависимостями VC для указанного примера - считаете это чем-то запредельным?
а автору был нужен только JSON, вот он и выбрал одну из самых простых в освоении реализацию nlohmann.
Она самая распространённая (одна из), но при чём тут простота в освоении? И судя по описанию, reflect-cpp зашла бы идеально
Я конечно хз, наверное для кого-то сложно освоить простые структуры в cpp... Но по идее они уже должны быть освоены, если вы пишете на плюсах. А для reflect-cpp больше ничего и не надо знать, кроме 2 функций.
А раз уж человек взял пушку в виде буста, то почему не юзал её и для json? Зачем ещё одна сторонняя либа?
Ответ на поверхности
0Bannon