egorpugin / tgbot

C++ library for Telegram Bot API 7.10 with generated API types and methods
MIT License
49 stars 6 forks source link

How to use it ? Is it even complete ? #2

Closed frak0d closed 1 year ago

egorpugin commented 2 years ago

Hi,

Yes it is complete.

To use it, you need to implement some http client with methods

struct curl_http_client {
    std::string make_request(const std::string &url, const tgbot::http_request_arguments &args) const;
    std::string make_request(const std::string &url, const std::string &json) const;
    ...
};

Then use it

tgbot::bot<curl_http_client> bot;
auto me = bot.api().getMe();

See complete example in archive. report_detection_bot.zip

Download sw client, run sw build or sw generate to generate VS solution in case you are on win.

frak0d commented 2 years ago

I mean, the #async branch had src folder as well. Why doesn't #master have that ?

I have made bots using tgbot-cpp in the past, but your library is confusing me.

What the the things User is expected to implement ?

A bare minimum docs/info on working of this library would be nice.

egorpugin commented 2 years ago

I mean, the #async branch had src folder as well. Why doesn't #master have that ?

I tried to make async http client. Curl has some issues when integrating with boost asio, it is not finished.

What the the things User is expected to implement ?

Check the project in archive that I attached. There is complete example. Also see in that tpost that you need to implement http client with two methods.


The thing is that the most reliable way of writing telegram bots is to use their official tdlib (https://github.com/tdlib/td). Libraries that implement TgBotAPI are very simple as you see: one file + html parser that parses their docs and generates some API code.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Hi,

Yes it is complete.

To use it, you need to implement some http client with methods

struct curl_http_client {
    std::string make_request(const std::string &url, const tgbot::http_request_arguments &args) const;
    std::string make_request(const std::string &url, const std::string &json) const;
    ...
};

Then use it

tgbot::bot<curl_http_client> bot;
auto me = bot.api().getMe();

See complete example in archive. report_detection_bot.zip

Download sw client, run sw build or sw generate to generate VS solution in case you are on win.

Error while trying to sw build:

C:\Users\User\report detection bot>sw build
[137/140] [config pch]
[138/140] C:/Users/User/report detection bot/sw.cpp
[139/140] C:/Users/User/.sw/storage/pkg/a0/ef/79bd/src/sdir/src/sw/driver/misc/delay_load_helper.cpp
[140/140] C:/Users/User/report detection bot/.sw/cfg/175713/loc.sw.self.535bd2-0.0.30.dll
Downloading: [pub.egorpugin.tgbot-1.1.4.6.0]/[Source Archive]
Downloading: [pub.egorpugin.tgbot.apitool-1.1.4.6.0]/[Source Archive]
Unpacking  : [pub.egorpugin.tgbot.apitool-1.1.4.6.0]/[Source Archive]
Unpacking  : [pub.egorpugin.tgbot-1.1.4.6.0]/[Source Archive]
[138/144] C:/Users/User/.sw/storage/pkg/0e/1e/7261/src/sdir/sw.cpp
[139/144] C:/Users/User/.sw/storage/pkg/9c/45/a31b/src/sdir/sw.cpp
[141/144] C:/Users/User/.sw/storage/pkg/87/aa/d8ea/src/sdir/sw.cpp
[142/144] C:/Users/User/.sw/storage/tmp/cfg/175713/loc.sw.self.dd6076-0.0.30.dll
[143/144] C:/Users/User/.sw/storage/tmp/cfg/175713/loc.sw.self.d6a81c-0.0.30.dll
[144/144] C:/Users/User/.sw/storage/tmp/cfg/175713/loc.sw.self.66f298-0.0.30.dll
Exception in file D:/dev/cppan2/client2/src/sw/builder/command.cpp:840, function execute1: When executing: C:/Users/User/.sw/storage/tmp/cfg/175713/loc.sw.self.d6a81c-0.0.30.dll
LINK : fatal error LNK1104:  㤠  䠩 "C:\Users\User\.sw\storage\tmp\cfg\175713\loc.sw.self.d6a81c-0.0.30.dll"
command failed: exit code = 1104 (0x450)
Total errors: 1
egorpugin commented 2 years ago

Try to 1) re-run, 2) re-run after removing sw storage dir C:/Users/User/.sw/storage.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Try to 1) re-run...

