Телеграм бот на С++ сложно? Давайте развеем этот миф

от автора

Предыстория

Всё началось с того что мне нужно было написать телеграм бота для компании в которой я работаю, у бота должны быть различные инструменты для проверки работоспособности отдельных узлов компании, в данной статье мы не будет касаться отдельных функций и разберём базовый пример настройки серверной части для телеграм бота с подключением к базе данных sqlite3, давайте же приступим!


Зависимости

  • sqlite3

  • libsqlite3-dev

  • g++

  • make

  • binutils

  • cmake

  • libboost-system-dev

  • libssl-dev

  • zlib1g-dev

  • libcurl4-openssl-dev

  • tgbot-cpp

Для начала нам необходимо установить некоторые библиотеки, пускай вас не смущает большой список ведь по факту больше всего нам нужны:

  • tgbot-cpp — Библиотека для работы с Telegram ботами

  • sqlite3 — Библиотека для работы с базами данных

Что бы быстро установить и собрать все необходимые библиотеки мы можем воспользоваться следующий bash скриптом (выполнять под root правами):

#!/bin/bash  # Installing sqlite3 apt install sqlite3 libsqlite3-dev -y  # Installing boost packages and cmake apt install g++ make binutils cmake libboost-system-dev libssl-dev zlib1g-dev libcurl4-openssl-dev -y  # Clone TgBot from repository, build and install git clone https://github.com/reo7sp/tgbot-cpp.git cd tgbot-cpp cmake . make -j4 sudo make install echo Libraries successfully installed and built

После успешной загрузки и сборки библиотек в корне домашней директории у вас появится папка tgbot-cpp, в ней находятся основные файлы библиотеки TgBot.

Далее создаём новый проект, либо же просто файл main.cpp — для простоты я именно так и сделаю.

На этом этапе хотелось бы сделать пояснение, мы сделаем простой backend включающий работу с базой данных, в базе данных у нас будет две таблицы.

  • MESSAGES — Для записи любых сообщений которые пользователи пишут в чате с ботом.

  • COMMANDS — Для записи используемых команд.

Написание кода

Первым делом подключаем все необходимые библиотеки:

#include <sqlite3.h> #include <tgbot/tgbot.h> #include <iostream>

После подключения библиотек у меня сразу идёт функция SaveMessage, эта функция которая выполняет sql запросы к БД и сохраняет наши сообщения, для удобства я вынес её в отдельную функцию.

int SaveMessage(std::string message, int chat_id, sqlite3* DB, char* messageError, int &exit){      std::string sql;     sqlite3_free(messageError);     if(message[0] == '/'){                  sql = "INSERT INTO COMMANDS(chat_id, command) VALUES(" + std::to_string(chat_id) + ", " + "'" + message + "'" + ");";         exit =sqlite3_exec(DB, sql.c_str(), NULL, 0, &messageError);             if(exit != SQLITE_OK){         std::cerr << "Error inserted new command in COMMANDS: " << messageError << std::endl;         sqlite3_free(messageError);         return -1;             }else{         std::cout << "Registed new command." << std::endl;         return 0;     }     }           sql = "INSERT INTO MESSAGES(chat_id, message) VALUES(" + std::to_string(chat_id) + ", " + "'" + message + "'" + ");";     exit =sqlite3_exec(DB, sql.c_str(), NULL, 0, &messageError);      if(exit != SQLITE_OK){         std::cerr << "Error inserted new message in MESSAGES: " << messageError << std::endl;         sqlite3_free(messageError);         return -1;     }else{         std::cout << "Registed new message." << std::endl;         return 0;     } } 

Далее у нас идёт основная функция main и первым что мы в ней видим это блок инициализации, давайте подробно разберём его:

