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.
(C++11 compiler ABSOLUTELY needed)
Documentation is automatically generated by Doxygen and deployed by continuous integration tool after build succeed.
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)
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...)
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
$ 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
$ 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
Deni, which tested some API methods, and proposed some examples! (He is listed in contributors)
@foxcpp, who given me a lot of advices for CMake! (issue #16)
@mandlm, PR #21, use this library within your CMake project
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.
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.
Arch Linux package: https://www.archlinux.org/packages/community/x86_64/libcurl-gnutls/
Debian package: https://packages.debian.org/search?keywords=libcurl4-gnutls-dev (the same applies for ubuntu, etc etc...)
Gentoo ebuild: https://packages.gentoo.org/packages/net-misc/curl (turn on the USEFLAG for gnutls)
Openmamba package: https://www.openmamba.org/distribution/distromatic.html?tag=devel&pkg=libcurl-gnutls.x86_64 This is the unique RPM package I found.
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.
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
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)
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
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.
../src $ cppcheck --enable=warning,information,performance,portability -x c++ --std=c++11 -I ../include *.cpp --suppress=missingIncludeSystem
$ clang-format -style=google -i *.cpp
$ clang-format -style=google -i *.h
Please note that some other features might also be not implemented.
Webhook update fetch. NOT SCHEDULED
ParseMode with captions. NOT SCHEDULED
Message's reply_markup. NOT SCHEDULED
Telegram Passport. NOT SCHEDULED
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.
Method getUpdates() is not publicly available.
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
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
}
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.
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:
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:
Do not log anything
This is achieved by setting the failbit of the ostream on.
#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;
}
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().
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()!!
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,...);