File indexing completed on 2024-04-28 04:55:47

0001 /*
0002     This file is part of Choqok, the KDE micro-blogging client
0003 
0004     SPDX-FileCopyrightText: 2013-2014 Andrea Scarpino <scarpino@kde.org>
0005     SPDX-FileCopyrightText: 2008-2012 Mehrdad Momeny <mehrdad.momeny@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0008 */
0009 
0010 #include "pumpiomicroblog.h"
0011 
0012 #include <QAction>
0013 #include <QFile>
0014 #include <QJsonDocument>
0015 #include <QMenu>
0016 #include <QMimeDatabase>
0017 #include <QTextDocument>
0018 
0019 #include <KIO/StoredTransferJob>
0020 #include <KPluginFactory>
0021 
0022 #include "accountmanager.h"
0023 #include "application.h"
0024 #include "choqokbehaviorsettings.h"
0025 #include "notifymanager.h"
0026 
0027 #include "pumpioaccount.h"
0028 #include "pumpiocomposerwidget.h"
0029 #include "pumpiodebug.h"
0030 #include "pumpioeditaccountwidget.h"
0031 #include "pumpiomessagedialog.h"
0032 #include "pumpiomicroblogwidget.h"
0033 #include "pumpiopost.h"
0034 #include "pumpiopostwidget.h"
0035 
0036 class PumpIOMicroBlog::Private
0037 {
0038 public:
0039     Private(): countOfTimelinesToSave(0)
0040     {}
0041     int countOfTimelinesToSave;
0042 };
0043 
0044 K_PLUGIN_CLASS_WITH_JSON(PumpIOMicroBlog, "choqok_pumpio.json")
0045 
0046 const QString PumpIOMicroBlog::inboxActivity(QLatin1String("/api/user/%1/inbox"));
0047 const QString PumpIOMicroBlog::outboxActivity(QLatin1String("/api/user/%1/feed"));
0048 
0049 const QString PumpIOMicroBlog::PublicCollection(QLatin1String("http://activityschema.org/collection/public"));
0050 
0051 PumpIOMicroBlog::PumpIOMicroBlog(QObject *parent, const QVariantList &args):
0052     MicroBlog(QStringLiteral("Pump.IO") , parent), d(new Private)
0053 {
0054     Q_UNUSED(args)
0055     setServiceName(QLatin1String("Pump.io"));
0056     setServiceHomepageUrl(QLatin1String("http://pump.io"));
0057     QStringList timelineNames;
0058     timelineNames << QLatin1String("Activity") << QLatin1String("Favorites") << QLatin1String("Inbox") << QLatin1String("Outbox");
0059     setTimelineNames(timelineNames);
0060     setTimelinesInfo();
0061 }
0062 
0063 PumpIOMicroBlog::~PumpIOMicroBlog()
0064 {
0065     qDeleteAll(m_timelinesInfos);
0066     delete d;
0067 }
0068 
0069 void PumpIOMicroBlog::abortAllJobs(Choqok::Account *theAccount)
0070 {
0071     for (KJob *job: m_accountJobs.keys(theAccount)) {
0072         job->kill(KJob::EmitResult);
0073     }
0074 }
0075 
0076 void PumpIOMicroBlog::abortCreatePost(Choqok::Account *theAccount, Choqok::Post *post)
0077 {
0078     if (m_createPostJobs.isEmpty()) {
0079         return;
0080     }
0081     if (post) {
0082         m_createPostJobs.key(post)->kill(KJob::EmitResult);
0083         return;
0084     }
0085 
0086     for (KJob *job: m_createPostJobs.keys()) {
0087         if (m_accountJobs[job] == theAccount) {
0088             job->kill(KJob::EmitResult);
0089         }
0090     }
0091 }
0092 
0093 void PumpIOMicroBlog::aboutToUnload()
0094 {
0095     for (Choqok::Account *acc: Choqok::AccountManager::self()->accounts()) {
0096         if (acc->microblog() == this) {
0097             d->countOfTimelinesToSave += acc->timelineNames().count();
0098         }
0099     }
0100     Q_EMIT saveTimelines();
0101 }
0102 
0103 QMenu *PumpIOMicroBlog::createActionsMenu(Choqok::Account *theAccount, QWidget *parent)
0104 {
0105     QMenu *menu = MicroBlog::createActionsMenu(theAccount, parent);
0106 
0107     QAction *directMessge = new QAction(QIcon::fromTheme(QLatin1String("mail-message-new")), i18n("Send Private Message..."), menu);
0108     directMessge->setData(theAccount->alias());
0109     connect(directMessge, &QAction::triggered, this, &PumpIOMicroBlog::showDirectMessageDialog);
0110     menu->addAction(directMessge);
0111 
0112     return menu;
0113 }
0114 
0115 Choqok::UI::MicroBlogWidget *PumpIOMicroBlog::createMicroBlogWidget(Choqok::Account *account, QWidget *parent)
0116 {
0117     return new PumpIOMicroBlogWidget(account, parent);
0118 }
0119 
0120 Choqok::UI::ComposerWidget *PumpIOMicroBlog::createComposerWidget(Choqok::Account *account, QWidget *parent)
0121 {
0122     return new PumpIOComposerWidget(account, parent);
0123 }
0124 
0125 ChoqokEditAccountWidget *PumpIOMicroBlog::createEditAccountWidget(Choqok::Account *account,
0126         QWidget *parent)
0127 {
0128     PumpIOAccount *acc = qobject_cast<PumpIOAccount * >(account);
0129     if (acc || !account) {
0130         return new PumpIOEditAccountWidget(this, acc, parent);
0131     } else {
0132         qCDebug(CHOQOK) << "Account passed here was not a valid PumpIOAccount!";
0133         return nullptr;
0134     }
0135 }
0136 
0137 Choqok::Account *PumpIOMicroBlog::createNewAccount(const QString &alias)
0138 {
0139     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(
0140                              Choqok::AccountManager::self()->findAccount(alias));
0141     if (!acc) {
0142         return new PumpIOAccount(this, alias);
0143     } else {
0144         qCDebug(CHOQOK) << "Cannot create a new PumpIOAccount!";
0145         return nullptr;
0146     }
0147 }
0148 
0149 void PumpIOMicroBlog::createPost(Choqok::Account *theAccount, Choqok::Post *post)
0150 {
0151     QVariantList to;
0152     QVariantMap thePublic;
0153     thePublic.insert(QLatin1String("objectType"), QLatin1String("collection"));
0154     thePublic.insert(QLatin1String("id"), PumpIOMicroBlog::PublicCollection);
0155     to.append(thePublic);
0156 
0157     createPost(theAccount, post, to);
0158 }
0159 
0160 void PumpIOMicroBlog::createPost(Choqok::Account *theAccount, Choqok::Post *post,
0161                                  const QVariantList &to, const QVariantList &cc)
0162 {
0163     if (!post || post->content.isEmpty()) {
0164         qCDebug(CHOQOK) << "ERROR: Status text is empty!";
0165         Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::OtherError,
0166                          i18n("Creating the new post failed. Text is empty."), MicroBlog::Critical);
0167         return;
0168     }
0169 
0170     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0171     if (acc) {
0172         QVariantMap object;
0173         if (!post->postId.isEmpty()) {
0174             object.insert(QLatin1String("id"), post->postId);
0175         }
0176         if (post->type.isEmpty()) {
0177             post->type = QLatin1String("note");
0178         }
0179         object.insert(QLatin1String("objectType"), post->type);
0180         //Convert URLs to href form
0181         post->content.replace(QRegExp(QLatin1String("((?:https?|ftp)://\\S+)")), QLatin1String("<a href=\"\\1\">\\1</a>"));
0182         object.insert(QLatin1String("content"), post->content);
0183 
0184         QVariantMap item;
0185         item.insert(QLatin1String("verb"), QLatin1String("post"));
0186         item.insert(QLatin1String("object"), object);
0187         item.insert(QLatin1String("to"), to);
0188         item.insert(QLatin1String("cc"), cc);
0189 
0190         const QByteArray data = QJsonDocument::fromVariant(item).toJson();
0191 
0192         QUrl url(acc->host());
0193         url = url.adjusted(QUrl::StripTrailingSlash);
0194         url.setPath(url.path() + outboxActivity.arg(acc->username()));
0195         KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo);
0196         job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json"));
0197         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation));
0198         if (!job) {
0199             qCDebug(CHOQOK) << "Cannot create an http POST request!";
0200             return;
0201         }
0202         m_accountJobs[job] = acc;
0203         m_createPostJobs[job] = post;
0204         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotCreatePost);
0205         job->start();
0206     } else {
0207         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0208     }
0209 }
0210 
0211 void PumpIOMicroBlog::createReply(Choqok::Account *theAccount, PumpIOPost *post)
0212 {
0213     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0214     if (acc) {
0215         post->type = QLatin1String("comment");
0216 
0217         QVariantMap object;
0218         object.insert(QLatin1String("objectType"), post->type);
0219         //Convert URLs to href form
0220         post->content.replace(QRegExp(QLatin1String("((?:https?|ftp)://\\S+)")), QLatin1String("<a href=\"\\1\">\\1</a>"));
0221         object.insert(QLatin1String("content"), QUrl::toPercentEncoding(post->content));
0222 
0223         if (!post->replyToPostId.isEmpty()) {
0224             QVariantMap inReplyTo;
0225             inReplyTo.insert(QLatin1String("id"), post->replyToPostId);
0226             inReplyTo.insert(QLatin1String("objectType"), post->replyToObjectType);
0227             object.insert(QLatin1String("inReplyTo"), inReplyTo);
0228         }
0229 
0230         QVariantMap item;
0231         item.insert(QLatin1String("verb"), QLatin1String("post"));
0232         item.insert(QLatin1String("object"), object);
0233 
0234         const QByteArray data = QJsonDocument::fromVariant(item).toJson();
0235 
0236         QUrl url(acc->host());
0237         url = url.adjusted(QUrl::StripTrailingSlash);
0238         url.setPath(url.path() + outboxActivity.arg(acc->username()));
0239         KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo);
0240         job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json"));
0241         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation));
0242         if (!job) {
0243             qCDebug(CHOQOK) << "Cannot create an http POST request!";
0244             return;
0245         }
0246         m_accountJobs[job] = acc;
0247         m_createPostJobs[job] = post;
0248         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotCreatePost);
0249         job->start();
0250     } else {
0251         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0252     }
0253 }
0254 
0255 void PumpIOMicroBlog::createPostWithMedia(Choqok::Account *theAccount, Choqok::Post *post,
0256         const QString &filePath)
0257 {
0258     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0259     if (acc) {
0260         QFile media(filePath);
0261         QByteArray data;
0262         if (media.open(QIODevice::ReadOnly)) {
0263             data = media.readAll();
0264             media.close();
0265         } else {
0266             qCDebug(CHOQOK) << "Cannot read the file";
0267             return;
0268         }
0269 
0270         const QMimeDatabase db;
0271         const QMimeType mimetype = db.mimeTypeForFileNameAndData(filePath, data);
0272         const QString mime = mimetype.name();
0273         if (mime == QLatin1String("application/octet-stream")) {
0274             qCDebug(CHOQOK) << "Cannot retrieve file mimetype";
0275             return;
0276         }
0277 
0278         QUrl url(acc->host());
0279         url = url.adjusted(QUrl::StripTrailingSlash);
0280         url.setPath(url.path() + QStringLiteral("/api/user/%1/uploads").arg(acc->username()));
0281         KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo);
0282         job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: ") + mime);
0283         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation));
0284         if (!job) {
0285             qCDebug(CHOQOK) << "Cannot create an http POST request!";
0286             return;
0287         }
0288         m_accountJobs[job] = acc;
0289         m_uploadJobs[job] = post;
0290         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotUpload);
0291         job->start();
0292     } else {
0293         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0294     }
0295 }
0296 
0297 Choqok::UI::PostWidget *PumpIOMicroBlog::createPostWidget(Choqok::Account *account,
0298         Choqok::Post *post,
0299         QWidget *parent)
0300 {
0301     return new PumpIOPostWidget(account, post, parent);
0302 }
0303 
0304 void PumpIOMicroBlog::fetchPost(Choqok::Account *theAccount, Choqok::Post *post)
0305 {
0306     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0307     if (acc) {
0308         if (!post->link.toDisplayString().startsWith(acc->host())) {
0309             qCDebug(CHOQOK) << "You can only fetch posts from your host!";
0310             return;
0311         }
0312         QUrl url(post->link);
0313 
0314         KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo);
0315         if (!job) {
0316             qCDebug(CHOQOK) << "Cannot create an http GET request!";
0317             return;
0318         }
0319         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation));
0320         m_accountJobs[job] = acc;
0321         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotFetchPost);
0322         job->start();
0323     } else {
0324         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0325     }
0326 }
0327 
0328 void PumpIOMicroBlog::removePost(Choqok::Account *theAccount, Choqok::Post *post)
0329 {
0330     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0331     if (acc) {
0332         QVariantMap object;
0333         object.insert(QLatin1String("id"), post->postId);
0334         object.insert(QLatin1String("objectType"), post->type);
0335 
0336         QVariantMap item;
0337         item.insert(QLatin1String("verb"), QLatin1String("delete"));
0338         item.insert(QLatin1String("object"), object);
0339 
0340         const QByteArray data = QJsonDocument::fromVariant(item).toJson();
0341 
0342         QUrl url(acc->host());
0343         url = url.adjusted(QUrl::StripTrailingSlash);
0344         url.setPath(url.path() + outboxActivity.arg(acc->username()));
0345         KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo);
0346         job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json"));
0347         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation));
0348         if (!job) {
0349             qCDebug(CHOQOK) << "Cannot create an http POST request!";
0350             return;
0351         }
0352         m_accountJobs[job] = acc;
0353         m_removePostJobs[job] = post;
0354         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotRemovePost);
0355         job->start();
0356     } else {
0357         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0358     }
0359 }
0360 
0361 QList< Choqok::Post * > PumpIOMicroBlog::loadTimeline(Choqok::Account *account,
0362         const QString &timelineName)
0363 {
0364     QList< Choqok::Post * > list;
0365     const QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(),
0366                              timelineName);
0367     const KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation);
0368     const QStringList tmpList = postsBackup.groupList();
0369 
0370     // don't load old archives
0371     if (tmpList.isEmpty() || !(QDateTime::fromString(tmpList.first()).isValid())) {
0372         return list;
0373     }
0374 
0375     QList<QDateTime> groupList;
0376     for (const QString &str: tmpList) {
0377         groupList.append(QDateTime::fromString(str));
0378     }
0379     std::sort(groupList.begin(), groupList.end());
0380     PumpIOPost *st;
0381     for (const QDateTime &datetime: groupList) {
0382         st = new PumpIOPost;
0383         KConfigGroup grp(&postsBackup, datetime.toString());
0384         st->creationDateTime = grp.readEntry("creationDateTime", QDateTime::currentDateTime());
0385         st->postId = grp.readEntry("postId", QString());
0386         st->link = grp.readEntry("link", QUrl());
0387         st->content = grp.readEntry("content", QString());
0388         st->source = grp.readEntry("source", QString());
0389         st->isFavorited = grp.readEntry("favorited", false);
0390         st->author.userId = grp.readEntry("authorId", QString());
0391         st->author.userName = grp.readEntry("authorUserName", QString());
0392         st->author.realName = grp.readEntry("authorRealName", QString());
0393         st->author.location = grp.readEntry("authorLocation", QString());
0394         st->author.description = grp.readEntry("authorDescription" , QString());
0395         st->author.profileImageUrl = grp.readEntry("authorProfileImageUrl", QUrl());
0396         st->author.homePageUrl = grp.readEntry("authorHomePageUrl", QUrl());
0397         st->type = grp.readEntry("type", QString());
0398         st->media = grp.readEntry("media", QUrl());
0399         st->isRead = grp.readEntry("isRead", true);
0400         st->conversationId = grp.readEntry("conversationId", QString());
0401         st->to = grp.readEntry("to", QStringList());
0402         st->cc = grp.readEntry("cc", QStringList());
0403         st->shares = grp.readEntry("shares", QStringList());
0404         st->replies = grp.readEntry("replies", QUrl());
0405         st->replyToPostId = grp.readEntry("replyToPostId", QString());
0406         st->replyToUser.userName = grp.readEntry("replyToUserName", QString());
0407         st->replyToObjectType = grp.readEntry("replyToObjectType", QString());
0408         list.append(st);
0409     }
0410 
0411     if (!list.isEmpty()) {
0412         setLastTimelineId(account, timelineName, list.last()->conversationId);
0413     }
0414 
0415     return list;
0416 }
0417 
0418 QUrl PumpIOMicroBlog::postUrl(Choqok::Account *account, const QString &username,
0419                                  const QString &postId) const
0420 {
0421     Q_UNUSED(account);
0422     return QUrl::fromUserInput(QString(postId).replace(QLatin1String("/api/"), QLatin1Char('/') + username + QLatin1Char('/')));
0423 }
0424 
0425 QUrl PumpIOMicroBlog::profileUrl(Choqok::Account *account, const QString &username) const
0426 {
0427     if (username.contains(QLatin1String("acct:"))) {
0428         return QUrl::fromUserInput(QStringLiteral("https://%1/%2").arg(hostFromAcct(username)).arg(userNameFromAcct(username)));
0429     } else {
0430         PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(account);
0431         QUrl url(acc->host());
0432         url = url.adjusted(QUrl::StripTrailingSlash);
0433         url.setPath(QLatin1Char('/') + username);
0434 
0435         return url;
0436     }
0437 }
0438 
0439 void PumpIOMicroBlog::saveTimeline(Choqok::Account *account, const QString &timelineName,
0440                                    const QList< Choqok::UI::PostWidget * > &timeline)
0441 {
0442     const QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(),
0443                              timelineName);
0444     KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation);
0445 
0446     ///Clear previous data:
0447     for (const QString &group: postsBackup.groupList()) {
0448         postsBackup.deleteGroup(group);
0449     }
0450 
0451     for (Choqok::UI::PostWidget *wd: timeline) {
0452         PumpIOPost *post = dynamic_cast<PumpIOPost * >(wd->currentPost());
0453         KConfigGroup grp(&postsBackup, post->creationDateTime.toString());
0454         grp.writeEntry("creationDateTime", post->creationDateTime);
0455         grp.writeEntry("postId", post->postId);
0456         grp.writeEntry("link", post->link);
0457         grp.writeEntry("content", post->content);
0458         grp.writeEntry("source", post->source);
0459         grp.writeEntry("favorited", post->isFavorited);
0460         grp.writeEntry("authorId", post->author.userId);
0461         grp.writeEntry("authorRealName", post->author.realName);
0462         grp.writeEntry("authorUserName", post->author.userName);
0463         grp.writeEntry("authorLocation", post->author.location);
0464         grp.writeEntry("authorDescription", post->author.description);
0465         grp.writeEntry("authorProfileImageUrl", post->author.profileImageUrl);
0466         grp.writeEntry("authorHomePageUrl", post->author.homePageUrl);
0467         grp.writeEntry("type", post->type);
0468         grp.writeEntry("media", post->media);
0469         grp.writeEntry("isRead", post->isRead);
0470         grp.writeEntry("conversationId", post->conversationId);
0471         grp.writeEntry("to", post->to);
0472         grp.writeEntry("cc", post->cc);
0473         grp.writeEntry("shares", post->shares);
0474         grp.writeEntry("replies", post->replies);
0475         grp.writeEntry("replyToPostId", post->replyToPostId);
0476         grp.writeEntry("replyToUserName", post->replyToUser.userName);
0477         grp.writeEntry("replyToObjectType", post->replyToObjectType);
0478     }
0479     postsBackup.sync();
0480 
0481     if (Choqok::Application::isShuttingDown()) {
0482         --d->countOfTimelinesToSave;
0483         if (d->countOfTimelinesToSave < 1) {
0484             Q_EMIT readyForUnload();
0485         }
0486     }
0487 }
0488 
0489 Choqok::TimelineInfo *PumpIOMicroBlog::timelineInfo(const QString &timelineName)
0490 {
0491     return m_timelinesInfos.value(timelineName);
0492 }
0493 
0494 void PumpIOMicroBlog::updateTimelines(Choqok::Account *theAccount)
0495 {
0496     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0497     if (acc) {
0498         for (const QString &timeline: acc->timelineNames()) {
0499             QUrl url(acc->host());
0500             url = url.adjusted(QUrl::StripTrailingSlash);
0501             url.setPath(url.path() + m_timelinesPaths[timeline].arg(acc->username()));
0502             QUrlQuery query;
0503 
0504             const QString lastActivityId(lastTimelineId(theAccount, timeline));
0505             if (!lastActivityId.isEmpty()) {
0506                 query.addQueryItem(QLatin1String("count"), QString::number(200));
0507                 query.addQueryItem(QLatin1String("since"), lastActivityId);
0508             } else {
0509                 query.addQueryItem(QLatin1String("count"), QString::number(Choqok::BehaviorSettings::countOfPosts()));
0510             }
0511             url.setQuery(query);
0512 
0513             KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo);
0514             if (!job) {
0515                 qCDebug(CHOQOK) << "Cannot create an http GET request!";
0516                 continue;
0517             }
0518             job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation));
0519             m_timelinesRequests[job] = timeline;
0520             m_accountJobs[job] = acc;
0521             connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotUpdateTimeline);
0522             job->start();
0523         }
0524     } else {
0525         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0526     }
0527 }
0528 
0529 void PumpIOMicroBlog::fetchFollowing(Choqok::Account *theAccount)
0530 {
0531     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0532     if (acc) {
0533         QUrl url(acc->host());
0534         url = url.adjusted(QUrl::StripTrailingSlash);
0535         url.setPath(url.path() + QStringLiteral("/api/user/%1/following").arg(acc->username()));
0536 
0537         QUrlQuery query;
0538         query.addQueryItem(QLatin1String("count"), QString::number(200));
0539 
0540         if (!acc->following().isEmpty()) {
0541             query.addQueryItem(QLatin1String("since"), acc->following().last());
0542         }
0543         url.setQuery(query);
0544 
0545         KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo);
0546         if (!job) {
0547             qCDebug(CHOQOK) << "Cannot create an http GET request!";
0548             return;
0549         }
0550         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation));
0551         m_accountJobs[job] = acc;
0552         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotFollowing);
0553         job->start();
0554     } else {
0555         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0556     }
0557 }
0558 
0559 void PumpIOMicroBlog::fetchLists(Choqok::Account *theAccount)
0560 {
0561     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0562     if (acc) {
0563         QUrl url(acc->host());
0564         url = url.adjusted(QUrl::StripTrailingSlash);
0565         url.setPath(url.path() + QStringLiteral("/api/user/%1/lists/person").arg(acc->username()));
0566 
0567         QUrlQuery query;
0568         query.addQueryItem(QLatin1String("count"), QString::number(200));
0569         url.setQuery(query);
0570 
0571         KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo);
0572         if (!job) {
0573             qCDebug(CHOQOK) << "Cannot create an http GET request!";
0574             return;
0575         }
0576         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation));
0577         m_accountJobs[job] = acc;
0578         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotLists);
0579         job->start();
0580     } else {
0581         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0582     }
0583 }
0584 
0585 void PumpIOMicroBlog::share(Choqok::Account *theAccount, Choqok::Post *post)
0586 {
0587     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0588     if (acc) {
0589         QVariantMap object;
0590         object.insert(QLatin1String("objectType"), post->type);
0591         object.insert(QLatin1String("id"), post->postId);
0592 
0593         QVariantMap item;
0594         item.insert(QLatin1String("verb"), QLatin1String("share"));
0595         item.insert(QLatin1String("object"), object);
0596 
0597         const QByteArray data = QJsonDocument::fromVariant(item).toJson();
0598 
0599         QUrl url(acc->host());
0600         url = url.adjusted(QUrl::StripTrailingSlash);
0601         url.setPath(url.path() + outboxActivity.arg(acc->username()));
0602         KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo);
0603         job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json"));
0604         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation));
0605         if (!job) {
0606             qCDebug(CHOQOK) << "Cannot create an http POST request!";
0607             return;
0608         }
0609         m_accountJobs[job] = acc;
0610         m_shareJobs[job] = post;
0611         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotShare);
0612         job->start();
0613     } else {
0614         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0615     }
0616 }
0617 
0618 void PumpIOMicroBlog::toggleFavorite(Choqok::Account *theAccount, Choqok::Post *post)
0619 {
0620     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0621     if (acc) {
0622         QVariantMap object;
0623         object.insert(QLatin1String("objectType"), post->type);
0624         object.insert(QLatin1String("id"), post->postId);
0625 
0626         QVariantMap item;
0627         item.insert(QLatin1String("verb"), post->isFavorited ? QLatin1String("unfavorite") : QLatin1String("favorite"));
0628         item.insert(QLatin1String("object"), object);
0629 
0630         const QByteArray data = QJsonDocument::fromVariant(item).toJson();
0631 
0632         QUrl url(acc->host());
0633         url = url.adjusted(QUrl::StripTrailingSlash);
0634         url.setPath(url.path() + outboxActivity.arg(acc->username()));
0635         KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo);
0636         job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json"));
0637         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation));
0638         if (!job) {
0639             qCDebug(CHOQOK) << "Cannot create an http POST request!";
0640             return;
0641         }
0642         m_accountJobs[job] = acc;
0643         m_favoriteJobs[job] = post;
0644         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotFavorite);
0645         job->start();
0646     } else {
0647         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0648     }
0649 }
0650 
0651 void PumpIOMicroBlog::showDirectMessageDialog()
0652 {
0653     qCDebug(CHOQOK);
0654     const QString alias = qobject_cast<QAction *>(sender())->data().toString();
0655     PumpIOAccount *theAccount = qobject_cast<PumpIOAccount *>(Choqok::AccountManager::self()->findAccount(alias));
0656     PumpIOMessageDialog *msg = new PumpIOMessageDialog(theAccount, Choqok::UI::Global::mainWindow());
0657     msg->show();
0658 }
0659 
0660 void PumpIOMicroBlog::slotCreatePost(KJob *job)
0661 {
0662     qCDebug(CHOQOK);
0663     if (!job) {
0664         qCDebug(CHOQOK) << "Job is null pointer";
0665         return;
0666     }
0667     Choqok::Post *post = m_createPostJobs.take(job);
0668     Choqok::Account *theAccount = m_accountJobs.take(job);
0669     if (!post || !theAccount) {
0670         qCDebug(CHOQOK) << "Account or Post is NULL pointer";
0671         return;
0672     }
0673     int ret = 1;
0674     if (job->error()) {
0675         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0676     } else {
0677         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
0678 
0679         const QJsonDocument json = QJsonDocument::fromJson(j->data());
0680         if (!json.isNull()) {
0681             const QVariantMap reply = json.toVariant().toMap();
0682             if (!reply[QLatin1String("object")].toMap().value(QLatin1String("id")).toString().isEmpty()) {
0683                 Choqok::NotifyManager::success(i18n("New post for account %1 submitted successfully", theAccount->alias()));
0684                 ret = 0;
0685                 Q_EMIT postCreated(theAccount, post);
0686             }
0687         } else {
0688             qCDebug(CHOQOK) << "Cannot parse JSON reply";
0689         }
0690     }
0691 
0692     if (ret) {
0693         Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError,
0694                          i18n("Creating the new post failed. %1", job->errorString()),
0695                          MicroBlog::Critical);
0696     }
0697 }
0698 
0699 void PumpIOMicroBlog::slotFavorite(KJob *job)
0700 {
0701     qCDebug(CHOQOK);
0702     if (!job) {
0703         qCDebug(CHOQOK) << "Job is null pointer";
0704         return;
0705     }
0706     Choqok::Post *post = m_favoriteJobs.take(job);
0707     Choqok::Account *theAccount = m_accountJobs.take(job);
0708     if (!post || !theAccount) {
0709         qCDebug(CHOQOK) << "Account or Post is NULL pointer";
0710         return;
0711     }
0712     if (job->error()) {
0713         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0714         Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
0715                      i18n("Cannot set/unset the post as favorite. %1", job->errorString()));
0716     } else {
0717         post->isFavorited = !post->isFavorited;
0718         Q_EMIT favorite(theAccount, post);
0719     }
0720 }
0721 
0722 void PumpIOMicroBlog::slotFetchPost(KJob *job)
0723 {
0724     qCDebug(CHOQOK);
0725     if (!job) {
0726         qCDebug(CHOQOK) << "Job is null pointer";
0727         return;
0728     }
0729     Choqok::Account *theAccount = m_accountJobs.take(job);
0730     if (!theAccount) {
0731         qCDebug(CHOQOK) << "Account or postId is NULL pointer";
0732         return;
0733     }
0734     int ret = 1;
0735     if (job->error()) {
0736         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0737     } else {
0738         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
0739 
0740         const QJsonDocument json = QJsonDocument::fromJson(j->data());
0741         if (!json.isNull()) {
0742             const QVariantMap reply = json.toVariant().toMap();
0743             PumpIOPost *post = new PumpIOPost;
0744             readPost(reply, post);
0745             ret = 0;
0746             Q_EMIT postFetched(theAccount, post);
0747         } else {
0748             qCDebug(CHOQOK) << "Cannot parse JSON reply";
0749         }
0750     }
0751 
0752     if (ret) {
0753         Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
0754                      i18n("Cannot fetch post. %1", job->errorString()),
0755                      MicroBlog::Critical);
0756     }
0757 }
0758 
0759 void PumpIOMicroBlog::slotFetchReplies(KJob *job)
0760 {
0761     qCDebug(CHOQOK);
0762     if (!job) {
0763         qCDebug(CHOQOK) << "Job is null pointer";
0764         return;
0765     }
0766     Choqok::Account *theAccount = m_accountJobs.take(job);
0767     if (!theAccount) {
0768         qCDebug(CHOQOK) << "Account or postId is NULL pointer";
0769         return;
0770     }
0771     int ret = 1;
0772     if (job->error()) {
0773         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0774     } else {
0775         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
0776 
0777         const QJsonDocument json = QJsonDocument::fromJson(j->data());
0778         if (!json.isNull()) {
0779             const QVariantMap reply = json.toVariant().toMap();
0780             const QVariantList items = reply[QLatin1String("items")].toList();
0781             for (int i = items.size() - 1; i >= 0; i--) {
0782                 QVariantMap item = items.at(i).toMap();
0783                 PumpIOPost *r = new PumpIOPost;
0784                 readPost(item, r);
0785                 r->replyToPostId = reply[QLatin1String("url")].toString().remove(QLatin1String("/replies"));
0786                 Q_EMIT postFetched(theAccount, r);
0787             }
0788             ret = 0;
0789         } else {
0790             qCDebug(CHOQOK) << "Cannot parse JSON reply";
0791         }
0792     }
0793 
0794     if (ret) {
0795         Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
0796                      i18n("Cannot fetch replies. %1", job->errorString()),
0797                      MicroBlog::Critical);
0798     }
0799 }
0800 
0801 void PumpIOMicroBlog::slotFollowing(KJob *job)
0802 {
0803     qCDebug(CHOQOK);
0804     if (!job) {
0805         qCDebug(CHOQOK) << "Job is null pointer";
0806         return;
0807     }
0808     Choqok::Account *theAccount = m_accountJobs.take(job);
0809     if (!theAccount) {
0810         qCDebug(CHOQOK) << "Account is NULL pointer";
0811         return;
0812     }
0813     if (job->error()) {
0814         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0815     }
0816     bool ret = 1;
0817     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0818     if (acc) {
0819         Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Following list for account %1 has been updated.",
0820                                                                  theAccount->alias()));
0821         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
0822 
0823         const QJsonDocument json = QJsonDocument::fromJson(j->data());
0824         if (!json.isNull()) {
0825             const QVariantList items = json.toVariant().toMap().value(QLatin1String("items")).toList();
0826             QStringList following;
0827             for (const QVariant &element: items) {
0828                 following.append(element.toMap().value(QLatin1String("id")).toString());
0829             }
0830             acc->setFollowing(following);
0831             ret = 0;
0832             Q_EMIT followingFetched(acc);
0833         } else {
0834             qCDebug(CHOQOK) << "Cannot parse JSON reply";
0835         }
0836     } else {
0837         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0838     }
0839 
0840     if (ret) {
0841         Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
0842                      i18n("Cannot retrieve the following list. %1", job->errorString()));
0843     }
0844 }
0845 
0846 void PumpIOMicroBlog::slotLists(KJob *job)
0847 {
0848     qCDebug(CHOQOK);
0849     if (!job) {
0850         qCDebug(CHOQOK) << "Job is null pointer";
0851         return;
0852     }
0853     Choqok::Account *theAccount = m_accountJobs.take(job);
0854     if (!theAccount) {
0855         qCDebug(CHOQOK) << "Account is NULL pointer";
0856         return;
0857     }
0858     if (job->error()) {
0859         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0860     }
0861     bool ret = 1;
0862     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
0863     if (acc) {
0864         Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Lists for account %1 has been updated.",
0865                                                                  theAccount->alias()));
0866         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
0867 
0868         const QJsonDocument json = QJsonDocument::fromJson(j->data());
0869         if (!json.isNull()) {
0870             const QVariantList items = json.toVariant().toMap().value(QLatin1String("items")).toList();
0871             QVariantList lists;
0872             for (const QVariant &element: items) {
0873                 QVariantMap e = element.toMap();
0874                 QVariantMap list;
0875                 list.insert(QLatin1String("id"), e.value(QLatin1String("id")).toString());
0876                 list.insert(QLatin1String("name"), e.value(QLatin1String("displayName")).toString());
0877                 lists.append(list);
0878             }
0879             acc->setLists(lists);
0880             ret = 0;
0881             Q_EMIT listsFetched(acc);
0882         } else {
0883             qCDebug(CHOQOK) << "Cannot parse JSON reply";
0884         }
0885     } else {
0886         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
0887     }
0888 
0889     if (ret) {
0890         Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
0891                      i18n("Cannot retrieve the lists. %1", job->errorString()));
0892     }
0893 }
0894 
0895 void PumpIOMicroBlog::slotShare(KJob *job)
0896 {
0897     qCDebug(CHOQOK);
0898     if (!job) {
0899         qCDebug(CHOQOK) << "Job is null pointer";
0900         return;
0901     }
0902     Choqok::Post *post = m_shareJobs.take(job);
0903     Choqok::Account *theAccount = m_accountJobs.take(job);
0904     if (!post || !theAccount) {
0905         qCDebug(CHOQOK) << "Account or Post is NULL pointer";
0906         return;
0907     }
0908     int ret = 1;
0909     if (job->error()) {
0910         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0911     } else {
0912         Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("The post has been shared."));
0913         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
0914 
0915         const QJsonDocument json = QJsonDocument::fromJson(j->data());
0916         if (!json.isNull()) {
0917             const QVariantMap object = json.toVariant().toMap().value(QLatin1String("object")).toMap();
0918             ret = 0;
0919         } else {
0920             qCDebug(CHOQOK) << "Cannot parse JSON reply";
0921         }
0922     }
0923 
0924     if (ret) {
0925         Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError,
0926                      i18n("Cannot share the post. %1", job->errorString()));
0927     }
0928 }
0929 
0930 void PumpIOMicroBlog::slotRemovePost(KJob *job)
0931 {
0932     qCDebug(CHOQOK);
0933     if (!job) {
0934         qCDebug(CHOQOK) << "Job is null pointer";
0935         return;
0936     }
0937     Choqok::Post *post = m_removePostJobs.take(job);
0938     Choqok::Account *theAccount = m_accountJobs.take(job);
0939     if (!post || !theAccount) {
0940         qCDebug(CHOQOK) << "Account or Post is NULL pointer";
0941         return;
0942     }
0943     int ret = 1;
0944     if (job->error()) {
0945         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0946     } else {
0947         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
0948 
0949         const QJsonDocument json = QJsonDocument::fromJson(j->data());
0950         if (!json.isNull()) {
0951             const QVariantMap object = json.toVariant().toMap().value(QLatin1String("object")).toMap();
0952             if (!object[QLatin1String("deleted")].toString().isEmpty()) {
0953                 Choqok::NotifyManager::success(i18n("Post removed successfully"));
0954                 ret = 0;
0955                 Q_EMIT postRemoved(theAccount, post);
0956             }
0957         } else {
0958             qCDebug(CHOQOK) << "Cannot parse JSON reply";
0959         }
0960     }
0961 
0962     if (ret) {
0963         Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError,
0964                          i18n("Removing the post failed. %1", job->errorString()),
0965                          MicroBlog::Critical);
0966     }
0967 }
0968 
0969 void PumpIOMicroBlog::slotUpdatePost(KJob *job)
0970 {
0971     qCDebug(CHOQOK);
0972     if (!job) {
0973         qCDebug(CHOQOK) << "Job is null pointer";
0974         return;
0975     }
0976     Choqok::Post *post = m_updateJobs.take(job);
0977     Choqok::Account *account = m_accountJobs.take(job);
0978     if (!post || !account) {
0979         qCDebug(CHOQOK) << "Account or Post is NULL pointer";
0980         return;
0981     }
0982     int ret = 1;
0983     if (job->error()) {
0984         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
0985     } else {
0986         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
0987 
0988         const QJsonDocument json = QJsonDocument::fromJson(j->data());
0989         if (!json.isNull()) {
0990             ret = 0;
0991             createPost(account, post);
0992         } else {
0993             qCDebug(CHOQOK) << "Cannot parse JSON reply";
0994         }
0995     }
0996 
0997     if (ret) {
0998         Q_EMIT error(account, Choqok::MicroBlog::CommunicationError,
0999                      i18n("An error occurred when updating the post"));
1000     }
1001 }
1002 
1003 void PumpIOMicroBlog::slotUpdateTimeline(KJob *job)
1004 {
1005     qCDebug(CHOQOK);
1006     if (!job) {
1007         qCDebug(CHOQOK) << "Job is null pointer";
1008         return;
1009     }
1010     Choqok::Account *account = m_accountJobs.take(job);
1011     if (!account) {
1012         qCDebug(CHOQOK) << "Account or Post is NULL pointer";
1013         return;
1014     }
1015     if (job->error()) {
1016         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
1017         Q_EMIT error(account, Choqok::MicroBlog::CommunicationError,
1018                      i18n("An error occurred when fetching the timeline"));
1019     } else {
1020         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
1021         const QList<Choqok::Post * > list = readTimeline(j->data());
1022         const QString timeline(m_timelinesRequests.take(job));
1023         if (!list.isEmpty()) {
1024             setLastTimelineId(account, timeline, list.last()->conversationId);
1025         }
1026 
1027         Q_EMIT timelineDataReceived(account, timeline, list);
1028     }
1029 }
1030 
1031 void PumpIOMicroBlog::slotUpload(KJob *job)
1032 {
1033     qCDebug(CHOQOK);
1034     if (!job) {
1035         qCDebug(CHOQOK) << "Job is null pointer";
1036         return;
1037     }
1038     Choqok::Post *post = m_uploadJobs.take(job);
1039     Choqok::Account *account = m_accountJobs.take(job);
1040     if (!post || !account) {
1041         qCDebug(CHOQOK) << "Account or Post is NULL pointer";
1042         return;
1043     }
1044     int ret = 1;
1045     if (job->error()) {
1046         qCDebug(CHOQOK) << "Job Error:" << job->errorString();
1047     } else {
1048         KIO::StoredTransferJob *j = qobject_cast<KIO::StoredTransferJob * >(job);
1049 
1050         const QJsonDocument json = QJsonDocument::fromJson(j->data());
1051         if (!json.isNull()) {
1052             const QVariantMap reply = json.toVariant().toMap();
1053             const QString id = reply[QLatin1String("id")].toString();
1054             if (!id.isEmpty()) {
1055                 post->postId = id;
1056                 post->type = reply[QLatin1String("objectType")].toString();
1057                 ret = 0;
1058                 updatePost(account, post);
1059             }
1060         } else {
1061             qCDebug(CHOQOK) << "Cannot parse JSON reply";
1062         }
1063 
1064     }
1065 
1066     if (ret) {
1067         Q_EMIT error(account, Choqok::MicroBlog::CommunicationError,
1068                      i18n("An error occurred when uploading the media"));
1069     }
1070 }
1071 
1072 void PumpIOMicroBlog::fetchReplies(Choqok::Account *theAccount, const QUrl &url)
1073 {
1074     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
1075     if (acc) {
1076         if (!url.toDisplayString().startsWith(acc->host())) {
1077             qCDebug(CHOQOK) << "You can only fetch replies from your host!";
1078             return;
1079         }
1080 
1081         KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo);
1082         if (!job) {
1083             qCDebug(CHOQOK) << "Cannot create an http GET request!";
1084             return;
1085         }
1086         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation));
1087         m_accountJobs[job] = acc;
1088         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotFetchReplies);
1089         job->start();
1090     } else {
1091         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
1092     }
1093 }
1094 
1095 QString PumpIOMicroBlog::lastTimelineId(Choqok::Account *theAccount,
1096                                         const QString &timeline) const
1097 {
1098     qCDebug(CHOQOK) << "Latest ID for timeline " << timeline << m_timelinesLatestIds[theAccount][timeline];
1099     return m_timelinesLatestIds[theAccount][timeline];
1100 }
1101 
1102 Choqok::Post *PumpIOMicroBlog::readPost(const QVariantMap &var, Choqok::Post *post)
1103 {
1104     PumpIOPost *p = dynamic_cast< PumpIOPost * >(post);
1105     if (p) {
1106         QVariantMap object;
1107         if (var.value(QLatin1String("verb")).toString() == QLatin1String("post") ||
1108                 var.value(QLatin1String("verb")).toString() == QLatin1String("share")) {
1109             object = var[QLatin1String("object")].toMap();
1110         } else {
1111             object = var;
1112         }
1113 
1114         QTextDocument content;
1115         if (!object[QLatin1String("displayName")].isNull()) {
1116             content.setHtml(object[QLatin1String("displayName")].toString());
1117             p->content = content.toPlainText().trimmed();
1118             p->content += QLatin1Char('\n');
1119         }
1120 
1121         content.setHtml(object[QLatin1String("content")].toString());
1122         p->content += content.toPlainText().trimmed();
1123 
1124         if (!object[QLatin1String("fullImage")].isNull()) {
1125             const QVariantMap fullImage = object[QLatin1String("fullImage")].toMap();
1126             if (!fullImage.isEmpty()) {
1127                 p->media = fullImage[QLatin1String("url")].toUrl();
1128             }
1129         }
1130         p->creationDateTime = QDateTime::fromString(var[QLatin1String("published")].toString(),
1131                               Qt::ISODate);
1132         p->creationDateTime.setTimeSpec(Qt::UTC);
1133         if (object[QLatin1String("pump_io")].isNull()) {
1134             p->link = object[QLatin1String("id")].toUrl();
1135         } else {
1136             p->link = object[QLatin1String("pump_io")].toMap().value(QLatin1String("proxyURL")).toUrl();
1137         }
1138         p->type = object[QLatin1String("objectType")].toString();
1139         p->isFavorited = object[QLatin1String("liked")].toBool();
1140         if (p->isFavorited) {
1141             p->isRead = true;
1142         }
1143         p->postId = object[QLatin1String("id")].toString();
1144         p->conversationId = var[QLatin1String("id")].toString();
1145 
1146         QString author;
1147         var[QLatin1String("author")].isNull() ? author = QLatin1String("actor") : author = QLatin1String("author");
1148         QVariantMap actor;
1149         if (var.value(QLatin1String("verb")).toString() == QLatin1String("share")) {
1150             actor = object[QLatin1String("author")].toMap();
1151             const QVariantList shares = object[QLatin1String("shares")].toMap().value(QLatin1String("items")).toList();
1152             for (const QVariant &element: shares) {
1153                 p->shares.append(element.toMap().value(QLatin1String("id")).toString());
1154             }
1155         } else {
1156             actor = var[author].toMap();
1157         }
1158         const QString userId = actor[QLatin1String("id")].toString();
1159         const QUrl homePageUrl = actor[QLatin1String("url")].toUrl();
1160         p->author.userId = userId;
1161         p->author.userName = actor[QLatin1String("preferredUsername")].toString();
1162         p->author.realName = actor[QLatin1String("displayName")].toString();
1163         p->author.homePageUrl = homePageUrl;
1164         p->author.location = actor[QLatin1String("location")].toMap().value(QLatin1String("displayName")).toString();
1165         p->author.description = actor[QLatin1String("summary")].toString();
1166 
1167         const QUrl profileImageUrl = actor[QLatin1String("image")].toMap().value(QLatin1String("url")).toUrl();
1168         if (!profileImageUrl.isEmpty()) {
1169             p->author.profileImageUrl = profileImageUrl;
1170         } else if (actor[QLatin1String("objectType")].toString() == QLatin1String("service")) {
1171             p->author.profileImageUrl = QUrl::fromUserInput(homePageUrl.toDisplayString() + QLatin1String("images/default.png"));
1172         } else {
1173             p->author.profileImageUrl = QUrl::fromUserInput(QStringLiteral("https://%1/images/default.png").arg(hostFromAcct(userId)));
1174         }
1175 
1176         if (!var[QLatin1String("generator")].isNull()) {
1177             p->source = var[QLatin1String("generator")].toMap().value(QLatin1String("displayName")).toString();
1178         }
1179 
1180         const QVariantList to = var[QLatin1String("to")].toList();
1181         for (const QVariant &element: to) {
1182             QVariantMap toElementMap = element.toMap();
1183             QString toElementType = toElementMap.value(QLatin1String("objectType")).toString();
1184             if (toElementType == QLatin1String("person") || toElementType == QLatin1String("collection")) {
1185                 const QString toId = toElementMap.value(QLatin1String("id")).toString();
1186 
1187                 if (toId.compare(QLatin1String("acct:")) != 0) {
1188                     p->to.append(toId);
1189                 }
1190             }
1191         }
1192 
1193         const QVariantList cc = var[QLatin1String("cc")].toList();
1194         for (const QVariant &element: cc) {
1195             QVariantMap ccElementMap = element.toMap();
1196             QString ccElementType = ccElementMap.value(QLatin1String("objectType")).toString();
1197             if (ccElementType == QLatin1String("person") || ccElementType == QLatin1String("collection")) {
1198                 const QString ccId = ccElementMap.value(QLatin1String("id")).toString();
1199 
1200                 if (ccId.compare(QLatin1String("acct:")) != 0) {
1201                     p->cc.append(ccId);
1202                 }
1203             }
1204         }
1205 
1206         const QVariantMap replies = object[QLatin1String("replies")].toMap();
1207         if (replies.value(QLatin1String("pump_io")).isNull()) {
1208             p->replies = replies[QLatin1String("url")].toUrl();
1209         } else {
1210             p->replies = replies[QLatin1String("pump_io")].toMap().value(QLatin1String("proxyURL")).toUrl();
1211         }
1212 
1213         return p;
1214     } else {
1215         qCDebug(CHOQOK) << "post is not a PumpIOPost!";
1216         return post;
1217     }
1218 }
1219 
1220 QList< Choqok::Post * > PumpIOMicroBlog::readTimeline(const QByteArray &buffer)
1221 {
1222     QList<Choqok::Post * > posts;
1223     const QJsonDocument json = QJsonDocument::fromJson(buffer);
1224     if (!json.isNull()) {
1225         const QVariantList list = json.toVariant().toMap().value(QLatin1String("items")).toList();
1226         for (const QVariant &element: list) {
1227             const QVariantMap elementMap = element.toMap();
1228             if (!elementMap[QLatin1String("object")].toMap().value(QLatin1String("deleted")).isNull()) {
1229                 // Skip deleted posts
1230                 continue;
1231             }
1232             posts.prepend(readPost(elementMap, new PumpIOPost));
1233         }
1234     } else {
1235         qCDebug(CHOQOK) << "Cannot parse JSON reply";
1236     }
1237 
1238     return posts;
1239 }
1240 
1241 void PumpIOMicroBlog::setLastTimelineId(Choqok::Account *theAccount,
1242                                         const QString &timeline,
1243                                         const QString &id)
1244 {
1245     m_timelinesLatestIds[theAccount][timeline] = id;
1246 }
1247 
1248 void PumpIOMicroBlog::setTimelinesInfo()
1249 {
1250     Choqok::TimelineInfo *t = new Choqok::TimelineInfo;
1251     t->name = i18nc("Timeline Name", "Activity");
1252     t->description = i18nc("Timeline description", "You and people you follow");
1253     t->icon = QLatin1String("user-home");
1254     m_timelinesInfos[QLatin1String("Activity")] = t;
1255     m_timelinesPaths[QLatin1String("Activity")] = inboxActivity + QLatin1String("/major");
1256 
1257     t = new Choqok::TimelineInfo;
1258     t->name = i18nc("Timeline Name", "Favorites");
1259     t->description = i18nc("Timeline description", "Posts you favorited");
1260     t->icon = QLatin1String("favorites");
1261     m_timelinesInfos[QLatin1String("Favorites")] = t;
1262     m_timelinesPaths[QLatin1String("Favorites")] = QLatin1String("/api/user/%1/favorites");
1263 
1264     t = new Choqok::TimelineInfo;
1265     t->name = i18nc("Timeline Name", "Inbox");
1266     t->description = i18nc("Timeline description", "Posts sent to you");
1267     t->icon = QLatin1String("mail-folder-inbox");
1268     m_timelinesInfos[QLatin1String("Inbox")] = t;
1269     m_timelinesPaths[QLatin1String("Inbox")] = inboxActivity + QLatin1String("/direct/major/");
1270 
1271     t = new Choqok::TimelineInfo;
1272     t->name = i18nc("Timeline Name", "Outbox");
1273     t->description = i18nc("Timeline description", "Posts you sent");
1274     t->icon = QLatin1String("mail-folder-outbox");
1275     m_timelinesInfos[QLatin1String("Outbox")] = t;
1276     m_timelinesPaths[QLatin1String("Outbox")] = outboxActivity + QLatin1String("/major/");
1277 }
1278 
1279 void PumpIOMicroBlog::updatePost(Choqok::Account *theAccount, Choqok::Post *post)
1280 {
1281     PumpIOAccount *acc = qobject_cast<PumpIOAccount *>(theAccount);
1282     if (acc) {
1283         QVariantMap object;
1284         object.insert(QLatin1String("id"), post->postId);
1285         object.insert(QLatin1String("objectType"), post->type);
1286         object.insert(QLatin1String("content"), QUrl::toPercentEncoding(post->content));
1287 
1288         // https://github.com/e14n/pump.io/issues/885
1289         QVariantList to;
1290         QVariantMap thePublic;
1291         thePublic.insert(QLatin1String("objectType"), QLatin1String("collection"));
1292         thePublic.insert(QLatin1String("id"), PumpIOMicroBlog::PublicCollection);
1293         to.append(thePublic);
1294 
1295         QVariantMap item;
1296         item.insert(QLatin1String("verb"), QLatin1String("update"));
1297         item.insert(QLatin1String("object"), object);
1298         item.insert(QLatin1String("to"), to);
1299 
1300         const QByteArray data = QJsonDocument::fromVariant(item).toJson();
1301 
1302         QUrl url(acc->host());
1303         url = url.adjusted(QUrl::StripTrailingSlash);
1304         url.setPath(url.path() + outboxActivity.arg(acc->username()));
1305         KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo);
1306         job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json"));
1307         job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation));
1308         if (!job) {
1309             qCDebug(CHOQOK) << "Cannot create an http POST request!";
1310             return;
1311         }
1312         m_accountJobs[job] = acc;
1313         m_updateJobs[job] = post;
1314         connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotUpdatePost);
1315         job->start();
1316     } else {
1317         qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!";
1318     }
1319 }
1320 
1321 QString PumpIOMicroBlog::hostFromAcct(const QString &acct)
1322 {
1323     if (acct.contains(QLatin1String("acct:"))) {
1324         return acct.split(QLatin1Char(':'))[1].split(QLatin1Char('@'))[1];
1325     }
1326 
1327     return acct;
1328 }
1329 
1330 QString PumpIOMicroBlog::userNameFromAcct(const QString &acct)
1331 {
1332     if (acct.contains(QLatin1String("acct:"))) {
1333         return acct.split(QLatin1Char(':'))[1].split(QLatin1Char('@'))[0];
1334     }
1335 
1336     return acct;
1337 }
1338 
1339 #include "moc_pumpiomicroblog.cpp"
1340 #include "pumpiomicroblog.moc"