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"