-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.cpp
149 lines (130 loc) · 5.64 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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_directories(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;
}