It worked! But not the bot itself Errors in build process: Инструкция точки останова (оператор __debugbreak() или аналогичный вызов) выполнена в report_detection_bot-0.0.1.exe. Visual Studio issue: Visual Studio thinks

egorpugin commented 2 years ago

Try to run sw generate, open VS solution, build debug version there and run. You will see exact issue.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Try to run sw generate, open VS solution, build debug version there and run. You will see exact issue.

Same problem as it was recently... started the debug building, got the cmd with "Not Found" text and then the last two screenies.

egorpugin commented 2 years ago

Well, try to stop process, open call stack. Check what is wrong. Probably you did not provide your bot token or settings or something like this.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Well, try to stop process, open call stack. Check what is wrong. Probably you did not provide your bot token or settings or something like this.

Oh, my bad... but what I need to fill here if I haven't any proxy?

egorpugin commented 2 years ago

Leave empty.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Leave empty.

A-a-and this appears image When I'm doing this image

egorpugin commented 2 years ago

You need to set these in settings file, not here. sw<setting> takes setting name argument, not setting value. And bot token is invalid setting name.

egorpugin commented 2 years ago

Just remove those settings lines completely and write your token in place where this variable is used.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Just remove those settings lines completely...

I can't because they are used in code.

egorpugin commented 2 years ago

Delete code that uses them.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Delete code that uses them.

Deleted, but still it doesn't work... and all what I know that error with 'std::visit' has 'E03E04' code in Visual Studio.

egorpugin commented 2 years ago

Close that window. It is bad. Never look at it.

egorpugin commented 2 years ago

Always look at Output window and send messages from there.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Always look at Output window and send messages from there.

main.cpp code:

#include "curl_http_client.h"

#include <primitives/http.h>
#include <primitives/sw/main.h>
#include <primitives/sw/settings.h>

#include <fstream>
#include <iostream>
#include <thread>

struct tg_bot : tgbot::bot<curl_http_client> {
    using base = tgbot::bot<curl_http_client>;

    std::string botname;
    std::string botvisiblename;

    static const int default_update_limit = 100;
    static const int default_update_timeout = 10;
    int net_delay_on_error = 1;

public:
    using base::base;

    void init() {
        auto me = api().getMe();
        if (!me.username)
            throw SW_RUNTIME_ERROR("Empty bot name");
        botname = *me.username;
        botvisiblename = me.first_name;
        printf("bot username: %s (%s)\n", me.username->c_str(), me.first_name.c_str());
    }
    tgbot::Integer process_updates(
        tgbot::Integer offset = 0,
        tgbot::Integer limit = default_update_limit,
        tgbot::Integer timeout = default_update_timeout,
        const tgbot::Optional<tgbot::Vector<String>> &allowed_updates = {}) {
        // update timeout here for getUpdates()
        ((curl_http_client&)http_client()).set_timeout(timeout);

        auto updates = api().getUpdates(offset, limit, timeout, allowed_updates);
        for (const auto &item : updates) {
            // if updates come unsorted, we must check this
            if (item.update_id >= offset)
                offset = item.update_id + 1;
            process_update(item);
        }
        return offset;
    }
    void process_update(const tgbot::Update &update) {
        try {
            handle_update(update);
        }
        catch (std::exception &e) {
            printf("error: %s\n", e.what());

            std::this_thread::sleep_for(std::chrono::seconds(net_delay_on_error));
            if (net_delay_on_error < 30)
                net_delay_on_error *= 2;
        }
    }
    void long_poll(
        tgbot::Integer limit = default_update_limit,
        tgbot::Integer timeout = default_update_timeout,
        const tgbot::Optional<tgbot::Vector<String>> &allowed_updates = {}) {
        tgbot::Integer offset = 0;
        while (1) {
            try {
                offset = process_updates(offset, limit, timeout, allowed_updates);
            } catch (std::exception &e) {
                printf("error: %s\n", e.what());
            }
        }
    }
    ///
    virtual void handle_update(const tgbot::Update &update) = 0;
};

struct user_data {
};

struct tg_report_detection_bot : tg_bot {
    static inline auto fn = "users.txt";
    std::unordered_map<tgbot::Integer, user_data> users;

    using tg_bot::tg_bot;

