StefanoBelli / xxtelebot

**DISCONTINUED** A simple C++11 Telegram Bot API implementation
https://stefanobelli.github.io/xxtelebot
MIT License
42 stars 7 forks source link
api bot cplusplus cpp cxx11 telegram telegram-bot telegram-bot-api tgbot

license GitHub release Travis Bot API version: 4.3

Discontinued project

xxtelebot: A simple C++11 Telegram Bot API implementation

C++11 Telegram Bot APIs

This API wrapper tries to be as conformant as possible to Telegram Bot API, which you are invited to read.

Telegram Bot API

(C++11 compiler ABSOLUTELY needed)

Documentation

Documentation is automatically generated by Doxygen and deployed by continuous integration tool after build succeed.

xxtelebot documentation

Compatability

This library works using Linux and OS X, with both GCC and Clang compilers.

Should work on BSD systems (It might work even on windows with MinGW)

Windows

Option 1: Use WSL (reccomended) Install required dependencies and follow instructions (see Using CMake below)

Option 2: Use MinGW (not tested) It will take long time... unless you already have each dependency to build this project already installed.

Honestly, I don't want to waste time to adjust build system and adapt WinHTTP for xxtelebot, considering that is very unlikely that someone wants to run a telegram bot using Windows. (You even have the two possibilities above...)

FreeBSD

Assuming default configuration, using /usr/bin/c++ and /bin/csh

# pkg update && pkg upgrade
# portsnap fetch extract update
# pkg install cmake jsoncpp autoconf pkgconf gnutls libnghttp2 libgcrypt
# cd /usr/ports/ftp/curl
# make config
/*
 * A configuration dialog will appear to configure libcurl features,
 * scroll at the end and you will be able to pick SSL/TLS library support:
 * choose GnuTLS
*/
# make -jN
# make install
# make clean
$ cd
$ git clone https://github.com/StefanoBelli/xxtelebot
$ mkdir xxtelebot-build
$ cd xxtelebot-build
$ setenv CXXFLAGS "-I/usr/local/include -L/usr/local/lib $CXXFLAGS"
$ cmake ../xxtelebot -DCMAKE_BUILD_TYPE="Release" -DBUILD_SHARED_LIBS=ON

Using CMake

$ mkdir build && cd build
$ cmake .. -DCMAKE_BUILD_TYPE="Release" \
                [-DCMAKE_CXX_FLAGS="your optimizing compiler flags"] \
                [-DBUILD_SHARED_LIBS=ON] \
                [-DXXTELEBOT_PKG_CONFIG="custom/pkgconfig/data"] \
                [-DCMAKE_INSTALL_PREFIX:PATH="custom prefix path, should be /usr on GNU/Linux systems"] 
$ make
# make install

Using pkg-config

$ g++ bot.cpp $(pkg-config --libs --cflags xxtelebot)

with your cmake project:

find_package(PkgConfig REQUIRED)
pkg_check_modules(XXTELEBOT QUIET xxtelebot)

# XXTELEBOT_FOUND
# XXTELEBOT_LIBRARIES
# XXTELEBOT_INCLUDE_DIRS

Thanks to

Issues

You are welcome to open issues, do it without freaking out and/or insults, attach your code (take care of your token), and what is not going well.

CURL SSL/TLS backend

This library requires you provide the GnuTLS implementation for libcurl. Just get it from your package manager.

Beware that if you use another backend, such as, OpenSSL, you won't get any error or warning! Linker just does its job and brings you the executable. To ensure that no race condition occour, you should use GnuTLS backend or NSS, which do not require us to specify a locking method.

Behaviour

Another connection gets openen when using API methods, so this may slow down a little bit your bot experience.

Unfortunately this is the most safe way we have to avoid race conditions, phenomen caused by multithreading on shared CURL instance.

Error reporting

Function will return surely (unless parsing errors of mine) the expected result.

If telegram API reports error, TelegramException gets thrown. So you may want to use try-catch blocks

Signal handling

What we would like to have, when SIGINT (^C sequence) is sent to the program is to exit loop and follow cleanup operations. In this case, we cannot do this. Assuming to use a control variable, this control variable gets successfully changed when asynchronus signal is received, BUT, loop exits at the next iteration, not immediatly. Not our expected behaviour. Let the program die, the operating system will free previously allocated memory. (CURL resources)

Example

A simple echo-back bot (long poll)

#include <string>
#include <tgbot/bot.h>

using namespace tgbot;
using namespace types;
using namespace methods;

void echoBack(const Message message, const Api& api) {
    api.sendMessage(std::to_string(message.chat.id), *message.text);
}

int main() {
    LongPollBot bot("token");
    //bot.callback(echoBack);
    bot.callback([](const Message m, const Api& a) {
        a.sendMessage(std::to_string(m.chat.id),"replying from a C++ lambda!");
    });

    bot.start();

    //unreachable code
}

I would suggest you to add a filter for updates, I mean, if your bot expects only messages, LongPollBot constructor allows you to add filters and get only certain update types

How to properly compile your own bot

USING STATIC LIBRARY

$ g++ bot.cpp -I${XXTELEBOT_DIR}/include ${OUTPUT_BUILD_DIR}/libxxtelebot.a -lcurl -ljsoncpp -pthread -lgcrypt

USING SHARED LIBRARY

