File indexing completed on 2025-01-05 03:53:31

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2012-02-02
0007  * Description : a tool to export items to ImageShack web service
0008  *
0009  * SPDX-FileCopyrightText: 2012      by Dodon Victor <dodonvictor at gmail dot com>
0010  * SPDX-FileCopyrightText: 2013-2018 by Caulier Gilles <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "imageshacktalker.h"
0017 
0018 // Qt includes
0019 
0020 #include <QJsonDocument>
0021 #include <QJsonParseError>
0022 #include <QJsonObject>
0023 #include <QJsonValue>
0024 #include <QJsonArray>
0025 #include <QDomDocument>
0026 #include <QDomElement>
0027 #include <QDomNode>
0028 #include <QXmlStreamReader>
0029 #include <QApplication>
0030 #include <QMimeDatabase>
0031 #include <QMimeType>
0032 #include <QUrlQuery>
0033 
0034 // KDE includes
0035 
0036 #include <klocalizedstring.h>
0037 
0038 // Local includes
0039 
0040 #include "digikam_debug.h"
0041 #include "digikam_version.h"
0042 #include "imageshacksession.h"
0043 #include "imageshackmpform.h"
0044 #include "networkmanager.h"
0045 
0046 using namespace Digikam;
0047 
0048 namespace DigikamGenericImageShackPlugin
0049 {
0050 
0051 class Q_DECL_HIDDEN ImageShackTalker::Private
0052 {
0053 public:
0054 
0055     enum State
0056     {
0057         IMGHCK_AUTHENTICATING,
0058         IMGHCK_DONOTHING,
0059         IMGHCK_GETGALLERIES,
0060         IMGHCK_ADDPHOTO,
0061         IMGHCK_ADDVIDEO,
0062         IMGHCK_ADDPHOTOGALLERY
0063     };
0064 
0065 public:
0066 
0067     explicit Private()
0068     {
0069         userAgent       = QString::fromLatin1("digiKam-ImageShack/%1").arg(digiKamVersion());
0070         photoApiUrl     = QUrl(QLatin1String("https://api.imageshack.com/v2/images"));
0071         videoApiUrl     = QUrl(QLatin1String("http://render.imageshack.us/upload_api.php"));     // krazy:exclude=insecurenet
0072         loginApiUrl     = QUrl(QLatin1String("https://my.imageshack.us/setlogin.php"));
0073         galleryUrl      = QUrl(QLatin1String("https://www.imageshack.us/gallery_api.php"));
0074         appKey          = QLatin1String("YPZ2L9WV2de2a1e08e8fbddfbcc1c5c39f94f92a");
0075         session         = nullptr;
0076         loginInProgress = false;
0077         reply           = nullptr;
0078         state           = IMGHCK_DONOTHING;
0079         netMngr         = nullptr;
0080     }
0081 
0082 public:
0083 
0084     ImageShackSession*     session;
0085 
0086     QString                userAgent;
0087     QUrl                   photoApiUrl;
0088     QUrl                   videoApiUrl;
0089     QUrl                   loginApiUrl;
0090     QUrl                   galleryUrl;
0091     QString                appKey;
0092 
0093     bool                   loginInProgress;
0094 
0095     QNetworkAccessManager* netMngr;
0096 
0097     QNetworkReply*         reply;
0098 
0099     State                  state;
0100 };
0101 
0102 ImageShackTalker::ImageShackTalker(ImageShackSession* const session)
0103     : d(new Private)
0104 {
0105     d->session = session;
0106     d->netMngr = NetworkManager::instance()->getNetworkManager(this);
0107 
0108     connect(d->netMngr, SIGNAL(finished(QNetworkReply*)),
0109             this, SLOT(slotFinished(QNetworkReply*)));
0110 }
0111 
0112 ImageShackTalker::~ImageShackTalker()
0113 {
0114     if (d->reply)
0115     {
0116         d->reply->abort();
0117     }
0118 
0119     delete d;
0120 }
0121 
0122 void ImageShackTalker::cancel()
0123 {
0124     if (d->reply)
0125     {
0126         d->reply->abort();
0127         d->reply = nullptr;
0128     }
0129 
0130     Q_EMIT signalBusy(false);
0131 }
0132 
0133 QString ImageShackTalker::getCallString(QMap<QString, QString>& args) const
0134 {
0135     QString result;
0136 
0137     for (QMap<QString, QString>::const_iterator it = args.constBegin();
0138          it != args.constEnd();
0139          ++it)
0140     {
0141         if (!result.isEmpty())
0142         {
0143             result.append(QLatin1String("&"));
0144         }
0145 
0146         result.append(it.key());
0147         result.append(QLatin1String("="));
0148         result.append(it.value());
0149     }
0150 
0151     return result;
0152 }
0153 
0154 void ImageShackTalker::slotFinished(QNetworkReply* reply)
0155 {
0156     if (reply != d->reply)
0157     {
0158         return;
0159     }
0160 
0161     d->reply = nullptr;
0162 
0163     if (reply->error() != QNetworkReply::NoError)
0164     {
0165         if      (d->state == Private::IMGHCK_AUTHENTICATING)
0166         {
0167             checkRegistrationCodeDone(reply->error(), reply->errorString());
0168             Q_EMIT signalBusy(false);
0169         }
0170         else if (d->state == Private::IMGHCK_GETGALLERIES)
0171         {
0172             Q_EMIT signalBusy(false);
0173             Q_EMIT signalGetGalleriesDone(reply->error(), reply->errorString());
0174         }
0175         else if (d->state == Private::IMGHCK_ADDPHOTO || d->state == Private::IMGHCK_ADDPHOTOGALLERY)
0176         {
0177             Q_EMIT signalBusy(false);
0178             Q_EMIT signalAddPhotoDone(reply->error(), reply->errorString());
0179         }
0180 
0181         d->state = Private::IMGHCK_DONOTHING;
0182         reply->deleteLater();
0183         return;
0184     }
0185 
0186     QByteArray buffer = reply->readAll();
0187 
0188     switch (d->state)
0189     {
0190         case Private::IMGHCK_AUTHENTICATING:
0191             parseAccessToken(buffer);
0192             break;
0193 
0194         case Private::IMGHCK_ADDPHOTOGALLERY:
0195             parseAddPhotoToGalleryDone(buffer);
0196             break;
0197 
0198         case Private::IMGHCK_ADDVIDEO:
0199         case Private::IMGHCK_ADDPHOTO:
0200             parseUploadPhotoDone(buffer);
0201             break;
0202 
0203         case Private::IMGHCK_GETGALLERIES:
0204             parseGetGalleries(buffer);
0205             break;
0206 
0207         default:
0208             break;
0209     }
0210 
0211     reply->deleteLater();
0212 }
0213 
0214 void ImageShackTalker::authenticate()
0215 {
0216     if (d->reply)
0217     {
0218         d->reply->abort();
0219         d->reply = nullptr;
0220     }
0221 
0222     Q_EMIT signalBusy(true);
0223     Q_EMIT signalJobInProgress(1, 4, i18n("Authenticating the user"));
0224 
0225     QUrl url(QLatin1String("https://api.imageshack.com/v2/user/login"));
0226     QUrlQuery q(url);
0227     q.addQueryItem(QLatin1String("user"), d->session->email());
0228     q.addQueryItem(QLatin1String("password"), d->session->password());
0229     url.setQuery(q);
0230 
0231     QNetworkRequest netRequest(url);
0232     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
0233 
0234     d->reply = d->netMngr->post(netRequest, QByteArray());
0235 
0236     d->state = Private::IMGHCK_AUTHENTICATING;
0237 }
0238 
0239 void ImageShackTalker::getGalleries()
0240 {
0241     if (d->reply)
0242     {
0243         d->reply->abort();
0244         d->reply = nullptr;
0245     }
0246 
0247     Q_EMIT signalBusy(true);
0248     Q_EMIT signalJobInProgress(3, 4, i18n("Getting galleries from server"));
0249 
0250     QUrl gUrl(d->galleryUrl);
0251 
0252     QUrlQuery q(gUrl);
0253     q.addQueryItem(QLatin1String("action"), QLatin1String("gallery_list"));
0254     q.addQueryItem(QLatin1String("user"), d->session->username());
0255     gUrl.setQuery(q);
0256 
0257     d->reply = d->netMngr->get(QNetworkRequest(gUrl));
0258 
0259     d->state = Private::IMGHCK_GETGALLERIES;
0260 }
0261 
0262 void ImageShackTalker::checkRegistrationCodeDone(int errCode, const QString& errMsg)
0263 {
0264     Q_EMIT signalBusy(false);
0265     Q_EMIT signalLoginDone(errCode, errMsg);
0266     d->loginInProgress = false;
0267 }
0268 
0269 void ImageShackTalker::parseAccessToken(const QByteArray &data)
0270 {
0271     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Data received is "<< data;
0272 
0273     QJsonParseError err;
0274     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0275 
0276     if (err.error != QJsonParseError::NoError)
0277     {
0278         Q_EMIT signalBusy(false);
0279         return;
0280     }
0281 
0282     QJsonObject jsonObject = doc.object();
0283 
0284     if (jsonObject[QLatin1String("success")].toBool())
0285     {
0286         d->session->setLoggedIn(true);
0287         QJsonObject obj          = jsonObject[QLatin1String("result")].toObject();
0288         d->session->setUsername(obj[QLatin1String("username")].toString());
0289         d->session->setEmail(obj[QLatin1String("email")].toString());
0290         d->session->setAuthToken(obj[QLatin1String("auth_token")].toString());
0291         checkRegistrationCodeDone(0,QLatin1String(""));
0292     }
0293     else
0294     {
0295         d->session->setLoggedIn(false);
0296         QJsonObject obj          = jsonObject[QLatin1String("error")].toObject();
0297         checkRegistrationCodeDone(obj[QLatin1String("error_code")].toInt(), obj[QLatin1String("error_message")].toString());
0298     }
0299 }
0300 
0301 void ImageShackTalker::parseGetGalleries(const QByteArray &data)
0302 {
0303     QDomDocument document;
0304 
0305     if (!document.setContent(data))
0306     {
0307         return;
0308     }
0309 
0310     QDomElement rootElem  = document.documentElement();
0311     QDomNodeList children = rootElem.childNodes();
0312 
0313     QStringList gTexts;
0314     QStringList gNames;
0315 
0316     for (int i = 0 ; i < children.size() ; ++i)
0317     {
0318         QDomElement e = children.at(i).toElement();
0319 
0320         if (e.tagName() == QLatin1String("gallery"))
0321         {
0322             QDomElement nameElem   = e.firstChildElement(QLatin1String("name"));
0323             QDomElement titleElem  = e.firstChildElement(QLatin1String("title"));
0324             QDomElement serverElem = e.firstChildElement(QLatin1String("server"));
0325 
0326             if (!nameElem.isNull())
0327             {
0328                 QString fmt;
0329                 fmt          = nameElem.firstChild().toText().data();
0330                 gNames << nameElem.firstChild().toText().data();
0331                 gTexts << titleElem.firstChild().toText().data();
0332             }
0333         }
0334     }
0335 
0336     d->state = Private::IMGHCK_DONOTHING;
0337 
0338     Q_EMIT signalUpdateGalleries(gTexts, gNames);
0339     Q_EMIT signalGetGalleriesDone(0, i18n("Successfully retrieved galleries"));
0340 }
0341 
0342 void ImageShackTalker::authenticationDone(int errCode, const QString& errMsg)
0343 {
0344     if (errCode)
0345     {
0346         d->session->logOut();
0347     }
0348 
0349     Q_EMIT signalBusy(false);
0350     Q_EMIT signalLoginDone(errCode, errMsg);
0351     d->loginInProgress = false;
0352 }
0353 
0354 void ImageShackTalker::logOut()
0355 {
0356     d->session->logOut();
0357     d->loginInProgress = false;
0358 }
0359 
0360 void ImageShackTalker::cancelLogIn()
0361 {
0362     logOut();
0363     Q_EMIT signalLoginDone(-1, QLatin1String("Canceled by the user!"));
0364 }
0365 
0366 QString ImageShackTalker::mimeType(const QString& path) const
0367 {
0368     QMimeDatabase db;
0369     QMimeType ptr = db.mimeTypeForUrl(QUrl::fromLocalFile(path));
0370 
0371     return ptr.name();
0372 }
0373 
0374 void ImageShackTalker::uploadItem(const QString& path, const QMap<QString, QString>& opts)
0375 {
0376     if (d->reply)
0377     {
0378         d->reply->abort();
0379         d->reply = nullptr;
0380     }
0381 
0382     Q_EMIT signalBusy(true);
0383     QMap<QString, QString> args;
0384     args[QLatin1String("key")]        = d->appKey;
0385     args[QLatin1String("fileupload")] = QUrl(path).fileName();
0386 
0387     ImageShackMPForm form;
0388 
0389     for (QMap<QString, QString>::const_iterator it = opts.constBegin();
0390          it != opts.constEnd();
0391          ++it)
0392     {
0393         form.addPair(it.key(), it.value());
0394     }
0395 
0396     for (QMap<QString, QString>::const_iterator it = args.constBegin();
0397          it != args.constEnd();
0398          ++it)
0399     {
0400         form.addPair(it.key(), it.value());
0401     }
0402 
0403     if (!form.addFile(QUrl(path).fileName(), path))
0404     {
0405         Q_EMIT signalBusy(false);
0406         return;
0407     }
0408 
0409     form.finish();
0410 
0411     QUrl uploadUrl = QUrl(d->photoApiUrl);
0412     d->state        = Private::IMGHCK_ADDPHOTO;
0413 
0414     QNetworkRequest netRequest(uploadUrl);
0415     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
0416     netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent);
0417 
0418     d->reply = d->netMngr->post(netRequest, form.formData());
0419 
0420     //uploadItemToGallery(path, QLatin1String(""), opts);
0421 }
0422 
0423 void ImageShackTalker::uploadItemToGallery(const QString& path,
0424                                            const QString& /*gallery*/,
0425                                            const QMap<QString, QString>& opts)
0426 {
0427     if (d->reply)
0428     {
0429         d->reply->abort();
0430         d->reply = nullptr;
0431     }
0432 
0433     Q_EMIT signalBusy(true);
0434     QMap<QString, QString> args;
0435     args[QLatin1String("key")]        = d->appKey;
0436     args[QLatin1String("fileupload")] = QUrl(path).fileName();
0437 
0438     ImageShackMPForm form;
0439 
0440     for (QMap<QString, QString>::const_iterator it = opts.constBegin();
0441          it != opts.constEnd();
0442          ++it)
0443     {
0444         form.addPair(it.key(), it.value());
0445     }
0446 
0447     for (QMap<QString, QString>::const_iterator it = args.constBegin();
0448          it != args.constEnd();
0449          ++it)
0450     {
0451         form.addPair(it.key(), it.value());
0452     }
0453 
0454     if (!form.addFile(QUrl(path).fileName(), path))
0455     {
0456         Q_EMIT signalBusy(false);
0457         return;
0458     }
0459 
0460     form.finish();
0461 
0462     // Check where to upload
0463 
0464     QString mime        = mimeType(path);
0465 
0466     QUrl uploadUrl;
0467 
0468     uploadUrl           = QUrl(d->photoApiUrl);
0469     d->state            = Private::IMGHCK_ADDPHOTO;
0470 
0471     QNetworkRequest netRequest(uploadUrl);
0472     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
0473     netRequest.setHeader(QNetworkRequest::UserAgentHeader, d->userAgent);
0474 
0475     d->reply = d->netMngr->post(netRequest, form.formData());
0476 }
0477 
0478 int ImageShackTalker::parseErrorResponse(const QDomElement& elem, QString& errMsg) const
0479 {
0480     int errCode = -1;
0481     QString err_code;
0482 
0483     for (QDomNode node = elem.firstChild();
0484          !node.isNull();
0485          node = node.nextSibling())
0486     {
0487         if (!node.isElement())
0488         {
0489             continue;
0490         }
0491 
0492         QDomElement e = node.toElement();
0493 
0494         if (e.tagName() == QLatin1String("error"))
0495         {
0496             err_code = e.attributeNode(QLatin1String("id")).value();
0497             errMsg   = e.text();
0498         }
0499     }
0500 
0501     if (err_code == QLatin1String("file_too_big"))
0502     {
0503         errCode = 501;
0504     }
0505     else
0506     {
0507         errCode = 502;
0508     }
0509 
0510     return errCode;
0511 }
0512 
0513 void ImageShackTalker::parseUploadPhotoDone(const QByteArray& data)
0514 {
0515     qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ParseUploadPhotoDone data is "<<data;
0516 
0517     QJsonParseError err;
0518     QJsonDocument doc = QJsonDocument::fromJson(data, &err);
0519 
0520     if (err.error != QJsonParseError::NoError)
0521     {
0522         Q_EMIT signalBusy(false);
0523         return;
0524     }
0525 
0526     QJsonObject jsonObject = doc.object();
0527 
0528     if ((d->state == Private::IMGHCK_ADDPHOTO) ||
0529         (d->state == Private::IMGHCK_ADDVIDEO) ||
0530         (d->state == Private::IMGHCK_ADDPHOTOGALLERY))
0531     {
0532         if (jsonObject[QLatin1String("success")].toBool())
0533         {
0534             Q_EMIT signalBusy(false);
0535             Q_EMIT signalAddPhotoDone(0,QLatin1String(""));
0536         }
0537         else
0538         {
0539             QJsonObject obj = jsonObject[QLatin1String("error")].toObject();
0540             Q_EMIT signalAddPhotoDone(obj[QLatin1String("error_code")].toInt(), obj[QLatin1String("error_message")].toString());
0541             Q_EMIT signalBusy(false);
0542         }
0543     }
0544 }
0545 
0546 void ImageShackTalker::parseAddPhotoToGalleryDone(const QByteArray& data)
0547 {
0548     //int errCode = -1;
0549     QString errMsg = QLatin1String("");
0550     QDomDocument domDoc(QLatin1String("galleryXML"));
0551 
0552     qCDebug(DIGIKAM_WEBSERVICES_LOG) << data;
0553 
0554     if (!domDoc.setContent(data))
0555     {
0556         return;
0557     }
0558 
0559     QDomElement rootElem = domDoc.documentElement();
0560 
0561     if (rootElem.isNull() || (rootElem.tagName() != QLatin1String("gallery")))
0562     {
0563         // TODO error checking
0564     }
0565     else
0566     {
0567         Q_EMIT signalBusy(false);
0568         Q_EMIT signalAddPhotoDone(0, QLatin1String(""));
0569     }
0570 }
0571 
0572 } // namespace DigikamGenericImageShackPlugin
0573 
0574 #include "moc_imageshacktalker.cpp"