Вступление

Создание 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)


  1. 0Bannon
    01.02.2025 01:02

    — Парень в бронированном костюме. А снять — кто ты без него?

    -3д моделлер, фрилансер, с++ разработчик тг ботов.


  1. tolyanski
    01.02.2025 01:02

    С boost во время работы IDE а также компиляции - не может быть температура процессора 37)

    UPD: а, там же GPU на картинке


    1. Sergo320 Автор
      01.02.2025 01:02

      так там не процессора, а видеокарты )


  1. mxr
    01.02.2025 01:02

    Скрытый текст

    Curl был бы более лаконичным решением, для простых http запросов уж точно.
    Но раз Вы используете boost, то почему используется nlohmann/json.hpp, а не boost JSON?