qTbot 0.89.ee6949a
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"
10#include "file.h"
12#include "qdir.h"
15#include "virtualfile.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 _loginReplay = sendRequest(QSharedPointer<TelegramGetMe>::create());
56 if (_loginReplay) {
57 connect(_loginReplay.get(), &QNetworkReply::finished,
58 this, &ITelegramBot::handleLogin,
59 Qt::DirectConnection);
60 connect(_loginReplay.get(), &QNetworkReply::errorOccurred,
61 this, &ITelegramBot::handleLoginErr,
62 Qt::DirectConnection);
63 return true;
64 }
65
66 return false;
67}
68
69bool ITelegramBot::sendMessage(const QVariant &chatId, const QString &text) {
70 return sendSpecificMessage(TelegramArgs{chatId, text});
71}
72
73bool ITelegramBot::sendLocationRequest(const QVariant &chatId, const QString &text, const QString &buttonText,
74 bool onetimeKeyboard) {
75
76 auto replyMarkup = QSharedPointer<QJsonObject>::create();
77 QJsonArray keyboard;
78 QJsonObject contactButton;
79 contactButton["text"] = buttonText;
80 contactButton["request_location"] = true;
81 QJsonArray row;
82 row.append(contactButton);
83 keyboard.append(row);
84 replyMarkup->insert("keyboard", keyboard);
85 replyMarkup->insert("resize_keyboard", true);
86 replyMarkup->insert("one_time_keyboard", onetimeKeyboard);
87
88 return sendSpecificMessage(TelegramArgs{chatId, text}, {{"reply_markup", replyMarkup}});
89}
90
91bool ITelegramBot::sendSelfContactRequest(const QVariant &chatId, const QString &text, const QString &buttonText,
92 bool onetimeKeyboard) {
93 auto replyMarkup = QSharedPointer<QJsonObject>::create();
94 QJsonArray keyboard;
95 QJsonObject contactButton;
96 contactButton["text"] = buttonText;
97 contactButton["request_contact"] = true;
98 QJsonArray row;
99 row.append(contactButton);
100 keyboard.append(row);
101 replyMarkup->insert("keyboard", keyboard);
102 replyMarkup->insert("resize_keyboard", true);
103 replyMarkup->insert("one_time_keyboard", onetimeKeyboard);
104
105 return sendSpecificMessage(TelegramArgs{chatId, text}, {{"reply_markup", replyMarkup}});
106}
107
109 const ExtraJsonObjects &extraObjects) {
110
111 if (!args.chatId.isValid() || args.chatId.isNull())
112 return false;
113
114 if (args.text.isEmpty()) {
115 return false;
116 }
117
118 auto msg = QSharedPointer<TelegramSendMsg>::create(args, extraObjects);
119
120 return sendMessageRequest(msg, args.msgIdCB);
121}
122
124 const KeyboardOnMessage &keyboard) {
125 return sendSpecificMessage(args, prepareInlineKeyBoard(keyboard));
126}
127
128bool ITelegramBot::deleteMessage(const QVariant &chatId, const QVariant &messageId) {
129 if (!chatId.isValid() || chatId.isNull())
130 return false;
131
132 if (!messageId.isValid() || messageId.isNull())
133 return false;
134
135 auto msg = QSharedPointer<TelegramDeleteMessage>::create(chatId,
136 messageId);
137
138 return sendMessageRequest(msg);
139}
140
142 const TelegramArgs& args,
143 const QList<QList<QString> > &keyboard,
144 bool onTimeKeyboard,
145 bool autoResizeKeyboard) {
146
147 if (!args.chatId.isValid() || args.chatId.isNull())
148 return false;
149
150 if (!messageId.isValid() || messageId.isNull())
151 return false;
152
153 auto msg = QSharedPointer<TelegramEditMessage>::create(messageId,
154 args,
155 prepareKeyboard(autoResizeKeyboard,
156 onTimeKeyboard,
157 keyboard));
158
159 return sendMessageRequest(msg, args.msgIdCB);
160}
161
163qTbot::ITelegramBot::prepareInlineKeyBoard(const KeyboardOnMessage &keyboard)
164{
165 ExtraJsonObjects extraObjects;
166 auto&& keyboardJson = QSharedPointer<QJsonObject>::create();
167 QJsonArray keyboardArray;
168
169 for (const auto& map : keyboard) {
170 QJsonArray keyboardLineArray;
171 for (auto it = map.begin(); it != map.end(); it = std::next(it)) {
172 auto&& callBackKey = QString("callback_data_%0").arg(rand());
173 keyboardLineArray.push_back(QJsonObject{ {"text", it.key()}, {"callback_data", callBackKey } });
174 _handleButtons[callBackKey] = {it.value()};
175 }
176 keyboardArray.push_back(keyboardLineArray);
177 }
178
179
180 (*keyboardJson)["inline_keyboard"] = keyboardArray;
181
182 extraObjects["reply_markup"] = keyboardJson;
183
184 return extraObjects;
185}
186
188qTbot::ITelegramBot::prepareKeyboard(bool autoResizeKeyboard,
189 bool onTimeKeyboard,
190 const QList<QList<QString>> &keyboard) {
191 ExtraJsonObjects extraObjects;
192 auto&& keyboardJson = QSharedPointer<QJsonObject>::create();
193 QJsonArray keyboardArray;
194
195 for (const auto &row :keyboard) {
196 QJsonArray keyboardLineArray;
197
198 for (auto it = row.begin(); it != row.end(); it = std::next(it)) {
199 keyboardLineArray.push_back(QJsonObject{ {"text", *it} });
200 }
201 keyboardArray.push_back(keyboardLineArray);
202
203 }
204
205 (*keyboardJson)["keyboard"] = keyboardArray;
206
207 (*keyboardJson)["resize_keyboard"] = autoResizeKeyboard;
208 (*keyboardJson)["one_time_keyboard"] = onTimeKeyboard;
209
210 extraObjects["reply_markup"] = keyboardJson;
211
212 return extraObjects;
213}
214
216 const TelegramArgs& args,
217
218 const KeyboardOnMessage &keyboard ) {
219
220 if (!args.chatId.isValid() || args.chatId.isNull())
221 return false;
222
223 if (!messageId.isValid() || messageId.isNull())
224 return false;
225
226 auto msg = QSharedPointer<TelegramEditMessage>::create(messageId,
227 args,
228 prepareInlineKeyBoard(keyboard));
229
230
231 return sendMessageRequest(msg);
232}
233
234bool ITelegramBot::editMessageKeyboard(const QVariant &messageId,
235 const QVariant &chatId,
236 const KeyboardOnMessage &keyboard,
237 const QString &callBackQueryId) {
238 if (!chatId.isValid() || chatId.isNull())
239 return false;
240
241 if (!messageId.isValid() || messageId.isNull())
242 return false;
243
244 auto msg = QSharedPointer<TelegramEditMessageReplyMarkup>::create(messageId,
245 TelegramArgs(chatId, "", 0, "html", false, callBackQueryId),
246 prepareInlineKeyBoard(keyboard));
247
248
249 return sendMessageRequest(msg);
250}
251
252bool ITelegramBot::editSpecificMessage(const QVariant &messageId,
253 const TelegramArgs& args) {
254
255 if (!args.chatId.isValid() || args.chatId.isNull())
256 return false;
257
258 if (!messageId.isValid() || messageId.isNull())
259 return false;
260
261 if (args.text.isEmpty())
262 return false;
263
264 auto msg = QSharedPointer<TelegramEditMessage>::create(messageId,
265 args
266 );
267
268
269 return sendMessageRequest(msg);
270}
271
273 const QList<QList<QString> > &keyboard,
274 bool onTimeKeyboard,
275 bool autoResizeKeyboard) {
276
277 if (!args.chatId.isValid() || args.chatId.isNull())
278 return false;
279
280 if (args.text.isEmpty()) {
281 return false;
282 }
283
284 return sendSpecificMessage(args, prepareKeyboard(autoResizeKeyboard, onTimeKeyboard, keyboard));
285}
286
287QSharedPointer<iFile> ITelegramBot::getFile(const QString &fileId, iFile::Type fileType) {
288
289 QSharedPointer<iFile> result = nullptr;
290
291 if (fileId.isEmpty()) {
292 return result;
293 }
294
295 auto localFilePath = findFileInlocatStorage(fileId);
296
297 if (!localFilePath.isEmpty()) {
298
299 if (fileType == iFile::Ram) {
300 QFile localFile(localFilePath);
301 if (localFile.open(QIODevice::ReadOnly)) {
302 auto&& virtualFile = QSharedPointer<VirtualFile>::create(nullptr);
303 virtualFile->setArray(localFile.readAll());
304 localFile.close();
305
306 result = virtualFile;
307 }
308
309 } else if (fileType == iFile::Local) {
310 result = QSharedPointer<File>::create(nullptr, localFilePath);
311 }
312
313 result->setDownloadProgress(1);
314 result->setFinished(true);
315 return result;
316 }
317
318 auto&& metaInfo = getFileInfoByUniqueId(fileId);
319 localFilePath = defaultFileStorageLocation() + "/" + fileId;
320
321 if (metaInfo) {
322 auto&& path = metaInfo->takePath();
323 if (path.size()) {
324 auto&& msg = QSharedPointer<TelegrammDownloadFile>::create(path);
325
326 QDir().mkpath(defaultFileStorageLocation());
327
328
329 if (localFilePath.isEmpty())
330 return result;
331
332 if (auto &&replay = sendRequest(msg)) {
333 // here i must be receive responce and prepare new request to file from the call back function.
334 if (fileType == iFile::Ram) {
335 result = QSharedPointer<VirtualFile>::create(replay);
336 } else if (fileType == iFile::Local) {
337 result = QSharedPointer<File>::create(replay, localFilePath);
338 }
339 }
340
341 return result;
342 }
343 }
344
345
346 if (fileType == iFile::Ram) {
347 result = QSharedPointer<VirtualFile>::create();
348 } else if (fileType == iFile::Local) {
349 result = QSharedPointer<File>::create(localFilePath);
350 }
351
352 auto&& metaReploay = getFileMeta(fileId, result.toWeakRef());
353 return result;
354}
355
356QSharedPointer<QNetworkReply> ITelegramBot::getFileMeta(const QString &fileId, const QWeakPointer<iFile>& receiver) {
357 auto msg = QSharedPointer<TelegramGetFile>::create(fileId);
358
359 if (auto&& ptr = sendRequest(msg)) {
360 connect(ptr.get(), &QNetworkReply::finished,
361 this, std::bind(&ITelegramBot::handleFileHeader, this, ptr.toWeakRef(), receiver));
362
363 return ptr;
364 }
365
366 return nullptr;
367}
368
369bool ITelegramBot::sendFile(const QFileInfo &file, const QVariant &chatId) {
370 return sendFileMessage({chatId}, file);
371}
372
373bool ITelegramBot::sendFile(const QByteArray &file, const QString &fileName, const QVariant &chatId) {
374 return sendFileMessage({chatId}, file, fileName);
375}
376
377bool ITelegramBot::sendFileMessage(const TelegramArgs &args, const QFileInfo &file) {
378 if (!args.chatId.isValid() || args.chatId.isNull())
379 return false;
380
381 if (!file.isReadable()) {
382 return false;
383 }
384
385 return sendMessageRequest(
386 QSharedPointer<TelegramSendPhoto>::create(args,
387 file), args.msgIdCB);
388}
389
390bool ITelegramBot::sendFileMessage(const TelegramArgs &args, const QByteArray &file, const QString &fileName) {
391 if (!args.chatId.isValid() || args.chatId.isNull())
392 return false;
393
394 if (!fileName.size()) {
395 return false;
396 }
397
398 if (!file.size()) {
399 return false;
400 }
401
402 return sendMessageRequest(QSharedPointer<TelegramSendDocument>::create(args, fileName, file), args.msgIdCB);
403}
404
406 const QFileInfo &photo,
407 const KeyboardOnMessage &keyboard) {
408 if (!args.chatId.isValid() || args.chatId.isNull())
409 return false;
410
411 if (!photo.isReadable()) {
412 return false;
413 }
414
415 return sendMessageRequest(
416 QSharedPointer<TelegramSendPhoto>::create(args,
417 photo,
418 prepareInlineKeyBoard(keyboard)), args.msgIdCB);
419}
420
422 const QByteArray &photo,
423 const QString &fileName,
424 const KeyboardOnMessage &keyboard) {
425
426 if (!args.chatId.isValid() || args.chatId.isNull()) {
427 return false;
428 }
429
430 if (!fileName.size()) {
431 return false;
432 }
433
434 if (!photo.size()) {
435 return false;
436 }
437
438 return sendMessageRequest(
439 QSharedPointer<TelegramSendPhoto>::create(args,
440 fileName,
441 photo,
442 prepareInlineKeyBoard(keyboard)),
443 args.msgIdCB);
444}
445
446bool ITelegramBot::sendFileById(const QString &fileID, const QVariant &chatId) {
447 Q_UNUSED(fileID)
448 Q_UNUSED(chatId)
449
450 throw "the sendFileById is not implemented";
451
452 return false;
453
454}
455
457 float latitude,
458 float longitude,
459 const KeyboardOnMessage &keyboard) {
460
461 if (!args.chatId.isValid() || args.chatId.isNull())
462 return false;
463
464 if (!(longitude && latitude)) {
465 return false;
466 }
467
468 return sendMessageRequest(QSharedPointer<TelegramSendLocation>::create(args,
469 latitude,
470 longitude,
471 prepareInlineKeyBoard(keyboard)));
472}
473
475 const QString &phone,
476 const QString &firstName,
477 const QString &secondName) {
478 if (!args.chatId.isValid() || args.chatId.isNull())
479 return false;
480
481 return sendMessageRequest(QSharedPointer<TelegramSendContact>::create(args,
482 firstName,
483 phone,
484 secondName));
485}
486
487int ITelegramBot::getFileSizeByUniqueId(const QString &id) const {
488 if (auto && file = _filesMetaInfo.value(id)) {
489 return file->fileSize();
490 }
491
492 return 0;
493}
494
495QSharedPointer<TelegramFile> ITelegramBot::getFileInfoByUniqueId(const QString &id) const {
496 return _filesMetaInfo.value(id, nullptr);
497}
498
499void ITelegramBot::onRequestError(const QSharedPointer<TelegramUpdateAnswer> &ansverWithError) const {
500 qWarning() << QString("code: %0 - %1").
501 arg(ansverWithError->errorCode()).
502 arg(ansverWithError->errorDescription());
503}
504
505void ITelegramBot::handleIncomeNewUpdate(const QSharedPointer<iUpdate> & update) {
507
508
509 if (auto&& tupdate = update.dynamicCast<TelegramUpdate>()) {
510
511 if (auto&& queryUpd = tupdate->callbackQueryUpdate()) {
512 auto &&handleButtonKey = queryUpd->callBackData();
513
514 if (auto&& cb = _handleButtons.value(handleButtonKey)) {
515 cb(handleButtonKey, queryUpd->messageId());
516 }
517 }
518 }
519}
520
521bool ITelegramBot::sendMessageRequest(const QSharedPointer<iRequest> &rquest,
522 const std::function<void (int)> &msgIdCB) {
523 auto&& reply = IBot::sendRequest(rquest);
524 if (reply) {
525 connect(reply.get(), &QNetworkReply::finished, this,
526 [ reply, msgIdCB, this]() {
527
528 if (reply->error() == QNetworkReply::NoError) {
529 QByteArray&& responseData = reply->readAll();
530 QJsonDocument json = QJsonDocument::fromJson(responseData);
531
532 const QJsonObject&& obj = json.object();
533 if (obj.contains("result")) {
534 unsigned long long chatId = obj["result"]["chat"]["id"].toInteger();
535 int messageID = obj["result"]["message_id"].toInt();
536 if (msgIdCB) {
537 msgIdCB(messageID);
538 }
539
540 if (chatId) {
541 _lastMessageId[chatId] = messageID;
542 }
543
544 return;
545 }
546 }
547
548 if (msgIdCB) {
549 msgIdCB(-1);
550 }
551 });
552 }
553
554 return bool(reply);
555}
556
557void ITelegramBot::handleLogin() {
558
559 if (_loginReplay) {
560 auto&& ans = makeMesasge<TelegramUpdateAnswer>(_loginReplay->readAll());
561
562 if (!ans->isValid()) {
563 qWarning() << "login error occured: ";
564 }
565
566 auto&& result = ans->result().toObject();
567
568 setId(result.value("id").toInteger());
569 setName( result.value("first_name").toString());
570 setUsername( result.value("username").toString());
571
572 _loginReplay.reset();
573 }
574
575}
576
577void ITelegramBot::handleLoginErr(QNetworkReply::NetworkError err) {
578 if (err) {
579 qDebug() << "Network error occured. code: " << err;
580 }
581 _loginReplay.reset();
582}
583
584void ITelegramBot::handleFileHeader(const QWeakPointer<QNetworkReply> &sender,
585 const QWeakPointer<iFile>& receiver) {
586 if (auto&& sharedPtr = sender.lock()) {
587 auto&& ansver = makeMesasge<TelegramUpdateAnswer>(sharedPtr->readAll());
588
589 if (!ansver->isValid()) {
590 onRequestError(ansver);
591 return;
592 }
593
594 auto &&fileMetaInfo = makeMesasge<TelegramFile>(ansver->result().toObject());
595
596 _filesMetaInfo.insert(fileMetaInfo->fileId(), fileMetaInfo);
597
598 if (auto&& sharedPtr = receiver.lock()) {
599 auto&& downloadRequest = QSharedPointer<TelegrammDownloadFile>::create(fileMetaInfo->takePath());
600 sharedPtr->setDownloadRequest(sendRequest(downloadRequest));
601 }
602 }
603}
604
605QString ITelegramBot::findFileInlocatStorage(const QString &fileId) const {
606 QDir defaultFileDir(defaultFileStorageLocation());
607
608 auto &&localStorageList = defaultFileDir.entryInfoList(QDir::Filter::NoDotAndDotDot | QDir::Files);
609 for (const auto& file: localStorageList) {
610 int size = file.size();
611 if (file.fileName().contains(fileId) && size && size == getFileSizeByUniqueId(fileId)) {
612 return file.absoluteFilePath();
613 }
614 }
615
616 return "";
617}
618
619void ITelegramBot::setUsername(const QString &newUsername) {
620 _username = newUsername;
621}
622
623QString ITelegramBot::makeUrl(const QSharedPointer<iRequest> &request) const {
624 return request->baseAddress() + "/bot" + token() + request->makeUpload();
625}
626
627void ITelegramBot::setId(unsigned long long newId) {
628 _id = newId;
629}
630
631const QString &ITelegramBot::username() const {
632 return _username;
633}
634
635int ITelegramBot::gelLastMessageId(unsigned long long &chatId) const {
636 return _lastMessageId.value(chatId, 0);
637}
638
639unsigned long long ITelegramBot::id() const {
640 return _id;
641}
642
643} // namespace qTbot
const QByteArray & token() const
token This is token value for authication on the remote server (bot)
Definition ibot.cpp:28
virtual void handleIncomeNewUpdate(const QSharedPointer< iUpdate > &)
handleIncomeNewUpdate This method just emit the sigReceiveUpdate signal.
Definition ibot.cpp:138
virtual QString defaultFileStorageLocation() const
defaultFileStorageLocation This method return default file storage location.
Definition ibot.cpp:134
void setToken(const QByteArray &newToken)
setToken This is setter of the IBot::token value.
Definition ibot.cpp:32
QSharedPointer< QNetworkReply > sendRequest(const QSharedPointer< iRequest > &rquest)
sendRequest This method sent custom requests to the server.
Definition ibot.cpp:52
bool sendFile(const QFileInfo &file, const QVariant &chatId) override
send file .
bool sendMessage(const QVariant &chatId, const QString &text) override
sendMessage This method sents text to the selected chat.
bool sendFileById(const QString &fileID, const QVariant &chatId)
sendFileById This is specific method of the telegram bot. sents file by id.
QSharedPointer< QNetworkReply > getFileMeta(const QString &fileId, const QWeakPointer< iFile > &receiver={nullptr})
getFileMeta This method receive meta information of the file.
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.
QSharedPointer< TelegramFile > getFileInfoByUniqueId(const QString &id) const
getFileInfoByUniqueId return a local saved meta information about 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 ...
QSharedPointer< iFile > getFile(const QString &fileId, iFile::Type fileType=iFile::Type::Ram) override
Get a file by its ID.
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.
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...
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.
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 TelegramUpdate class contains base information about updates from telegram.
Type
The Type enum is type of the file object.
Definition ifile.h:28
@ Ram
This is memory saved file. All received bytes will be saved into QByteArray object.
Definition ifile.h:32
@ Local
This is local file, all receive bytes will be save directed into file.
Definition ifile.h:30
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.
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: "".