Skip to content

Commit

Permalink
added QrCodeBot example
Browse files Browse the repository at this point in the history
This example shows how to program a Telegram Bot that can generate QrCode images from text and extract text from QrCode Images.
  • Loading branch information
baderouaich committed Oct 16, 2023
1 parent 69260e3 commit ce06d77
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ see [examples](examples/) for more:
|:--------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------:|
| [WeatherBot](examples/WeatherBot) | This example shows how to program a Telegram Bot that displays the weather information of a city using the [weather api](https://www.weatherapi.com/). | <img src="examples/WeatherBot/img/preview.jpg" alt="preview" width="200"> |
| [EarthquakeBot](examples/EarthquakeBot) | This example shows how to program a Telegram Bot that will alert you if there is a recent earthquake somewhere in the world. | <img src="examples/EarthquakeBot/img/alerts.jpg" alt="preview" width="200"> |
| [QrCodeBot](examples/QrCodeBot) | This example shows how to program a Telegram Bot that can generate QrCode images from text and extract text from QrCode Images. | <img src="examples/QrCodeBot/img/encode.jpg" alt="preview" width="200"> |
| [UrlShortenerBot](examples/UrlShortenerBot) | This example shows how to program Telegram Bot for shortening URLs. | <img src="examples/UrlShortenerBot/img/preview.jpg" alt="preview" width="200"> |
| [Buttons](examples/Buttons) | This example shows how to program a basic Telegram Bot that uses inline keyboard buttons to interact with users. | <img src="examples/Buttons/img/preview.jpg" alt="preview" width="200"> |

Expand Down
30 changes: 30 additions & 0 deletions examples/QrCodeBot/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.10)
project(qrcode_bot)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)
FetchContent_Declare(tgbotxx
GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx"
GIT_TAG main
)
FetchContent_MakeAvailable(tgbotxx)

# Qr Code Processing Library
FetchContent_Declare(ZXing
GIT_REPOSITORY "https://github.com/zxing-cpp/zxing-cpp"
GIT_TAG master
)
FetchContent_MakeAvailable(ZXing)

# Stb libs for image read/write
FetchContent_Declare(stb
GIT_REPOSITORY https://github.com/nothings/stb.git
GIT_TAG master
)
FetchContent_MakeAvailable(stb)

add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC tgbotxx ZXing)
target_include_directories(${PROJECT_NAME} PUBLIC ${stb_SOURCE_DIR})
22 changes: 22 additions & 0 deletions examples/QrCodeBot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## QR Code Bot
This example shows how to program a Telegram Bot that can generate QrCode images from text and extract text from QrCode Images.

### Run
```bash
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j8
./qrcode_bot YOUR_BOT_TOKEN
```

### How to create a new Bot and obtain its private token ?
1. Open the Telegram mobile app and search BotFather
2. Send BotFather a command /newbot
3. Follow instructions to create a new Bot
4. After you finish the instructions, you will receive a Bot Token, make sure you keep it secured.

### Encode (Text to QRCode Image)
<img src="img/encode.jpg" alt="Encode" width="300"/>

### Decode (QRCode Image to Text)
<img src="img/decode.jpg" alt="Decode" width="300"/>
149 changes: 149 additions & 0 deletions examples/QrCodeBot/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#include <csignal> // std::signal
#include <string>
#include <tgbotxx/tgbotxx.hpp> // tgbotxx
using namespace tgbotxx;

#include <BarcodeFormat.h>
#include <BitMatrix.h>
#include <CharacterSet.h>
#include <GTIN.h>
#include <MultiFormatWriter.h>
#include <ReadBarcode.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

