Awesome
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.
(C++11 compiler ABSOLUTELY needed)
Documentation
Documentation is automatically generated by Doxygen and deployed by continuous integration tool after build succeed.
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
-
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
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.
-
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.
Behaviour
- With long poll bots, you have a connection always on to the telegram API endpoint, used to retrieve updates.
- When an update is received, update gets dispatched and assigned to the default callback assigned, if no callback registered, update gets ignored forever.
- A thread gets spawned and the callback actually called, then detached from program execution, it is expected to terminate when assigned callback ends
- Your stuffs...
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.
-
Webhook update fetch. NOT SCHEDULED
-
ParseMode with captions. NOT SCHEDULED
-
Message's reply_markup. NOT SCHEDULED
-
Telegram Passport. NOT SCHEDULED
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<InlineQueryResult>> 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.
- http://en.cppreference.com/w/cpp/thread/lock_guard
- http://en.cppreference.com/w/cpp/atomic/atomic
- http://en.cppreference.com/w/cpp/thread/mutex
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:
- tgbot::TelegramException is meaning of an error given by Telegram Bot API (if it is raised while fetching updates most likely is recoverable)
- std::runtime_error is meaning any other error (CURL)
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. TelegramException_s_) are met.
You can:
- Change the output stream (stdout by default)
- Do not output anything at all
- Change time formatting
- Choose if log EACH update you get (useless in most cases, will only slow down your bot experience) [DISABLED BY DEFAULT]
- Log from your callbacks (do not copy Logger, I do not reccomend to do this. Copying is viable only through constructor.)
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;
}
- Log on stderr
#include <tgbot/bot.h>
//... fold
int main() {
LongPollBot bot { "tok" };
bot.getLogger().setStream(std::cerr);
bot.start();
return 0;
}
- Log to file
#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;
}
- Log any update and change date formatting
#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:
- simple info/error log
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,...);