sqlite3* db; char* messageError = NULL; int exit = 0; exit = sqlite3_open("telegram.db", &db);  const char* TOKEN = "WRITE YOUR TOKEN"; TgBot::Bot bot(TOKEN);
  • sqlite3* db — Указатель на нашу базу данных.

  • char* messageError = NULL; — Указатель на строку в которую мы будем записывать ошибки в случае выполнения sql запроса.

  • int exit = 0; — Целочисленная переменная для записи кода ошибки.

exit = sqlite3_open("telegram.db", &db);  const char* TOKEN = "WRITE YOUR TOKEN";

Обратите внимание, в этих двух строках кода вам необходимо подставить свои данные вместо моих.

  • telegram.db — Название создаваемой / открываемой базы данных (создаётся в случае отсутствия), вы можете назвать иначе, но обязательно должно быть расширение .db

  • TOKEN = «WRITE YOUR TOKEN«; — Токен телеграм бота который вам выдал BotFather.

Следующим шагом нам необходимо сделать обработку команд и сообщений, не буду долго заострять на этом внимание, в конечном итоге вы сами с этим разберётесь внимательно просмотрев следующий кусок кода:

bot.getEvents().onCommand("start", [&bot, &db, &messageError, &exit](TgBot::Message::Ptr message) {         //TgBot::InlineKeyboardMarkup::Ptr keyboard = ;                 bot.getApi().sendMessage(message->chat->id, "Hi", nullptr, 0, nullptr);                 std::cout << "User " << message->chat->id << " started bot\n";         });      bot.getEvents().onAnyMessage([&bot, &db, &messageError, &exit](TgBot::Message::Ptr message){         SaveMessage(message->text.c_str(), message->chat->id, db, messageError, exit);             if(StringTools::startsWith(message->text, "/start")){                 bot.getApi().deleteMessage(message->chat->id, message->messageId);                 return;             }         });  

Далее идёт заключительный заключительный кусочек кода, который является по сути циклом всего того что написано до него самого, LongPoll — длинное прослушивание, означает что мы включаем наш сервер на решим прослушивания / ожидания команд, если разобраться то это обычный цикл:

    try {         std::cout << "Bot username: " << bot.getApi().getMe()->username << std::endl;         TgBot::TgLongPoll longPoll(bot);             while (true) {                 longPoll.start();             }         } catch (TgBot::TgException& e) {             std::cerr << "Error: " << e.what() << std::endl;         }     sqlite3_close(db);     return 0; 

Компиляция и первый запуск

С написанием кода мы закончили, теперь нам необходимо скомпилировать наш сервер и запустить в первый раз (что бы наш сервер создал базу данных)

При компиляции нам необходимо явно указать файлы наших библиотеки, если вы устанавливали этих так как я описал в этой статье то можете смело использовать вот эту команду:

g++ main.cpp -o server -I/usr/local/include -lTgBot -lboost_system -lssl -lcrypto -lpthread -lsqlite3

Давайте подробнее что мы описали в этой команде:

  • server — Название выходного исполняемого файла, можете назвать как душе угодно, у меня же пусть будет server

  • -I/usr/local/include — Путь до библиотек, и всё что после него это перечисление подключаемых библиотек.

После того как мы успешно скомпилировали наш файл и запустили, через команду ls можем убедиться что у нас создался файл базы данных — в моём случае это telegram.db.

Созданная база данных при первом запуске

Созданная база данных при первом запуске

Как я уже говорил, записывать в базу мы будем команды и сообщения, следовательно в созданной базе данных нам нужно создать две эти таблицы.

Переходим в режим работы с базами данных, указывая название нашей базы:

sqlite3 telegram.db

В режиме sqlite3 просто копируем два следующих sql запроса которые создают соответствующие таблицы:

CREATE TABLE "MESSAGES" ( "id"INTEGER, "chat_id"INTEGER, "message"TEXT, PRIMARY KEY("id" AUTOINCREMENT) );
CREATE TABLE "COMMANDS" ( "id"INTEGER, "chat_id"INTEGER, "command"TEXT, PRIMARY KEY("id" AUTOINCREMENT) );

Готово! Таблицы созданы, теперь мы можем повторно запустить наш сервер и попробовать написать что то боту!

Для начала пошлём боту команду /start, и напишем какое-нибудь сообщение бот ответил нам:

Саму команду /start мы удаляем из диалога в коде, что бы не захламлять чат

Саму команду /start мы удаляем из диалога в коде, что бы не захламлять чат

После того как мы написали боту, следует обратить внимание на консоль, там у нас появились предупреждения о новых записях в БД:

Предупреждения о новых записях в БД

Предупреждения о новых записях в БД

Теперь что бы убедиться мы можем зайти в режим sqlite3 и выполнить простой SELECT запрос, что бы убедиться что записи есть:

SELECT * FROM COMMANDS;
SELECT * FROM MESSAGES;

В результате мы получаем следующую картину:

Результаты SELECT запросов

Результаты SELECT запросов

В итоге мы получаем простейший вариант клиент серверного приложения, в качестве клиентской части которого выступает Telegram, нельзя не отметить тот факт что большинство современных приложений построены именно по такому принципу.


Надеюсь что данная статья для вас была полезной, для удобства прикреплю ниже полный код, всем спасибо за внимание!

Полный код проекта

#include <sqlite3.h> #include <tgbot/tgbot.h> #include <iostream>  int SaveMessage(std::string message, int chat_id, sqlite3* DB, char* messageError, int &exit){      std::string sql;     sqlite3_free(messageError);     if(message[0] == '/'){          sql = "INSERT INTO COMMANDS(chat_id, command) VALUES(" + std::to_string(chat_id) + ", " + "'" + message + "'" + ");";         exit =sqlite3_exec(DB, sql.c_str(), NULL, 0, &messageError);             if(exit != SQLITE_OK){         std::cerr << "Error inserted new command in COMMANDS: " << messageError << std::endl;         sqlite3_free(messageError);         return -1;             }else{         std::cout << "Registed new command." << std::endl;         return 0;     }     }       sql = "INSERT INTO MESSAGES(chat_id, message) VALUES(" + std::to_string(chat_id) + ", " + "'" + message + "'" + ");";     exit =sqlite3_exec(DB, sql.c_str(), NULL, 0, &messageError);      if(exit != SQLITE_OK){         std::cerr << "Error inserted new message in MESSAGES: " << messageError << std::endl;         sqlite3_free(messageError);         return -1;     }else{         std::cout << "Registed new message." << std::endl;         return 0;     } }   int main(){      sqlite3* db;     char* messageError = NULL;     int exit = 0;     exit = sqlite3_open("telegram.db", &db);     const char* TOKEN = "WRITE YOUR TOKEN";     TgBot::Bot bot(TOKEN);      bot.getEvents().onCommand("start", [&bot, &db, &messageError, &exit](TgBot::Message::Ptr message) {         //TgBot::InlineKeyboardMarkup::Ptr keyboard = ;                 bot.getApi().sendMessage(message->chat->id, "Hi", nullptr, 0, nullptr);                 std::cout << "User " << message->chat->id << " started bot\n";         });      bot.getEvents().onAnyMessage([&bot, &db, &messageError, &exit](TgBot::Message::Ptr message){         SaveMessage(message->text.c_str(), message->chat->id, db, messageError, exit);             if(StringTools::startsWith(message->text, "/start")){                 bot.getApi().deleteMessage(message->chat->id, message->messageId);                 return;             }         });          try {         std::cout << "Bot username: " << bot.getApi().getMe()->username << std::endl;         TgBot::TgLongPoll longPoll(bot);             while (true) {                 longPoll.start();             }         } catch (TgBot::TgException& e) {             std::cerr << "Error: " << e.what() << std::endl;         }     sqlite3_close(db);     return 0;   }


ссылка на оригинал статьи https://habr.com/ru/articles/844628/