File indexing completed on 2025-01-19 03:53:14

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2018-06-29
0007  * Description : a tool to export images to Twitter social network
0008  *
0009  * SPDX-FileCopyrightText: 2018 by Tarek Talaat <tarektalaat93 at gmail dot com>
0010  * SPDX-FileCopyrightText: 2019 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "twittertalker.h"
0017 
0018 // Qt includes
0019 
0020 #include <QJsonDocument>
0021 #include <QJsonParseError>
0022 #include <QJsonObject>
0023 #include <QJsonValue>
0024 #include <QJsonArray>
0025 #include <QByteArray>
0026 #include <QUrl>
0027 #include <QUrlQuery>
0028 #include <QFileInfo>
0029 #include <QWidget>
0030 #include <QScopedPointer>
0031 #include <QSettings>
0032 #include <QMessageBox>
0033 #include <QApplication>
0034 #include <QDesktopServices>
0035 
0036 /*
0037 #include <QWebEngineView>
0038 #include <QWebEnginePage>
0039 #include <QWebEngineProfile>
0040 #include <QWebEngineCookieStore>
0041 */
0042 
0043 // KDE includes
0044 
0045 #include <klocalizedstring.h>
0046 
0047 // Local includes
0048 
0049 #include "digikam_debug.h"
0050 #include "digikam_version.h"
0051 #include "wstoolutils.h"
0052 #include "twitterwindow.h"
0053 #include "twittermpform.h"
0054 #include "previewloadthread.h"
0055 #include "networkmanager.h"
0056 #include "o0settingsstore.h"
0057 #include "o1requestor.h"
0058 
0059 namespace DigikamGenericTwitterPlugin
0060 {
0061 
0062 QStringList imageFormat(QString::fromLatin1("jpg,png,gif,webp").split(QLatin1Char(',')));
0063 
0064 class Q_DECL_HIDDEN TwTalker::Private
0065 {
0066 public:
0067 
0068     enum State
0069     {
0070         TW_USERNAME = 0,
0071         TW_LISTFOLDERS,
0072         TW_CREATEFOLDER,
0073         TW_ADDPHOTO,
0074         TW_CREATETWEET,
0075         TW_UPLOADINIT,
0076         TW_UPLOADAPPEND,
0077         TW_UPLOADSTATUSCHECK,
0078         TW_UPLOADFINALIZE
0079     };
0080 
0081 public:
0082 
0083     explicit Private()
0084       : clientId        (QLatin1String("lkRgRsucipXsUEvKh0ECblreC")),
0085         clientSecret    (QLatin1String("6EThTiPQHZTMo7F83iLHrfNO89fkDVvM9hVWaYH9D49xEOyMBe")),
0086         authUrl         (QLatin1String("https://api.twitter.com/oauth/authenticate")),
0087         requestTokenUrl (QLatin1String("https://api.twitter.com/oauth/request_token")),
0088         accessTokenUrl  (QLatin1String("https://api.twitter.com/oauth/access_token")),
0089 /*
0090         scope           (QLatin1String("User.Read Files.ReadWrite")),
0091 */
0092         redirectUrl     (QLatin1String("http://127.0.0.1:8000")),                            // krazy:exclude=insecurenet
0093         uploadUrl       (QLatin1String("https://upload.twitter.com/1.1/media/upload.json")),
0094         segmentIndex    (0),
0095         parent          (nullptr),
0096         netMngr         (nullptr),
0097         reply           (nullptr),
0098         state           (TW_USERNAME),
0099         settings        (nullptr),
0100         o1Twitter       (nullptr),
0101         requestor       (nullptr)
0102     {
0103     }
0104 
0105 public:
0106 
0107     QString                clientId;
0108     QString                clientSecret;
0109     QString                authUrl;
0110     QString                requestTokenUrl;
0111     QString                accessTokenUrl;
0112     QString                scope;
0113     QString                redirectUrl;
0114     QString                accessToken;
0115     QString                uploadUrl;
0116     QString                mediaUploadedPath;
0117     QString                mediaId;
0118 
0119     int                    segmentIndex;
0120 
0121     QWidget*               parent;
0122 
0123     QNetworkAccessManager* netMngr;
0124 
0125     QNetworkReply*         reply;
0126 
0127     State                  state;
0128 
0129     QMap<QString, QString> urlParametersMap;
0130 /*
0131     QWebEngineView*        view;
0132 */
0133     QSettings*             settings;
0134 
0135     O1Twitter*             o1Twitter;
0136     O1Requestor*           requestor;
0137 };
0138 
0139 TwTalker::TwTalker(QWidget* const parent)
0140     : d(new Private)
0141 {
0142     d->parent  = parent;
0143     d->netMngr = NetworkManager::instance()->getNetworkManager(this);
0144 
0145     connect(d->netMngr, SIGNAL(finished(QNetworkReply*)),
0146             this, SLOT(slotFinished(QNetworkReply*)));
0147 
0148     d->o1Twitter = new O1Twitter(this);
0149     d->o1Twitter->setClientId(d->clientId);
0150     d->o1Twitter->setClientSecret(d->clientSecret);
0151     d->o1Twitter->setLocalPort(8000);
0152 
0153     d->requestor = new O1Requestor(d->netMngr, d->o1Twitter, this);
0154 
0155     d->settings                  = WSToolUtils::getOauthSettings(this);
0156     O0SettingsStore* const store = new O0SettingsStore(d->settings, QLatin1String(O2_ENCRYPTION_KEY), this);
0157     store->setGroupKey(QLatin1String("Twitter"));
0158     d->o1Twitter->setStore(store);
0159 
0160     connect(d->o1Twitter, SIGNAL(linkingFailed()),
0161             this, SLOT(slotLinkingFailed()));
0162 
0163     connect(d->o1Twitter, SIGNAL(linkingSucceeded()),
0164             this, SLOT(slotLinkingSucceeded()));
0165 
0166     connect(d->o1Twitter, SIGNAL(openBrowser(QUrl)),
0167             this, SLOT(slotOpenBrowser(QUrl)));
0168 }
0169 
0170 TwTalker::~TwTalker()
0171 {
0172     if (d->reply)
0173     {
0174         d->reply->abort();
0175     }
0176 
0177     WSToolUtils::removeTemporaryDir("twitter");
0178 
0179     delete d;
0180 }
0181 
0182 void TwTalker::link()
0183 {
0184 /*
0185     Q_EMIT signalBusy(true);
0186     QUrl url(d->requestTokenUrl);
0187     QNetworkRequest netRequest(url);
0188     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
0189     netRequest.setRawHeader("Authorization", QString::fromLatin1("OAuth oauth_callback= \"%1\"").arg(d->redirectUrl).toUtf8());
0190     QNetworkAccessManager requestMngr;
0191     QNetworkReply* reply;
0192     reply = requestMngr.post(netRequest);
0193 
0194     if (reply->error() != QNetworkReply::NoError){
0195 
0196     }
0197 
0198     QByteArray buffer;
0199     buffer.append(reply->readAll());
0200     QString response = fromLatin1(buffer);
0201 
0202     QMultiMap<QString, QString> headers;
0203 
0204     // Discard the first line
0205     response = response.mid(response.indexOf('\n') + 1).trimmed();
0206 
0207     Q_FOREACH (QString line, response.split('\n'))
0208     {
0209         int colon = line.indexOf(':');
0210         QString headerName = line.left(colon).trimmed();
0211         QString headerValue = line.mid(colon + 1).trimmed();
0212 
0213         headers.insert(headerName, headerValue);
0214     }
0215 
0216     QString oauthToken = headers[oauth_token];
0217     QSting oauthTokenSecret = headers[oauth_token_secret];
0218 
0219     QUrlQuery query(url);
0220     query.addQueryItem(QLatin1String("client_id"),     d->clientId);
0221     query.addQueryItem(QLatin1String("scope"),         d->scope);
0222     query.addQueryItem(QLatin1String("redirect_uri"),  d->redirectUrl);
0223     query.addQueryItem(QLatin1String("response_type"), "token");
0224     url.setQuery(query);
0225 
0226     d->view = new QWebEngineView(d->parent);
0227     d->view->setWindowFlags(Qt::Dialog);
0228     d->view->load(url);
0229     d->view->show();
0230 
0231     connect(d->view, SIGNAL(urlChanged(QUrl)),
0232             this, SLOT(slotCatchUrl(QUrl)));
0233 */
0234 
0235     Q_EMIT signalBusy(true);
0236     d->o1Twitter->link();
0237 }
0238 
0239 void TwTalker::unLink()
0240 {
0241 /*
0242     d->accessToken = QString();
0243     d->view->page()->profile()->cookieStore()->deleteAllCookies();
0244     Q_EMIT oneDriveLinkingSucceeded();
0245 */
0246 
0247     d->o1Twitter->unlink();
0248 
0249     d->settings->beginGroup(QLatin1String("Twitter"));
0250     d->settings->remove(QString());
0251     d->settings->endGroup();
0252 }
0253 
0254 void TwTalker::slotOpenBrowser(const QUrl& url)
0255 {
0256     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Open Browser...";
0257     QDesktopServices::openUrl(url);
0258 }
0259 
0260 QMap<QString, QString> TwTalker::ParseUrlParameters(const QString& url)
0261 {
0262     QMap<QString, QString> urlParameters;
0263 
0264     if (url.indexOf(QLatin1Char('?')) == -1)
0265     {
0266         return urlParameters;
0267     }
0268 
0269     QString tmp           = url.right(url.length()-url.indexOf(QLatin1Char('?'))-1);
0270     tmp                   = tmp.right(tmp.length() - tmp.indexOf(QLatin1Char('#'))-1);
0271     QStringList paramlist = tmp.split(QLatin1Char('&'));
0272 
0273     for (int i = 0 ; i < paramlist.count() ; ++i)
0274     {
0275         QStringList paramarg = paramlist.at(i).split(QLatin1Char('='));
0276         urlParameters.insert(paramarg.at(0),paramarg.at(1));
0277     }
0278 
0279     return urlParameters;
0280 }
0281 
0282 void TwTalker::slotLinkingFailed()
0283 {
0284     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter fail";
0285 
0286     Q_EMIT signalBusy(false);
0287 }
0288 
0289 void TwTalker::slotLinkingSucceeded()
0290 {
0291     if (!d->o1Twitter->linked())
0292     {
0293         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "UNLINK to Twitter ok";
0294 
0295         Q_EMIT signalBusy(false);
0296 
0297         return;
0298     }
0299 
0300     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "LINK to Twitter ok";
0301     QVariantMap extraTokens = d->o1Twitter->extraTokens();
0302 
0303     if (!extraTokens.isEmpty())
0304     {
0305         //Q_EMIT extraTokensReady(extraTokens);
0306 
0307         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Extra tokens in response:";
0308 
0309         Q_FOREACH (const QString& key, extraTokens.keys())
0310         {
0311             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "\t"
0312                                              << key
0313                                              << ":"
0314                                              << (extraTokens.value(key).toString().left(3) + QLatin1String("..."));
0315         }
0316     }
0317 
0318     Q_EMIT signalLinkingSucceeded();
0319 
0320     getUserName();
0321 }
0322 
0323 bool TwTalker::authenticated()
0324 {
0325     return d->o1Twitter->linked();
0326 }
0327 
0328 void TwTalker::cancel()
0329 {
0330     if (d->reply)
0331     {
0332         d->reply->abort();
0333         d->reply = nullptr;
0334     }
0335 
0336     Q_EMIT signalBusy(false);
0337 }
0338 
0339 bool TwTalker::addPhoto(const QString& imgPath,
0340                         const QString& /* uploadFolder */,
0341                         bool rescale,
0342                         int maxDim,
0343                         int imageQuality)
0344 {
0345 
0346     QFileInfo imgFileInfo(imgPath);
0347     QString path;
0348     bool chunked = false;
0349 
0350     qCDebug(DIGIKAM_WEBSERVICES_LOG) << imgFileInfo.suffix();
0351 
0352     if ((imgFileInfo.suffix() != QLatin1String("gif")) &&
0353         (imgFileInfo.suffix() != QLatin1String("mp4")))
0354     {
0355         QImage image     = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage();
0356         qint64 imageSize = QFileInfo(imgPath).size();
0357         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "SIZE of image using qfileinfo:   " << imageSize;
0358         qCDebug(DIGIKAM_WEBSERVICES_LOG) << " ";
0359 
0360         if (image.isNull())
0361         {
0362             Q_EMIT signalBusy(false);
0363             return false;
0364         }
0365 
0366         path = WSToolUtils::makeTemporaryDir("twitter").filePath(imgFileInfo.baseName().trimmed() + QLatin1String(".jpg"));
0367 
0368         if (rescale && ((image.width() > maxDim) || (image.height() > maxDim)))
0369         {
0370             image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation);
0371         }
0372 
0373         image.save(path, "JPEG", imageQuality);
0374 
0375         QScopedPointer<DMetadata> meta(new DMetadata);
0376 
0377         if (meta->load(imgPath))
0378         {
0379             meta->setItemDimensions(image.size());
0380             meta->setItemOrientation(DMetadata::ORIENTATION_NORMAL);
0381             meta->setMetadataWritingMode((int)DMetadata::WRITE_TO_FILE_ONLY);
0382             meta->save(path, true);
0383         }
0384     }
0385     else
0386     {
0387         path = imgPath;
0388         chunked = true;
0389     }
0390 
0391     if (chunked)
0392     {
0393         return addPhotoInit(path);
0394     }
0395     else
0396     {
0397         return addPhotoSingleUpload(path);
0398     }
0399 }
0400 
0401 bool TwTalker::addPhotoSingleUpload(const QString& imgPath)
0402 {
0403     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoSingleUpload";
0404     Q_EMIT signalBusy(true);
0405 
0406     TwMPForm form;
0407 
0408     if (!form.addFile(imgPath))
0409     {
0410         Q_EMIT signalBusy(false);
0411         return false;
0412     }
0413 
0414     form.finish();
0415 
0416     if (form.formData().isEmpty())
0417     {
0418         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Form DATA Empty:";
0419     }
0420 
0421     if (form.formData().isNull())
0422     {
0423         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Form DATA null:";
0424     }
0425 
0426     QUrl url = QUrl(QLatin1String("https://upload.twitter.com/1.1/media/upload.json"));
0427 
0428     QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
0429 
0430     QNetworkRequest request(url);
0431     request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
0432     d->reply = d->requestor->post(request, reqParams, form.formData());
0433 
0434     d->state = Private::TW_ADDPHOTO;
0435 
0436     return true;
0437 }
0438 
0439 bool TwTalker::addPhotoInit(const QString& imgPath)
0440 {
0441     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoInit";
0442     Q_EMIT signalBusy(true);
0443 
0444     TwMPForm form;
0445     QByteArray mediaType, mediaCategory;
0446     QFileInfo fileInfo(imgPath);
0447     QString fileFormat(fileInfo.suffix());
0448 
0449     form.addPair(form.createPair("command", "INIT"));
0450     form.addPair(form.createPair("total_bytes", QString::number(QFileInfo(imgPath).size()).toLatin1()));
0451 
0452     /* (Feb 2019)
0453      * Image file must be <= 5MB
0454      * Gif must be <= 15MB
0455      * Video must be <= 512MB
0456      */
0457 
0458     if      (imageFormat.indexOf(fileFormat) != -1)
0459     {
0460         mediaType = "image/jpeg";
0461 
0462         if (fileFormat == QLatin1String("gif"))
0463         {
0464 
0465             if (fileInfo.size() > 15728640)
0466             {
0467                 Q_EMIT signalBusy(false);
0468                 Q_EMIT signalAddPhotoFailed(i18n("File too big to upload"));
0469 
0470                 return false;
0471             }
0472 
0473             mediaCategory = "TWEET_GIF";
0474         }
0475         else
0476         {
0477             if (fileInfo.size() > 5242880)
0478             {
0479                 Q_EMIT signalBusy(false);
0480                 Q_EMIT signalAddPhotoFailed(i18n("File too big to upload"));
0481 
0482                 return false;
0483             }
0484 
0485             mediaCategory = "TWEET_IMAGE";
0486         }
0487     }
0488     else if (fileFormat == QLatin1String("mp4"))
0489     {
0490         if (fileInfo.size() > 536870912)
0491         {
0492             Q_EMIT signalBusy(false);
0493             Q_EMIT signalAddPhotoFailed(i18n("File too big to upload"));
0494             return false;
0495         }
0496 
0497         mediaType = "video/mp4";
0498         mediaCategory = "TWEET_VIDEO";
0499     }
0500     else
0501     {
0502         Q_EMIT signalBusy(false);
0503         Q_EMIT signalAddPhotoFailed(i18n("Media format is not supported yet"));
0504         return false;
0505     }
0506 
0507     form.addPair(form.createPair("media_type", mediaType));
0508     form.addPair(form.createPair("media_category", mediaCategory));
0509     form.finish();
0510 
0511     qCDebug(DIGIKAM_WEBSERVICES_LOG) << form.formData();
0512 
0513     QUrl url(d->uploadUrl);
0514 
0515     QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
0516 
0517     QNetworkRequest request(url);
0518     request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
0519     d->reply = d->requestor->post(request, reqParams, form.formData());
0520 
0521     d->mediaUploadedPath = imgPath;
0522 
0523     d->state = Private::TW_UPLOADINIT;
0524 
0525     return true;
0526 }
0527 
0528 bool TwTalker::addPhotoAppend(const QString& mediaId, int segmentIndex)
0529 {
0530     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoAppend: ";
0531 
0532     static TwMPForm form;
0533 
0534     if (segmentIndex == 0)
0535     {
0536         form.addPair(form.createPair("command", "APPEND"));
0537         form.addPair(form.createPair("media_id", mediaId.toLatin1()));
0538         form.addFile(d->mediaUploadedPath, true);
0539         d->segmentIndex = form.numberOfChunks() - 1;
0540     }
0541     QByteArray data(form.formData());
0542     data.append(form.createPair("segment_index", QString::number(segmentIndex).toLatin1()));
0543     data.append(form.createPair("media", form.getChunk(segmentIndex)));
0544     data.append(form.border());
0545 
0546     QUrl url(d->uploadUrl);
0547     QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
0548 
0549     QNetworkRequest request(url);
0550     request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
0551     d->reply = d->requestor->post(request, reqParams, data);
0552 
0553     d->state = Private::TW_UPLOADAPPEND;
0554 
0555     // Reset form for later uploads
0556 
0557     if (segmentIndex == d->segmentIndex)
0558     {
0559         form.reset();
0560     }
0561 
0562     return true;
0563 }
0564 
0565 bool TwTalker::addPhotoFinalize(const QString& mediaId)
0566 {
0567     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "addPhotoFinalize: ";
0568 
0569     TwMPForm form;
0570 
0571     form.addPair(form.createPair("command", "FINALIZE"));
0572     form.addPair(form.createPair("media_id", mediaId.toLatin1()));
0573     form.finish();
0574 
0575     qCDebug(DIGIKAM_WEBSERVICES_LOG) << form.formData();
0576 
0577     QUrl url(d->uploadUrl);
0578 
0579     QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
0580 
0581     QNetworkRequest request(url);
0582     request.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
0583     d->reply = d->requestor->post(request, reqParams, form.formData());
0584 
0585     d->state = Private::TW_UPLOADFINALIZE;
0586 
0587     return true;
0588 }
0589 
0590 void TwTalker::getUserName()
0591 {
0592     /*
0593      * The endpoint below allows to get more than just account name (e.g. profile avatar, links to tweets posted, etc.)
0594      * Look at debug message printed to console for further ideas and exploration
0595      */
0596     QUrl url(QLatin1String("https://api.twitter.com/1.1/account/verify_credentials.json"));
0597 
0598     QNetworkRequest request(url);
0599     QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
0600 
0601     d->reply = d->requestor->get(request, reqParams);
0602     d->state = Private::TW_USERNAME;
0603 
0604     Q_EMIT signalBusy(true);
0605 }
0606 
0607 void TwTalker::createTweet(const QString& mediaId)
0608 {
0609     QUrl url = QUrl(QLatin1String("https://api.twitter.com/1.1/statuses/update.json"));
0610 
0611     QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
0612     reqParams << O0RequestParameter(QByteArray("status"), QByteArray(""));
0613     reqParams << O0RequestParameter(QByteArray("media_ids"), mediaId.toUtf8());
0614     QByteArray  postData                = O1::createQueryParameters(reqParams);
0615 
0616     QNetworkRequest request(url);
0617     request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String(O2_MIME_TYPE_XFORM));
0618     d->reply = d->requestor->post(request, reqParams, postData);
0619 
0620     d->state = Private::TW_CREATETWEET;
0621 }
0622 
0623 void TwTalker::slotCheckUploadStatus()
0624 {
0625     QUrl url = QUrl(d->uploadUrl);
0626 
0627     QList<O0RequestParameter> reqParams = QList<O0RequestParameter>();
0628     reqParams << O0RequestParameter(QByteArray("command"), QByteArray("STATUS"));
0629     reqParams << O0RequestParameter(QByteArray("media_id"), d->mediaId.toUtf8());
0630 
0631     QUrlQuery query;
0632     query.addQueryItem(QLatin1String("command"), QLatin1String("STATUS"));
0633     query.addQueryItem(QLatin1String("media_id"), d->mediaId);
0634 
0635     url.setQuery(query);
0636     qCDebug(DIGIKAM_WEBSERVICES_LOG) << url.toString();
0637 
0638     QNetworkRequest request(url);
0639     d->reply = d->requestor->get(request, reqParams);
0640     d->state = Private::TW_UPLOADSTATUSCHECK;
0641 }
0642 
0643 void TwTalker::slotFinished(QNetworkReply* reply)
0644 {
0645     if (reply != d->reply)
0646     {
0647         return;
0648     }
0649 
0650     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "TwTalker::slotFinished";
0651 
0652     d->reply = nullptr;
0653 
0654     if (reply->error() != QNetworkReply::NoError)
0655     {
0656         if (d->state != Private::TW_CREATEFOLDER)
0657         {
0658             qCDebug(DIGIKAM_WEBSERVICES_LOG) << reply->readAll();
0659             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "status code: " << reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
0660             Q_EMIT signalBusy(false);
0661             QMessageBox::critical(QApplication::activeWindow(),
0662                                   i18nc("@title:window", "Error"), reply->errorString());
0663 
0664             reply->deleteLater();
0665             return;
0666         }
0667     }
0668 
0669     QByteArray buffer = reply->readAll();
0670     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "status code: " << reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
0671     static int segmentIndex = 0;
0672 
0673     switch (d->state)
0674     {
0675         case Private::TW_LISTFOLDERS:
0676             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_LISTFOLDERS";
0677             parseResponseListFolders(buffer);
0678             break;
0679 
0680         case Private::TW_CREATEFOLDER:
0681             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_CREATEFOLDER";
0682             parseResponseCreateFolder(buffer);
0683             break;
0684 
0685         case Private::TW_ADDPHOTO:
0686             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_ADDPHOTO";
0687             parseResponseAddPhoto(buffer);
0688             break;
0689 
0690         case Private::TW_USERNAME:
0691             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_USERNAME";
0692             parseResponseUserName(buffer);
0693             break;
0694 
0695         case Private::TW_CREATETWEET:
0696             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_CREATETWEET";
0697             parseResponseCreateTweet(buffer);
0698             break;
0699 
0700         case Private::TW_UPLOADINIT:
0701             segmentIndex = 0;
0702             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADINIT";
0703             parseResponseAddPhotoInit(buffer);
0704             break;
0705 
0706         case Private::TW_UPLOADAPPEND:
0707             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADAPPEND (at index " << segmentIndex << ")";
0708             segmentIndex++;
0709             parseResponseAddPhotoAppend(buffer, segmentIndex);
0710             break;
0711 
0712         case Private::TW_UPLOADSTATUSCHECK:
0713             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADSTATUSCHECK";
0714             parseCheckUploadStatus(buffer);
0715             break;
0716 
0717         case Private::TW_UPLOADFINALIZE:
0718             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "In TW_UPLOADFINALIZE";
0719             parseResponseAddPhotoFinalize(buffer);
0720             break;
0721 
0722         default:
0723             break;
0724     }
0725 
0726     reply->deleteLater();
0727 }
0728 
0729 void TwTalker::parseResponseAddPhoto(const QByteArray& data)
0730 {
0731     QJsonParseError err;
0732     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0733     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhoto: " << doc;
0734 
0735     if (err.error != QJsonParseError::NoError)
0736     {
0737         Q_EMIT signalBusy(false);
0738         Q_EMIT signalAddPhotoFailed(i18n("Failed to upload photo"));
0739         return;
0740     }
0741 
0742     QJsonObject jsonObject = doc.object();
0743     QString mediaId        = jsonObject[QLatin1String("media_id_string")].toString();
0744     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "media id: " << mediaId;
0745 
0746     // We haven't Q_EMIT signalAddPhotoSucceeded() here yet, since we need to update the status first
0747 
0748     createTweet(mediaId);
0749 }
0750 
0751 void TwTalker::parseResponseAddPhotoInit(const QByteArray& data)
0752 {
0753     QJsonParseError err;
0754     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0755     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoInit: " << doc;
0756 
0757     if (err.error != QJsonParseError::NoError)
0758     {
0759         Q_EMIT signalBusy(false);
0760         Q_EMIT signalAddPhotoFailed(i18n("Failed to upload photo"));
0761         return;
0762     }
0763 
0764     QJsonObject jsonObject = doc.object();
0765     d->mediaId             = jsonObject[QLatin1String("media_id_string")].toString();
0766     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "media id: " << d->mediaId;
0767 
0768     // We haven't Q_EMIT signalAddPhotoSucceeded() here yet, since we need to update the status first
0769 
0770     addPhotoAppend(d->mediaId);
0771 }
0772 
0773 void TwTalker::parseResponseAddPhotoAppend(const QByteArray& /*data*/, int segmentIndex)
0774 {
0775     /* (Fev. 2019)
0776      * Currently, we don't parse data of response from addPhotoAppend, since the response is with HTTP 204
0777      * This is indeed an expected response code, because the response should be with an empty body.
0778      * However, in order to keep a compatible prototype of parseResponse methodes and reserve for future change,
0779      * we should keep argument const QByteArray& data.
0780      */
0781     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoAppend: ";
0782 
0783     if (segmentIndex <= d->segmentIndex)
0784     {
0785         addPhotoAppend(d->mediaId, segmentIndex);
0786     }
0787     else
0788     {
0789         addPhotoFinalize(d->mediaId);
0790     }
0791 }
0792 
0793 void TwTalker::parseResponseAddPhotoFinalize(const QByteArray& data)
0794 {
0795     QJsonParseError err;
0796     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0797     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhotoFinalize: " << doc;
0798 
0799     if (err.error != QJsonParseError::NoError)
0800     {
0801         Q_EMIT signalBusy(false);
0802         Q_EMIT signalAddPhotoFailed(i18n("Failed to upload photo"));
0803 
0804         return;
0805     }
0806 
0807     QJsonObject jsonObject    = doc.object();
0808     QJsonValue processingInfo = jsonObject[QLatin1String("processing_info")];
0809 
0810     if (processingInfo != QJsonValue::Undefined)
0811     {
0812         QString state = processingInfo.toObject()[QLatin1String("state")].toString();
0813         qCDebug(DIGIKAM_WEBSERVICES_LOG) << "state: " << state;
0814 
0815         if (state == QLatin1String("pending"))
0816         {
0817             QTimer::singleShot(processingInfo.toObject()[QLatin1String("check_after_secs")].toInt()*1000 /*msec*/,
0818                                this, SLOT(slotCheckUploadStatus()));
0819         }
0820     }
0821     else
0822     {
0823         // We haven't Q_EMIT signalAddPhotoSucceeded() here yet, since we need to update the status first
0824 
0825         createTweet(d->mediaId);
0826     }
0827 }
0828 
0829 void TwTalker::parseCheckUploadStatus(const QByteArray& data)
0830 {
0831     QJsonParseError err;
0832     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0833     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseCheckUploadStatus: " << doc;
0834 
0835     if (err.error != QJsonParseError::NoError)
0836     {
0837         Q_EMIT signalBusy(false);
0838         Q_EMIT signalAddPhotoFailed(i18n("Failed to upload photo"));
0839         return;
0840     }
0841 
0842     QJsonObject jsonObject     = doc.object();
0843     QJsonObject processingInfo = jsonObject[QLatin1String("processing_info")].toObject();
0844     QString state              = processingInfo[QLatin1String("state")].toString();
0845 
0846     if      (state == QLatin1String("in_progress"))
0847     {
0848         QTimer::singleShot(processingInfo[QLatin1String("check_after_secs")].toInt()*1000 /*msec*/, this, SLOT(slotCheckUploadStatus()));
0849     }
0850     else if (state == QLatin1String("failed"))
0851     {
0852         QJsonObject error = processingInfo[QLatin1String("error")].toObject();
0853         Q_EMIT signalBusy(false);
0854         Q_EMIT signalAddPhotoFailed(i18n("Failed to upload photo\n"
0855                                        "Code: %1, name: %2, message: %3",
0856                                        error[QLatin1String("code")].toInt(),
0857                                        error[QLatin1String("name")].toString(),
0858                                        error[QLatin1String("message")].toString()));
0859         return;
0860     }
0861     else // succeeded
0862     {
0863         // We haven't Q_EMIT signalAddPhotoSucceeded() here yet, since we need to update the status first
0864 
0865         createTweet(d->mediaId);
0866     }
0867 }
0868 
0869 void TwTalker::parseResponseUserName(const QByteArray& data)
0870 {
0871     QJsonParseError err;
0872     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0873     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseUserName: "<<doc;
0874 
0875     if (err.error != QJsonParseError::NoError)
0876     {
0877         Q_EMIT signalBusy(false);
0878         return;
0879     }
0880 
0881     QJsonObject obj     = doc.object();
0882     QString name        = obj[QLatin1String("name")].toString();
0883     QString screenName  = obj[QLatin1String("screen_name")].toString();
0884     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "user full name: "<<name;
0885     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "user screen name: @" << screenName;
0886 
0887     Q_EMIT signalBusy(false);
0888     Q_EMIT signalSetUserName(QString::fromLatin1("%1 (@%2)").arg(name).arg(screenName));
0889 }
0890 
0891 void TwTalker::parseResponseCreateTweet(const QByteArray& data)
0892 {
0893     QJsonParseError err;
0894     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0895     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateTweet: " << doc;
0896 
0897     if (err.error != QJsonParseError::NoError)
0898     {
0899         Q_EMIT signalBusy(false);
0900         Q_EMIT signalAddPhotoFailed(i18n("Failed to create tweet for photo uploaded"));
0901 
0902         return;
0903     }
0904 
0905     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tweet posted successfully!";
0906     Q_EMIT signalBusy(false);
0907     Q_EMIT signalAddPhotoSucceeded();
0908 }
0909 
0910 void TwTalker::parseResponseListFolders(const QByteArray& data)
0911 {
0912     QJsonParseError err;
0913     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0914 
0915     if (err.error != QJsonParseError::NoError)
0916     {
0917         Q_EMIT signalBusy(false);
0918         Q_EMIT signalListAlbumsFailed(i18n("Failed to list folders"));
0919         return;
0920     }
0921 
0922     QJsonObject jsonObject = doc.object();
0923 
0924     //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Json: " << doc;
0925 
0926     QJsonArray jsonArray   = jsonObject[QLatin1String("value")].toArray();
0927 
0928     //qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Json response: " << jsonArray;
0929 
0930     QList<QPair<QString, QString> > list;
0931     list.append(qMakePair(QLatin1String(""), QLatin1String("root")));
0932 
0933     Q_FOREACH (const QJsonValue& value, jsonArray)
0934     {
0935         QString path;
0936         QString folderName;
0937         QJsonObject folder;
0938 
0939         QJsonObject obj = value.toObject();
0940         folder          = obj[QLatin1String("folder")].toObject();
0941 
0942         if (!folder.isEmpty())
0943         {
0944             folderName    = obj[QLatin1String("name")].toString();
0945             path          = QLatin1Char('/') + folderName;
0946             qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Folder Name is" << folderName;
0947             list.append(qMakePair(path, folderName));
0948         }
0949     }
0950 
0951     Q_EMIT signalBusy(false);
0952     Q_EMIT signalListAlbumsDone(list);
0953 }
0954 
0955 void TwTalker::parseResponseCreateFolder(const QByteArray& data)
0956 {
0957     QJsonDocument doc1      = QJsonDocument::fromJson(data);
0958     QJsonObject jsonObject = doc1.object();
0959     bool fail              = jsonObject.contains(QLatin1String("error"));
0960 
0961     Q_EMIT signalBusy(false);
0962 
0963     if (fail)
0964     {
0965       QJsonParseError err;
0966       QJsonDocument doc2 = QJsonDocument::fromJson(data, &err);
0967       qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateFolder ERROR: " << doc2;
0968 
0969       Q_EMIT signalCreateFolderFailed(jsonObject[QLatin1String("error_summary")].toString());
0970     }
0971     else
0972     {
0973         Q_EMIT signalCreateFolderSucceeded();
0974     }
0975 }
0976 
0977 } // namespace DigikamGenericTwitterPlugin
0978 
0979 #include "moc_twittertalker.cpp"