1 /* ============================================================
2 *
3 * This file is a part of digiKam project
4 * https://www.digikam.org
5 *
6 * Date : 2018-06-29
7 * Description : a tool to export images to Twitter social network
8 *
9 * Copyright (C) 2018 by Tarek Talaat <tarektalaat93 at gmail dot com>
10 * Copyright (C) 2019 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
11 *
12 * This program is free software; you can redistribute it
13 * and/or modify it under the terms of the GNU General
14 * Public License as published by the Free Software Foundation;
15 * either version 2, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * ============================================================ */
23
24 #include "twittertalker.h"
25
26 // Qt includes
27
28 #include <QJsonDocument>
29 #include <QJsonParseError>
30 #include <QJsonObject>
31 #include <QJsonValue>
32 #include <QJsonArray>
33 #include <QByteArray>
34 #include <QUrl>
35 #include <QUrlQuery>
36 #include <QList>
37 #include <QPair>
38 #include <QFileInfo>
39 #include <QWidget>
40 #include <QScopedPointer>
41 #include <QSettings>
42 #include <QMessageBox>
43 #include <QApplication>
44 #include <QDesktopServices>
45 #include <QNetworkAccessManager>
46
47 /*
48 #include <QWebEngineView>
49 #include <QWebEnginePage>
50 #include <QWebEngineProfile>
51 #include <QWebEngineCookieStore>
52 */
53
54 // KDE includes
55
56 #include <klocalizedstring.h>
57
58 // Local includes
59
60 #include "digikam_debug.h"
61 #include "digikam_version.h"
62 #include "wstoolutils.h"
63 #include "twitterwindow.h"
64 #include "twitteritem.h"
65 #include "twittermpform.h"
66 #include "previewloadthread.h"
67 #include "o0settingsstore.h"
68 #include "o1requestor.h"
69
70 namespace DigikamGenericTwitterPlugin
71 {
72
73 QStringList imageFormat(QString::fromLatin1("jpg,png,gif,webp").split(QLatin1Char(',')));
74
75 class Q_DECL_HIDDEN TwTalker::Private
76 {
77 public:
78
79 enum State
80 {
81 TW_USERNAME = 0,
82 TW_LISTFOLDERS,
83 TW_CREATEFOLDER,
84 TW_ADDPHOTO,
85 TW_CREATETWEET,
86 TW_UPLOADINIT,
87 TW_UPLOADAPPEND,
88 TW_UPLOADSTATUSCHECK,
89 TW_UPLOADFINALIZE
90 };
91
92 public:
93
Private()94 explicit Private()
95 : clientId (QLatin1String("lkRgRsucipXsUEvKh0ECblreC")),
96 clientSecret (QLatin1String("6EThTiPQHZTMo7F83iLHrfNO89fkDVvM9hVWaYH9D49xEOyMBe")),
97 authUrl (QLatin1String("https://api.twitter.com/oauth/authenticate")),
98 requestTokenUrl (QLatin1String("https://api.twitter.com/oauth/request_token")),
99 accessTokenUrl (QLatin1String("https://api.twitter.com/oauth/access_token")),
100 /*
101 scope (QLatin1String("User.Read Files.ReadWrite")),
102 */
103 redirectUrl (QLatin1String("http://127.0.0.1:8000")), // krazy:exclude=insecurenet
104 uploadUrl (QLatin1String("https://upload.twitter.com/1.1/media/upload.json")),
105 segmentIndex (0),
106 parent (nullptr),
107 netMngr (nullptr),
108 reply (nullptr),
109 state (TW_USERNAME),
110 settings (nullptr),
111 o1Twitter (nullptr),
112 requestor (nullptr)
113 {
114 }
115
116 public:
117
118 QString clientId;
119 QString clientSecret;
120 QString authUrl;
121 QString requestTokenUrl;
122 QString accessTokenUrl;
123 QString scope;
124 QString redirectUrl;
125 QString accessToken;
126 QString uploadUrl;
127 QString mediaUploadedPath;
128 QString mediaId;
129
130 int segmentIndex;
131
132 QWidget* parent;
133
134 QNetworkAccessManager* netMngr;
135
136 QNetworkReply* reply;
137
138 State state;
139
140 QMap<QString, QString> urlParametersMap;
141 /*
142 QWebEngineView* view;
143 */
144 QSettings* settings;
145
146 O1Twitter* o1Twitter;
147 O1Requestor* requestor;
148 };
149
TwTalker(QWidget * const parent)150 TwTalker::TwTalker(QWidget* const parent)
151 : d(new Private)
152 {
153 d->parent = parent;
154 d->netMngr = new QNetworkAccessManager(this);
155
156 connect(d->netMngr, SIGNAL(finished(QNetworkReply*)),
157 this, SLOT(slotFinished(QNetworkReply*)));
158
159 d->o1Twitter = new O1Twitter(this);
160 d->o1Twitter->setClientId(d->clientId);
161 d->o1Twitter->setClientSecret(d->clientSecret);
162 d->o1Twitter->setLocalPort(8000);
163
164 d->requestor = new O1Requestor(d->netMngr, d->o1Twitter, this);
165
166 d->settings = WSToolUtils::getOauthSettings(this);
167 O0SettingsStore* const store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this);
168 store->setGroupKey(QLatin1String("Twitter"));
169 d->o1Twitter->setStore(store);
170
171 connect(d->o1Twitter, SIGNAL(linkingFailed()),
172 this, SLOT(slotLinkingFailed()));
173
174 connect(d->o1Twitter, SIGNAL(linkingSucceeded()),
175 this, SLOT(slotLinkingSucceeded()));
176
177 connect(d->o1Twitter, SIGNAL(openBrowser(QUrl)),
178 this, SLOT(slotOpenBrowser(QUrl)));
179 }
180
~TwTalker()181 TwTalker::~TwTalker()
182 {
183 if (d->reply)
184 {
185 d->reply->abort();
186 }
187
188 WSToolUtils::removeTemporaryDir("twitter");
189
190 delete d;
191 }
192
link()193 void TwTalker::link()
194 {
195 /*
196 emit signalBusy(true);
197 QUrl url(d->requestTokenUrl);
198 QNetworkRequest netRequest(url);
199 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
200 netRequest.setRawHeader("Authorization", QString::fromLatin1("OAuth oauth_callback= \"%1\"").arg(d->redirectUrl).toUtf8());
201 QNetworkAccessManager requestMngr;
202 QNetworkReply* reply;
203 reply = requestMngr.post(netRequest);
204
205 if (reply->error() != QNetworkReply::NoError){
206
207 }
208
209 QByteArray buffer;
210 buffer.append(reply->readAll());
211 QString response = fromLatin1(buffer);
212
213 QMultiMap<QString, QString> headers;
214
215 // Discard the first line
216 response = response.mid(response.indexOf('\n') + 1).trimmed();
217
218 foreach (QString line, response.split('\n'))
219 {
220 int colon = line.indexOf(':');
221 QString headerName = line.left(colon).trimmed();
222 QString headerValue = line.mid(colon + 1).trimmed();
223
224 headers.insert(headerName, headerValue);
225 }
226
227 QString oauthToken = headers[oauth_token];
228 QSting oauthTokenSecret = headers[oauth_token_secret];
229
230 QUrlQuery query(url);
231 query.addQueryItem(QLatin1String("client_id"), d->clientId);
232 query.addQueryItem(QLatin1String("scope"), d->scope);
233 query.addQueryItem(QLatin1String("redirect_uri"), d->redirectUrl);
234 query.addQueryItem(QLatin1String("response_type"), "token");
235 url.setQuery(query);
236
237 d->view = new QWebEngineView(d->parent);
238 d->view->setWindowFlags(Qt::Dialog);
239 d->view->load(url);
240 d->view->show();
241
242 connect(d->view, SIGNAL(urlChanged(QUrl)),
243 this, SLOT(slotCatchUrl(QUrl)));
244 */
245
246 emit signalBusy(true);
247 d->o1Twitter->link();
248 }
249
unLink()250 void TwTalker::unLink()
251 {
252 /*
253 d->accessToken = QString();
254 d->view->page()->profile()->cookieStore()->deleteAllCookies();
255 emit oneDriveLinkingSucceeded();
256 */
257
258 d->o1Twitter->unlink();
259
260 d->settings->beginGroup(QLatin1String("Twitter"));
261 d->settings->remove(QString());
262 d->settings->endGroup();
263 }
264
slotOpenBrowser(const QUrl & url)265 void TwTalker::slotOpenBrowser(const QUrl& url)
266 {
267 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser...";
268 QDesktopServices::openUrl(url);
269 }
270
ParseUrlParameters(const QString & url)271 QMap<QString, QString> TwTalker::ParseUrlParameters(const QString& url)
272 {
273 QMap<QString, QString> urlParameters;
274
275 if (url.indexOf(QLatin1Char('?')) == -1)
276 {
277 return urlParameters;
278 }
279
280 QString tmp = url.right(url.length()-url.indexOf(QLatin1Char('?'))-1);
281 tmp = tmp.right(tmp.length() - tmp.indexOf(QLatin1Char('#'))-1);
282 QStringList paramlist = tmp.split(QLatin1Char('&'));
283
284 for (int i = 0 ; i < paramlist.count() ; ++i)
285 {
286 QStringList paramarg = paramlist.at(i).split(QLatin1Char('='));
287 urlParameters.insert(paramarg.at(0),paramarg.at(1));
288 }
289
290 return urlParameters;
291 }
292
slotLinkingFailed()293 void TwTalker::slotLinkingFailed()
294 {
295 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter fail";
296
297 emit signalBusy(false);
298 }
299
slotLinkingSucceeded()300 void TwTalker::slotLinkingSucceeded()
301 {
302 if (!d->o1Twitter->linked())
303 {
304 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Twitter ok";
305
306 emit signalBusy(false);
307
308 return;
309 }
310
311 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter ok";
312 QVariantMap extraTokens = d->o1Twitter->extraTokens();
313
314 if (!extraTokens.isEmpty())
315 {
316 //emit extraTokensReady(extraTokens);
317
318 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Extra tokens in response:";
319
320 foreach (const QString& key, extraTokens.keys())
321 {
322 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "\t"
323 << key
324 << ":"
325 << (extraTokens.value(key).toString().left(3) + QLatin1String("..."));
326 }
327 }
328
329 emit signalLinkingSucceeded();
330
331 getUserName();
332 }
333
authenticated()334 bool TwTalker::authenticated()
335 {
336 return d->o1Twitter->linked();
337 }
338
cancel()339 void TwTalker::cancel()
340 {
341 if (d->reply)
342 {
343 d->reply->abort();
344 d->reply = nullptr;
345 }
346
347 emit signalBusy(false);
348 }
349
addPhoto(const QString & imgPath,const QString &,bool rescale,int maxDim,int imageQuality)350 bool TwTalker::addPhoto(const QString& imgPath,
351 const QString& /* uploadFolder */,
352 bool rescale,
353 int maxDim,
354 int imageQuality)
355 {
356
357 QFileInfo imgFileInfo(imgPath);
358 QString path;
359 bool chunked = false;
360
361 qCDebug(DIGIKAM_WEBSERVICES_LOG) << imgFileInfo.suffix();
362
363 if ((imgFileInfo.suffix() != QLatin1String("gif")) &&
364 (imgFileInfo.suffix() != QLatin1String("mp4")))
365 {
366 QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage();
367 qint64 imageSize = QFileInfo(imgPath).size();
368 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SIZE of image using qfileinfo: " << imageSize;
369 qCDebug(DIGIKAM_WEBSERVICES_LOG) << " ";
370
371 if (image.isNull())
372 {
373 emit signalBusy(false);
374 return false;
375 }
376
377 path = WSToolUtils::makeTemporaryDir("twitter").filePath(imgFileInfo.baseName().trimmed() + QLatin1String(".jpg"));
378
379 if (rescale && ((image.width() > maxDim) || (image.height() > maxDim)))
380 {
381 image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation);
382 }
383
384 image.save(path, "JPEG", imageQuality);
385
386 QScopedPointer<DMetadata> meta(new DMetadata);
387
388 if (meta->load(imgPath))
389 {
390 meta->setItemDimensions(image.size());
391 meta->setItemOrientation(DMetadata::ORIENTATION_NORMAL);
392 meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY);
393 meta->save(path, true);
394 }
395 }
396 else
397 {
398 path = imgPath;
399 chunked = true;
400 }
401
402 if (chunked)
403 {
404 return addPhotoInit(path);
405 }
406 else
407 {
408 return addPhotoSingleUpload(path);
409 }
410 }
411
addPhotoSingleUpload(const QString & imgPath)412 bool TwTalker::addPhotoSingleUpload(const QString& imgPath)
413 {
414 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoSingleUpload";
415 emit signalBusy(true);
416
417 TwMPForm form;
418
419 if (!form.addFile(imgPath))
420 {
421 emit signalBusy(false);
422 return false;
423 }
424
425 form.finish();
426
427 if (form.formData().isEmpty())
428 {
429 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Form DATA Empty:";
430 }
431
432 if (form.formData().isNull())
433 {
434 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Form DATA null:";
435 }
436
437 QUrl url = QUrl(QLatin1String("https://upload.twitter.com/1.1/media/upload.json"));
438
439 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
440
441 QNetworkRequest request(url);
442 request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
443 d->reply = d->requestor->post(request, reqParams, form.formData());
444
445 d->state = Private::TW_ADDPHOTO;
446
447 return true;
448 }
449
addPhotoInit(const QString & imgPath)450 bool TwTalker::addPhotoInit(const QString& imgPath)
451 {
452 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoInit";
453 emit signalBusy(true);
454
455 TwMPForm form;
456 QByteArray mediaType, mediaCategory;
457 QFileInfo fileInfo(imgPath);
458 QString fileFormat(fileInfo.suffix());
459
460 form.addPair(form.createPair("command", "INIT"));
461 form.addPair(form.createPair("total_bytes", QString::number(QFileInfo(imgPath).size()).toLatin1()));
462
463 /* (Feb 2019)
464 * Image file must be <= 5MB
465 * Gif must be <= 15MB
466 * Video must be <= 512MB
467 */
468
469 if (imageFormat.indexOf(fileFormat) != -1)
470 {
471 mediaType = "image/jpeg";
472
473 if (fileFormat == QLatin1String("gif"))
474 {
475
476 if (fileInfo.size() > 15728640)
477 {
478 emit signalBusy(false);
479 emit signalAddPhotoFailed(i18n("File too big to upload"));
480
481 return false;
482 }
483
484 mediaCategory = "TWEET_GIF";
485 }
486 else
487 {
488 if (fileInfo.size() > 5242880)
489 {
490 emit signalBusy(false);
491 emit signalAddPhotoFailed(i18n("File too big to upload"));
492
493 return false;
494 }
495
496 mediaCategory = "TWEET_IMAGE";
497 }
498 }
499 else if (fileFormat == QLatin1String("mp4"))
500 {
501 if (fileInfo.size() > 536870912)
502 {
503 emit signalBusy(false);
504 emit signalAddPhotoFailed(i18n("File too big to upload"));
505 return false;
506 }
507
508 mediaType = "video/mp4";
509 mediaCategory = "TWEET_VIDEO";
510 }
511 else
512 {
513 emit signalBusy(false);
514 emit signalAddPhotoFailed(i18n("Media format is not supported yet"));
515 return false;
516 }
517
518 form.addPair(form.createPair("media_type", mediaType));
519 form.addPair(form.createPair("media_category", mediaCategory));
520 form.finish();
521
522 qCDebug(DIGIKAM_WEBSERVICES_LOG) << form.formData();
523
524 QUrl url(d->uploadUrl);
525
526 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
527
528 QNetworkRequest request(url);
529 request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
530 d->reply = d->requestor->post(request, reqParams, form.formData());
531
532 d->mediaUploadedPath = imgPath;
533
534 d->state = Private::TW_UPLOADINIT;
535
536 return true;
537 }
538
addPhotoAppend(const QString & mediaId,int segmentIndex)539 bool TwTalker::addPhotoAppend(const QString& mediaId, int segmentIndex)
540 {
541 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoAppend: ";
542
543 static TwMPForm form;
544
545 if (segmentIndex == 0)
546 {
547 form.addPair(form.createPair("command", "APPEND"));
548 form.addPair(form.createPair("media_id", mediaId.toLatin1()));
549 form.addFile(d->mediaUploadedPath, true);
550 d->segmentIndex = form.numberOfChunks() - 1;
551 }
552 QByteArray data(form.formData());
553 data.append(form.createPair("segment_index", QString::number(segmentIndex).toLatin1()));
554 data.append(form.createPair("media", form.getChunk(segmentIndex)));
555 data.append(form.border());
556
557 QUrl url(d->uploadUrl);
558 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
559
560 QNetworkRequest request(url);
561 request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
562 d->reply = d->requestor->post(request, reqParams, data);
563
564 d->state = Private::TW_UPLOADAPPEND;
565
566 // Reset form for later uploads
567
568 if (segmentIndex == d->segmentIndex)
569 {
570 form.reset();
571 }
572
573 return true;
574 }
575
addPhotoFinalize(const QString & mediaId)576 bool TwTalker::addPhotoFinalize(const QString& mediaId)
577 {
578 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoFinalize: ";
579
580 TwMPForm form;
581
582 form.addPair(form.createPair("command", "FINALIZE"));
583 form.addPair(form.createPair("media_id", mediaId.toLatin1()));
584 form.finish();
585
586 qCDebug(DIGIKAM_WEBSERVICES_LOG) << form.formData();
587
588 QUrl url(d->uploadUrl);
589
590 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
591
592 QNetworkRequest request(url);
593 request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
594 d->reply = d->requestor->post(request, reqParams, form.formData());
595
596 d->state = Private::TW_UPLOADFINALIZE;
597
598 return true;
599 }
600
getUserName()601 void TwTalker::getUserName()
602 {
603 /*
604 * The endpoint below allows to get more than just account name (e.g. profile avatar, links to tweets posted, etc.)
605 * Look at debug message printed to console for futher ideas and exploitation
606 */
607 QUrl url(QLatin1String("https://api.twitter.com/1.1/account/verify_credentials.json"));
608
609 QNetworkRequest request(url);
610 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
611
612 d->reply = d->requestor->get(request, reqParams);
613 d->state = Private::TW_USERNAME;
614
615 emit signalBusy(true);
616 }
617
createTweet(const QString & mediaId)618 void TwTalker::createTweet(const QString& mediaId)
619 {
620 QUrl url = QUrl(QLatin1String("https://api.twitter.com/1.1/statuses/update.json"));
621
622 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
623 reqParams << O0RequestParameter(QByteArray("status"), QByteArray(""));
624 reqParams << O0RequestParameter(QByteArray("media_ids"), mediaId.toUtf8());
625 QByteArray postData = O1::createQueryParameters(reqParams);
626
627 QNetworkRequest request(url);
628 request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM));
629 d->reply = d->requestor->post(request, reqParams, postData);
630
631 d->state = Private::TW_CREATETWEET;
632 }
633
slotCheckUploadStatus()634 void TwTalker::slotCheckUploadStatus()
635 {
636 QUrl url = QUrl(d->uploadUrl);
637
638 QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
639 reqParams << O0RequestParameter(QByteArray("command"), QByteArray("STATUS"));
640 reqParams << O0RequestParameter(QByteArray("media_id"), d->mediaId.toUtf8());
641
642 QUrlQuery query;
643 query.addQueryItem(QLatin1String("command"), QLatin1String("STATUS"));
644 query.addQueryItem(QLatin1String("media_id"), d->mediaId);
645
646 url.setQuery(query);
647 qCDebug(DIGIKAM_WEBSERVICES_LOG) << url.toString();
648
649 QNetworkRequest request(url);
650 d->reply = d->requestor->get(request, reqParams);
651 d->state = Private::TW_UPLOADSTATUSCHECK;
652 }
653
slotFinished(QNetworkReply * reply)654 void TwTalker::slotFinished(QNetworkReply* reply)
655 {
656 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "TwTalker::slotFinished";
657
658 if (reply != d->reply)
659 {
660 return;
661 }
662
663 d->reply = nullptr;
664
665 if (reply->error() != QNetworkReply::NoError)
666 {
667 if (d->state != Private::TW_CREATEFOLDER)
668 {
669 qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->readAll();
670 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "status code: " << reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
671 emit signalBusy(false);
672 QMessageBox::critical(QApplication::activeWindow(),
673 i18n("Error"), reply->errorString());
674
675 reply->deleteLater();
676 return;
677 }
678 }
679
680 QByteArray buffer = reply->readAll();
681 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "status code: " << reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
682 static int segmentIndex = 0;
683
684 switch (d->state)
685 {
686 case Private::TW_LISTFOLDERS:
687 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_LISTFOLDERS";
688 parseResponseListFolders(buffer);
689 break;
690
691 case Private::TW_CREATEFOLDER:
692 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_CREATEFOLDER";
693 parseResponseCreateFolder(buffer);
694 break;
695
696 case Private::TW_ADDPHOTO:
697 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_ADDPHOTO";
698 parseResponseAddPhoto(buffer);
699 break;
700
701 case Private::TW_USERNAME:
702 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_USERNAME";
703 parseResponseUserName(buffer);
704 break;
705
706 case Private::TW_CREATETWEET:
707 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_CREATETWEET";
708 parseResponseCreateTweet(buffer);
709 break;
710
711 case Private::TW_UPLOADINIT:
712 segmentIndex = 0;
713 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADINIT";
714 parseResponseAddPhotoInit(buffer);
715 break;
716
717 case Private::TW_UPLOADAPPEND:
718 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADAPPEND (at index " << segmentIndex << ")";
719 segmentIndex++;
720 parseResponseAddPhotoAppend(buffer, segmentIndex);
721 break;
722
723 case Private::TW_UPLOADSTATUSCHECK:
724 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADSTATUSCHECK";
725 parseCheckUploadStatus(buffer);
726 break;
727
728 case Private::TW_UPLOADFINALIZE:
729 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADFINALIZE";
730 parseResponseAddPhotoFinalize(buffer);
731 break;
732
733 default:
734 break;
735 }
736
737 reply->deleteLater();
738 }
739
parseResponseAddPhoto(const QByteArray & data)740 void TwTalker::parseResponseAddPhoto(const QByteArray& data)
741 {
742 QJsonParseError err;
743 QJsonDocument doc = QJsonDocument::fromJson(data, &err);
744 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhoto: " << doc;
745
746 if (err.error != QJsonParseError::NoError)
747 {
748 emit signalBusy(false);
749 emit signalAddPhotoFailed(i18n("Failed to upload photo"));
750 return;
751 }
752
753 QJsonObject jsonObject = doc.object();
754 QString mediaId = jsonObject[QLatin1String("media_id_string")].toString();
755 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "media id: " << mediaId;
756
757 // We haven't emit signalAddPhotoSucceeded() here yet, since we need to update the status first
758
759 createTweet(mediaId);
760 }
761
parseResponseAddPhotoInit(const QByteArray & data)762 void TwTalker::parseResponseAddPhotoInit(const QByteArray& data)
763 {
764 QJsonParseError err;
765 QJsonDocument doc = QJsonDocument::fromJson(data, &err);
766 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoInit: " << doc;
767
768 if (err.error != QJsonParseError::NoError)
769 {
770 emit signalBusy(false);
771 emit signalAddPhotoFailed(i18n("Failed to upload photo"));
772 return;
773 }
774
775 QJsonObject jsonObject = doc.object();
776 d->mediaId = jsonObject[QLatin1String("media_id_string")].toString();
777 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "media id: " << d->mediaId;
778
779 // We haven't emit signalAddPhotoSucceeded() here yet, since we need to update the status first
780
781 addPhotoAppend(d->mediaId);
782 }
783
parseResponseAddPhotoAppend(const QByteArray &,int segmentIndex)784 void TwTalker::parseResponseAddPhotoAppend(const QByteArray& /*data*/, int segmentIndex)
785 {
786 /* (Fev. 2019)
787 * Currently, we don't parse data of response from addPhotoAppend, since the response is with HTTP 204
788 * This is indeed an expected response code, because the response should be with an empty body.
789 * However, in order to keep a compatible prototype of parseResponse methodes and reserve for future change,
790 * we should keep argument const QByteArray& data.
791 */
792 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoAppend: ";
793
794 if (segmentIndex <= d->segmentIndex)
795 {
796 addPhotoAppend(d->mediaId, segmentIndex);
797 }
798 else
799 {
800 addPhotoFinalize(d->mediaId);
801 }
802 }
803
parseResponseAddPhotoFinalize(const QByteArray & data)804 void TwTalker::parseResponseAddPhotoFinalize(const QByteArray& data)
805 {
806 QJsonParseError err;
807 QJsonDocument doc = QJsonDocument::fromJson(data, &err);
808 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoFinalize: " << doc;
809
810 if (err.error != QJsonParseError::NoError)
811 {
812 emit signalBusy(false);
813 emit signalAddPhotoFailed(i18n("Failed to upload photo"));
814
815 return;
816 }
817
818 QJsonObject jsonObject = doc.object();
819 QJsonValue processingInfo = jsonObject[QLatin1String("processing_info")];
820
821 if (processingInfo != QJsonValue::Undefined)
822 {
823 QString state = processingInfo.toObject()[QLatin1String("state")].toString();
824 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "state: " << state;
825
826 if (state == QLatin1String("pending"))
827 {
828 QTimer::singleShot(processingInfo.toObject()[QLatin1String("check_after_secs")].toInt()*1000 /*msec*/,
829 this, SLOT(slotCheckUploadStatus()));
830 }
831 }
832 else
833 {
834 // We haven't emit signalAddPhotoSucceeded() here yet, since we need to update the status first
835
836 createTweet(d->mediaId);
837 }
838 }
839
parseCheckUploadStatus(const QByteArray & data)840 void TwTalker::parseCheckUploadStatus(const QByteArray& data)
841 {
842 QJsonParseError err;
843 QJsonDocument doc = QJsonDocument::fromJson(data, &err);
844 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseCheckUploadStatus: " << doc;
845
846 if (err.error != QJsonParseError::NoError)
847 {
848 emit signalBusy(false);
849 emit signalAddPhotoFailed(i18n("Failed to upload photo"));
850 return;
851 }
852
853 QJsonObject jsonObject = doc.object();
854 QJsonObject processingInfo = jsonObject[QLatin1String("processing_info")].toObject();
855 QString state = processingInfo[QLatin1String("state")].toString();
856
857 if (state == QLatin1String("in_progress"))
858 {
859 QTimer::singleShot(processingInfo[QLatin1String("check_after_secs")].toInt()*1000 /*msec*/, this, SLOT(slotCheckUploadStatus()));
860 }
861 else if (state == QLatin1String("failed"))
862 {
863 QJsonObject error = processingInfo[QLatin1String("error")].toObject();
864 emit signalBusy(false);
865 emit signalAddPhotoFailed(i18n("Failed to upload photo\n"
866 "Code: %1, name: %2, message: %3",
867 error[QLatin1String("code")].toInt(),
868 error[QLatin1String("name")].toString(),
869 error[QLatin1String("message")].toString()));
870 return;
871 }
872 else // succeeded
873 {
874 // We haven't emit signalAddPhotoSucceeded() here yet, since we need to update the status first
875
876 createTweet(d->mediaId);
877 }
878 }
879
parseResponseUserName(const QByteArray & data)880 void TwTalker::parseResponseUserName(const QByteArray& data)
881 {
882 QJsonParseError err;
883 QJsonDocument doc = QJsonDocument::fromJson(data, &err);
884 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseUserName: "<<doc;
885
886 if (err.error != QJsonParseError::NoError)
887 {
888 emit signalBusy(false);
889 return;
890 }
891
892 QJsonObject obj = doc.object();
893 QString name = obj[QLatin1String("name")].toString();
894 QString screenName = obj[QLatin1String("screen_name")].toString();
895 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "user full name: "<<name;
896 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "user screen name: @" << screenName;
897
898 emit signalBusy(false);
899 emit signalSetUserName(QString::fromLatin1("%1 (@%2)").arg(name).arg(screenName));
900 }
901
parseResponseCreateTweet(const QByteArray & data)902 void TwTalker::parseResponseCreateTweet(const QByteArray& data)
903 {
904 QJsonParseError err;
905 QJsonDocument doc = QJsonDocument::fromJson(data, &err);
906 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateTweet: " << doc;
907
908 if (err.error != QJsonParseError::NoError)
909 {
910 emit signalBusy(false);
911 emit signalAddPhotoFailed(i18n("Failed to create tweet for photo uploaded"));
912
913 return;
914 }
915
916 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tweet posted successfully!";
917 emit signalBusy(false);
918 emit signalAddPhotoSucceeded();
919 }
920
parseResponseListFolders(const QByteArray & data)921 void TwTalker::parseResponseListFolders(const QByteArray& data)
922 {
923 QJsonParseError err;
924 QJsonDocument doc = QJsonDocument::fromJson(data, &err);
925
926 if (err.error != QJsonParseError::NoError)
927 {
928 emit signalBusy(false);
929 emit signalListAlbumsFailed(i18n("Failed to list folders"));
930 return;
931 }
932
933 QJsonObject jsonObject = doc.object();
934
935 //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Json: " << doc;
936
937 QJsonArray jsonArray = jsonObject[QLatin1String("value")].toArray();
938
939 //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Json response: " << jsonArray;
940
941 QList<QPair<QString, QString> > list;
942 list.append(qMakePair(QLatin1String(""), QLatin1String("root")));
943
944 foreach (const QJsonValue& value, jsonArray)
945 {
946 QString path;
947 QString folderName;
948 QJsonObject folder;
949
950 QJsonObject obj = value.toObject();
951 folder = obj[QLatin1String("folder")].toObject();
952
953 if (!folder.isEmpty())
954 {
955 folderName = obj[QLatin1String("name")].toString();
956 path = QLatin1Char('/') + folderName;
957 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Folder Name is" << folderName;
958 list.append(qMakePair(path, folderName));
959 }
960 }
961
962 emit signalBusy(false);
963 emit signalListAlbumsDone(list);
964 }
965
parseResponseCreateFolder(const QByteArray & data)966 void TwTalker::parseResponseCreateFolder(const QByteArray& data)
967 {
968 QJsonDocument doc1 = QJsonDocument::fromJson(data);
969 QJsonObject jsonObject = doc1.object();
970 bool fail = jsonObject.contains(QLatin1String("error"));
971
972 emit signalBusy(false);
973
974 if (fail)
975 {
976 QJsonParseError err;
977 QJsonDocument doc2 = QJsonDocument::fromJson(data, &err);
978 qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateFolder ERROR: " << doc2;
979
980 emit signalCreateFolderFailed(jsonObject[QLatin1String("error_summary")].toString());
981 }
982 else
983 {
984 emit signalCreateFolderSucceeded();
985 }
986 }
987
988 } // namespace DigikamGenericTwitterPlugin
989