qTbot 0.2.102.bb22a69
qTbot is base back end library for your c++ Qt projects.
itelegrambot.cpp
Go to the documentation of this file.
1//#
2//# Copyright (C) 2018-2024 QuasarApp.
3//# Distributed under the GPLv3 software license, see the accompanying
4//# Everyone is permitted to copy and distribute verbatim copies
5//# of this license document, but changing it is not allowed.
6//#
7
8#include "itelegrambot.h"
11#include "qdir.h"
14#include "httpexception.h"
15#include "internalexception.h"
16#include <QNetworkAccessManager>
17
26
27#include <QNetworkReply>
28#include <QSharedPointer>
29#include <QDebug>
30#include <QJsonArray>
31#include <QJsonObject>
32#include <QJsonDocument>
33
37
38
39namespace qTbot {
40
44
47
48bool ITelegramBot::login(const QByteArray &token) {
49 if (token.isEmpty()) {
50 return false;
51 }
52
54
55 QFuture<QByteArray> loginFuture = sendRequest(QSharedPointer<TelegramGetMe>::create());
56 loginFuture.
57 then(this, [this](const QByteArray& data) {
58 ITelegramBot::handleLogin(data);
59 } ).
60 onFailed(this, [this](const HttpException& exeption){
61 handleLoginErr(exeption.code());
62 });
63
64 return loginFuture.isValid();
65}
66
67bool ITelegramBot::sendMessage(const QVariant &chatId,
68 const QString &text,
70 TelegramArgs arg{chatId, text};
71 arg.requestPriority = priority;
72 return sendSpecificMessage(arg);
73}
74
75bool ITelegramBot::sendLocationRequest(const QVariant &chatId,
76 const QString &text,
77 const QString &buttonText,
78 bool onetimeKeyboard) {
79
80 auto replyMarkup = QSharedPointer<QJsonObject>::create();
81 QJsonArray keyboard;
82 QJsonObject contactButton;
83 contactButton["text"] = buttonText;
84 contactButton["request_location"] = true;
85 QJsonArray row;
86 row.append(contactButton);
87 keyboard.append(row);
88 replyMarkup->insert("keyboard", keyboard);
89 replyMarkup->insert("resize_keyboard", true);
90 replyMarkup->insert("one_time_keyboard", onetimeKeyboard);
91
92 return sendSpecificMessage(TelegramArgs{chatId, text}, {{"reply_markup", replyMarkup}});
93}
94
95bool ITelegramBot::sendSelfContactRequest(const QVariant &chatId, const QString &text, const QString &buttonText,
96 bool onetimeKeyboard) {
97 auto replyMarkup = QSharedPointer<QJsonObject>::create();
98 QJsonArray keyboard;
99 QJsonObject contactButton;
100 contactButton["text"] = buttonText;
101 contactButton["request_contact"] = true;
102 QJsonArray row;
103 row.append(contactButton);
104 keyboard.append(row);
105 replyMarkup->insert("keyboard", keyboard);
106 replyMarkup->insert("resize_keyboard", true);
107 replyMarkup->insert("one_time_keyboard", onetimeKeyboard);
108
109 return sendSpecificMessage(TelegramArgs{chatId, text}, {{"reply_markup", replyMarkup}});
110}
111
113 const ExtraJsonObjects &extraObjects) {
114
115 if (!args.chatId.isValid() || args.chatId.isNull())
116 return false;
117
118 if (args.text.isEmpty()) {
119 return false;
120 }
121
122 auto msg = QSharedPointer<TelegramSendMsg>::create(args, extraObjects);
123
124 return sendMessageRequest(msg, args.msgIdCB);
125}
126
128 const KeyboardOnMessage &keyboard) {
129 return sendSpecificMessage(args, prepareInlineKeyBoard(keyboard));
130}
131
132bool ITelegramBot::deleteMessage(const QVariant &chatId, const QVariant &messageId) {
133 if (!chatId.isValid() || chatId.isNull())
134 return false;
135
136 if (!messageId.isValid() || messageId.isNull())
137 return false;
138
139 auto msg = QSharedPointer<TelegramDeleteMessage>::create(chatId,
140 messageId);
141
142 return sendMessageRequest(msg);
143}
144
146 const TelegramArgs& args,
147 const QList<QList<QString> > &keyboard,
148 bool onTimeKeyboard,
149 bool autoResizeKeyboard) {
150
151 if (!args.chatId.isValid() || args.chatId.isNull())
152 return false;
153
154 if (!messageId.isValid() || messageId.isNull())
155 return false;
156
157 auto msg = QSharedPointer<TelegramEditMessage>::create(messageId,
158 args,
159 prepareKeyboard(autoResizeKeyboard,
160 onTimeKeyboard,
161 keyboard));
162
163 return sendMessageRequest(msg, args.msgIdCB);
164}
165
167qTbot::ITelegramBot::prepareInlineKeyBoard(const KeyboardOnMessage &keyboard)
168{
169 ExtraJsonObjects extraObjects;
170 auto&& keyboardJson = QSharedPointer<QJsonObject>::create();
171 QJsonArray keyboardArray;
172
173 for (const auto& map : keyboard) {
174 QJsonArray keyboardLineArray;
175 for (auto it = map.begin(); it != map.end(); it = std::next(it)) {
176 auto&& callBackKey = QString("callback_data_%0").arg(rand());
177 keyboardLineArray.push_back(QJsonObject{ {"text", it.key()}, {"callback_data", callBackKey } });
178 _handleButtons[callBackKey] = {it.value()};
179 }
180 keyboardArray.push_back(keyboardLineArray);
181 }
182
183
184 (*keyboardJson)["inline_keyboard"] = keyboardArray;
185
186 extraObjects["reply_markup"] = keyboardJson;
187
188 return extraObjects;
189}
190
192qTbot::ITelegramBot::prepareKeyboard(bool autoResizeKeyboard,
193 bool onTimeKeyboard,
194 const QList<QList<QString>> &keyboard) {
195 ExtraJsonObjects extraObjects;
196 auto&& keyboardJson = QSharedPointer<QJsonObject>::create();
197 QJsonArray keyboardArray;
198
199 for (const auto &row :keyboard) {
200 QJsonArray keyboardLineArray;
201
202 for (auto it = row.begin(); it != row.end(); it = std::next(it)) {
203 keyboardLineArray.push_back(QJsonObject{ {"text", *it} });
204 }
205 keyboardArray.push_back(keyboardLineArray);
206
207 }
208
209 (*keyboardJson)["keyboard"] = keyboardArray;
210
211 (*keyboardJson)["resize_keyboard"] = autoResizeKeyboard;
212 (*keyboardJson)["one_time_keyboard"] = onTimeKeyboard;
213
214 extraObjects["reply_markup"] = keyboardJson;
215
216 return extraObjects;
217}
218
220 const TelegramArgs& args,
221
222 const KeyboardOnMessage &keyboard ) {
223
224 if (!args.chatId.isValid() || args.chatId.isNull())
225 return false;
226
227 if (!messageId.isValid() || messageId.isNull())
228 return false;
229
230 auto msg = QSharedPointer<TelegramEditMessage>::create(messageId,
231 args,
232 prepareInlineKeyBoard(keyboard));
233
234
235 return sendMessageRequest(msg);
236}
237
238bool ITelegramBot::editMessageKeyboard(const QVariant &messageId,
239 const QVariant &chatId,
240 const KeyboardOnMessage &keyboard,
241 const QString &callBackQueryId) {
242 if (!chatId.isValid() || chatId.isNull())
243 return false;
244
245 if (!messageId.isValid() || messageId.isNull())
246 return false;
247
248 auto msg = QSharedPointer<TelegramEditMessageReplyMarkup>::create(messageId,
249 TelegramArgs(chatId, "", 0, "html", false, callBackQueryId),
250 prepareInlineKeyBoard(keyboard));
251
252
253 return sendMessageRequest(msg);
254}
255
256bool ITelegramBot::editSpecificMessage(const QVariant &messageId,
257 const TelegramArgs& args) {
258
259 if (!args.chatId.isValid() || args.chatId.isNull())
260 return false;
261
262 if (!messageId.isValid() || messageId.isNull())
263 return false;
264
265 if (args.text.isEmpty())
266 return false;
267
268 auto msg = QSharedPointer<TelegramEditMessage>::create(messageId,
269 args
270 );
271
272
273 return sendMessageRequest(msg);
274}
275
277 const QList<QList<QString> > &keyboard,
278 bool onTimeKeyboard,
279 bool autoResizeKeyboard) {
280
281 if (!args.chatId.isValid() || args.chatId.isNull())
282 return false;
283
284 if (args.text.isEmpty()) {
285 return false;
286 }
287
288 return sendSpecificMessage(args, prepareKeyboard(autoResizeKeyboard, onTimeKeyboard, keyboard));
289}
290
291QFuture<QByteArray> ITelegramBot::getFile(const QString &fileId, FileType fileType) {
292
293
294 if (fileId.isEmpty()) {
295 return {};
296 }
297
298 auto localFilePath = findFileInlocatStorage(fileId);
299
300 if (!localFilePath.isEmpty()) {
301 QPromise<QByteArray> fileDataResult;
302
303 if (fileType == FileType::Ram) {
304 QFile localFile(localFilePath);
305 if (localFile.open(QIODevice::ReadOnly)) {
306 fileDataResult.addResult(localFile.readAll());
307 localFile.close();
308 }
309
310 } else if (fileType == FileType::Local) {
311 fileDataResult.addResult(localFilePath.toUtf8());
312 }
313
314 fileDataResult.setProgressRange(0,1);
315 fileDataResult.setProgressValue(1);
316
317 fileDataResult.finish();
318
319 return fileDataResult.future();
320 }
321
322 auto&& metaInfo = getFileInfoByUniqueId(fileId);
323 localFilePath = defaultFileStorageLocation() + "/" + fileId;
324
325 if (metaInfo) {
326 auto&& path = metaInfo->takePath();
327 if (path.size()) {
328 auto&& msg = QSharedPointer<TelegrammDownloadFile>::create(path);
329
330 QDir().mkpath(defaultFileStorageLocation());
331
332
333 if (localFilePath.isEmpty())
334 return {};
335
336 QFuture<QByteArray> replay;
337 if (fileType == FileType::Ram) {
338 replay = sendRequest(msg);
339 } else {
340 replay = sendRequest(msg, localFilePath);
341 }
342
343 return replay;
344 }
345 }
346
347 auto longWay = QSharedPointer<QPromise<QByteArray>>::create();
348 longWay->start();
349
350 auto&& future = getFileMeta(fileId);
351 if (!future.isValid()) {
352 return {};
353 }
354
355 future.then([this, fileId, fileType, longWay](const QByteArray& header){
356 handleFileHeader(header);
357
358 auto&& future = getFile(fileId, fileType);
359
360 if (!future.isValid()) {
361 longWay->setException(InternalException("Failed to wrote file into internal cache!"));
362 return;
363 };
364
365 future.then([longWay](const QByteArray& data){
366 longWay->addResult(data);
367 });
368
369
370 }).onFailed([longWay](const QException& exep){
371 longWay->setException(exep);
372 });
373
374 return longWay->future();
375}
376
377QFuture<QByteArray> ITelegramBot::getFileMeta(const QString &fileId) {
378 auto msg = QSharedPointer<TelegramGetFile>::create(fileId);
379 auto && future = sendRequest(msg);
380 if (future.isValid()) {
381 return future;
382 }
383
384 return {};
385}
386
387bool ITelegramBot::sendFile(const QFileInfo &file, const QVariant &chatId) {
388 return sendFileMessage({chatId}, file);
389}
390
391bool ITelegramBot::sendFile(const QByteArray &file, const QString &fileName, const QVariant &chatId) {
392 return sendFileMessage({chatId}, file, fileName);
393}
394
395bool ITelegramBot::sendFileMessage(const TelegramArgs &args, const QFileInfo &file) {
396 if (!args.chatId.isValid() || args.chatId.isNull())
397 return false;
398
399 if (!file.isReadable()) {
400 return false;
401 }
402
403 return sendMessageRequest(
404 QSharedPointer<TelegramSendPhoto>::create(args,
405 file), args.msgIdCB);
406}
407
408bool ITelegramBot::sendFileMessage(const TelegramArgs &args, const QByteArray &file, const QString &fileName) {
409 if (!args.chatId.isValid() || args.chatId.isNull())
410 return false;
411
412 if (!fileName.size()) {
413 return false;
414 }
415
416 if (!file.size()) {
417 return false;
418 }
419
420 return sendMessageRequest(QSharedPointer<TelegramSendDocument>::create(args, fileName, file), args.msgIdCB);
421}
422
424 const QFileInfo &photo,
425 const KeyboardOnMessage &keyboard) {
426 if (!args.chatId.isValid() || args.chatId.isNull())
427 return false;
428
429 if (!photo.isReadable()) {
430 return false;
431 }
432
433 return sendMessageRequest(
434 QSharedPointer<TelegramSendPhoto>::create(args,
435 photo,
436 prepareInlineKeyBoard(keyboard)), args.msgIdCB);
437}
438
440 const QByteArray &photo,
441 const QString &fileName,
442 const KeyboardOnMessage &keyboard) {
443
444 if (!args.chatId.isValid() || args.chatId.isNull()) {
445 return false;
446 }
447
448 if (!fileName.size()) {
449 return false;
450 }
451
452 if (!photo.size()) {
453 return false;
454 }
455
456 return sendMessageRequest(
457 QSharedPointer<TelegramSendPhoto>::create(args,
458 fileName,
459 photo,
460 prepareInlineKeyBoard(keyboard)),
461 args.msgIdCB);
462}
463
464bool ITelegramBot::sendFileById(const QString &fileID, const QVariant &chatId) {
465 Q_UNUSED(fileID)
466 Q_UNUSED(chatId)
467
468 throw "the sendFileById is not implemented";
469
470 return false;
471
472}
473
475 float latitude,
476 float longitude,
477 const KeyboardOnMessage &keyboard) {
478
479 if (!args.chatId.isValid() || args.chatId.isNull())
480 return false;
481
482 if (!(longitude && latitude)) {
483 return false;
484 }
485
486 return sendMessageRequest(QSharedPointer<TelegramSendLocation>::create(args,
487 latitude,
488 longitude,
489 prepareInlineKeyBoard(keyboard)));
490}
491
493 const QString &phone,
494 const QString &firstName,
495 const QString &secondName) {
496 if (!args.chatId.isValid() || args.chatId.isNull())
497 return false;
498
499 return sendMessageRequest(QSharedPointer<TelegramSendContact>::create(args,
500 firstName,
501 phone,
502 secondName));
503}
504
505int ITelegramBot::getFileSizeByUniqueId(const QString &id) const {
506 if (auto && file = _filesMetaInfo.value(id)) {
507 return file->fileSize();
508 }
509
510 return 0;
511}
512
513QSharedPointer<TelegramFile> ITelegramBot::getFileInfoByUniqueId(const QString &id) const {
514 return _filesMetaInfo.value(id, nullptr);
515}
516
517void ITelegramBot::onRequestError(const QSharedPointer<TelegramUpdateAnswer> &ansverWithError) const {
518 qWarning() << QString("code: %0 - %1").
519 arg(ansverWithError->errorCode()).
520 arg(ansverWithError->errorDescription());
521}
522
523void ITelegramBot::handleIncomeNewUpdate(const QSharedPointer<iUpdate> & update) {
525
526
527 if (auto&& tupdate = update.dynamicCast<TelegramUpdate>()) {
528
529 if (auto&& queryUpd = tupdate->callbackQueryUpdate()) {
530 auto &&handleButtonKey = queryUpd->callBackData();
531
532 if (auto&& cb = _handleButtons.value(handleButtonKey)) {
533 cb(handleButtonKey, queryUpd->messageId());
534 }
535 }
536 }
537}
538
539bool ITelegramBot::sendMessageRequest(const QSharedPointer<iRequest> &rquest,
540 const std::function<void (int)> &msgIdCB) {
541 auto&& future = IBot::sendRequest(rquest);
542 if (future.isValid()) {
543 future.then(this, [this, msgIdCB](const QByteArray& responseData){
544
545 QJsonDocument json = QJsonDocument::fromJson(responseData);
546
547 const QJsonObject&& obj = json.object();
548 if (obj.contains("result")) {
549 unsigned long long chatId = obj["result"]["chat"]["id"].toInteger();
550 int messageID = obj["result"]["message_id"].toInt();
551 if (msgIdCB) {
552 msgIdCB(messageID);
553 }
554
555 if (chatId) {
556 _lastMessageId[chatId] = messageID;
557 }
558
559 return;
560 }
561 }).onFailed([msgIdCB](){
562
563 if (msgIdCB) {
564 msgIdCB(-1);
565 }
566 });
567
568 return true;
569 }
570
571 return false;
572}
573
574void ITelegramBot::handleLogin(const QByteArray&ansver) {
575
577
578 if (!ans->isValid()) {
579 qWarning() << "login error occured: ";
580 return;
581 }
582
583 auto&& result = ans->result().toObject();
584
585 setId(result.value("id").toInteger());
586 setName( result.value("first_name").toString());
587 setUsername( result.value("username").toString());
588
589}
590
591void ITelegramBot::handleLoginErr(QNetworkReply::NetworkError err) {
592 if (err) {
593 qCritical() << "Network error occured. code: " << err;
594 }
595}
596
597void ITelegramBot::handleFileHeader(const QByteArray& header) {
599
600 if (!ansver->isValid()) {
602 return;
603 }
604
605 auto &&fileMetaInfo = makeMesasge<TelegramFile>(ansver->result().toObject());
606
607 _filesMetaInfo.insert(fileMetaInfo->fileId(), fileMetaInfo);
608}
609
610QString ITelegramBot::findFileInlocatStorage(const QString &fileId) const {
612
613 auto &&localStorageList = defaultFileDir.entryInfoList(QDir::Filter::NoDotAndDotDot | QDir::Files);
614 for (const auto& file: localStorageList) {
615 int size = file.size();
616 if (file.fileName().contains(fileId) && size && size == getFileSizeByUniqueId(fileId)) {
617 return file.absoluteFilePath();
618 }
619 }
620
621 return "";
622}
623
624void ITelegramBot::setUsername(const QString &newUsername) {
625 _username = newUsername;
626}
627
628QString ITelegramBot::makeUrl(const QSharedPointer<iRequest> &request) const {
629 return request->baseAddress() + "/bot" + token() + request->makeUpload();
630}
631
632void ITelegramBot::setId(unsigned long long newId) {
633 _id = newId;
634}
635
636const QString &ITelegramBot::username() const {
637 return _username;
638}
639
640int ITelegramBot::gelLastMessageId(unsigned long long &chatId) const {
641 return _lastMessageId.value(chatId, 0);
642}
643
644unsigned long long ITelegramBot::id() const {
645 return _id;
646}
647
648} // namespace qTbot
The HttpException class is base exaption that will raise on all errors of the HTTP protocol,...
QNetworkReply::NetworkError code() const
void setName(const QString &newName)
setName This method sets new value for the IBot::name field.
Definition ibot.cpp:299
static QSharedPointer< MessageType > makeMesasge(const QByteArray &data, Args &&...args)
makeMesasge This is factory method tha can create a messages types.
Definition ibot.h:216
FileType
The FileType enum is is file types, deffine how we should download a file - as a local object in file...
Definition ibot.h:44
@ Local
Definition ibot.h:51
@ Ram
The Ram is a Virtual type of download files will save all file data into QFuture bytes array.
Definition ibot.h:46
const QByteArray & token() const
token This is token value for authication on the remote server (bot)
Definition ibot.cpp:34
virtual void handleIncomeNewUpdate(const QSharedPointer< iUpdate > &)
handleIncomeNewUpdate This method just emit the sigReceiveUpdate signal.
Definition ibot.cpp:174
virtual QString defaultFileStorageLocation() const
defaultFileStorageLocation This method return default file storage location.
Definition ibot.cpp:170
QFuture< QByteArray > sendRequest(const QSharedPointer< iRequest > &rquest)
sendRequest This method sent custom requests to the server.
Definition ibot.cpp:131
void setToken(const QByteArray &newToken)
setToken This is setter of the IBot::token value.
Definition ibot.cpp:38
bool sendFile(const QFileInfo &file, const QVariant &chatId) override
send file .
bool sendFileById(const QString &fileID, const QVariant &chatId)
sendFileById This is specific method of the telegram bot. sents file by id.
int getFileSizeByUniqueId(const QString &id) const
getFileSizeByUniqueId This method return size of the file by id
bool editMessageKeyboard(const QVariant &messageId, const QVariant &chatId, const KeyboardOnMessage &keyboard={}, const QString &callBackQueryId="")
Edits a keyboard of message in a chat.
const QString & username() const
username This is bots login
void setId(unsigned long long newId)
setId This method sets new value for the ITelegramBot::id property.
int gelLastMessageId(unsigned long long &chatId) const
gelLastMessageId this method returns last sendet message id.
QFuture< QByteArray > getFile(const QString &fileId, FileType fileType=FileType::Ram) override
getFile This method sent request to get a file by id. The files can be saved into local storage if th...
QSharedPointer< TelegramFile > getFileInfoByUniqueId(const QString &id) const
getFileInfoByUniqueId return a local saved meta information about the file.
QFuture< QByteArray > getFileMeta(const QString &fileId)
getFileMeta This method receive meta information of the file.
bool sendFileMessage(const TelegramArgs &args, const QFileInfo &file)
sendFileMessage This method sents a message with file.
virtual void onRequestError(const QSharedPointer< TelegramUpdateAnswer > &ansverWithError) const
onRequestError This method invokent when telegram server sent error responce. Default implementation ...
bool editSpecificMessage(const QVariant &messageId, const TelegramArgs &args)
Edits a specific message in a chat.
bool sendSpecificMessageWithKeyboard(const TelegramArgs &args, const KeyboardOnMessage &keyboard)
Sends a specific message with a custom keyboard to a chat. This function sends a specific message to ...
void handleIncomeNewUpdate(const QSharedPointer< iUpdate > &) override
handleIncomeNewUpdate This method just emit the sigReceiveUpdate signal.
bool sendLocation(const TelegramArgs &args, float latitude, float longitude, const KeyboardOnMessage &keyboard={})
sendLocation This method sents locatin to user.
unsigned long long id() const
id This method return bots id number.
bool sendLocationRequest(const QVariant &chatId, const QString &text, const QString &buttonText, bool onetimeKeyboard)
sendLocationRequest This method setn into chat button that will automaticaly sent geo location to bot...
bool sendSelfContactRequest(const QVariant &chatId, const QString &text, const QString &buttonText, bool onetimeKeyboard)
sendSelfContactRequest This method sent into chat button that will automaticaly sent self contact inf...
bool sendMessage(const QVariant &chatId, const QString &text, iRequest::RequestPriority priority=iRequest::NormalPriority) override
sendMessage This method sents text to the selected chat.
virtual bool sendMessageRequest(const QSharedPointer< iRequest > &rquest, const std::function< void(int msgId)> &msgIdCB={})
sendMessageRequest This method invoke when bot will be sent eny messages into chat.
void setUsername(const QString &newUsername)
setUsername This method sets new value for the ITelegramBot::username property.
QString makeUrl(const QSharedPointer< iRequest > &request) const override
makeUrl This method prepare a prefix url for http requests.
bool sendContact(const TelegramArgs &args, const QString &phone, const QString &firstName, const QString &secondName="")
sendContact This method sents a contact data.
bool sendPhoto(const TelegramArgs &args, const QFileInfo &photo, const KeyboardOnMessage &keyboard={})
sendPhoto This method will send image into chat with chatId
bool login(const QByteArray &token) override
login This method get bae information of the bot from remote server.
bool sendSpecificMessage(const TelegramArgs &args, const qTbot::ExtraJsonObjects &extraObjects={})
Sends a specific message to a chat.
bool deleteMessage(const QVariant &chatId, const QVariant &messageId) override
deleteMessage This is main method to delete messages.
bool editSpecificMessageWithKeyboard(const QVariant &messageId, const TelegramArgs &args, const QList< QList< QString > > &keyboard={}, bool onTimeKeyboard=false, bool autoResizeKeyboard=false)
Edits a specific message with a custom keyboard in a chat.
The InternalException class contais string value to describe what happened.
The TelegramUpdate class contains base information about updates from telegram.
RequestPriority
The RequestPriority enum.
Definition irequest.h:52
QHash< QString, QSharedPointer< QJsonObject > > ExtraJsonObjects
ExtraJsonObjects hash map of the extra objects of the message.
Definition irequest.h:26
QList< QHash< QString, ButtonCB > > KeyboardOnMessage
The TelegramArgs class is base structure for the all tellegram message arguments.
iRequest::RequestPriority requestPriority
std::function< void(int msgId)> msgIdCB
msgIdCB This is id message call bak function. Will be inwoked when request finished successful.
QVariant chatId
Chat ID where the message will be sent. Default: {}.
QString text
Text of the message. Default: "".