    tg_report_detection_bot(auto &&token, auto &&client) : tg_bot(token, client) {
        std::ifstream ifile(fn);
        std::string s;
        while (std::getline(ifile, s)) {
            if (s.empty())
                continue;
            auto id = std::stoull(s.substr(1));
            if (s[0] == '+')
                users.emplace(id, user_data{});
            else
                users.erase(id);
        }
    }
    std::ofstream &file() {
        static std::ofstream of(fn, std::ios::out | std::ios::app);
        return of;
    }
    void add_user(auto id) {
        users.erase(id);
        users.emplace(id, user_data{});
        file() << '+' << id << std::endl;
    }
    void remove_user(auto id) {
        users.erase(id);
        file() << '-' << id << std::endl;
    }

    void handle_update(const tgbot::Update &update) override {
        if (update.message)
            handle_message(*update.message);
    }
    void handle_message(const tgbot::Message &message) {
        if (!message.text || !message.text->starts_with("/"sv))
            return;
        if (message.chat && message.chat->id < 0 && !users.empty()) {
            if (*message.text == "/report"sv) {
                for (auto &&member : api().getChatAdministrators(message.chat->id)) {
                    std::visit([this, &message](auto &&u) {
                        if (!users.contains(u.user->id))
                            return;
                        tgbot::sendMessageRequest r;
                        r.chat_id = u.user->id;
                        r.text = "report is called";
                        if (mess age.chat->username) {
                            r.text += " in chat t.me/" + *message.chat->username + "/" + std::to_string(message.message_id);
                        };
                        else {
                            r.text += " in private chat";
                        };
                        api().sendMessage(r);
                    }, member);
                }
            }
        }
        else {
            handle_command(message);
        }
    }
    void handle_command(const tgbot::Message &message) {
        auto &t = *message.text;
        auto cmd = t.substr(1, t.find_first_of(" @"));
        if (cmd == "start"sv) {
            add_user(message.from->id);
            api().sendMessage(message.from->id, "ok");
        } else if (cmd == "stop"sv) {
            remove_user(message.from->id);
            api().sendMessage(message.from->id, "ok");
        } else if (cmd == "help"sv) {
            auto text =
                "Available commands:\n"
                "/start\n"
                "/stop\n"
                "/help\n"
                ;
            api().sendMessage(message.from->id, text);
        } else {
            auto text = "Command '" + *message.text +  "' is not implemented.";
            api().sendMessage(message.from->id, text);
        }
    }
};

int main(int argc, char *argv[]) {
    primitives::http::setupSafeTls();

    sw::setting<std::string> bot_token("bot_token");

    // read
    sw::getSettings(sw::SettingsType::Local);

    curl_http_client client;
    // setup
    {
        auto curl = client.curl_settings();
        primitives::http::setup_curl_ssl(curl);

        //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
        //curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);

        //curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
        //curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
    }
    // setup proxy
    //{
    //   auto curl = client.curl_settings();
    //    if (!proxy_host.getValue().empty()) {
    //        curl_easy_setopt(curl, CURLOPT_PROXY, proxy_host.getValue().c_str());
    //        curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
    //        if (proxy_host.getValue().find("socks5") == 0) {
    //            curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
    //            curl_easy_setopt(curl, CURLOPT_SOCKS5_AUTH, CURLAUTH_BASIC);
    //            curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
    //        }
    //        if (!proxy_user.getValue().empty())
    //           curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user.getValue().c_str());
    //    }
    //}

    auto bot = std::make_unique<tg_report_detection_bot>(bot_token, client);
    bot->init();
    bot->long_poll();

    return 0;
}

