diff --git a/helperlibs/twitterapihelper/twitterapimicroblog.cpp b/helperlibs/twitterapihelper/twitterapimicroblog.cpp index b1859b0f..149873e5 100644 --- a/helperlibs/twitterapihelper/twitterapimicroblog.cpp +++ b/helperlibs/twitterapihelper/twitterapimicroblog.cpp @@ -1,1578 +1,1578 @@ /* This file is part of Choqok, the KDE micro-blogging client Copyright (C) 2008-2012 Mehrdad Momeny This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see http://www.gnu.org/licenses/ */ #include "twitterapimicroblog.h" #include #include #include #include #include #include #include #include "account.h" #include "accountmanager.h" #include "application.h" #include "choqokappearancesettings.h" #include "choqokbehaviorsettings.h" #include "choqokuiglobal.h" #include "editaccountwidget.h" #include "microblogwidget.h" #include "notifymanager.h" #include "postwidget.h" #include "timelinewidget.h" #include "twitterapiaccount.h" #include "twitterapicomposerwidget.h" #include "twitterapidebug.h" #include "twitterapidmessagedialog.h" #include "twitterapipostwidget.h" #include "twitterapisearch.h" #include "twitterapisearchdialog.h" #include "twitterapisearchtimelinewidget.h" class TwitterApiMicroBlog::Private { public: Private(): countOfTimelinesToSave(0), friendsCursor(QLatin1String("-1")) { monthes[QLatin1String("Jan")] = 1; monthes[QLatin1String("Feb")] = 2; monthes[QLatin1String("Mar")] = 3; monthes[QLatin1String("Apr")] = 4; monthes[QLatin1String("May")] = 5; monthes[QLatin1String("Jun")] = 6; monthes[QLatin1String("Jul")] = 7; monthes[QLatin1String("Aug")] = 8; monthes[QLatin1String("Sep")] = 9; monthes[QLatin1String("Oct")] = 10; monthes[QLatin1String("Nov")] = 11; monthes[QLatin1String("Dec")] = 12; } int countOfTimelinesToSave; QString friendsCursor; QString followersCursor; QMap monthes; }; TwitterApiMicroBlog::TwitterApiMicroBlog(const QString &componentName, QObject *parent) : MicroBlog(componentName, parent), d(new Private) { qCDebug(CHOQOK); QStringList timelineTypes; timelineTypes << QLatin1String("Home") << QLatin1String("Reply") << QLatin1String("Inbox") << QLatin1String("Outbox") << QLatin1String("Favorite") << QLatin1String("ReTweets") << QLatin1String("Public"); setTimelineNames(timelineTypes); timelineApiPath[QLatin1String("Home")] = QLatin1String("/statuses/home_timeline.json"); timelineApiPath[QLatin1String("Reply")] = QLatin1String("/statuses/replies.json"); timelineApiPath[QLatin1String("Inbox")] = QLatin1String("/direct_messages.json"); timelineApiPath[QLatin1String("Outbox")] = QLatin1String("/direct_messages/sent.json"); timelineApiPath[QLatin1String("Favorite")] = QLatin1String("/favorites/list.json"); timelineApiPath[QLatin1String("ReTweets")] = QLatin1String("/statuses/retweets_of_me.json"); timelineApiPath[QLatin1String("Public")] = QLatin1String("/statuses/public_timeline.json"); setTimelineInfos(); } void TwitterApiMicroBlog::setTimelineInfos() { Choqok::TimelineInfo *t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Home"); t->description = i18nc("Timeline description", "You and your friends"); t->icon = QLatin1String("user-home"); mTimelineInfos[QLatin1String("Home")] = std::move(t); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Reply"); t->description = i18nc("Timeline description", "Replies to you"); t->icon = QLatin1String("edit-undo"); mTimelineInfos[QLatin1String("Reply")] = std::move(t); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Inbox"); t->description = i18nc("Timeline description", "Your incoming private messages"); t->icon = QLatin1String("mail-folder-inbox"); mTimelineInfos[QLatin1String("Inbox")] = std::move(t); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Outbox"); t->description = i18nc("Timeline description", "Private messages you have sent"); t->icon = QLatin1String("mail-folder-outbox"); mTimelineInfos[QLatin1String("Outbox")] = std::move(t); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Favorite"); t->description = i18nc("Timeline description", "Your favorites"); t->icon = QLatin1String("favorites"); mTimelineInfos[QLatin1String("Favorite")] = std::move(t); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Public"); t->description = i18nc("Timeline description", "Public timeline"); t->icon = QLatin1String("folder-green"); mTimelineInfos[QLatin1String("Public")] = std::move(t); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "ReTweets"); t->description = i18nc("Timeline description", "Your posts that were ReTweeted by others"); t->icon = QLatin1String("folder-red"); mTimelineInfos[QLatin1String("ReTweets")] = std::move(t); } TwitterApiMicroBlog::~TwitterApiMicroBlog() { qDeleteAll(mTimelineInfos); delete d; } QMenu *TwitterApiMicroBlog::createActionsMenu(Choqok::Account *theAccount, QWidget *parent) { QMenu *menu = MicroBlog::createActionsMenu(theAccount, parent); QAction *directMessge = new QAction(QIcon::fromTheme(QLatin1String("mail-message-new")), i18n("Send Private Message..."), menu); directMessge->setData(theAccount->alias()); connect(directMessge, SIGNAL(triggered(bool)), SLOT(showDirectMessageDialog())); menu->addAction(directMessge); QAction *search = new QAction(QIcon::fromTheme(QLatin1String("edit-find")), i18n("Search..."), menu); search->setData(theAccount->alias()); connect(search, SIGNAL(triggered(bool)), SLOT(showSearchDialog())); menu->addAction(search); QAction *updateFriendsList = new QAction(QIcon::fromTheme(QLatin1String("arrow-down")), i18n("Update Friends List"), menu); updateFriendsList->setData(theAccount->alias()); connect(updateFriendsList, &QAction::triggered, this, &TwitterApiMicroBlog::slotUpdateFriendsList); menu->addAction(updateFriendsList); return menu; } QList< Choqok::Post * > TwitterApiMicroBlog::loadTimeline(Choqok::Account *account, const QString &timelineName) { QList< Choqok::Post * > list; if (timelineName.compare(QLatin1String("Favorite")) == 0) { return list; //NOTE Won't cache favorites, and this is for compatibility with older versions! } qCDebug(CHOQOK) << timelineName; QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(), timelineName); KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation); QStringList tmpList = postsBackup.groupList(); /// to don't load old archives if (tmpList.isEmpty() || !(QDateTime::fromString(tmpList.first()).isValid())) { return list; } ///-------------- QList groupList; for (const QString &str: tmpList) { groupList.append(QDateTime::fromString(str)); } - qSort(groupList); + std::sort(groupList.begin(), groupList.end()); int count = groupList.count(); if (count) { Choqok::Post *st = nullptr; for (int i = 0; i < count; ++i) { st = new Choqok::Post; KConfigGroup grp(&postsBackup, groupList[i].toString()); st->creationDateTime = grp.readEntry("creationDateTime", QDateTime::currentDateTime()); st->postId = grp.readEntry("postId", QString()); st->content = grp.readEntry("text", QString()); st->source = grp.readEntry("source", QString()); st->replyToPostId = grp.readEntry("inReplyToPostId", QString()); st->replyToUser.userId = grp.readEntry("inReplyToUserId", QString()); st->isFavorited = grp.readEntry("favorited", false); st->replyToUser.userName = grp.readEntry("inReplyToUserName", QString()); st->author.userId = grp.readEntry("authorId", QString()); st->author.userName = grp.readEntry("authorUserName", QString()); st->author.realName = grp.readEntry("authorRealName", QString()); st->author.homePageUrl = grp.readEntry("authorHomePageUrl", QUrl()); st->author.profileImageUrl = grp.readEntry("authorProfileImageUrl", QUrl()); st->author.description = grp.readEntry("authorDescription" , QString()); st->author.isProtected = grp.readEntry("isProtected", false); st->isPrivate = grp.readEntry("isPrivate" , false); st->author.location = grp.readEntry("authorLocation", QString()); st->link = postUrl(account, st->author.userName, st->postId); st->isRead = grp.readEntry("isRead", true); st->repeatedFromUser.userName = grp.readEntry("repeatedFrom", QString()); st->repeatedFromUser.homePageUrl = grp.readEntry("repeatedFromUserHomePage", QUrl()); st->repeatedPostId = grp.readEntry("repeatedPostId", QString()); st->repeatedDateTime = grp.readEntry("repeatedDateTime", QDateTime()); st->conversationId = grp.readEntry("conversationId", QString()); st->media = grp.readEntry("mediaUrl", QUrl()); st->quotedPost.postId = grp.readEntry("quotedPostId", QString()); st->quotedPost.user.profileImageUrl = grp.readEntry("quotedProfileUrl", QUrl()); st->quotedPost.content = grp.readEntry("quotedContent", QString()); st->quotedPost.user.userName = grp.readEntry("quotedUsername", QString()); list.append(st); } mTimelineLatestId[account][timelineName] = st->postId; } return list; } void TwitterApiMicroBlog::saveTimeline(Choqok::Account *account, const QString &timelineName, const QList< Choqok::UI::PostWidget * > &timeline) { if (timelineName.compare(QLatin1String("Favorite")) != 0) { qCDebug(CHOQOK); QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(), timelineName); KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation); ///Clear previous data: for (const QString &group: postsBackup.groupList()) { postsBackup.deleteGroup(group); } for (Choqok::UI::PostWidget *wd: timeline) { const Choqok::Post *post = (wd->currentPost()); KConfigGroup grp(&postsBackup, post->creationDateTime.toString()); grp.writeEntry("creationDateTime", post->creationDateTime); grp.writeEntry("postId", post->postId); grp.writeEntry("text", post->content); grp.writeEntry("source", post->source); grp.writeEntry("inReplyToPostId", post->replyToPostId); grp.writeEntry("inReplyToUserId", post->replyToUser.userId); grp.writeEntry("favorited", post->isFavorited); grp.writeEntry("inReplyToUserName", post->replyToUser.userName); grp.writeEntry("authorId", post->author.userId); grp.writeEntry("authorUserName", post->author.userName); grp.writeEntry("authorRealName", post->author.realName); grp.writeEntry("authorHomePageUrl", post->author.homePageUrl); grp.writeEntry("authorProfileImageUrl", post->author.profileImageUrl); grp.writeEntry("authorDescription" , post->author.description); grp.writeEntry("isPrivate" , post->isPrivate); grp.writeEntry("authorLocation" , post->author.location); grp.writeEntry("isProtected" , post->author.isProtected); grp.writeEntry("isRead" , post->isRead); grp.writeEntry("repeatedFrom", post->repeatedFromUser.userName); grp.writeEntry("repeatedFromUserHomePage", post->repeatedFromUser.homePageUrl); grp.writeEntry("repeatedPostId", post->repeatedPostId); grp.writeEntry("repeatedDateTime", post->repeatedDateTime); grp.writeEntry("conversationId", post->conversationId); grp.writeEntry("mediaUrl", post->media); grp.writeEntry("quotedPostId", post->quotedPost.postId); grp.writeEntry("quotedProfileUrl", post->quotedPost.user.profileImageUrl); grp.writeEntry("quotedContent", post->quotedPost.content); grp.writeEntry("quotedUsername", post->quotedPost.user.userName); } postsBackup.sync(); } if (Choqok::Application::isShuttingDown()) { --d->countOfTimelinesToSave; if (d->countOfTimelinesToSave < 1) { Q_EMIT readyForUnload(); } } } Choqok::UI::ComposerWidget *TwitterApiMicroBlog::createComposerWidget(Choqok::Account *account, QWidget *parent) { return new TwitterApiComposerWidget(account, parent); } TwitterApiSearchTimelineWidget *TwitterApiMicroBlog::createSearchTimelineWidget(Choqok::Account *theAccount, QString name, const SearchInfo &info, QWidget *parent) { return new TwitterApiSearchTimelineWidget(theAccount, name, info, parent); } void TwitterApiMicroBlog::createPost(Choqok::Account *theAccount, Choqok::Post *post) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QByteArray data; QVariantMap params; if (!post || post->content.isEmpty()) { qCDebug(CHOQOK) << "ERROR: Status text is empty!"; Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::OtherError, i18n("Creating the new post failed. Text is empty."), MicroBlog::Critical); return; } if (!post->isPrivate) { ///Status Update QUrl url = account->apiUrl(); url.setPath(url.path() + QLatin1String("/statuses/update.json")); params.insert(QLatin1String("status"), post->content); if (!post->replyToPostId.isEmpty()) { params.insert(QLatin1String("in_reply_to_status_id"), post->replyToPostId.toLatin1()); } data = "status="; data += QUrl::toPercentEncoding(post->content); if (!post->replyToPostId.isEmpty()) { data += "&in_reply_to_status_id="; data += post->replyToPostId.toLatin1(); } if (!account->usingOAuth()) { data += "&source=Choqok"; } KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } job->addMetaData(QStringLiteral("content-type"), QStringLiteral("Content-Type: application/x-www-form-urlencoded")); job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation, params))); mCreatePostMap[ job ] = post; mJobsAccount[job] = theAccount; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotCreatePost); job->start(); } else {///Direct message QString recipientScreenName = post->replyToUser.userName; QUrl url = account->apiUrl(); url.setPath(url.path() + QLatin1String("/direct_messages/new.json")); params.insert(QLatin1String("user"), recipientScreenName.toLatin1()); params.insert(QLatin1String("text"), post->content); data = "user="; data += recipientScreenName.toLatin1(); data += "&text="; data += QUrl::toPercentEncoding(post->content); if (!account->usingOAuth()) { data += "&source=Choqok"; } KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; // QString errMsg = i18n ( "Creating the new post failed. Cannot create an http POST request. Please check your KDE installation." ); // emit errorPost ( theAccount, post, Choqok::MicroBlog::OtherError, errMsg, MicroBlog::Critical ); return; } job->addMetaData(QStringLiteral("content-type"), QStringLiteral("Content-Type: application/x-www-form-urlencoded")); job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation, params))); mCreatePostMap[ job ] = post; mJobsAccount[job] = theAccount; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotCreatePost); job->start(); } } void TwitterApiMicroBlog::repeatPost(Choqok::Account *theAccount, const QString &postId) { qCDebug(CHOQOK); if (postId.isEmpty()) { qCCritical(CHOQOK) << "ERROR: PostId is empty!"; return; } TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QStringLiteral("/statuses/retweet/%1.json").arg(postId)); QByteArray data; KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } job->addMetaData(QStringLiteral("content-type"), QStringLiteral("Content-Type: application/x-www-form-urlencoded")); job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); Choqok::Post *post = new Choqok::Post; post->postId = postId; mCreatePostMap[ job ] = post; mJobsAccount[job] = theAccount; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotCreatePost); job->start(); } void TwitterApiMicroBlog::slotCreatePost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = mCreatePostMap.take(job); Choqok::Account *theAccount = mJobsAccount.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError, i18n("Creating the new post failed: %1", job->errorString()), MicroBlog::Critical); } else { KIO::StoredTransferJob *stj = qobject_cast< KIO::StoredTransferJob * > (job); if (!post->isPrivate) { readPost(theAccount, stj->data(), post); if (post->isError) { QString errorMsg; errorMsg = checkForError(stj->data()); if (errorMsg.isEmpty()) { // We get the error message by parsing the JSON output, if there was a parsing error, then we don't have an error message, while there were still an error because of the error flag qCCritical(CHOQOK) << "Creating post: JSON parsing error:" << stj->data() ; Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::ParsingError, i18n("Creating the new post failed. The result data could not be parsed."), MicroBlog::Critical); } else { qCCritical(CHOQOK) << "Server Error:" << errorMsg ; Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::ServerError, i18n("Creating the new post failed, with error: %1", errorMsg), MicroBlog::Critical); } } else { Choqok::NotifyManager::success(i18n("New post for account %1 submitted successfully", theAccount->alias())); Q_EMIT postCreated(theAccount, post); } } else { Choqok::NotifyManager::success(i18n("Private message sent successfully")); Q_EMIT postCreated(theAccount, post); } } } void TwitterApiMicroBlog::abortAllJobs(Choqok::Account *theAccount) { for (KJob *job: mJobsAccount.keys(theAccount)) { job->kill(KJob::EmitResult); } } void TwitterApiMicroBlog::abortCreatePost(Choqok::Account *theAccount, Choqok::Post *post) { if (mCreatePostMap.isEmpty()) { return; } if (post) { mCreatePostMap.key(post)->kill(KJob::EmitResult); } else { for (KJob *job: mCreatePostMap.keys()) { if (mJobsAccount[job] == theAccount) { job->kill(KJob::EmitResult); } } } } void TwitterApiMicroBlog::fetchPost(Choqok::Account *theAccount, Choqok::Post *post) { qCDebug(CHOQOK); if (!post || post->postId.isEmpty()) { return; } TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QStringLiteral("/statuses/show/%1.json").arg(post->postId)); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; // QString errMsg = i18n ( "Fetching the new post failed. Cannot create an HTTP GET request." // "Please check your KDE installation." ); // emit errorPost ( theAccount, post, Choqok::MicroBlog::OtherError, errMsg, Low ); return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::GetOperation))); mFetchPostMap[ job ] = post; mJobsAccount[ job ] = theAccount; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotFetchPost); job->start(); } void TwitterApiMicroBlog::slotFetchPost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCWarning(CHOQOK) << "NULL Job returned"; return; } Choqok::Post *post = mFetchPostMap.take(job); Choqok::Account *theAccount = mJobsAccount.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Fetching the new post failed. %1", job->errorString()), Low); } else { KIO::StoredTransferJob *stj = qobject_cast (job); readPost(theAccount, stj->data(), post); if (post->isError) { QString errorMsg; errorMsg = checkForError(stj->data()); if (errorMsg.isEmpty()) { qCDebug(CHOQOK) << "Parsing Error"; Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::ParsingError, i18n("Fetching new post failed. The result data could not be parsed."), Low); } else { qCCritical(CHOQOK) << "Fetching post: Server Error:" << errorMsg; Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::ServerError, i18n("Fetching new post failed, with error:%1", errorMsg), Low); } } else { post->isError = true; Q_EMIT postFetched(theAccount, post); } } } void TwitterApiMicroBlog::removePost(Choqok::Account *theAccount, Choqok::Post *post) { qCDebug(CHOQOK); if (!post->postId.isEmpty()) { TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); if (!post->isPrivate) { url.setPath(url.path() + QStringLiteral("/statuses/destroy/%1.json").arg(post->postId)); } else { url.setPath(url.path() + QStringLiteral("/direct_messages/destroy/%1.json").arg(post->postId)); } KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; // QString errMsg = i18n ( "Removing the post failed. Cannot create an HTTP POST request. Please check your KDE installation." ); // emit errorPost ( theAccount, post, Choqok::MicroBlog::OtherError, errMsg, MicroBlog::Critical ); return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mRemovePostMap[job] = post; mJobsAccount[job] = theAccount; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotRemovePost); job->start(); } } void TwitterApiMicroBlog::slotRemovePost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer."; return; } Choqok::Post *post = mRemovePostMap.take(job); Choqok::Account *theAccount = mJobsAccount.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT errorPost(theAccount, post, CommunicationError, i18n("Removing the post failed. %1", job->errorString()), MicroBlog::Critical); } else { KIO::StoredTransferJob *stj = qobject_cast(job); QString errMsg = checkForError(stj->data()); if (errMsg.isEmpty()) { Q_EMIT postRemoved(theAccount, post); } else { qCCritical(CHOQOK) << "Server error on removing post:" << errMsg; Q_EMIT errorPost(theAccount, post, ServerError, i18n("Removing the post failed. %1", errMsg), MicroBlog::Critical); } } } void TwitterApiMicroBlog::createFavorite(Choqok::Account *theAccount, const QString &postId) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QLatin1String("/favorites/create.json")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("id"), postId); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; // QString errMsg = i18n ( "The Favorite creation failed. Cannot create an http POST request. " // "Please check your KDE installation." ); // emit error ( theAccount, OtherError, errMsg ); return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mFavoriteMap[job] = postId; mJobsAccount[job] = theAccount; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotCreateFavorite); job->start(); } void TwitterApiMicroBlog::slotCreateFavorite(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer."; return; } Choqok::Account *theAccount = mJobsAccount.take(job); QString postId = mFavoriteMap.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, CommunicationError, i18n("Favorite creation failed. %1", job->errorString())); } else { KIO::StoredTransferJob *stJob = qobject_cast(job); QString err = checkForError(stJob->data()); if (!err.isEmpty()) { Q_EMIT error(theAccount, ServerError, err, Critical); return; } else { Q_EMIT favoriteCreated(theAccount, postId); } } } void TwitterApiMicroBlog::removeFavorite(Choqok::Account *theAccount, const QString &postId) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QLatin1String("/favorites/destroy.json")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("id"), postId); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; // QString errMsg = i18n ( "Removing the favorite failed. Cannot create an http POST request. " // "Please check your KDE installation." ); // emit error ( theAccount, OtherError, errMsg ); return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mFavoriteMap[job] = postId; mJobsAccount[job] = theAccount; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotRemoveFavorite); job->start(); } void TwitterApiMicroBlog::slotRemoveFavorite(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer."; return; } QString id = mFavoriteMap.take(job); Choqok::Account *theAccount = mJobsAccount.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, CommunicationError, i18n("Removing the favorite failed. %1", job->errorString())); } else { KIO::StoredTransferJob *stJob = qobject_cast(job); QString err = checkForError(stJob->data()); if (!err.isEmpty()) { Q_EMIT error(theAccount, ServerError, err, Critical); return; } else { Q_EMIT favoriteRemoved(theAccount, id); } } } void TwitterApiMicroBlog::listFriendsUsername(TwitterApiAccount *theAccount, bool active) { friendsList.clear(); d->friendsCursor = QLatin1String("-1"); if (theAccount) { requestFriendsScreenName(theAccount, active); } } void TwitterApiMicroBlog::requestFriendsScreenName(TwitterApiAccount *theAccount, bool active) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1String("/friends/list.json")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("cursor"), d->friendsCursor); urlQuery.addQueryItem(QLatin1String("count"), QLatin1String("200")); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::GetOperation))); mJobsAccount[job] = theAccount; if (active) { connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotRequestFriendsScreenNameActive); } else { connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotRequestFriendsScreenNamePassive); } job->start(); Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Updating friends list for account %1...", theAccount->alias())); } void TwitterApiMicroBlog::slotRequestFriendsScreenNameActive(KJob *job) { finishRequestFriendsScreenName(job, true); } void TwitterApiMicroBlog::slotRequestFriendsScreenNamePassive(KJob *job) { finishRequestFriendsScreenName(job, false); } void TwitterApiMicroBlog::finishRequestFriendsScreenName(KJob *job, bool active) { qCDebug(CHOQOK); TwitterApiAccount *theAccount = qobject_cast(mJobsAccount.take(job)); KIO::StoredTransferJob *stJob = qobject_cast(job); Choqok::MicroBlog::ErrorLevel level = active ? Critical : Low; if (stJob->error()) { Q_EMIT error(theAccount, ServerError, i18n("Friends list for account %1 could not be updated:\n%2", theAccount->username(), stJob->errorString()), level); return; } QStringList newList = readFriendsScreenName(theAccount, stJob->data()); newList.removeDuplicates(); if (! checkForError(stJob->data()).isEmpty()) { // if an error occurred, do not replace the friends list. theAccount->setFriendsList(friendsList); Q_EMIT friendsUsernameListed(theAccount, friendsList); } else if (QString::compare(d->friendsCursor, QLatin1String("0"))) { // if the cursor is not "0", there is more friends data to be had friendsList << newList; requestFriendsScreenName(theAccount, active); } else { friendsList << newList; theAccount->setFriendsList(friendsList); Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Friends list for account %1 has been updated.", theAccount->alias())); Q_EMIT friendsUsernameListed(theAccount, friendsList); } } void TwitterApiMicroBlog::listFollowersUsername(TwitterApiAccount* theAccount, bool active) { followersList.clear(); d->followersCursor = QLatin1String("-1"); if ( theAccount ) { requestFollowersScreenName(theAccount, active); } } void TwitterApiMicroBlog::requestFollowersScreenName(TwitterApiAccount* theAccount, bool active) { qCDebug(CHOQOK); TwitterApiAccount* account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1String("/followers/list.json")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("cursor"), d->followersCursor); urlQuery.addQueryItem(QLatin1String("count"), QLatin1String("200")); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::GetOperation))); mJobsAccount[job] = theAccount; if (active) { connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotRequestFollowersScreenNameActive); } else { connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotRequestFollowersScreenNamePassive); } job->start(); Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Updating followers list for account %1...", theAccount->alias())); } void TwitterApiMicroBlog::slotRequestFollowersScreenNameActive(KJob* job) { finishRequestFollowersScreenName(job, true); } void TwitterApiMicroBlog::slotRequestFollowersScreenNamePassive(KJob* job) { finishRequestFollowersScreenName(job, false); } void TwitterApiMicroBlog::finishRequestFollowersScreenName(KJob* job, bool active) { qCDebug(CHOQOK); TwitterApiAccount *theAccount = qobject_cast( mJobsAccount.take(job) ); KIO::StoredTransferJob* stJob = qobject_cast( job ); Choqok::MicroBlog::ErrorLevel level = active ? Critical : Low; if (stJob->error()) { Q_EMIT error(theAccount, ServerError, i18n("Followers list for account %1 could not be updated:\n%2", theAccount->username(), stJob->errorString()), level); return; } QStringList newList = readFollowersScreenName(theAccount, stJob->data()); newList.removeDuplicates(); if (!checkForError(stJob->data()).isEmpty()) { // if an error occurred, do not replace the friends list. theAccount->setFollowersList(followersList); Q_EMIT followersUsernameListed(theAccount, followersList); } else if (QString::compare(d->followersCursor, QLatin1String("0"))) { // if the cursor is not "0", there is more friends data to be had followersList << newList; requestFollowersScreenName(theAccount, active); } else { followersList << newList; theAccount->setFollowersList(followersList); Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Followers list for account %1 has been updated.", theAccount->alias())); Q_EMIT followersUsernameListed(theAccount, followersList); } } void TwitterApiMicroBlog::updateTimelines(Choqok::Account *theAccount) { qCDebug(CHOQOK); for (const QString &tm: theAccount->timelineNames()) { requestTimeLine(theAccount, tm, mTimelineLatestId[theAccount][tm]); } } void TwitterApiMicroBlog::requestTimeLine(Choqok::Account *theAccount, QString type, QString latestStatusId, int page, QString maxId) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + timelineApiPath[type]); QUrlQuery urlQuery; // needed because lists have different parameter names but // returned timelines have the same JSON format if (timelineApiPath[type].contains(QLatin1String("lists/statuses"))) { // type contains @username/timelinename const QString slug = type.mid(type.indexOf(QLatin1String("/")) + 1); urlQuery.addQueryItem(QLatin1String("slug"), slug); const QString owner = type.mid(1, type.indexOf(QLatin1String("/")) - 1); urlQuery.addQueryItem(QLatin1String("owner_screen_name"), owner); } else { int countOfPost = Choqok::BehaviorSettings::countOfPosts(); if (!latestStatusId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("since_id"), latestStatusId); countOfPost = 200; } urlQuery.addQueryItem(QLatin1String("count"), QString::number(countOfPost)); if (!maxId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("max_id"), maxId); } if (page) { urlQuery.addQueryItem(QLatin1String("page"), QString::number(page)); } } url.setQuery(urlQuery); qCDebug(CHOQOK) << "Latest" << type << "Id:" << latestStatusId;// << "apiReq:" << url; KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo) ; if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; // QString errMsg = i18n ( "Cannot create an http GET request. Please check your KDE installation." ); // emit error ( theAccount, OtherError, errMsg, Low ); return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::GetOperation))); mRequestTimelineMap[job] = type; mJobsAccount[job] = theAccount; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotRequestTimeline); job->start(); } void TwitterApiMicroBlog::slotRequestTimeline(KJob *job) { qCDebug(CHOQOK);//TODO Add error detection if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Account *theAccount = mJobsAccount.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, CommunicationError, i18n("Timeline update failed: %1", job->errorString()), Low); return; } QString type = mRequestTimelineMap.take(job); if (isValidTimeline(type)) { KIO::StoredTransferJob *j = qobject_cast(job); QList list; if (type == QLatin1String("Inbox") || type == QLatin1String("Outbox")) { list = readDirectMessages(theAccount, j->data()); } else { list = readTimeline(theAccount, j->data()); } if (!list.isEmpty()) { mTimelineLatestId[theAccount][type] = list.last()->postId; Q_EMIT timelineDataReceived(theAccount, type, list); } } } QByteArray TwitterApiMicroBlog::authorizationHeader(TwitterApiAccount *theAccount, const QUrl &requestUrl, QNetworkAccessManager::Operation method, const QVariantMap ¶ms) { QByteArray auth; if (theAccount->usingOAuth()) { auth = theAccount->oauthInterface()->authorizationHeader(requestUrl, method, params); } else { auth = theAccount->username().toUtf8() + ':' + theAccount->password().toUtf8(); auth = auth.toBase64().prepend("Basic "); } return auth; } void TwitterApiMicroBlog::setRepeatedOfInfo(Choqok::Post *post, Choqok::Post *repeatedPost) { post->content = repeatedPost->content; post->replyToPostId = repeatedPost->replyToPostId; post->replyToUser.userId = repeatedPost->replyToUser.userId; post->replyToUser.userName = repeatedPost->replyToUser.userName; post->repeatedPostId = repeatedPost->postId; post->repeatedDateTime = repeatedPost->creationDateTime; if (Choqok::AppearanceSettings::showRetweetsInChoqokWay()) { post->repeatedFromUser.userName = repeatedPost->author.userName; post->repeatedFromUser.homePageUrl = repeatedPost->author.homePageUrl; } else { post->repeatedFromUser.userName = post->author.userName; post->repeatedFromUser.homePageUrl = post->author.homePageUrl; post->author = repeatedPost->author; } - + if (!repeatedPost->quotedPost.content.isEmpty()) { - post->quotedPost = repeatedPost->quotedPost; + post->quotedPost = repeatedPost->quotedPost; } } void TwitterApiMicroBlog::setQuotedPost(Choqok::Post* post, Choqok::Post* quotedPost) { post->quotedPost.user.profileImageUrl = quotedPost->author.profileImageUrl; post->quotedPost.user.userName = quotedPost->author.userName; post->quotedPost.postId = quotedPost->postId; post->quotedPost.content = quotedPost->content; } QDateTime TwitterApiMicroBlog::dateFromString(const QString &date) { char s[10]; int year, day, hours, minutes, seconds, tz; sscanf(qPrintable(date), "%*s %s %d %d:%d:%d %d %d", s, &day, &hours, &minutes, &seconds, &tz, &year); int month = d->monthes[QLatin1String(s)]; QDateTime recognized(QDate(year, month, day), QTime(hours, minutes, seconds)); if (tz == 0) { //tz is the timezone, in Twitter it's always UTC(0) in Identica it's local +/-NUMBER recognized.setTimeSpec(Qt::UTC); } return recognized.toLocalTime(); } void TwitterApiMicroBlog::aboutToUnload() { d->countOfTimelinesToSave = 0; for (Choqok::Account *acc: Choqok::AccountManager::self()->accounts()) { if (acc->microblog() == this) { d->countOfTimelinesToSave += acc->timelineNames().count(); } } Q_EMIT saveTimelines(); } void TwitterApiMicroBlog::showDirectMessageDialog(TwitterApiAccount *theAccount, const QString &toUsername) { qCDebug(CHOQOK); if (!theAccount) { QAction *act = qobject_cast(sender()); theAccount = qobject_cast( Choqok::AccountManager::self()->findAccount(act->data().toString())); } TwitterApiDMessageDialog *dmsg = new TwitterApiDMessageDialog(theAccount, Choqok::UI::Global::mainWindow()); if (!toUsername.isEmpty()) { dmsg->setTo(toUsername); } dmsg->show(); } Choqok::TimelineInfo *TwitterApiMicroBlog::timelineInfo(const QString &timelineName) { if (isValidTimeline(timelineName)) { return mTimelineInfos.value(timelineName); } else { return nullptr; } } void TwitterApiMicroBlog::showSearchDialog(TwitterApiAccount *theAccount) { if (!theAccount) { QAction *act = qobject_cast(sender()); theAccount = qobject_cast( Choqok::AccountManager::self()->findAccount(act->data().toString())); } QPointer searchDlg = new TwitterApiSearchDialog(theAccount, Choqok::UI::Global::mainWindow()); searchDlg->show(); } void TwitterApiMicroBlog::slotUpdateFriendsList() { QAction *act = qobject_cast(sender()); TwitterApiAccount *theAccount = qobject_cast( Choqok::AccountManager::self()->findAccount(act->data().toString())); listFriendsUsername(theAccount, true); } void TwitterApiMicroBlog::createFriendship(Choqok::Account *theAccount, const QString &username) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QLatin1String("/friendships/create.json")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo) ; qCDebug(CHOQOK) << url; if (!job) { qCCritical(CHOQOK) << "Cannot create an http POST request!"; return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mJobsAccount[job] = theAccount; mFriendshipMap[ job ] = username; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotCreateFriendship); job->start(); } void TwitterApiMicroBlog::slotCreateFriendship(KJob *job) { qCDebug(CHOQOK); if (!job) { qCCritical(CHOQOK) << "Job is a null Pointer!"; return; } TwitterApiAccount *theAccount = qobject_cast(mJobsAccount.take(job)); QString username = mFriendshipMap.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, CommunicationError, i18n("Creating friendship with %1 failed. %2", username, job->errorString())); return; } KIO::StoredTransferJob *stj = qobject_cast(job); Choqok::User *user = readUserInfo(stj->data()); if (user /*&& user->userName.compare(username, Qt::CaseInsensitive)*/) { Q_EMIT friendshipCreated(theAccount, username); Choqok::NotifyManager::success(i18n("You are now listening to %1's posts.", username)); theAccount->setFriendsList(QStringList()); listFriendsUsername(theAccount); } else { QString errorMsg = checkForError(stj->data()); if (errorMsg.isEmpty()) { qCDebug(CHOQOK) << "Parse Error:" << stj->data(); Q_EMIT error(theAccount, ParsingError, i18n("Creating friendship with %1 failed: the server returned invalid data.", username)); } else { qCDebug(CHOQOK) << "Server error:" << errorMsg; Q_EMIT error(theAccount, ServerError, i18n("Creating friendship with %1 failed: %2", username, errorMsg)); } } } void TwitterApiMicroBlog::destroyFriendship(Choqok::Account *theAccount, const QString &username) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QLatin1String("/friendships/destroy.json")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo) ; if (!job) { qCCritical(CHOQOK) << "Cannot create an http POST request!"; return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mJobsAccount[job] = theAccount; mFriendshipMap[ job ] = username; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotDestroyFriendship); job->start(); } void TwitterApiMicroBlog::slotDestroyFriendship(KJob *job) { qCDebug(CHOQOK); if (!job) { qCCritical(CHOQOK) << "Job is a null Pointer!"; return; } TwitterApiAccount *theAccount = qobject_cast(mJobsAccount.take(job)); QString username = mFriendshipMap.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, CommunicationError, i18n("Destroying friendship with %1 failed. %2", username, job->errorString())); return; } KIO::StoredTransferJob *stj = qobject_cast(job); Choqok::User *user = readUserInfo(stj->data()); if (user /*&& user->userName.compare( username, Qt::CaseInsensitive )*/) { Q_EMIT friendshipDestroyed(theAccount, username); Choqok::NotifyManager::success(i18n("You will not receive %1's updates.", username)); theAccount->setFriendsList(QStringList()); listFriendsUsername(theAccount); } else { QString errorMsg = checkForError(stj->data()); if (errorMsg.isEmpty()) { qCDebug(CHOQOK) << "Parse Error:" << stj->data(); Q_EMIT error(theAccount, ParsingError, i18n("Destroying friendship with %1 failed: the server returned invalid data.", username)); } else { qCDebug(CHOQOK) << "Server error:" << errorMsg; Q_EMIT error(theAccount, ServerError, i18n("Destroying friendship with %1 failed: %2", username, errorMsg)); } } } void TwitterApiMicroBlog::blockUser(Choqok::Account *theAccount, const QString &username) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QLatin1String("/blocks/create.json")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo) ; if (!job) { qCCritical(CHOQOK) << "Cannot create an http POST request!"; return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mJobsAccount[job] = theAccount; mFriendshipMap[ job ] = username; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotBlockUser); job->start(); } void TwitterApiMicroBlog::reportUserAsSpam(Choqok::Account *theAccount, const QString &username) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1String("/users/report_spam.json")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo) ; if (!job) { qCCritical(CHOQOK) << "Cannot create an http POST request!"; return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mJobsAccount[job] = theAccount; mFriendshipMap[ job ] = username; connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiMicroBlog::slotReportUser); job->start(); } void TwitterApiMicroBlog::slotBlockUser(KJob *job) { qCDebug(CHOQOK); if (!job) { qCCritical(CHOQOK) << "Job is a null Pointer!"; return; } Choqok::Account *theAccount = mJobsAccount.take(job); QString username = mFriendshipMap.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, CommunicationError, i18n("Blocking %1 failed. %2", username, job->errorString())); return; } Choqok::User *user = readUserInfo(qobject_cast(job)->data()); if (user /*&& user->userName.compare( username, Qt::CaseInsensitive )*/) { Q_EMIT userBlocked(theAccount, username); Choqok::NotifyManager::success(i18n("You will no longer be disturbed by %1.", username)); } else { qCDebug(CHOQOK) << "Parse Error:" << qobject_cast(job)->data(); Q_EMIT error(theAccount, ParsingError, i18n("Blocking %1 failed: the server returned invalid data.", username)); } //TODO Check for failor! } void TwitterApiMicroBlog::slotReportUser(KJob *job) { qCDebug(CHOQOK); if (!job) { qCCritical(CHOQOK) << "Job is a null Pointer!"; return; } Choqok::Account *theAccount = mJobsAccount.take(job); QString username = mFriendshipMap.take(job); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, CommunicationError, i18n("Reporting %1 failed. %2", username, job->errorString())); return; } Choqok::User *user = readUserInfo(qobject_cast(job)->data()); if (user) { Choqok::NotifyManager::success(i18n("Report sent successfully.")); } else { qCDebug(CHOQOK) << "Parse Error:" << qobject_cast(job)->data(); Q_EMIT error(theAccount, ParsingError, i18n("Reporting %1 failed: the server returned invalid data.", username)); } } ///=================================================================== QString TwitterApiMicroBlog::checkForError(const QByteArray &buffer) { const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { const QVariantMap map = json.toVariant().toMap(); if (map.contains(QLatin1String("errors"))) { QStringList errors; for (const QVariant &msg: map[QLatin1String("errors")].toList()) { errors.append(msg.toMap()[QLatin1String("message")].toString()); qCCritical(CHOQOK) << "Error:" << errors.last(); } return errors.join(QLatin1Char(';')); } } return QString(); } QList< Choqok::Post * > TwitterApiMicroBlog::readTimeline(Choqok::Account *theAccount, const QByteArray &buffer) { QList postList; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { for (const QVariant &list: json.toVariant().toList()) { Choqok::Post *post = readPost(theAccount, list.toMap(), new Choqok::Post); if (post) { postList.prepend(post); } } } else { const QString err = checkForError(buffer); if (err.isEmpty()) { qCCritical(CHOQOK) << "JSON parsing failed.\nBuffer was: \n" << buffer; Q_EMIT error(theAccount, ParsingError, i18n("Could not parse the data that has been received from the server.")); } else { Q_EMIT error(theAccount, ServerError, err); } } return postList; } Choqok::Post *TwitterApiMicroBlog::readPost(Choqok::Account *theAccount, const QByteArray &buffer, Choqok::Post *post) { const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { return readPost(theAccount, json.toVariant().toMap(), post); } else { if (!post) { qCCritical(CHOQOK) << "TwitterApiMicroBlog::readPost: post is NULL!"; post = new Choqok::Post; } Q_EMIT errorPost(theAccount, post, ParsingError, i18n("Could not parse the data that has been received from the server.")); qCCritical(CHOQOK) << "JSon parsing failed. Buffer was:" << buffer; post->isError = true; return post; } } Choqok::Post *TwitterApiMicroBlog::readPost(Choqok::Account *theAccount, const QVariantMap &var, Choqok::Post *post) { if (!post) { qCCritical(CHOQOK) << "TwitterApiMicroBlog::readPost: post is NULL!"; return nullptr; } post->content = var[QLatin1String("text")].toString(); post->creationDateTime = dateFromString(var[QLatin1String("created_at")].toString()); post->isFavorited = var[QLatin1String("favorited")].toBool(); post->postId = var[QLatin1String("id")].toString(); post->replyToPostId = var[QLatin1String("in_reply_to_status_id")].toString(); post->replyToUser.userId = var[QLatin1String("in_reply_to_user_id")].toString(); post->replyToUser.userName = var[QLatin1String("in_reply_to_screen_name")].toString(); post->source = var[QLatin1String("source")].toString(); QVariantMap userMap = var[QLatin1String("user")].toMap(); post->author.description = userMap[QLatin1String("description")].toString(); post->author.location = userMap[QLatin1String("location")].toString(); post->author.realName = userMap[QLatin1String("name")].toString(); post->author.userId = userMap[QLatin1String("id")].toString(); post->author.userName = userMap[QLatin1String("screen_name")].toString(); post->author.profileImageUrl = userMap[QLatin1String("profile_image_url")].toUrl(); QVariantMap entities = var[QLatin1String("entities")].toMap(); QVariantMap mediaMap; QVariantList media = entities[QLatin1String("media")].toList(); if (media.size() > 0) { mediaMap = media.at(0).toMap(); post->media = QUrl::fromUserInput(mediaMap[QLatin1String("media_url")].toString() + QLatin1String(":small")); QVariantMap sizes = mediaMap[QLatin1String("sizes")].toMap(); QVariantMap w = sizes[QLatin1String("small")].toMap(); } else { post->media = QUrl(); } QVariantMap retweetedMap = var[QLatin1String("retweeted_status")].toMap(); if (!retweetedMap.isEmpty()) { Choqok::Post *retweetedPost = readPost(theAccount, retweetedMap, new Choqok::Post); setRepeatedOfInfo(post, retweetedPost); delete retweetedPost; } QVariantMap quotedMap = var[QLatin1String("quoted_status")].toMap(); if (!quotedMap.isEmpty()) { Choqok::Post *quotedPost = readPost(theAccount, quotedMap, new Choqok::Post); setQuotedPost(post, quotedPost); delete quotedPost; } post->link = postUrl(theAccount, post->author.userName, post->postId); post->isRead = post->isFavorited || (post->repeatedFromUser.userName.compare(theAccount->username(), Qt::CaseInsensitive) == 0); - + if(post->postId.isEmpty() || post->author.userName.isEmpty()) post->isError = true; - + return post; } QList< Choqok::Post * > TwitterApiMicroBlog::readDirectMessages(Choqok::Account *theAccount, const QByteArray &buffer) { QList postList; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { for (const QVariant &list: json.toVariant().toList()) { postList.prepend(readDirectMessage(theAccount, list.toMap())); } } else { const QString err = checkForError(buffer); if (err.isEmpty()) { qCCritical(CHOQOK) << "JSON parsing failed.\nBuffer was: \n" << buffer; Q_EMIT error(theAccount, ParsingError, i18n("Could not parse the data that has been received from the server.")); } else { Q_EMIT error(theAccount, ServerError, err); } } return postList; } Choqok::Post *TwitterApiMicroBlog::readDirectMessage(Choqok::Account *theAccount, const QByteArray &buffer) { const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { return readDirectMessage(theAccount, json.toVariant().toMap()); } else { Choqok::Post *post = new Choqok::Post; post->isError = true; return post; } } Choqok::Post *TwitterApiMicroBlog::readDirectMessage(Choqok::Account *theAccount, const QVariantMap &var) { Choqok::Post *msg = new Choqok::Post; msg->isPrivate = true; QString senderId, recipientId, senderScreenName, recipientScreenName, senderName, senderDescription, recipientName, recipientDescription; QUrl senderProfileImageUrl, recipientProfileImageUrl; msg->creationDateTime = dateFromString(var[QLatin1String("created_at")].toString()); msg->content = var[QLatin1String("text")].toString(); msg->postId = var[QLatin1String("id")].toString();; senderId = var[QLatin1String("sender_id")].toString(); recipientId = var[QLatin1String("recipient_id")].toString(); senderScreenName = var[QLatin1String("sender_screen_name")].toString(); recipientScreenName = var[QLatin1String("recipient_screen_name")].toString(); QVariantMap sender = var[QLatin1String("sender")].toMap(); senderProfileImageUrl = sender[QLatin1String("profile_image_url")].toUrl(); senderName = sender[QLatin1String("name")].toString(); senderDescription = sender[QLatin1String("description")].toString(); QVariantMap recipient = var[QLatin1String("recipient")].toMap(); recipientProfileImageUrl = recipient[QLatin1String("profile_image_url")].toUrl(); recipientName = recipient[QLatin1String("name")].toString(); recipientDescription = recipient[QLatin1String("description")].toString(); if (senderScreenName.compare(theAccount->username(), Qt::CaseInsensitive) == 0) { msg->author.description = recipientDescription; msg->author.userName = recipientScreenName; msg->author.profileImageUrl = recipientProfileImageUrl; msg->author.realName = recipientName; msg->author.userId = recipientId; msg->replyToUser.userId = recipientId; msg->replyToUser.userName = recipientScreenName; msg->isRead = true; } else { msg->author.description = senderDescription; msg->author.userName = senderScreenName; msg->author.profileImageUrl = senderProfileImageUrl; msg->author.realName = senderName; msg->author.userId = senderId; msg->replyToUser.userId = recipientId; msg->replyToUser.userName = recipientScreenName; } return msg; } Choqok::User *TwitterApiMicroBlog::readUserInfo(const QByteArray &buffer) { Choqok::User *user = nullptr; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { user = new Choqok::User(readUser(nullptr, json.toVariant().toMap())); } else { QString err = i18n("Retrieving the friends list failed. The data returned from the server is corrupted."); qCDebug(CHOQOK) << "JSON parse error:the buffer is: \n" << buffer; Q_EMIT error(nullptr, ParsingError, err, Critical); } return user; } QStringList TwitterApiMicroBlog::readFriendsScreenName(Choqok::Account *theAccount, const QByteArray &buffer) { QStringList list; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { const QVariantMap map = json.toVariant().toMap(); QVariantList jsonList = map[QLatin1String("users")].toList(); QString nextCursor = map[QLatin1String("next_cursor_str")].toString(); if (nextCursor.isEmpty()) { nextCursor = QLatin1String("0"); // we probably ran the rate limit; stop bugging the server already } for (const QVariant &user: jsonList) { list << user.toMap()[QLatin1String("screen_name")].toString(); } d->friendsCursor = nextCursor; } else { QString err = i18n("Retrieving the friends list failed. The data returned from the server is corrupted."); qCDebug(CHOQOK) << "JSON parse error:the buffer is: \n" << buffer; Q_EMIT error(theAccount, ParsingError, err, Critical); } return list; } QStringList TwitterApiMicroBlog::readFollowersScreenName(Choqok::Account *theAccount, const QByteArray &buffer) { QStringList list; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { const QVariantMap map = json.toVariant().toMap(); QVariantList jsonList = map[QLatin1String("users")].toList(); QString nextCursor = map[QLatin1String("next_cursor_str")].toString(); if (nextCursor.isEmpty()) { nextCursor = QLatin1String("0"); // we probably ran the rate limit; stop bugging the server already } for (const QVariant &user: jsonList) { list << user.toMap()[QLatin1String("screen_name")].toString(); } d->followersCursor = nextCursor; } else { QString err = i18n("Retrieving the followers list failed. The data returned from the server is corrupted."); qCDebug(CHOQOK) << "JSON parse error:the buffer is: \n" << buffer; Q_EMIT error(theAccount, ParsingError, err, Critical); } return list; } Choqok::User TwitterApiMicroBlog::readUser(Choqok::Account *theAccount, const QVariantMap &map) { Q_UNUSED(theAccount); Choqok::User u; u.description = map[QLatin1String("description")].toString(); u.homePageUrl = map[QLatin1String("url")].toUrl(); u.isProtected = map[QLatin1String("protected")].toBool(); u.location = map[QLatin1String("location")].toString(); u.profileImageUrl = map[QLatin1String("profile_image_url")].toUrl(); u.realName = map[QLatin1String("name")].toString(); u.userId = map[QLatin1String("id_str")].toString(); u.userName = map[QLatin1String("screen_name")].toString(); return u; } diff --git a/microblogs/mastodon/mastodonmicroblog.cpp b/microblogs/mastodon/mastodonmicroblog.cpp index 824aff5a..a850305b 100644 --- a/microblogs/mastodon/mastodonmicroblog.cpp +++ b/microblogs/mastodon/mastodonmicroblog.cpp @@ -1,990 +1,990 @@ /* This file is part of Choqok, the KDE micro-blogging client Copyright (C) 2017 Andrea Scarpino This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see http://www.gnu.org/licenses/ */ #include "mastodonmicroblog.h" #include #include #include #include #include #include #include #include #include #include "accountmanager.h" #include "application.h" #include "choqokappearancesettings.h" #include "choqokbehaviorsettings.h" #include "notifymanager.h" #include "postwidget.h" #include "mastodonaccount.h" #include "mastodoncomposerwidget.h" #include "mastodondebug.h" #include "mastodondmessagedialog.h" #include "mastodoneditaccountwidget.h" #include "mastodonpost.h" #include "mastodonpostwidget.h" class MastodonMicroBlog::Private { public: Private(): countOfTimelinesToSave(0) {} int countOfTimelinesToSave; }; K_PLUGIN_FACTORY_WITH_JSON(MastodonMicroBlogFactory, "choqok_mastodon.json", registerPlugin < MastodonMicroBlog > ();) const QString MastodonMicroBlog::homeTimeline(QLatin1String("/api/v1/timelines/home")); const QString MastodonMicroBlog::publicTimeline(QLatin1String("/api/v1/timelines/public")); const QString MastodonMicroBlog::favouritesTimeline(QLatin1String("/api/v1/favourites")); MastodonMicroBlog::MastodonMicroBlog(QObject *parent, const QVariantList &args): MicroBlog(QStringLiteral("Mastodon") , parent), d(new Private) { Q_UNUSED(args) setServiceName(QLatin1String("Mastodon")); setServiceHomepageUrl(QLatin1String("https://mastodon.social")); QStringList timelineNames; timelineNames << QLatin1String("Home") << QLatin1String("Local") << QLatin1String("Federated") << QLatin1String("Favourites"); setTimelineNames(timelineNames); setTimelinesInfo(); } MastodonMicroBlog::~MastodonMicroBlog() { qDeleteAll(m_timelinesInfos); delete d; } void MastodonMicroBlog::aboutToUnload() { for (Choqok::Account *acc: Choqok::AccountManager::self()->accounts()) { if (acc->microblog() == this) { d->countOfTimelinesToSave += acc->timelineNames().count(); } } Q_EMIT saveTimelines(); } ChoqokEditAccountWidget *MastodonMicroBlog::createEditAccountWidget(Choqok::Account *account, QWidget *parent) { MastodonAccount *acc = qobject_cast(account); if (acc || !account) { return new MastodonEditAccountWidget(this, acc, parent); } else { qCDebug(CHOQOK) << "Account passed here was not a valid MastodonAccount!"; return 0; } } Choqok::UI::ComposerWidget *MastodonMicroBlog::createComposerWidget(Choqok::Account *account, QWidget *parent) { return new MastodonComposerWidget(account, parent); } void MastodonMicroBlog::createPost(Choqok::Account *theAccount, Choqok::Post *post) { if (!post || post->content.isEmpty()) { qCDebug(CHOQOK) << "ERROR: Status text is empty!"; Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::OtherError, i18n("Creating the new post failed. Text is empty."), MicroBlog::Critical); return; } MastodonAccount *acc = qobject_cast(theAccount); if (acc) { QVariantMap object; object.insert(QLatin1String("status"), post->content); const QByteArray data = QJsonDocument::fromVariant(object).toJson(); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1String("/api/v1/statuses")); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_createPostJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotCreatePost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!"; } } Choqok::Account *MastodonMicroBlog::createNewAccount(const QString &alias) { MastodonAccount *acc = qobject_cast( Choqok::AccountManager::self()->findAccount(alias)); if (!acc) { return new MastodonAccount(this, alias); } else { qCDebug(CHOQOK) << "Cannot create a new MastodonAccount!"; return 0; } } QString MastodonMicroBlog::lastTimelineId(Choqok::Account *theAccount, const QString &timeline) const { qCDebug(CHOQOK) << "Latest ID for timeline " << timeline << m_timelinesLatestIds[theAccount][timeline]; return m_timelinesLatestIds[theAccount][timeline]; } QList< Choqok::Post * > MastodonMicroBlog::readTimeline(const QByteArray &buffer) { QList posts; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { const QVariantList list = json.array().toVariantList(); for (const QVariant &element: list) { posts.prepend(readPost(element.toMap(), new MastodonPost)); } } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } return posts; } Choqok::Post *MastodonMicroBlog::readPost(const QVariantMap &var, Choqok::Post *post) { MastodonPost *p = dynamic_cast< MastodonPost * >(post); if (p) { QVariantMap reblog = var[QLatin1String("reblog")].toMap(); QVariantMap status; if (reblog.isEmpty()) { status = var; } else { status = reblog; } QTextDocument content; content.setHtml(status[QLatin1String("spoiler_text")].toString() + QLatin1String("
") + status[QLatin1String("content")].toString()); p->content += content.toPlainText().trimmed(); p->creationDateTime = QDateTime::fromString(var[QLatin1String("created_at")].toString(), Qt::ISODate); p->creationDateTime.setTimeSpec(Qt::UTC); p->link = status[QLatin1String("url")].toUrl(); p->isFavorited = var[QLatin1String("favourited")].toBool(); if (p->isFavorited) { p->isRead = true; } p->postId = var[QLatin1String("id")].toString(); p->conversationId = var[QLatin1String("id")].toString(); QVariantMap application = var[QLatin1String("application")].toMap(); if (!application.isEmpty()) { const QString client = application[QLatin1String("name")].toString(); if (application[QLatin1String("website")].toString().isEmpty()) { p->source = client; } else { p->source = QStringLiteral("%2").arg(application[QLatin1String("website")].toString()).arg(client); } } if (var[QLatin1String("visibility")].toString().compare(QLatin1String("direct")) == 0) { p->isPrivate = true; } QVariantMap account = status[QLatin1Literal("account")].toMap(); p->author.userId = account[QLatin1String("id")].toString(); p->author.userName = account[QLatin1String("acct")].toString(); p->author.realName = account[QLatin1String("display_name")].toString(); p->author.homePageUrl = account[QLatin1String("url")].toUrl(); QTextDocument description; description.setHtml(account[QLatin1String("note")].toString()); p->author.description = description.toPlainText().trimmed(); p->author.profileImageUrl = account[QLatin1String("avatar")].toUrl(); p->replyToPostId = var[QLatin1String("in_reply_to_id")].toString(); p->replyToUser.userId = var[QLatin1String("in_reply_to_account_id")].toString(); if (!reblog.isEmpty()) { p->repeatedDateTime = QDateTime::fromString(var[QLatin1String("created_at")].toString(), Qt::ISODate); p->repeatedDateTime.setTimeSpec(Qt::UTC); p->repeatedPostId = var[QLatin1String("id")].toString(); const QVariantMap repeatedFrom = var[QLatin1Literal("account")].toMap(); p->repeatedFromUser.userId = repeatedFrom[QLatin1String("id")].toString(); p->repeatedFromUser.userName = repeatedFrom[QLatin1String("acct")].toString(); p->repeatedFromUser.homePageUrl = repeatedFrom[QLatin1String("url")].toUrl(); } return p; } else { qCDebug(CHOQOK) << "post is not a MastodonPost!"; return post; } } void MastodonMicroBlog::createReply(Choqok::Account *theAccount, MastodonPost *post) { MastodonAccount *acc = qobject_cast(theAccount); if (acc) { QVariantMap object; object.insert(QLatin1String("status"), post->content); if (!post->replyToPostId.isEmpty()) { object.insert(QLatin1String("in_reply_to_id"), post->replyToPostId); } const QByteArray data = QJsonDocument::fromVariant(object).toJson(); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1String("/api/v1/statuses")); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_createPostJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotCreatePost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!"; } } void MastodonMicroBlog::toggleReblog(Choqok::Account *theAccount, Choqok::Post *post) { MastodonAccount *acc = qobject_cast(theAccount); if (acc) { QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); if (acc->username().compare(post->repeatedFromUser.userName) == 0) { url.setPath(url.path() + QStringLiteral("/api/v1/statuses/%1/unreblog").arg(post->postId)); } else { url.setPath(url.path() + QStringLiteral("/api/v1/statuses/%1/reblog").arg(post->postId)); } KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_shareJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotReblog); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!"; } } void MastodonMicroBlog::slotReblog(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_shareJobs.take(job); Choqok::Account *theAccount = m_accountJobs.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("The post has been shared.")); KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { ret = 0; } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot share the post. %1", job->errorString())); } } void MastodonMicroBlog::toggleFavorite(Choqok::Account *theAccount, Choqok::Post *post) { MastodonAccount *acc = qobject_cast(theAccount); if (acc) { QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); if (post->isFavorited) { url.setPath(url.path() + QStringLiteral("/api/v1/statuses/%1/unfavourite").arg(post->postId)); } else { url.setPath(url.path() + QStringLiteral("/api/v1/statuses/%1/favourite").arg(post->postId)); } KIO::StoredTransferJob *job = KIO::storedHttpPost(QByteArray(), url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_favoriteJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotFavorite); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!"; } } void MastodonMicroBlog::slotFavorite(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_favoriteJobs.take(job); Choqok::Account *theAccount = m_accountJobs.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot set/unset the post as favorite. %1", job->errorString())); } else { post->isFavorited = !post->isFavorited; Q_EMIT favorite(theAccount, post); } } void MastodonMicroBlog::setLastTimelineId(Choqok::Account *theAccount, const QString &timeline, const QString &id) { m_timelinesLatestIds[theAccount][timeline] = id; } void MastodonMicroBlog::setTimelinesInfo() { Choqok::TimelineInfo *t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Home"); t->description = i18nc("Timeline description", "You and people you follow"); t->icon = QLatin1String("user-home"); m_timelinesInfos[QLatin1String("Home")] = t; m_timelinesPaths[QLatin1String("Home")] = homeTimeline; t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Local"); t->description = i18nc("Timeline description", "Local timeline"); t->icon = QLatin1String("folder-public"); m_timelinesInfos[QLatin1String("Local")] = t; m_timelinesPaths[QLatin1String("Local")] = publicTimeline; t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Federated"); t->description = i18nc("Timeline description", "Federated timeline"); t->icon = QLatin1String("folder-remote"); m_timelinesInfos[QLatin1String("Federated")] = t; m_timelinesPaths[QLatin1String("Federated")] = publicTimeline; t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Favourites"); t->description = i18nc("Timeline description", "Favourites"); t->icon = QLatin1String("favorites"); m_timelinesInfos[QLatin1String("Favourites")] = t; m_timelinesPaths[QLatin1String("Favourites")] = favouritesTimeline; } void MastodonMicroBlog::removePost(Choqok::Account *theAccount, Choqok::Post *post) { MastodonAccount *acc = qobject_cast(theAccount); if (acc) { QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QStringLiteral("/api/v1/statuses/%1").arg(post->postId)); KIO::TransferJob *job = KIO::http_delete(url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_removePostJobs[job] = post; connect(job, &KIO::TransferJob::result, this, &MastodonMicroBlog::slotRemovePost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!"; } } QList MastodonMicroBlog::loadTimeline(Choqok::Account *account, const QString &timelineName) { QList< Choqok::Post * > list; const QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(), timelineName); const KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation); const QStringList tmpList = postsBackup.groupList(); // don't load old archives if (tmpList.isEmpty() || !(QDateTime::fromString(tmpList.first()).isValid())) { return list; } QList groupList; for (const QString &str: tmpList) { groupList.append(QDateTime::fromString(str)); } - qSort(groupList); + std::sort(groupList.begin(), groupList.end()); MastodonPost *st; for (const QDateTime &datetime: groupList) { st = new MastodonPost; KConfigGroup grp(&postsBackup, datetime.toString()); st->creationDateTime = grp.readEntry("creationDateTime", QDateTime::currentDateTime()); st->postId = grp.readEntry("postId", QString()); st->link = grp.readEntry("link", QUrl()); st->content = grp.readEntry("content", QString()); st->source = grp.readEntry("source", QString()); st->isFavorited = grp.readEntry("favorited", false); st->author.userId = grp.readEntry("authorId", QString()); st->author.userName = grp.readEntry("authorUserName", QString()); st->author.realName = grp.readEntry("authorRealName", QString()); st->author.description = grp.readEntry("authorDescription" , QString()); st->author.profileImageUrl = grp.readEntry("authorProfileImageUrl", QUrl()); st->author.homePageUrl = grp.readEntry("authorHomePageUrl", QUrl()); st->isRead = grp.readEntry("isRead", true); st->conversationId = grp.readEntry("conversationId", QString()); st->replyToPostId = grp.readEntry("replyToPostId", QString()); st->replyToUser.userId = grp.readEntry("replyToUserId", QString()); st->repeatedFromUser.userId = grp.readEntry("repeatedFromUserId", QString()); st->repeatedFromUser.userName = grp.readEntry("repeatedFromUserName", QString()); st->repeatedFromUser.homePageUrl = grp.readEntry("repeatedFromUserHomePage", QUrl()); st->repeatedPostId = grp.readEntry("repeatedPostId", QString()); st->repeatedDateTime = grp.readEntry("repeatedDateTime", QDateTime()); list.append(st); } if (!list.isEmpty()) { setLastTimelineId(account, timelineName, list.last()->conversationId); } return list; } QUrl MastodonMicroBlog::profileUrl(Choqok::Account *account, const QString &username) const { if (username.contains(QLatin1Char('@'))) { return QUrl::fromUserInput(QStringLiteral("https://%1/@%2").arg(hostFromAcct(username)).arg(userNameFromAcct(username))); } else { MastodonAccount *acc = qobject_cast(account); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(QLatin1String("/@") + username); return url; } } QString MastodonMicroBlog::generateRepeatedByUserTooltip(const QString &username) const { if (Choqok::AppearanceSettings::showRetweetsInChoqokWay()) { return i18n("Boost of %1", username); } else { return i18n("Boosted by %1", username); } } void MastodonMicroBlog::showDirectMessageDialog(MastodonAccount *theAccount, const QString &toUsername) { qCDebug(CHOQOK); if (!theAccount) { QAction *act = qobject_cast(sender()); theAccount = qobject_cast( Choqok::AccountManager::self()->findAccount(act->data().toString())); } MastodonDMessageDialog *dmsg = new MastodonDMessageDialog(theAccount, Choqok::UI::Global::mainWindow()); if (!toUsername.isEmpty()) { dmsg->setTo(toUsername); } dmsg->show(); } QString MastodonMicroBlog::hostFromAcct(const QString &acct) { if (acct.contains(QLatin1Char('@'))) { return acct.split(QLatin1Char('@'))[1]; } else { return acct; } } QString MastodonMicroBlog::userNameFromAcct(const QString &acct) { if (acct.contains(QLatin1Char('@'))) { return acct.split(QLatin1Char('@'))[0]; } else { return acct; } } void MastodonMicroBlog::saveTimeline(Choqok::Account *account, const QString &timelineName, const QList< Choqok::UI::PostWidget * > &timeline) { const QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(), timelineName); KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation); ///Clear previous data: for (const QString &group: postsBackup.groupList()) { postsBackup.deleteGroup(group); } for (Choqok::UI::PostWidget *wd: timeline) { MastodonPost *post = dynamic_cast(wd->currentPost()); KConfigGroup grp(&postsBackup, post->creationDateTime.toString()); grp.writeEntry("creationDateTime", post->creationDateTime); grp.writeEntry("postId", post->postId); grp.writeEntry("link", post->link); grp.writeEntry("content", post->content); grp.writeEntry("source", post->source); grp.writeEntry("favorited", post->isFavorited); grp.writeEntry("authorId", post->author.userId); grp.writeEntry("authorRealName", post->author.realName); grp.writeEntry("authorUserName", post->author.userName); grp.writeEntry("authorDescription", post->author.description); grp.writeEntry("authorProfileImageUrl", post->author.profileImageUrl); grp.writeEntry("authorHomePageUrl", post->author.homePageUrl); grp.writeEntry("isRead", post->isRead); grp.writeEntry("conversationId", post->conversationId); grp.writeEntry("replyToPostId", post->replyToPostId); grp.writeEntry("replyToUserId", post->replyToUser.userId); grp.writeEntry("repeatedFromUserId", post->repeatedFromUser.userId); grp.writeEntry("repeatedFromUserName", post->repeatedFromUser.userName); grp.writeEntry("repeatedFromUserHomePage", post->repeatedFromUser.homePageUrl); grp.writeEntry("repeatedPostId", post->repeatedPostId); grp.writeEntry("repeatedDateTime", post->repeatedDateTime); } postsBackup.sync(); if (Choqok::Application::isShuttingDown()) { --d->countOfTimelinesToSave; if (d->countOfTimelinesToSave < 1) { Q_EMIT readyForUnload(); } } } Choqok::TimelineInfo *MastodonMicroBlog::timelineInfo(const QString &timelineName) { return m_timelinesInfos.value(timelineName); } void MastodonMicroBlog::updateTimelines(Choqok::Account *theAccount) { MastodonAccount *acc = qobject_cast(theAccount); if (acc) { for (const QString &timeline: acc->timelineNames()) { QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + m_timelinesPaths[timeline]); QUrlQuery query; if (timeline.compare(QLatin1String("Local")) == 0) { query.addQueryItem(QLatin1String("local"), QLatin1String("true")); } url.setQuery(query); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; continue; } job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); m_timelinesRequests[job] = timeline; m_accountJobs[job] = acc; connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotUpdateTimeline); job->start(); } } else { qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!"; } } QString MastodonMicroBlog::authorizationMetaData(MastodonAccount *account) const { return QStringLiteral("Authorization: Bearer ") + account->oAuth()->token(); } Choqok::UI::PostWidget *MastodonMicroBlog::createPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) { return new MastodonPostWidget(account, post, parent); } void MastodonMicroBlog::fetchPost(Choqok::Account *theAccount, Choqok::Post *post) { MastodonAccount *acc = qobject_cast(theAccount); if (acc) { if (!post->link.toDisplayString().startsWith(acc->host())) { qCDebug(CHOQOK) << "You can only fetch posts from your host!"; return; } QUrl url(post->link); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc)); m_accountJobs[job] = acc; connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotFetchPost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a MastodonAccount!"; } } void MastodonMicroBlog::slotCreatePost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_createPostJobs.take(job); Choqok::Account *theAccount = m_accountJobs.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantMap reply = json.toVariant().toMap(); if (!reply[QLatin1String("id")].toString().isEmpty()) { Choqok::NotifyManager::success(i18n("New post for account %1 submitted successfully.", theAccount->alias())); ret = 0; Q_EMIT postCreated(theAccount, post); } } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError, i18n("Creating the new post failed. %1", job->errorString()), MicroBlog::Critical); } } void MastodonMicroBlog::slotFetchPost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Account *theAccount = m_accountJobs.take(job); if (!theAccount) { qCDebug(CHOQOK) << "Account or postId is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantMap reply = json.toVariant().toMap(); MastodonPost *post = new MastodonPost; readPost(reply, post); ret = 0; Q_EMIT postFetched(theAccount, post); } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot fetch post. %1", job->errorString()), MicroBlog::Critical); } } void MastodonMicroBlog::slotRemovePost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_removePostJobs.take(job); Choqok::Account *theAccount = m_accountJobs.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::TransferJob *j = qobject_cast(job); if (j->metaData().contains(QStringLiteral("responsecode"))) { int responseCode = j->queryMetaData(QStringLiteral("responsecode")).toInt(); if (responseCode == 200 || responseCode == 404) { ret = 0; Q_EMIT postRemoved(theAccount, post); } } } if (ret) { Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError, i18n("Removing the post failed. %1", job->errorString()), MicroBlog::Critical); } } void MastodonMicroBlog::slotUpdateTimeline(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Account *account = m_accountJobs.take(job); if (!account) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(account, Choqok::MicroBlog::CommunicationError, i18n("An error occurred when fetching the timeline")); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QList list = readTimeline(j->data()); const QString timeline(m_timelinesRequests.take(job)); if (!list.isEmpty()) { setLastTimelineId(account, timeline, list.last()->conversationId); } Q_EMIT timelineDataReceived(account, timeline, list); } } void MastodonMicroBlog::fetchFollowers(MastodonAccount* theAccount, bool active) { qCDebug(CHOQOK); QUrl url(theAccount->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QStringLiteral("/api/v1/accounts/%1/followers").arg(theAccount->id())); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("limit"), QLatin1String("80")); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(theAccount)); mJobsAccount[job] = theAccount; if (active) { connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotRequestFollowersScreenNameActive); } else { connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotRequestFollowersScreenNamePassive); } job->start(); Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Updating followers list for account %1...", theAccount->alias())); } void MastodonMicroBlog::slotRequestFollowersScreenNameActive(KJob* job) { finishRequestFollowersScreenName(job, true); } void MastodonMicroBlog::slotRequestFollowersScreenNamePassive(KJob* job) { finishRequestFollowersScreenName(job, false); } void MastodonMicroBlog::finishRequestFollowersScreenName(KJob *job, bool active) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::MicroBlog::ErrorLevel level = active ? Critical : Low; MastodonAccount *account = qobject_cast(mJobsAccount.take(job)); if (!account) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(account, ServerError, i18n("Followers list for account %1 could not be updated:\n%2", account->username(), job->errorString()), level); return; } else { KIO::StoredTransferJob *j = qobject_cast(job); const QByteArray buffer = j->data(); const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { QStringList followers; for (const QVariant &user: json.array().toVariantList()) { followers.append(user.toMap()[QLatin1String("acct")].toString()); } account->setFollowers(followers); } else { QString err = i18n("Retrieving the followers list failed. The data returned from the server is corrupted."); qCDebug(CHOQOK) << "JSON parse error:the buffer is: \n" << buffer; Q_EMIT error(account, ParsingError, err, Critical); } } } void MastodonMicroBlog::fetchFollowing(MastodonAccount* theAccount, bool active) { qCDebug(CHOQOK); QUrl url(theAccount->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QStringLiteral("/api/v1/accounts/%1/following").arg(theAccount->id())); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("limit"), QLatin1String("80")); url.setQuery(urlQuery); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(theAccount)); mJobsAccount[job] = theAccount; if (active) { connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotRequestFollowingScreenNameActive); } else { connect(job, &KIO::StoredTransferJob::result, this, &MastodonMicroBlog::slotRequestFollowingScreenNamePassive); } job->start(); Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Updating following list for account %1...", theAccount->alias())); } void MastodonMicroBlog::slotRequestFollowingScreenNameActive(KJob* job) { finishRequestFollowingScreenName(job, true); } void MastodonMicroBlog::slotRequestFollowingScreenNamePassive(KJob* job) { finishRequestFollowingScreenName(job, false); } void MastodonMicroBlog::finishRequestFollowingScreenName(KJob *job, bool active) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::MicroBlog::ErrorLevel level = active ? Critical : Low; MastodonAccount *account = qobject_cast(mJobsAccount.take(job)); if (!account) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(account, ServerError, i18n("Following list for account %1 could not be updated:\n%2", account->username(), job->errorString()), level); return; } else { KIO::StoredTransferJob *j = qobject_cast(job); const QByteArray buffer = j->data(); const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { QStringList following; for (const QVariant &user: json.array().toVariantList()) { following.append(user.toMap()[QLatin1String("acct")].toString()); } account->setFollowing(following); } else { QString err = i18n("Retrieving the following list failed. The data returned from the server is corrupted."); qCDebug(CHOQOK) << "JSON parse error:the buffer is: \n" << buffer; Q_EMIT error(account, ParsingError, err, Critical); } } } #include "mastodonmicroblog.moc" diff --git a/microblogs/ocs/ocsmicroblog.cpp b/microblogs/ocs/ocsmicroblog.cpp index 27490ed9..9b8b5e93 100644 --- a/microblogs/ocs/ocsmicroblog.cpp +++ b/microblogs/ocs/ocsmicroblog.cpp @@ -1,311 +1,311 @@ /* This file is part of Choqok, the KDE micro-blogging client Copyright (C) 2010-2012 Mehrdad Momeny This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see http://www.gnu.org/licenses/ */ #include "ocsmicroblog.h" #include #include #include #include #include #include "application.h" #include "accountmanager.h" #include "editaccountwidget.h" #include "postwidget.h" #include "ocsaccount.h" #include "ocsdebug.h" #include "ocsconfigurewidget.h" K_PLUGIN_FACTORY_WITH_JSON(OCSMicroblogFactory, "choqok_ocs.json", registerPlugin < OCSMicroblog > ();) OCSMicroblog::OCSMicroblog(QObject *parent, const QVariantList &) : MicroBlog(QLatin1String("choqok_ocs"), parent) , mProviderManager(new Attica::ProviderManager) , mIsOperational(false) { connect(mProviderManager, &Attica::ProviderManager::defaultProvidersLoaded, this, &OCSMicroblog::slotDefaultProvidersLoaded); mProviderManager->loadDefaultProviders(); setServiceName(QLatin1String("Social Desktop Activities")); } OCSMicroblog::~OCSMicroblog() { delete mProviderManager; } void OCSMicroblog::saveTimeline(Choqok::Account *account, const QString &timelineName, const QList< Choqok::UI::PostWidget * > &timeline) { qCDebug(CHOQOK); QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(), timelineName); KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation); ///Clear previous data: for (const QString &group: postsBackup.groupList()) { postsBackup.deleteGroup(group); } for (Choqok::UI::PostWidget *wd: timeline) { const Choqok::Post *post = wd->currentPost(); KConfigGroup grp(&postsBackup, post->creationDateTime.toString()); grp.writeEntry("creationDateTime", post->creationDateTime); grp.writeEntry("postId", post->postId); grp.writeEntry("text", post->content); grp.writeEntry("authorId", post->author.userId); grp.writeEntry("authorUserName", post->author.userName); grp.writeEntry("authorRealName", post->author.realName); grp.writeEntry("authorProfileImageUrl", post->author.profileImageUrl); grp.writeEntry("authorDescription" , post->author.description); grp.writeEntry("authorLocation" , post->author.location); grp.writeEntry("authorUrl" , post->author.homePageUrl); grp.writeEntry("link", post->link); grp.writeEntry("isRead" , post->isRead); } postsBackup.sync(); if (Choqok::Application::isShuttingDown()) { Q_EMIT readyForUnload(); } } QList< Choqok::Post * > OCSMicroblog::loadTimeline(Choqok::Account *account, const QString &timelineName) { qCDebug(CHOQOK) << timelineName; QList< Choqok::Post * > list; QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(), timelineName); KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation); QStringList tmpList = postsBackup.groupList(); QList groupList; for (const QString &str: tmpList) { groupList.append(QDateTime::fromString(str)); } - qSort(groupList); + std::sort(groupList.begin(), groupList.end()); int count = groupList.count(); if (count) { Choqok::Post *st = nullptr; for (int i = 0; i < count; ++i) { st = new Choqok::Post; KConfigGroup grp(&postsBackup, groupList[i].toString()); st->creationDateTime = grp.readEntry("creationDateTime", QDateTime::currentDateTime()); st->postId = grp.readEntry("postId", QString()); st->content = grp.readEntry("text", QString()); st->author.userId = grp.readEntry("authorId", QString()); st->author.userName = grp.readEntry("authorUserName", QString()); st->author.realName = grp.readEntry("authorRealName", QString()); st->author.profileImageUrl = grp.readEntry("authorProfileImageUrl", QUrl()); st->author.description = grp.readEntry("authorDescription" , QString()); st->author.location = grp.readEntry("authorLocation", QString()); st->author.homePageUrl = grp.readEntry("authorUrl", QUrl()); st->link = grp.readEntry("link", QUrl()); st->isRead = grp.readEntry("isRead", true); list.append(st); } } return list; } Choqok::Account *OCSMicroblog::createNewAccount(const QString &alias) { OCSAccount *acc = qobject_cast(Choqok::AccountManager::self()->findAccount(alias)); if (!acc) { return new OCSAccount(this, alias); } else { return nullptr;//If there's an account with this alias, So We can't create a new one } } ChoqokEditAccountWidget *OCSMicroblog::createEditAccountWidget(Choqok::Account *account, QWidget *parent) { qCDebug(CHOQOK); OCSAccount *acc = qobject_cast(account); if (acc || !account) { return new OCSConfigureWidget(this, acc, parent); } else { qCDebug(CHOQOK) << "Account passed here was not a valid OCSAccount!"; return nullptr; } } void OCSMicroblog::createPost(Choqok::Account *theAccount, Choqok::Post *post) { if (!mIsOperational) { Q_EMIT errorPost(theAccount, post, OtherError, i18n("OCS plugin is not initialized yet. Try again later.")); return; } qCDebug(CHOQOK); OCSAccount *acc = qobject_cast(theAccount); Attica::PostJob *job = acc->provider().postActivity(post->content); mJobsAccount.insert(job, acc); mJobsPost.insert(job, post); connect(job, &Attica::PostJob::finished, this, &OCSMicroblog::slotCreatePost); job->start(); } void OCSMicroblog::slotCreatePost(Attica::BaseJob *job) { OCSAccount *acc = mJobsAccount.take(job); Choqok::Post *post = mJobsPost.take(job); Q_EMIT postCreated(acc, post); } void OCSMicroblog::abortCreatePost(Choqok::Account *theAccount, Choqok::Post *post) { qCDebug(CHOQOK); Q_UNUSED(post); OCSAccount *acc = qobject_cast(theAccount); Attica::BaseJob *job = mJobsAccount.key(acc); if (job) { job->abort(); } } void OCSMicroblog::fetchPost(Choqok::Account *theAccount, Choqok::Post *post) { Q_UNUSED(theAccount); Q_UNUSED(post); KMessageBox::sorry(choqokMainWindow, i18n("Not Supported")); } void OCSMicroblog::removePost(Choqok::Account *theAccount, Choqok::Post *post) { Q_UNUSED(theAccount); Q_UNUSED(post); KMessageBox::sorry(choqokMainWindow, i18n("Not Supported")); } Attica::ProviderManager *OCSMicroblog::providerManager() { return mProviderManager; } void OCSMicroblog::updateTimelines(Choqok::Account *theAccount) { if (!mIsOperational) { scheduledTasks.insertMulti(theAccount, Update); return; } qCDebug(CHOQOK); OCSAccount *acc = qobject_cast(theAccount); if (!acc) { qCCritical(CHOQOK) << "OCSMicroblog::updateTimelines: acc is not an OCSAccount"; return; } Attica::ListJob *job = acc->provider().requestActivities(); mJobsAccount.insert(job, acc); connect(job, &Attica::BaseJob::finished, this, &OCSMicroblog::slotTimelineLoaded); job->start(); } void OCSMicroblog::slotTimelineLoaded(Attica::BaseJob *job) { qCDebug(CHOQOK); OCSAccount *acc = mJobsAccount.take(job); if (job->metadata().error() == Attica::Metadata::NoError) { Attica::Activity::List actList = static_cast< Attica::ListJob * >(job)->itemList(); Q_EMIT timelineDataReceived(acc, QLatin1String("Activity"), parseActivityList(actList)); } else { Q_EMIT error(acc, ServerError, job->metadata().message(), Low); } } QList< Choqok::Post * > OCSMicroblog::parseActivityList(const Attica::Activity::List &list) { qCDebug(CHOQOK) << list.count(); QList< Choqok::Post * > resultList; for (const Attica::Activity &act: list) { Choqok::Post *pst = new Choqok::Post; pst->postId = act.id(); pst->content = act.message(); pst->creationDateTime = act.timestamp(); pst->link = act.link(); pst->isError = !act.isValid(); pst->author.userId = act.associatedPerson().id(); pst->author.userName = act.associatedPerson().id(); pst->author.homePageUrl = QUrl::fromUserInput(act.associatedPerson().homepage()); pst->author.location = QStringLiteral("%1(%2)").arg(act.associatedPerson().country()) .arg(act.associatedPerson().city()); pst->author.profileImageUrl = act.associatedPerson().avatarUrl(); pst->author.realName = QStringLiteral("%1 %2").arg(act.associatedPerson().firstName()) .arg(act.associatedPerson().lastName()); resultList.insert(0, pst); } return resultList; } Choqok::TimelineInfo *OCSMicroblog::timelineInfo(const QString &timelineName) { if (timelineName == QLatin1String("Activity")) { Choqok::TimelineInfo *info = new Choqok::TimelineInfo; info->name = i18nc("Timeline Name", "Activity"); info->description = i18n("Social activities"); info->icon = QLatin1String("user-home"); return info; } else { qCCritical(CHOQOK) << "timelineName is not valid!"; return nullptr; } } bool OCSMicroblog::isOperational() { return mIsOperational; } void OCSMicroblog::slotDefaultProvidersLoaded() { qCDebug(CHOQOK); mIsOperational = true; Q_EMIT initialized(); for (Choqok::Account *acc: scheduledTasks.keys()) { switch (scheduledTasks.value(acc)) { case Update: updateTimelines(acc); break; default: break; }; } } QUrl OCSMicroblog::profileUrl(Choqok::Account *account, const QString &username) const { OCSAccount *acc = qobject_cast(account); if (acc->providerUrl().host().contains(QLatin1String("opendesktop.org"))) { return QUrl::fromUserInput(QStringLiteral("https://opendesktop.org/usermanager/search.php?username=%1").arg(username)); } return QUrl(); } void OCSMicroblog::aboutToUnload() { Q_EMIT saveTimelines(); } #include "ocsmicroblog.moc" diff --git a/microblogs/pumpio/pumpiomicroblog.cpp b/microblogs/pumpio/pumpiomicroblog.cpp index 0b8f6c44..6b74e021 100644 --- a/microblogs/pumpio/pumpiomicroblog.cpp +++ b/microblogs/pumpio/pumpiomicroblog.cpp @@ -1,1354 +1,1354 @@ /* This file is part of Choqok, the KDE micro-blogging client Copyright (C) 2013-2014 Andrea Scarpino Copyright (C) 2008-2012 Mehrdad Momeny This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see http://www.gnu.org/licenses/ */ #include "pumpiomicroblog.h" #include #include #include #include #include #include #include #include #include "accountmanager.h" #include "application.h" #include "choqokbehaviorsettings.h" #include "notifymanager.h" #include "pumpioaccount.h" #include "pumpiocomposerwidget.h" #include "pumpiodebug.h" #include "pumpioeditaccountwidget.h" #include "pumpiomessagedialog.h" #include "pumpiomicroblogwidget.h" #include "pumpiopost.h" #include "pumpiopostwidget.h" class PumpIOMicroBlog::Private { public: Private(): countOfTimelinesToSave(0) {} int countOfTimelinesToSave; }; K_PLUGIN_FACTORY_WITH_JSON(PumpIOMicroBlogFactory, "choqok_pumpio.json", registerPlugin < PumpIOMicroBlog > ();) const QString PumpIOMicroBlog::inboxActivity(QLatin1String("/api/user/%1/inbox")); const QString PumpIOMicroBlog::outboxActivity(QLatin1String("/api/user/%1/feed")); const QString PumpIOMicroBlog::PublicCollection(QLatin1String("http://activityschema.org/collection/public")); PumpIOMicroBlog::PumpIOMicroBlog(QObject *parent, const QVariantList &args): MicroBlog(QStringLiteral("Pump.IO") , parent), d(new Private) { Q_UNUSED(args) setServiceName(QLatin1String("Pump.io")); setServiceHomepageUrl(QLatin1String("http://pump.io")); QStringList timelineNames; timelineNames << QLatin1String("Activity") << QLatin1String("Favorites") << QLatin1String("Inbox") << QLatin1String("Outbox"); setTimelineNames(timelineNames); setTimelinesInfo(); } PumpIOMicroBlog::~PumpIOMicroBlog() { qDeleteAll(m_timelinesInfos); delete d; } void PumpIOMicroBlog::abortAllJobs(Choqok::Account *theAccount) { for (KJob *job: m_accountJobs.keys(theAccount)) { job->kill(KJob::EmitResult); } } void PumpIOMicroBlog::abortCreatePost(Choqok::Account *theAccount, Choqok::Post *post) { if (m_createPostJobs.isEmpty()) { return; } if (post) { m_createPostJobs.key(post)->kill(KJob::EmitResult); return; } for (KJob *job: m_createPostJobs.keys()) { if (m_accountJobs[job] == theAccount) { job->kill(KJob::EmitResult); } } } void PumpIOMicroBlog::aboutToUnload() { for (Choqok::Account *acc: Choqok::AccountManager::self()->accounts()) { if (acc->microblog() == this) { d->countOfTimelinesToSave += acc->timelineNames().count(); } } Q_EMIT saveTimelines(); } QMenu *PumpIOMicroBlog::createActionsMenu(Choqok::Account *theAccount, QWidget *parent) { QMenu *menu = MicroBlog::createActionsMenu(theAccount, parent); QAction *directMessge = new QAction(QIcon::fromTheme(QLatin1String("mail-message-new")), i18n("Send Private Message..."), menu); directMessge->setData(theAccount->alias()); connect(directMessge, &QAction::triggered, this, &PumpIOMicroBlog::showDirectMessageDialog); menu->addAction(directMessge); return menu; } Choqok::UI::MicroBlogWidget *PumpIOMicroBlog::createMicroBlogWidget(Choqok::Account *account, QWidget *parent) { return new PumpIOMicroBlogWidget(account, parent); } Choqok::UI::ComposerWidget *PumpIOMicroBlog::createComposerWidget(Choqok::Account *account, QWidget *parent) { return new PumpIOComposerWidget(account, parent); } ChoqokEditAccountWidget *PumpIOMicroBlog::createEditAccountWidget(Choqok::Account *account, QWidget *parent) { PumpIOAccount *acc = qobject_cast(account); if (acc || !account) { return new PumpIOEditAccountWidget(this, acc, parent); } else { qCDebug(CHOQOK) << "Account passed here was not a valid PumpIOAccount!"; return nullptr; } } Choqok::Account *PumpIOMicroBlog::createNewAccount(const QString &alias) { PumpIOAccount *acc = qobject_cast( Choqok::AccountManager::self()->findAccount(alias)); if (!acc) { return new PumpIOAccount(this, alias); } else { qCDebug(CHOQOK) << "Cannot create a new PumpIOAccount!"; return nullptr; } } void PumpIOMicroBlog::createPost(Choqok::Account *theAccount, Choqok::Post *post) { QVariantList to; QVariantMap thePublic; thePublic.insert(QLatin1String("objectType"), QLatin1String("collection")); thePublic.insert(QLatin1String("id"), PumpIOMicroBlog::PublicCollection); to.append(thePublic); createPost(theAccount, post, to); } void PumpIOMicroBlog::createPost(Choqok::Account *theAccount, Choqok::Post *post, const QVariantList &to, const QVariantList &cc) { if (!post || post->content.isEmpty()) { qCDebug(CHOQOK) << "ERROR: Status text is empty!"; Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::OtherError, i18n("Creating the new post failed. Text is empty."), MicroBlog::Critical); return; } PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { QVariantMap object; if (!post->postId.isEmpty()) { object.insert(QLatin1String("id"), post->postId); } if (post->type.isEmpty()) { post->type = QLatin1String("note"); } object.insert(QLatin1String("objectType"), post->type); //Convert URLs to href form post->content.replace(QRegExp(QLatin1String("((?:https?|ftp)://\\S+)")), QLatin1String("\\1")); object.insert(QLatin1String("content"), post->content); QVariantMap item; item.insert(QLatin1String("verb"), QLatin1String("post")); item.insert(QLatin1String("object"), object); item.insert(QLatin1String("to"), to); item.insert(QLatin1String("cc"), cc); const QByteArray data = QJsonDocument::fromVariant(item).toJson(); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + outboxActivity.arg(acc->username())); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_createPostJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotCreatePost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } void PumpIOMicroBlog::createReply(Choqok::Account *theAccount, PumpIOPost *post) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { post->type = QLatin1String("comment"); QVariantMap object; object.insert(QLatin1String("objectType"), post->type); //Convert URLs to href form post->content.replace(QRegExp(QLatin1String("((?:https?|ftp)://\\S+)")), QLatin1String("\\1")); object.insert(QLatin1String("content"), QUrl::toPercentEncoding(post->content)); if (!post->replyToPostId.isEmpty()) { QVariantMap inReplyTo; inReplyTo.insert(QLatin1String("id"), post->replyToPostId); inReplyTo.insert(QLatin1String("objectType"), post->replyToObjectType); object.insert(QLatin1String("inReplyTo"), inReplyTo); } QVariantMap item; item.insert(QLatin1String("verb"), QLatin1String("post")); item.insert(QLatin1String("object"), object); const QByteArray data = QJsonDocument::fromVariant(item).toJson(); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + outboxActivity.arg(acc->username())); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_createPostJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotCreatePost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } void PumpIOMicroBlog::createPostWithMedia(Choqok::Account *theAccount, Choqok::Post *post, const QString &filePath) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { QFile media(filePath); QByteArray data; if (media.open(QIODevice::ReadOnly)) { data = media.readAll(); media.close(); } else { qCDebug(CHOQOK) << "Cannot read the file"; return; } const QMimeDatabase db; const QMimeType mimetype = db.mimeTypeForFileNameAndData(filePath, data); const QString mime = mimetype.name(); if (mime == QLatin1String("application/octet-stream")) { qCDebug(CHOQOK) << "Cannot retrieve file mimetype"; return; } QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QStringLiteral("/api/user/%1/uploads").arg(acc->username())); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: ") + mime); job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_uploadJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotUpload); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } Choqok::UI::PostWidget *PumpIOMicroBlog::createPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) { return new PumpIOPostWidget(account, post, parent); } void PumpIOMicroBlog::fetchPost(Choqok::Account *theAccount, Choqok::Post *post) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { if (!post->link.toDisplayString().startsWith(acc->host())) { qCDebug(CHOQOK) << "You can only fetch posts from your host!"; return; } QUrl url(post->link); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation)); m_accountJobs[job] = acc; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotFetchPost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } void PumpIOMicroBlog::removePost(Choqok::Account *theAccount, Choqok::Post *post) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { QVariantMap object; object.insert(QLatin1String("id"), post->postId); object.insert(QLatin1String("objectType"), post->type); QVariantMap item; item.insert(QLatin1String("verb"), QLatin1String("delete")); item.insert(QLatin1String("object"), object); const QByteArray data = QJsonDocument::fromVariant(item).toJson(); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + outboxActivity.arg(acc->username())); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_removePostJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotRemovePost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } QList< Choqok::Post * > PumpIOMicroBlog::loadTimeline(Choqok::Account *account, const QString &timelineName) { QList< Choqok::Post * > list; const QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(), timelineName); const KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation); const QStringList tmpList = postsBackup.groupList(); // don't load old archives if (tmpList.isEmpty() || !(QDateTime::fromString(tmpList.first()).isValid())) { return list; } QList groupList; for (const QString &str: tmpList) { groupList.append(QDateTime::fromString(str)); } - qSort(groupList); + std::sort(groupList.begin(), groupList.end()); PumpIOPost *st; for (const QDateTime &datetime: groupList) { st = new PumpIOPost; KConfigGroup grp(&postsBackup, datetime.toString()); st->creationDateTime = grp.readEntry("creationDateTime", QDateTime::currentDateTime()); st->postId = grp.readEntry("postId", QString()); st->link = grp.readEntry("link", QUrl()); st->content = grp.readEntry("content", QString()); st->source = grp.readEntry("source", QString()); st->isFavorited = grp.readEntry("favorited", false); st->author.userId = grp.readEntry("authorId", QString()); st->author.userName = grp.readEntry("authorUserName", QString()); st->author.realName = grp.readEntry("authorRealName", QString()); st->author.location = grp.readEntry("authorLocation", QString()); st->author.description = grp.readEntry("authorDescription" , QString()); st->author.profileImageUrl = grp.readEntry("authorProfileImageUrl", QUrl()); st->author.homePageUrl = grp.readEntry("authorHomePageUrl", QUrl()); st->type = grp.readEntry("type", QString()); st->media = grp.readEntry("media", QUrl()); st->isRead = grp.readEntry("isRead", true); st->conversationId = grp.readEntry("conversationId", QString()); st->to = grp.readEntry("to", QStringList()); st->cc = grp.readEntry("cc", QStringList()); st->shares = grp.readEntry("shares", QStringList()); st->replies = grp.readEntry("replies", QUrl()); st->replyToPostId = grp.readEntry("replyToPostId", QString()); st->replyToUser.userName = grp.readEntry("replyToUserName", QString()); st->replyToObjectType = grp.readEntry("replyToObjectType", QString()); list.append(st); } if (!list.isEmpty()) { setLastTimelineId(account, timelineName, list.last()->conversationId); } return list; } QUrl PumpIOMicroBlog::postUrl(Choqok::Account *account, const QString &username, const QString &postId) const { Q_UNUSED(account); return QUrl::fromUserInput(QString(postId).replace(QLatin1String("/api/"), QLatin1Char('/') + username + QLatin1Char('/'))); } QUrl PumpIOMicroBlog::profileUrl(Choqok::Account *account, const QString &username) const { if (username.contains(QLatin1String("acct:"))) { return QUrl::fromUserInput(QStringLiteral("https://%1/%2").arg(hostFromAcct(username)).arg(userNameFromAcct(username))); } else { PumpIOAccount *acc = qobject_cast(account); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(QLatin1Char('/') + username); return url; } } void PumpIOMicroBlog::saveTimeline(Choqok::Account *account, const QString &timelineName, const QList< Choqok::UI::PostWidget * > &timeline) { const QString fileName = Choqok::AccountManager::generatePostBackupFileName(account->alias(), timelineName); KConfig postsBackup(fileName, KConfig::NoGlobals, QStandardPaths::DataLocation); ///Clear previous data: for (const QString &group: postsBackup.groupList()) { postsBackup.deleteGroup(group); } for (Choqok::UI::PostWidget *wd: timeline) { PumpIOPost *post = dynamic_cast(wd->currentPost()); KConfigGroup grp(&postsBackup, post->creationDateTime.toString()); grp.writeEntry("creationDateTime", post->creationDateTime); grp.writeEntry("postId", post->postId); grp.writeEntry("link", post->link); grp.writeEntry("content", post->content); grp.writeEntry("source", post->source); grp.writeEntry("favorited", post->isFavorited); grp.writeEntry("authorId", post->author.userId); grp.writeEntry("authorRealName", post->author.realName); grp.writeEntry("authorUserName", post->author.userName); grp.writeEntry("authorLocation", post->author.location); grp.writeEntry("authorDescription", post->author.description); grp.writeEntry("authorProfileImageUrl", post->author.profileImageUrl); grp.writeEntry("authorHomePageUrl", post->author.homePageUrl); grp.writeEntry("type", post->type); grp.writeEntry("media", post->media); grp.writeEntry("isRead", post->isRead); grp.writeEntry("conversationId", post->conversationId); grp.writeEntry("to", post->to); grp.writeEntry("cc", post->cc); grp.writeEntry("shares", post->shares); grp.writeEntry("replies", post->replies); grp.writeEntry("replyToPostId", post->replyToPostId); grp.writeEntry("replyToUserName", post->replyToUser.userName); grp.writeEntry("replyToObjectType", post->replyToObjectType); } postsBackup.sync(); if (Choqok::Application::isShuttingDown()) { --d->countOfTimelinesToSave; if (d->countOfTimelinesToSave < 1) { Q_EMIT readyForUnload(); } } } Choqok::TimelineInfo *PumpIOMicroBlog::timelineInfo(const QString &timelineName) { return m_timelinesInfos.value(timelineName); } void PumpIOMicroBlog::updateTimelines(Choqok::Account *theAccount) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { for (const QString &timeline: acc->timelineNames()) { QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + m_timelinesPaths[timeline].arg(acc->username())); QUrlQuery query; const QString lastActivityId(lastTimelineId(theAccount, timeline)); if (!lastActivityId.isEmpty()) { query.addQueryItem(QLatin1String("count"), QString::number(200)); query.addQueryItem(QLatin1String("since"), lastActivityId); } else { query.addQueryItem(QLatin1String("count"), QString::number(Choqok::BehaviorSettings::countOfPosts())); } url.setQuery(query); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; continue; } job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation)); m_timelinesRequests[job] = timeline; m_accountJobs[job] = acc; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotUpdateTimeline); job->start(); } } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } void PumpIOMicroBlog::fetchFollowing(Choqok::Account *theAccount) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QStringLiteral("/api/user/%1/following").arg(acc->username())); QUrlQuery query; query.addQueryItem(QLatin1String("count"), QString::number(200)); if (!acc->following().isEmpty()) { query.addQueryItem(QLatin1String("since"), acc->following().last()); } url.setQuery(query); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation)); m_accountJobs[job] = acc; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotFollowing); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } void PumpIOMicroBlog::fetchLists(Choqok::Account *theAccount) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QStringLiteral("/api/user/%1/lists/person").arg(acc->username())); QUrlQuery query; query.addQueryItem(QLatin1String("count"), QString::number(200)); url.setQuery(query); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation)); m_accountJobs[job] = acc; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotLists); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } void PumpIOMicroBlog::share(Choqok::Account *theAccount, Choqok::Post *post) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { QVariantMap object; object.insert(QLatin1String("objectType"), post->type); object.insert(QLatin1String("id"), post->postId); QVariantMap item; item.insert(QLatin1String("verb"), QLatin1String("share")); item.insert(QLatin1String("object"), object); const QByteArray data = QJsonDocument::fromVariant(item).toJson(); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + outboxActivity.arg(acc->username())); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_shareJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotShare); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } void PumpIOMicroBlog::toggleFavorite(Choqok::Account *theAccount, Choqok::Post *post) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { QVariantMap object; object.insert(QLatin1String("objectType"), post->type); object.insert(QLatin1String("id"), post->postId); QVariantMap item; item.insert(QLatin1String("verb"), post->isFavorited ? QLatin1String("unfavorite") : QLatin1String("favorite")); item.insert(QLatin1String("object"), object); const QByteArray data = QJsonDocument::fromVariant(item).toJson(); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + outboxActivity.arg(acc->username())); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_favoriteJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotFavorite); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } void PumpIOMicroBlog::showDirectMessageDialog() { qCDebug(CHOQOK); const QString alias = qobject_cast(sender())->data().toString(); PumpIOAccount *theAccount = qobject_cast(Choqok::AccountManager::self()->findAccount(alias)); PumpIOMessageDialog *msg = new PumpIOMessageDialog(theAccount, Choqok::UI::Global::mainWindow()); msg->show(); } void PumpIOMicroBlog::slotCreatePost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_createPostJobs.take(job); Choqok::Account *theAccount = m_accountJobs.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantMap reply = json.toVariant().toMap(); if (!reply[QLatin1String("object")].toMap().value(QLatin1String("id")).toString().isEmpty()) { Choqok::NotifyManager::success(i18n("New post for account %1 submitted successfully", theAccount->alias())); ret = 0; Q_EMIT postCreated(theAccount, post); } } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError, i18n("Creating the new post failed. %1", job->errorString()), MicroBlog::Critical); } } void PumpIOMicroBlog::slotFavorite(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_favoriteJobs.take(job); Choqok::Account *theAccount = m_accountJobs.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot set/unset the post as favorite. %1", job->errorString())); } else { post->isFavorited = !post->isFavorited; Q_EMIT favorite(theAccount, post); } } void PumpIOMicroBlog::slotFetchPost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Account *theAccount = m_accountJobs.take(job); if (!theAccount) { qCDebug(CHOQOK) << "Account or postId is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantMap reply = json.toVariant().toMap(); PumpIOPost *post = new PumpIOPost; readPost(reply, post); ret = 0; Q_EMIT postFetched(theAccount, post); } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot fetch post. %1", job->errorString()), MicroBlog::Critical); } } void PumpIOMicroBlog::slotFetchReplies(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Account *theAccount = m_accountJobs.take(job); if (!theAccount) { qCDebug(CHOQOK) << "Account or postId is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantMap reply = json.toVariant().toMap(); const QVariantList items = reply[QLatin1String("items")].toList(); for (int i = items.size() - 1; i >= 0; i--) { QVariantMap item = items.at(i).toMap(); PumpIOPost *r = new PumpIOPost; readPost(item, r); r->replyToPostId = reply[QLatin1String("url")].toString().remove(QLatin1String("/replies")); Q_EMIT postFetched(theAccount, r); } ret = 0; } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot fetch replies. %1", job->errorString()), MicroBlog::Critical); } } void PumpIOMicroBlog::slotFollowing(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Account *theAccount = m_accountJobs.take(job); if (!theAccount) { qCDebug(CHOQOK) << "Account is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } bool ret = 1; PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Following list for account %1 has been updated.", theAccount->alias())); KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantList items = json.toVariant().toMap().value(QLatin1String("items")).toList(); QStringList following; for (const QVariant &element: items) { following.append(element.toMap().value(QLatin1String("id")).toString()); } acc->setFollowing(following); ret = 0; Q_EMIT followingFetched(acc); } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } if (ret) { Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot retrieve the following list. %1", job->errorString())); } } void PumpIOMicroBlog::slotLists(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Account *theAccount = m_accountJobs.take(job); if (!theAccount) { qCDebug(CHOQOK) << "Account is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } bool ret = 1; PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Lists for account %1 has been updated.", theAccount->alias())); KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantList items = json.toVariant().toMap().value(QLatin1String("items")).toList(); QVariantList lists; for (const QVariant &element: items) { QVariantMap e = element.toMap(); QVariantMap list; list.insert(QLatin1String("id"), e.value(QLatin1String("id")).toString()); list.insert(QLatin1String("name"), e.value(QLatin1String("displayName")).toString()); lists.append(list); } acc->setLists(lists); ret = 0; Q_EMIT listsFetched(acc); } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } if (ret) { Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot retrieve the lists. %1", job->errorString())); } } void PumpIOMicroBlog::slotShare(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_shareJobs.take(job); Choqok::Account *theAccount = m_accountJobs.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("The post has been shared.")); KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantMap object = json.toVariant().toMap().value(QLatin1String("object")).toMap(); ret = 0; } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Cannot share the post. %1", job->errorString())); } } void PumpIOMicroBlog::slotRemovePost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_removePostJobs.take(job); Choqok::Account *theAccount = m_accountJobs.take(job); if (!post || !theAccount) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantMap object = json.toVariant().toMap().value(QLatin1String("object")).toMap(); if (!object[QLatin1String("deleted")].toString().isEmpty()) { Choqok::NotifyManager::success(i18n("Post removed successfully")); ret = 0; Q_EMIT postRemoved(theAccount, post); } } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT errorPost(theAccount, post, Choqok::MicroBlog::CommunicationError, i18n("Removing the post failed. %1", job->errorString()), MicroBlog::Critical); } } void PumpIOMicroBlog::slotUpdatePost(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_updateJobs.take(job); Choqok::Account *account = m_accountJobs.take(job); if (!post || !account) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { ret = 0; createPost(account, post); } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT error(account, Choqok::MicroBlog::CommunicationError, i18n("An error occurred when updating the post")); } } void PumpIOMicroBlog::slotUpdateTimeline(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Account *account = m_accountJobs.take(job); if (!account) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(account, Choqok::MicroBlog::CommunicationError, i18n("An error occurred when fetching the timeline")); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QList list = readTimeline(j->data()); const QString timeline(m_timelinesRequests.take(job)); if (!list.isEmpty()) { setLastTimelineId(account, timeline, list.last()->conversationId); } Q_EMIT timelineDataReceived(account, timeline, list); } } void PumpIOMicroBlog::slotUpload(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "Job is null pointer"; return; } Choqok::Post *post = m_uploadJobs.take(job); Choqok::Account *account = m_accountJobs.take(job); if (!post || !account) { qCDebug(CHOQOK) << "Account or Post is NULL pointer"; return; } int ret = 1; if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); } else { KIO::StoredTransferJob *j = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(j->data()); if (!json.isNull()) { const QVariantMap reply = json.toVariant().toMap(); const QString id = reply[QLatin1String("id")].toString(); if (!id.isEmpty()) { post->postId = id; post->type = reply[QLatin1String("objectType")].toString(); ret = 0; updatePost(account, post); } } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } } if (ret) { Q_EMIT error(account, Choqok::MicroBlog::CommunicationError, i18n("An error occurred when uploading the media")); } } void PumpIOMicroBlog::fetchReplies(Choqok::Account *theAccount, const QUrl &url) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { if (!url.toDisplayString().startsWith(acc->host())) { qCDebug(CHOQOK) << "You can only fetch replies from your host!"; return; } KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::GetOperation)); m_accountJobs[job] = acc; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotFetchReplies); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } QString PumpIOMicroBlog::lastTimelineId(Choqok::Account *theAccount, const QString &timeline) const { qCDebug(CHOQOK) << "Latest ID for timeline " << timeline << m_timelinesLatestIds[theAccount][timeline]; return m_timelinesLatestIds[theAccount][timeline]; } Choqok::Post *PumpIOMicroBlog::readPost(const QVariantMap &var, Choqok::Post *post) { PumpIOPost *p = dynamic_cast< PumpIOPost * >(post); if (p) { QVariantMap object; if (var.value(QLatin1String("verb")).toString() == QLatin1String("post") || var.value(QLatin1String("verb")).toString() == QLatin1String("share")) { object = var[QLatin1String("object")].toMap(); } else { object = var; } QTextDocument content; if (!object[QLatin1String("displayName")].isNull()) { content.setHtml(object[QLatin1String("displayName")].toString()); p->content = content.toPlainText().trimmed(); p->content += QLatin1Char('\n'); } content.setHtml(object[QLatin1String("content")].toString()); p->content += content.toPlainText().trimmed(); if (!object[QLatin1String("fullImage")].isNull()) { const QVariantMap fullImage = object[QLatin1String("fullImage")].toMap(); if (!fullImage.isEmpty()) { p->media = fullImage[QLatin1String("url")].toUrl(); } } p->creationDateTime = QDateTime::fromString(var[QLatin1String("published")].toString(), Qt::ISODate); p->creationDateTime.setTimeSpec(Qt::UTC); if (object[QLatin1String("pump_io")].isNull()) { p->link = object[QLatin1String("id")].toUrl(); } else { p->link = object[QLatin1String("pump_io")].toMap().value(QLatin1String("proxyURL")).toUrl(); } p->type = object[QLatin1String("objectType")].toString(); p->isFavorited = object[QLatin1String("liked")].toBool(); if (p->isFavorited) { p->isRead = true; } p->postId = object[QLatin1String("id")].toString(); p->conversationId = var[QLatin1String("id")].toString(); QString author; var[QLatin1String("author")].isNull() ? author = QLatin1String("actor") : author = QLatin1String("author"); QVariantMap actor; if (var.value(QLatin1String("verb")).toString() == QLatin1String("share")) { actor = object[QLatin1String("author")].toMap(); const QVariantList shares = object[QLatin1String("shares")].toMap().value(QLatin1String("items")).toList(); for (const QVariant &element: shares) { p->shares.append(element.toMap().value(QLatin1String("id")).toString()); } } else { actor = var[author].toMap(); } const QString userId = actor[QLatin1String("id")].toString(); const QUrl homePageUrl = actor[QLatin1String("url")].toUrl(); p->author.userId = userId; p->author.userName = actor[QLatin1String("preferredUsername")].toString(); p->author.realName = actor[QLatin1String("displayName")].toString(); p->author.homePageUrl = homePageUrl; p->author.location = actor[QLatin1String("location")].toMap().value(QLatin1String("displayName")).toString(); p->author.description = actor[QLatin1String("summary")].toString(); const QUrl profileImageUrl = actor[QLatin1String("image")].toMap().value(QLatin1String("url")).toUrl(); if (!profileImageUrl.isEmpty()) { p->author.profileImageUrl = profileImageUrl; } else if (actor[QLatin1String("objectType")].toString() == QLatin1String("service")) { p->author.profileImageUrl = QUrl::fromUserInput(homePageUrl.toDisplayString() + QLatin1String("images/default.png")); } else { p->author.profileImageUrl = QUrl::fromUserInput(QStringLiteral("https://%1/images/default.png").arg(hostFromAcct(userId))); } if (!var[QLatin1String("generator")].isNull()) { p->source = var[QLatin1String("generator")].toMap().value(QLatin1String("displayName")).toString(); } const QVariantList to = var[QLatin1String("to")].toList(); for (const QVariant &element: to) { QVariantMap toElementMap = element.toMap(); QString toElementType = toElementMap.value(QLatin1String("objectType")).toString(); if (toElementType == QLatin1String("person") || toElementType == QLatin1String("collection")) { const QString toId = toElementMap.value(QLatin1String("id")).toString(); if (toId.compare(QLatin1String("acct:")) != 0) { p->to.append(toId); } } } const QVariantList cc = var[QLatin1String("cc")].toList(); for (const QVariant &element: cc) { QVariantMap ccElementMap = element.toMap(); QString ccElementType = ccElementMap.value(QLatin1String("objectType")).toString(); if (ccElementType == QLatin1String("person") || ccElementType == QLatin1String("collection")) { const QString ccId = ccElementMap.value(QLatin1String("id")).toString(); if (ccId.compare(QLatin1String("acct:")) != 0) { p->cc.append(ccId); } } } const QVariantMap replies = object[QLatin1String("replies")].toMap(); if (replies.value(QLatin1String("pump_io")).isNull()) { p->replies = replies[QLatin1String("url")].toUrl(); } else { p->replies = replies[QLatin1String("pump_io")].toMap().value(QLatin1String("proxyURL")).toUrl(); } return p; } else { qCDebug(CHOQOK) << "post is not a PumpIOPost!"; return post; } } QList< Choqok::Post * > PumpIOMicroBlog::readTimeline(const QByteArray &buffer) { QList posts; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { const QVariantList list = json.toVariant().toMap().value(QLatin1String("items")).toList(); for (const QVariant &element: list) { const QVariantMap elementMap = element.toMap(); if (!elementMap[QLatin1String("object")].toMap().value(QLatin1String("deleted")).isNull()) { // Skip deleted posts continue; } posts.prepend(readPost(elementMap, new PumpIOPost)); } } else { qCDebug(CHOQOK) << "Cannot parse JSON reply"; } return posts; } void PumpIOMicroBlog::setLastTimelineId(Choqok::Account *theAccount, const QString &timeline, const QString &id) { m_timelinesLatestIds[theAccount][timeline] = id; } void PumpIOMicroBlog::setTimelinesInfo() { Choqok::TimelineInfo *t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Activity"); t->description = i18nc("Timeline description", "You and people you follow"); t->icon = QLatin1String("user-home"); m_timelinesInfos[QLatin1String("Activity")] = t; m_timelinesPaths[QLatin1String("Activity")] = inboxActivity + QLatin1String("/major"); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Favorites"); t->description = i18nc("Timeline description", "Posts you favorited"); t->icon = QLatin1String("favorites"); m_timelinesInfos[QLatin1String("Favorites")] = t; m_timelinesPaths[QLatin1String("Favorites")] = QLatin1String("/api/user/%1/favorites"); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Inbox"); t->description = i18nc("Timeline description", "Posts sent to you"); t->icon = QLatin1String("mail-folder-inbox"); m_timelinesInfos[QLatin1String("Inbox")] = t; m_timelinesPaths[QLatin1String("Inbox")] = inboxActivity + QLatin1String("/direct/major/"); t = new Choqok::TimelineInfo; t->name = i18nc("Timeline Name", "Outbox"); t->description = i18nc("Timeline description", "Posts you sent"); t->icon = QLatin1String("mail-folder-outbox"); m_timelinesInfos[QLatin1String("Outbox")] = t; m_timelinesPaths[QLatin1String("Outbox")] = outboxActivity + QLatin1String("/major/"); } void PumpIOMicroBlog::updatePost(Choqok::Account *theAccount, Choqok::Post *post) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { QVariantMap object; object.insert(QLatin1String("id"), post->postId); object.insert(QLatin1String("objectType"), post->type); object.insert(QLatin1String("content"), QUrl::toPercentEncoding(post->content)); // https://github.com/e14n/pump.io/issues/885 QVariantList to; QVariantMap thePublic; thePublic.insert(QLatin1String("objectType"), QLatin1String("collection")); thePublic.insert(QLatin1String("id"), PumpIOMicroBlog::PublicCollection); to.append(thePublic); QVariantMap item; item.insert(QLatin1String("verb"), QLatin1String("update")); item.insert(QLatin1String("object"), object); item.insert(QLatin1String("to"), to); const QByteArray data = QJsonDocument::fromVariant(item).toJson(); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + outboxActivity.arg(acc->username())); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo); job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->addMetaData(QLatin1String("customHTTPHeader"), acc->oAuth()->authorizationHeader(url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_updateJobs[job] = post; connect(job, &KIO::StoredTransferJob::result, this, &PumpIOMicroBlog::slotUpdatePost); job->start(); } else { qCDebug(CHOQOK) << "theAccount is not a PumpIOAccount!"; } } QString PumpIOMicroBlog::hostFromAcct(const QString &acct) { if (acct.contains(QLatin1String("acct:"))) { return acct.split(QLatin1Char(':'))[1].split(QLatin1Char('@'))[1]; } return acct; } QString PumpIOMicroBlog::userNameFromAcct(const QString &acct) { if (acct.contains(QLatin1String("acct:"))) { return acct.split(QLatin1Char(':'))[1].split(QLatin1Char('@'))[0]; } return acct; } #include "pumpiomicroblog.moc"