$ g++ bot.cpp -I${XXTELEBOT_DIR}/include -L${OUTPUT_BUILD_DIR} -lxxtelebot

On latest compiler is not needed to specify C++ standard, which is already set to C++11 (if needed just enable -std=c++11)

Others may result in linkage error.

cppcheck

../src $ cppcheck --enable=warning,information,performance,portability -x c++ --std=c++11 -I ../include *.cpp --suppress=missingIncludeSystem

clang-format

$ clang-format -style=google -i *.cpp
$ clang-format -style=google -i *.h

Missing implementation

Please note that some other features might also be not implemented.

systemd service unit

You can find a template for the bot systemd service inside data/ directory.

Edit the unit template for your own bot and then:

# cp data/bot.service.template /usr/lib/systemd/system/bot.service
# systemctl daemon-reload
# systemctl enable bot.service

Your bot will be started at next reboot, systemd will get the control on your bot and you should look at logs and stop via journalctl and systemctl tools.

Note that you may need to uncomment the Environmental config option under Service section, depending on your LD_LIBRARY_PATH value and where CMake decides to put the shared library.

Various infos

Note on getUpdates:

Method getUpdates() is not publicly available.

The dark side of Inline Query answers

After we recieve our inline query, we have to answer it, done using answerInlineQuery method.

answerInlineQuery accepts the following parameter: std::vector<::tgbot::types::Ptr> const&

Because of project structure we have to use a smart pointer (std::unique_ptr), each specific result is a derived class of InlineQueryResult.

#include <tgbot/bot.h>
#include <tgbot/utils/make_ptr.h>

using namespace tgbot;
using namespace types;
using namespace methods;
using namespace methods::types;

int main() {
    //alias provided by project headers
    InlineQueryResultsVector results;

    //first result
    Ptr<InlineQueryResultPhoto> photo = utils::makePtr<InlineQueryResultPhoto>();
    photo->type = iqrTypePhoto; //also iqrTypeAudio, iqrType<type> exist
    photo->id = "unique_identifier";

    //put first result in the container
    results.push_back(std::move(photo));

    //repeat this each time it is required

    //use results with answerInlineQuery
}

Multithreading

This library doesn't involve you in handling multiple threads, but remember that if you are using a shared resource (e.g. global variable), you may encounter race conditions when multiple threads try to access it. Lock accesses if needed.

Exception handling

Ensure you handle exceptions properly.

void echoBackCallback(...) {
    api.sendMessage(...); //may throw TelegramException, what(): Too Many Requests
}

This kind of exception should be handled in order to avoid program stop (SIGABRT).

No need to handle main thread ("bot API message fetch") exception. If it raises probably something bad is happening and bot should stop.

Anyway you can't handle that to resume the bot normal execution

try {
    bot.start();
} catch(...) {
    //then what?
}

Please note:

Logging

A logging facility is now provided by this library, and by default, it will log on stdout.

Unless specified, you'll get when the bot starts listening for events and only when errors (e.g. TelegramExceptions) are met.

You can:

Some examples:

#include <tgbot/bot.h>

//... fold

int main() {
    std::cout.setstate(std::ios::failbit);
    LongPollBot bot { "tok" };
    bot.start();

    return 0;
}
#include <tgbot/bot.h>

//... fold

int main() {
    LongPollBot bot { "tok" };
    bot.getLogger().setStream(std::cerr);
    bot.start();

    return 0;
}
#include <fstream>
#include <tgbot/bot.h>

//... fold

int main() {

    std::ofstream outfile("log.txt");

    LongPollBot bot { "tok" };
    bot.getLogger().setStream(outfile);
    bot.start();

    return 0;
}
#include <fstream>
#include <tgbot/bot.h>

//... fold

int main() {

    std::ofstream outfile("log.txt");

    LongPollBot bot { "tok" };
    bot.getLogger().setStream(outfile);
    bot.getLogger().setDateFormat("%H:%M");
    bot.notifyEachUpdate(true);
    bot.start();

    return 0;
}

Logging from callbacks

You can log your own:

void messageCallback(const Message m, const Api& api) {
    api.sendMessage(...);
    api.getLogger().info("sent message back!!");
    api.getLogger().error("something went wrong :/");

    //that's fine
}

However, if you want to, say, change the stream or date format, you have to perform a const_cast to Logger&

This is because we are getting the logger via a const instance of Api and we are using the const specifier member function version, then we are getting a const lvalue reference to Logger (const Logger&).

void messageCallback(const Message m, const Api& api) {
    api.sendMessage(...);
    const_cast<Logger&>(api.getLogger()).setStream(std::cerr);
    api.getLogger().info("sent message back!!");
    api.getLogger().error("something went wrong :/");

    //remember to restore previous stream if you want to...
    //that's fine
}

NOTE THAT IT WILL BE USER RESPONSIBILITY TO LOCK ALL OPERATIONS RELATED TO setStream() and setDateFormat() inside user-defined callbacks.

Just use info() and error().

CURL

If you want to use curl to let the bot able to perform some http requests, just don't call curl_global_init() and curl_global_cleanup()!!

Writing your own matchers

RegisterCallback allows you to write your own matcher instead of using utils::whenStarts() and whenContains(). Make it allow 2 parameters and return bool.

callback([](const std::string& word, const char* match) {
    return true;
}, ...);

//...

bool matcher(const std::string& word, const char* match) {
    return true;
}

callback(matcher,...);