class QrCodeBot : public Bot {
public:
QrCodeBot(const std::string& token) : Bot(token) {}

private:
void onStart() override {
// Drop pending updates
api()->deleteWebhook(true);

// Register my commands
Ptr<BotCommand> startCmd(new BotCommand());
startCmd->command = "/start";
startCmd->description = "Start interacting with the bot";
api()->setMyCommands({startCmd});

std::cout << "Bot " << api()->getMe()->username << " Started\n";
}

void onStop() override {
std::cout << "\nStopping Bot. Please wait...\n";
}

void onNonCommandMessage(const Ptr<Message>& message) override try {
if (not message->photo.empty()) { // Did user send a photo ?
// Convert QrCode image to text
api()->sendChatAction(message->chat->id, "typing");
std::string qrCodeText = extractTextFromQrCodeImage(message->photo);
api()->sendMessage(message->chat->id, qrCodeText);
} else if (not message->text.empty()) { // Did user send a text ?
// Convert text to QrCode image
api()->sendChatAction(message->chat->id, "upload_photo");
cpr::File qrCodePhoto = convertTextToQrCodeImage(message->text);
api()->sendPhoto(message->chat->id, qrCodePhoto);
}
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
api()->sendMessage(message->chat->id, "Internal error");
}

void onCommand(const Ptr<Message>& message) override {
if (message->text == "/start") {
api()->sendMessage(message->chat->id, "Welcome to QrCodeBot! Please send a text to generate a QrCode Image, or send a QrCode Image to extract text from it.");
}
}

protected:
/// Extracts UTF-8 text from a QR Code Image
/// @param photos multiple resolution photo sizes from Message
/// @returns Extracted UTF-8 text from the QR Code image
std::string extractTextFromQrCodeImage(const std::vector<Ptr<PhotoSize>>& photos) {
// Get the highest resolution image (Telegram provides 4 resolutions of photo sent by user)
const Ptr<PhotoSize>& photo = *std::max_element(photos.begin(), photos.end(), [](const Ptr<PhotoSize>& A, const Ptr<PhotoSize>& B) {
return A->height < B->height && A->width < B->width;
});

// Download photo from Telegram servers
Ptr<File> file = api()->getFile(photo->fileId);
std::string bytes = api()->downloadFile(file->filePath, [](long total, long downloaded) -> bool {
std::cout << "Downloading photo " << downloaded << '/' << total << " bytes \r" << std::flush;
return true;
});
std::cout << std::endl;

// Save downloaded photo to ./photos/input/PHOTO.jpg
fs::path photosDir = "photos/input";
if (!fs::exists(photosDir)) fs::create_directory(photosDir);
fs::path photoPath = photosDir / fs::path(file->filePath).filename();
std::ofstream ofs{photoPath, std::ios::binary};
ofs.write(bytes.data(), bytes.size());
ofs.close();
bytes.clear();

// Load back image using stbi image
int width{}, height{}, channels{};
std::unique_ptr<stbi_uc, void (*)(void *)> buffer(stbi_load(photoPath.string().c_str(), &width, &height, &channels, 3), stbi_image_free);
if (!buffer) {
throw Exception("Failed to read image: " + photoPath.string());
}

// Decode qr code image
ZXing::DecodeHints hints{};
hints.setTextMode(ZXing::TextMode::HRI); // Human Readable Interpretation
hints.setEanAddOnSymbol(ZXing::EanAddOnSymbol::Read);
hints.setTryHarder(true);
ZXing::ImageView image{buffer.get(), width, height, ZXing::ImageFormat::RGB};
auto result = ZXing::ReadBarcode(image, hints);
if (result.isValid()) {
return result.text();
}
return ZXing::ToString(result.error());
}

/// Converts @text to a QR Code image
/// @param text UTF-8 Text to convert
/// @returns cpr::File filename of the generated image
cpr::File convertTextToQrCodeImage(const std::string& text) {
ZXing::MultiFormatWriter writer(ZXing::BarcodeFormat::QRCode);
writer.setMargin(-1);
writer.setEncoding(ZXing::CharacterSet::UTF8);
ZXing::BitMatrix matrix = writer.encode(text, 128, 128);
ZXing::Matrix<std::uint8_t> bitmap = ZXing::ToMatrix<std::uint8_t>(matrix);

fs::path photosDir = "photos/output";
if (!fs::exists(photosDir)) fs::create_directories(photosDir);
fs::path photoPath = photosDir / (std::to_string(std::time(nullptr)) + ".jpg");
// int success = stbi_write_png(filePath.c_str(), bitmap.width(), bitmap.height(), 1, bitmap.data(), 0);
int success = stbi_write_jpg(photoPath.c_str(), bitmap.width(), bitmap.height(), 1, bitmap.data(), 0);
if (!success) {
throw Exception("Failed to write image: " + photoPath.string());
}
return cpr::File(photoPath.string());
}
};


int main(int argc, const char *argv[]) {
if (argc < 2) {
std::cerr << "Usage:\nqrcode_bot \"BOT_TOKEN\"\n";
return EXIT_FAILURE;
}
static std::unique_ptr<QrCodeBot> BOT;
std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C
if (BOT) {
BOT->stop();
}
std::exit(EXIT_SUCCESS);
});

BOT = std::make_unique<QrCodeBot>(argv[1]);
BOT->start();
return EXIT_SUCCESS;
}

0 comments on commit ce06d77

Please sign in to comment.