Output errors: 2>C:\Users\User\report_detection_bot\src\main.cpp(130,34): error C2760: синтаксическая ошибка: непредвиденный элемент "идентификатор". Ожидается ")" 2>C:\Users\User\report_detection_bot\src\main.cpp(130,52): error C2760: синтаксическая ошибка: непредвиденный элемент ")". Ожидается ";" 2>C:\Users\User\report_detection_bot\src\main.cpp(130,52): error C2760: синтаксическая ошибка: непредвиденный элемент ")". Ожидается "}" 2>C:\Users\User\report_detection_bot\src\main.cpp(130,52): error C2059: синтаксическая ошибка: ) 2>C:\Users\User\report_detection_bot\src\main.cpp(130,1): error C2143: синтаксическая ошибка: отсутствие ";" перед "{" 2>C:\Users\User\report_detection_bot\src\main.cpp(131,29): error C2065: r: необъявленный идентификатор 2>C:\Users\User\report_detection_bot\src\main.cpp(133,1): error C2181: недопустимый else без парного if 2>C:\Users\User\report_detection_bot\src\main.cpp(134,29): error C2065: r: необъявленный идентификатор 2>C:\Users\User\report_detection_bot\src\main.cpp(136,43): error C2065: r: необъявленный идентификатор 2>C:\Users\User\report_detection_bot\src\main.cpp(137,22): error C2059: синтаксическая ошибка: , 2>C:\Users\User\report_detection_bot\src\main.cpp(137,1): error C2059: синтаксическая ошибка: ) 2>C:\Users\User\report_detection_bot\src\main.cpp(141,9): error C2059: синтаксическая ошибка: else 2>C:\Users\User\report_detection_bot\src\main.cpp(141,1): error C2143: синтаксическая ошибка: отсутствие ";" перед "{" 2>C:\Users\User\report_detection_bot\src\main.cpp(141,1): error C2447: {: отсутствует заголовок функции (возможно, используется формальный список старого типа) 2>C:\Users\User\report_detection_bot\src\main.cpp(144,5): error C2059: синтаксическая ошибка: } 2>C:\Users\User\report_detection_bot\src\main.cpp(144,1): error C2143: синтаксическая ошибка: отсутствие ";" перед "}" 2>C:\Users\User\report_detection_bot\src\main.cpp(145,56): error C2143: синтаксическая ошибка: отсутствие ";" перед "{" 2>C:\Users\User\report_detection_bot\src\main.cpp(145,56): error C2447: {: отсутствует заголовок функции (возможно, используется формальный список старого типа) 2>Сборка проекта "report_detection_bot-0.0.1.vcxproj" завершена с ошибкой.

egorpugin commented 2 years ago

Did you check line 130?

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Did you check line 130?

if (mess age.chat->username) {

egorpugin commented 2 years ago

See error?

Amirhan-Taipovjan-Greatest-I commented 2 years ago

See error?

I don't know exactly, but I think that there's no error.

egorpugin commented 2 years ago

Space in variable name.

Amirhan-Taipovjan-Greatest-I commented 2 years ago

Space in variable name.

I found all mistakes in code, but still error (but new): Вызвано исключение по адресу 0x00007FFEED554FD9 в report_detection_bot-0.0.1.exe: исключение Microsoft C++: std::runtime_error по адресу памяти 0x0000004ABB6FF480.

Main.cpp:

#include "curl_http_client.h"

#include <primitives/http.h>
#include <primitives/sw/main.h>
#include <primitives/sw/settings.h>

#include <fstream>
#include <iostream>
#include <thread>

struct tg_bot : tgbot::bot<curl_http_client> {
    using base = tgbot::bot<curl_http_client>;

    std::string botname;
    std::string botvisiblename;

    static const int default_update_limit = 100;
    static const int default_update_timeout = 10;
    int net_delay_on_error = 1;

public:
    using base::base;

    void init() {
        auto me = api().getMe();
        if (!me.username)
            throw SW_RUNTIME_ERROR("Empty bot name");
        botname = *me.username;
        botvisiblename = me.first_name;
        printf("bot username: %s (%s)\n", me.username->c_str(), me.first_name.c_str());
    }
    tgbot::Integer process_updates(
        tgbot::Integer offset = 0,
        tgbot::Integer limit = default_update_limit,
        tgbot::Integer timeout = default_update_timeout,
        const tgbot::Optional<tgbot::Vector<String>> &allowed_updates = {}) {
        // update timeout here for getUpdates()
        ((curl_http_client&)http_client()).set_timeout(timeout);

        auto updates = api().getUpdates(offset, limit, timeout, allowed_updates);
        for (const auto &item : updates) {
            // if updates come unsorted, we must check this
            if (item.update_id >= offset)
                offset = item.update_id + 1;
            process_update(item);
        }
        return offset;
    }
    void process_update(const tgbot::Update &update) {
        try {
            handle_update(update);
        }
        catch (std::exception &e) {
            printf("error: %s\n", e.what());

            std::this_thread::sleep_for(std::chrono::seconds(net_delay_on_error));
            if (net_delay_on_error < 30)
                net_delay_on_error *= 2;
        }
    }
    void long_poll(
        tgbot::Integer limit = default_update_limit,
        tgbot::Integer timeout = default_update_timeout,
        const tgbot::Optional<tgbot::Vector<String>> &allowed_updates = {}) {
        tgbot::Integer offset = 0;
        while (1) {
            try {
                offset = process_updates(offset, limit, timeout, allowed_updates);
            } catch (std::exception &e) {
                printf("error: %s\n", e.what());
            }
        }
    }
    ///
    virtual void handle_update(const tgbot::Update &update) = 0;
};

struct user_data {
};

struct tg_report_detection_bot : tg_bot {
    static inline auto fn = "users.txt";
    std::unordered_map<tgbot::Integer, user_data> users;

    using tg_bot::tg_bot;

    tg_report_detection_bot(auto &&token, auto &&client) : tg_bot(token, client) {
        std::ifstream ifile(fn);
        std::string s;
        while (std::getline(ifile, s)) {
            if (s.empty())
                continue;
            auto id = std::stoull(s.substr(1));
            if (s[0] == '+')
                users.emplace(id, user_data{});
            else
                users.erase(id);
        }
    }
    std::ofstream &file() {
        static std::ofstream of(fn, std::ios::out | std::ios::app);
        return of;
    }
    void add_user(auto id) {
        users.erase(id);
        users.emplace(id, user_data{});
        file() << '+' << id << std::endl;
    }
    void remove_user(auto id) {
        users.erase(id);
        file() << '-' << id << std::endl;
    }

    void handle_update(const tgbot::Update &update) override {
        if (update.message)
            handle_message(*update.message);
    }
    void handle_message(const tgbot::Message &message) {
        if (!message.text || !message.text->starts_with("/"sv))
            return;
        if (message.chat && message.chat->id < 0 && !users.empty()) {
            if (*message.text == "/report"sv) {
                for (auto &&member : api().getChatAdministrators(message.chat->id)) {
                    std::visit([this, &message](auto &&u) {
                        if (!users.contains(u.user->id))
                            return;
                        tgbot::sendMessageRequest r;
                        r.chat_id = u.user->id;
                        r.text = "report is called";
                        if (message.chat->username)
                            r.text += " in chat t.me/" + *message.chat->username + "/" + std::to_string(message.message_id);
                        else
                            r.text += " in private chat";
                        api().sendMessage(r);
                    }, member);
                }
            }
        }
        else {
            handle_command(message);
        }
    }
    void handle_command(const tgbot::Message &message) {
        auto &t = *message.text;
        auto cmd = t.substr(1, t.find_first_of(" @"));
        if (cmd == "start"sv) {
            add_user(message.from->id);
            api().sendMessage(message.from->id, "ok");
        } else if (cmd == "stop"sv) {
            remove_user(message.from->id);
            api().sendMessage(message.from->id, "ok");
        } else if (cmd == "help"sv) {
            auto text =
                "Available commands:\n"
                "/start\n"
                "/stop\n"
                "/help\n"
                ;
            api().sendMessage(message.from->id, text);
        } else {
            auto text = "Command '" + *message.text +  "' is not implemented.";
            api().sendMessage(message.from->id, text);
        }
    }
};

int main(int argc, char *argv[]) {
    primitives::http::setupSafeTls();

    sw::setting<std::string> bot_token("bot_token");

    // read
    sw::getSettings(sw::SettingsType::Local);

    curl_http_client client;
    // setup
    {
        auto curl = client.curl_settings();
        primitives::http::setup_curl_ssl(curl);

        //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
        //curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);

        //curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
        //curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2);
    }
    // setup proxy
    //{
    //   auto curl = client.curl_settings();
    //    if (!proxy_host.getValue().empty()) {
    //        curl_easy_setopt(curl, CURLOPT_PROXY, proxy_host.getValue().c_str());
    //        curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
    //        if (proxy_host.getValue().find("socks5") == 0) {
    //            curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
    //            curl_easy_setopt(curl, CURLOPT_SOCKS5_AUTH, CURLAUTH_BASIC);
    //            curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
    //        }
    //        if (!proxy_user.getValue().empty())
    //           curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user.getValue().c_str());
    //    }
    //}

    auto bot = std::make_unique<tg_report_detection_bot>(bot_token, client);
    bot->init();
    bot->long_poll();

    return 0;
}