Skip to content

Smertig/banana

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

99 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

License: MIT GitHub Actions GitHub Actions

🍌 banana - thin wrapper over Telegram Bot API written in C++17.

Key Features

  • Simple API
  • Single interface for both blocking, non-blocking and even coroutine-based operations
  • Generic in terms of networking backend (bundled support for WinAPI, cpr and boost::beast)
  • Extendable (see custom-agent example)
  • Automatically generated from Telegram Bot API 7.4 (thanks ark0f/tg-bot-api)
  • Cross-platform (tested on Windows, Linux, macOS)

Example

#include <banana/api.hpp>
#include <banana/agent/default.hpp>

int main(int argc, char** argv) {
    // create agent once
    banana::agent::default_blocking agent("<TG_BOT_TOKEN>");

    // use API
    banana::api::send_message(agent, { /* .chat_id = */ "@smertig", /* .text = */ "Hello, world!" });
}

Documentation

Previous Release

Latest Release

Master

Notes about breaking changes

TL;DR: use designated initialization from C++20 and forget about breaking changes.

Most of banana is automatically generated from the Telegram Bot API documentation. The main problem here is that there's no guarantee for the order of function parameters and members of types. Let's have a look at the following example:

struct send_message_args_t {
    variant_t<integer_t, string_t> chat_id; // Unique identifier for the target chat or username of the target channel (in the format @channelusername)
    string_t                       text;    // Text of the message to be sent, 1-4096 characters after entities parsing
    // ...
};

There are several similar ways to call send_message:

// #1 (simple)
banana::api::send_message(agent, { "@username", "Hello from banana!" });

// #2 (designated initialization, C++20)
banana::api::send_message(agent, { .chat_id = "@username", .text = "Hello from banana!" });

// #3 (verbose)
banana::api::send_message_args_t args;
args.chat_id = "@username";
args.text = "Hello from banana!";
banana::api::send_message(agent, std::move(args));

However, they differ in the way they are affected by breaking changes. Nothing prevents the Telegram API team from updating send_message parameters (and documentation) in the following way:

struct send_message_args_t {
    variant_t<integer_t, string_t> chat_id;
    string_t                       some_new_fancy_param; // This param is used only for a new shiny feature
    string_t                       text;
    // ...
};

How does this change affect existing code?

// #1 (simple) - silently breaks πŸ’£, sometimes compilation error ❌ (in case of incompatible types)
banana::api::send_message(agent, { "@username", "Hello from banana!" });

// #2 (designated initialization, C++20) - fine πŸ€
banana::api::send_message(agent, { .chat_id = "@username", .text = "Hello from banana!" });

// #3 (verbose) - fine πŸ€
banana::api::send_message_args_t args;
args.chat_id = "@username";
args.text = "Hello from banana!";

That's why it's highly preferable to use C++20 designated initialization instead of a raw aggregate initialization.

There's also another case - when no new parameter is added, but existing parameters are swapped:

struct send_message_args_t {
    string_t                       text;
    variant_t<integer_t, string_t> chat_id;
    // ...
};

This case is more tricky, because it can break designated initialization as well:

// #1 (simple) - silently breaks πŸ’£, sometimes compilation error ❌ (in case of incompatible types)
banana::api::send_message(agent, { "@username", "Hello from banana!" });

// #2 (designated initialization, C++20) - compilation error because of incorrect designators order ❌
banana::api::send_message(agent, { .chat_id = "@username", .text = "Hello from banana!" });

// #3 (verbose) - fine πŸ€
banana::api::send_message_args_t args;
args.chat_id = "@username";
args.text = "Hello from banana!";

However, a compilation error is still much better than a silent change in logic, and also more readable than manual per-field initialization. That's why I recommend switching to C++20 and using designated initialization.