Вступление
Создание 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 человеку то потратил время на написании статьи не зря. Спасибо за внимание.
Комментарии (3)
mxr
01.02.2025 01:02Скрытый текст
Curl был бы более лаконичным решением, для простых http запросов уж точно.
Но раз Вы используете boost, то почему используетсяnlohmann/json.hpp
, а не boost JSON?
0Bannon