diff --git a/helperlibs/gnusocialapihelper/gnusocialapimicroblog.cpp b/helperlibs/gnusocialapihelper/gnusocialapimicroblog.cpp index b1b8a61e..44d7f245 100644 --- a/helperlibs/gnusocialapihelper/gnusocialapimicroblog.cpp +++ b/helperlibs/gnusocialapihelper/gnusocialapimicroblog.cpp @@ -1,464 +1,465 @@ /* 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 "gnusocialapimicroblog.h" #include #include #include #include #include #include #include #include #include "account.h" #include "accountmanager.h" #include "choqokappearancesettings.h" #include "composerwidget.h" #include "editaccountwidget.h" #include "mediamanager.h" #include "microblogwidget.h" #include "postwidget.h" #include "timelinewidget.h" #include "twitterapimicroblogwidget.h" #include "twitterapipostwidget.h" #include "twitterapitimelinewidget.h" #include "gnusocialapiaccount.h" #include "gnusocialapicomposerwidget.h" #include "gnusocialapidebug.h" #include "gnusocialapidmessagedialog.h" #include "gnusocialapipostwidget.h" #include "gnusocialapisearch.h" GNUSocialApiMicroBlog::GNUSocialApiMicroBlog(const QString &componentName, QObject *parent = 0) : TwitterApiMicroBlog(componentName, parent), friendsPage(1) { qCDebug(CHOQOK); setServiceName(QLatin1String("GNU social")); mTimelineInfos[QLatin1String("ReTweets")]->name = i18nc("Timeline name", "Repeated"); mTimelineInfos[QLatin1String("ReTweets")]->description = i18nc("Timeline description", "Your posts that were repeated by others"); } GNUSocialApiMicroBlog::~GNUSocialApiMicroBlog() { qCDebug(CHOQOK); } Choqok::Account *GNUSocialApiMicroBlog::createNewAccount(const QString &alias) { GNUSocialApiAccount *acc = qobject_cast(Choqok::AccountManager::self()->findAccount(alias)); if (!acc) { return new GNUSocialApiAccount(this, alias); } else { return 0; } } Choqok::UI::MicroBlogWidget *GNUSocialApiMicroBlog::createMicroBlogWidget(Choqok::Account *account, QWidget *parent) { return new TwitterApiMicroBlogWidget(account, parent); } Choqok::UI::TimelineWidget *GNUSocialApiMicroBlog::createTimelineWidget(Choqok::Account *account, const QString &timelineName, QWidget *parent) { return new TwitterApiTimelineWidget(account, timelineName, parent); } Choqok::UI::PostWidget *GNUSocialApiMicroBlog::createPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) { return new GNUSocialApiPostWidget(account, post, parent); } Choqok::UI::ComposerWidget *GNUSocialApiMicroBlog::createComposerWidget(Choqok::Account *account, QWidget *parent) { return new GNUSocialApiComposerWidget(account, parent); } Choqok::Post *GNUSocialApiMicroBlog::readPost(Choqok::Account *account, const QVariantMap &var, Choqok::Post *post) { if (!post) { qCCritical(CHOQOK) << "post is NULL!"; return 0; } if (var[QLatin1String("source")].toString().compare(QLatin1String("linkback")) == 0) { // Skip linkback statuses return 0; } post = TwitterApiMicroBlog::readPost(account, var, post); - post->author.homePageUrl = var[QLatin1String("user")].toMap()[QLatin1String("statusnet_profile_url")].toString(); + post->author.homePageUrl = var[QLatin1String("user")].toMap()[QLatin1String("statusnet_profile_url")].toUrl(); if (var.contains(QLatin1String("uri"))) { - post->link = var[QLatin1String("uri")].toString(); + post->link = var[QLatin1String("uri")].toUrl(); } else if (var.contains(QLatin1String("external_url"))) { - post->link = var[QLatin1String("external_url")].toString(); + post->link = var[QLatin1String("external_url")].toUrl(); } else { QVariantMap retweeted = var[QLatin1String("retweeted_status")].toMap(); QVariantMap userMap; if (!retweeted.isEmpty()) { userMap = retweeted[QLatin1String("user")].toMap(); } else { userMap = var[QLatin1String("user")].toMap(); } if (retweeted.contains(QLatin1String("uri"))) { - post->link = var[QLatin1String("uri")].toString(); + post->link = var[QLatin1String("uri")].toUrl(); } else { // Last try, compone the url. However this only works for GNU Social instances. const QUrl profileUrl = userMap[QLatin1String("statusnet_profile_url")].toUrl(); - post->link = QStringLiteral("%1://%2/notice/%3").arg(profileUrl.scheme()).arg(profileUrl.host()).arg(post->postId); + post->link = QUrl::fromUserInput(QStringLiteral("%1://%2/notice/%3") + .arg(profileUrl.scheme()).arg(profileUrl.host()).arg(post->postId)); } } return post; } QUrl GNUSocialApiMicroBlog::profileUrl(Choqok::Account *account, const QString &username) const { if (username.contains(QLatin1Char('@'))) { const QStringList lst = username.split(QLatin1Char('@'), QString::SkipEmptyParts); if (lst.count() == 2) { return QUrl::fromUserInput(QStringLiteral("https://%1/%2").arg(lst[1]).arg(lst[0])); } else { return QUrl(); } } else { GNUSocialApiAccount *acc = qobject_cast(account); QUrl url(acc->host()); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(QLatin1Char('/') + username); return url; } } -QString GNUSocialApiMicroBlog::postUrl(Choqok::Account *account, const QString &username, +QUrl GNUSocialApiMicroBlog::postUrl(Choqok::Account *account, const QString &username, const QString &postId) const { Q_UNUSED(username) TwitterApiAccount *acc = qobject_cast(account); if (acc) { QUrl url(acc->homepageUrl()); url.setPath(url.path() + QStringLiteral("/notice/%1").arg(postId)); - return url.toDisplayString(); + return url; } else { - return QString(); + return QUrl(); } } TwitterApiSearch *GNUSocialApiMicroBlog::searchBackend() { if (!mSearchBackend) { mSearchBackend = new GNUSocialApiSearch(this); } return mSearchBackend; } void GNUSocialApiMicroBlog::createPostWithAttachment(Choqok::Account *theAccount, Choqok::Post *post, const QString &mediumToAttach) { if (mediumToAttach.isEmpty()) { TwitterApiMicroBlog::createPost(theAccount, post); } else { const QUrl picUrl = QUrl::fromUserInput(mediumToAttach); KIO::StoredTransferJob *picJob = KIO::storedGet(picUrl, KIO::Reload, KIO::HideProgressInfo); picJob->exec(); if (picJob->error()) { qCCritical(CHOQOK) << "Job error:" << picJob->errorString(); KMessageBox::detailedError(Choqok::UI::Global::mainWindow(), i18n("Uploading medium failed: cannot read the medium file."), picJob->errorString()); return; } const QByteArray picData = picJob->data(); if (picData.count() == 0) { qCCritical(CHOQOK) << "Cannot read the media file, please check if it exists."; KMessageBox::error(Choqok::UI::Global::mainWindow(), i18n("Uploading medium failed: cannot read the medium file.")); return; } ///Documentation: http://identi.ca/notice/17779990 TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QStringLiteral("/statuses/update.%1").arg(format)); const QMimeDatabase db; QByteArray fileContentType = db.mimeTypeForUrl(picUrl).name().toUtf8(); QMap formdata; formdata[QLatin1String("status")] = post->content.toUtf8(); formdata[QLatin1String("in_reply_to_status_id")] = post->replyToPostId.toLatin1(); formdata[QLatin1String("source")] = QCoreApplication::applicationName().toLatin1(); QMap mediafile; mediafile[QLatin1String("name")] = "media"; mediafile[QLatin1String("filename")] = picUrl.fileName().toUtf8(); mediafile[QLatin1String("mediumType")] = fileContentType; mediafile[QLatin1String("medium")] = picData; QList< QMap > listMediafiles; listMediafiles.append(mediafile); QByteArray data = Choqok::MediaManager::createMultipartFormData(formdata, listMediafiles); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo) ; if (!job) { qCCritical(CHOQOK) << "Cannot create a http POST request!"; return; } job->addMetaData(QStringLiteral("content-type"), QStringLiteral("Content-Type: multipart/form-data; boundary=AaB03x")); job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mCreatePostMap[ job ] = post; mJobsAccount[job] = theAccount; connect(job, SIGNAL(result(KJob*)), SLOT(slotCreatePost(KJob*))); job->start(); } } QString GNUSocialApiMicroBlog::generateRepeatedByUserTooltip(const QString &username) { if (Choqok::AppearanceSettings::showRetweetsInChoqokWay()) { return i18n("Repeat of %1", username); } else { return i18n("Repeated by %1", username); } } QString GNUSocialApiMicroBlog::repeatQuestion() { return i18n("Repeat this notice?"); } void GNUSocialApiMicroBlog::listFriendsUsername(TwitterApiAccount *theAccount, bool active) { Q_UNUSED(active); friendsList.clear(); if (theAccount) { doRequestFriendsScreenName(theAccount, 1); } } QStringList GNUSocialApiMicroBlog::readFriendsScreenName(Choqok::Account *theAccount, const QByteArray &buffer) { QStringList list; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { for (const QJsonValue &u: json.array()) { const QJsonObject user = u.toObject(); if (user.contains(QStringLiteral("statusnet_profile_url"))) { list.append(user.value(QLatin1String("statusnet_profile_url")).toString()); } } } 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; } void GNUSocialApiMicroBlog::requestFriendsScreenName(TwitterApiAccount *theAccount, bool active) { Q_UNUSED(active); doRequestFriendsScreenName(theAccount, 1); } void GNUSocialApiMicroBlog::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())); } GNUSocialApiDMessageDialog *dmsg = new GNUSocialApiDMessageDialog(theAccount, Choqok::UI::Global::mainWindow()); if (!toUsername.isEmpty()) { dmsg->setTo(toUsername); } dmsg->show(); } void GNUSocialApiMicroBlog::doRequestFriendsScreenName(TwitterApiAccount *theAccount, int page) { qCDebug(CHOQOK); TwitterApiAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QStringLiteral("/statuses/friends.%1").arg(format)); QVariantMap params; if (page > 1) { params.insert(QLatin1String("page"), QByteArray::number(page)); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("page"), QString::number(page)); 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, params))); mJobsAccount[job] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRequestFriendsScreenName(KJob*))); job->start(); } void GNUSocialApiMicroBlog::slotRequestFriendsScreenName(KJob *job) { qCDebug(CHOQOK); TwitterApiAccount *theAccount = qobject_cast(mJobsAccount.take(job)); if (job->error()) { Q_EMIT error(theAccount, ServerError, i18n("Friends list for account %1 could not be updated:\n%2", theAccount->username(), job->errorString()), Normal); return; } KIO::StoredTransferJob *stJob = qobject_cast(job); QStringList newList = readFriendsScreenName(theAccount, stJob->data()); friendsList << newList; if (newList.count() == 100) { doRequestFriendsScreenName(theAccount, ++friendsPage); } else { friendsList.removeDuplicates(); theAccount->setFriendsList(friendsList); Q_EMIT friendsUsernameListed(theAccount, friendsList); } } /*QStringList GNUSocialApiMicroBlog::readUsersScreenNameFromXml(Choqok::Account* theAccount, const QByteArray& buffer) { qCDebug(CHOQOK); QStringList list; QDomDocument document; document.setContent( buffer ); QDomElement root = document.documentElement(); if ( root.tagName() != "users" ) { QString err = checkXmlForError(buffer); if(!err.isEmpty()){ Q_EMIT error(theAccount, ServerError, err, Critical); } else { err = i18n( "Retrieving the friends list failed. The data returned from the server is corrupted." ); qCDebug(CHOQOK) << "there's no users tag in XML\t the XML is: \n" << buffer; Q_EMIT error(theAccount, ParsingError, err, Critical); list<(theAccount); QUrl url = account->apiUrl(); url.setPath(QStringLiteral("/statusnet/conversation/%1.%2").arg(conversationId).arg(format)); 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))); mFetchConversationMap[ job ] = conversationId; mJobsAccount[ job ] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFetchConversation(KJob*))); job->start(); } QString GNUSocialApiMicroBlog::usernameFromProfileUrl(const QString &profileUrl) { // Remove the initial slash from path return QUrl(profileUrl).path().remove(0, 1); } QString GNUSocialApiMicroBlog::hostFromProfileUrl(const QString &profileUrl) { return QUrl(profileUrl).host(); } void GNUSocialApiMicroBlog::slotFetchConversation(KJob *job) { qCDebug(CHOQOK); if (!job) { qCWarning(CHOQOK) << "NULL Job returned"; return; } QList posts; QString conversationId = mFetchConversationMap.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 conversation failed. %1", job->errorString()), Normal); } else { KIO::StoredTransferJob *stj = qobject_cast (job); //if(format=="json"){ posts = readTimeline(theAccount, stj->data()); //} else { // posts = readTimelineFromXml ( theAccount, stj->data() ); //} if (!posts.isEmpty()) { Q_EMIT conversationFetched(theAccount, conversationId, posts); } } } #include "gnusocialapimicroblog.moc" diff --git a/helperlibs/gnusocialapihelper/gnusocialapimicroblog.h b/helperlibs/gnusocialapihelper/gnusocialapimicroblog.h index b2e633c9..0270a397 100644 --- a/helperlibs/gnusocialapihelper/gnusocialapimicroblog.h +++ b/helperlibs/gnusocialapihelper/gnusocialapimicroblog.h @@ -1,93 +1,93 @@ /* 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/ */ #ifndef GNUSOCIALAPIMICROBLOGPLUGIN_H #define GNUSOCIALAPIMICROBLOGPLUGIN_H #include #include "twitterapimicroblog.h" class GNUSocialApiSearch; class KJob; /** This plugin is to GNU social service. @Note GNU social was called StatusNet and Laconcia previously, So I just renamed it on UI :D @author Mehrdad Momeny \ */ class CHOQOK_HELPER_EXPORT GNUSocialApiMicroBlog : public TwitterApiMicroBlog { Q_OBJECT public: GNUSocialApiMicroBlog(const QString &componentName, QObject *parent); ~GNUSocialApiMicroBlog(); virtual Choqok::Account *createNewAccount(const QString &alias) override; virtual Choqok::UI::MicroBlogWidget *createMicroBlogWidget(Choqok::Account *account, QWidget *parent) override; virtual Choqok::UI::TimelineWidget *createTimelineWidget(Choqok::Account *account, const QString &timelineName, QWidget *parent) override; virtual Choqok::UI::PostWidget *createPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) override; virtual Choqok::UI::ComposerWidget *createComposerWidget(Choqok::Account *account, QWidget *parent) override; virtual QUrl profileUrl(Choqok::Account *account, const QString &username) const override; - virtual QString postUrl(Choqok::Account *account, const QString &username, const QString &postId) const override; + virtual QUrl postUrl(Choqok::Account *account, const QString &username, const QString &postId) const override; virtual TwitterApiSearch *searchBackend() override; void createPostWithAttachment(Choqok::Account *theAccount, Choqok::Post *post, const QString &mediumToAttach = QString()); virtual QString generateRepeatedByUserTooltip(const QString &username) override; virtual QString repeatQuestion() override; void fetchConversation(Choqok::Account *theAccount, const QString &conversationId); virtual void requestFriendsScreenName(TwitterApiAccount *theAccount, bool active) override; virtual void showDirectMessageDialog(TwitterApiAccount *theAccount = 0, const QString &toUsername = QString()) override; static QString usernameFromProfileUrl(const QString &profileUrl); static QString hostFromProfileUrl(const QString &profileUrl); Q_SIGNALS: void conversationFetched(Choqok::Account *theAccount, const QString &conversationId, QList posts); protected: using TwitterApiMicroBlog::readPost; virtual Choqok::Post *readPost(Choqok::Account *account, const QVariantMap &var, Choqok::Post *post) override; virtual void listFriendsUsername(TwitterApiAccount *theAccount, bool active = false) override; virtual QStringList readFriendsScreenName(Choqok::Account *theAccount, const QByteArray &buffer) override; protected Q_SLOTS: void slotFetchConversation(KJob *job); void slotRequestFriendsScreenName(KJob *job); private: void doRequestFriendsScreenName(TwitterApiAccount *theAccount, int page); QMap mFetchConversationMap; QPointer mSearchBackend; int friendsPage; }; #endif diff --git a/helperlibs/gnusocialapihelper/gnusocialapisearch.cpp b/helperlibs/gnusocialapihelper/gnusocialapisearch.cpp index 3e32c660..c9e3b77b 100644 --- a/helperlibs/gnusocialapihelper/gnusocialapisearch.cpp +++ b/helperlibs/gnusocialapihelper/gnusocialapisearch.cpp @@ -1,332 +1,332 @@ /* 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 "gnusocialapisearch.h" #include #include #include #include "twitterapiaccount.h" #include "gnusocialapidebug.h" const QRegExp GNUSocialApiSearch::m_rId(QLatin1String("tag:.+,[\\d-]+:(\\d+)")); const QRegExp GNUSocialApiSearch::mIdRegExp(QLatin1String("(?:user|(?:.*notice))/([0-9]+)")); GNUSocialApiSearch::GNUSocialApiSearch(QObject *parent): TwitterApiSearch(parent) { qCDebug(CHOQOK); mSearchCode[ReferenceGroup] = QLatin1Char('!'); mSearchCode[ToUser] = QLatin1Char('@'); mSearchCode[FromUser].clear(); mSearchCode[ReferenceHashtag] = QLatin1Char('#'); mSearchTypes[ReferenceHashtag].first = i18nc("Dents are Identica posts", "Dents Including This Hashtag"); mSearchTypes[ReferenceHashtag].second = true; mSearchTypes[ReferenceGroup].first = i18nc("Dents are Identica posts", "Dents Including This Group"); mSearchTypes[ReferenceGroup].second = false; mSearchTypes[FromUser].first = i18nc("Dents are Identica posts", "Dents From This User"); mSearchTypes[FromUser].second = false; mSearchTypes[ToUser].first = i18nc("Dents are Identica posts", "Dents To This User"); mSearchTypes[ToUser].second = false; } GNUSocialApiSearch::~GNUSocialApiSearch() { } QUrl GNUSocialApiSearch::buildUrl(const SearchInfo &searchInfo, QString sinceStatusId, uint count, uint page) { qCDebug(CHOQOK); QString formattedQuery; switch (searchInfo.option) { case ToUser: formattedQuery = searchInfo.query + QLatin1String("/replies/rss"); break; case FromUser: formattedQuery = searchInfo.query + QLatin1String("/rss"); break; case ReferenceGroup: formattedQuery = QLatin1String("group/") + searchInfo.query + QLatin1String("/rss"); break; case ReferenceHashtag: formattedQuery = searchInfo.query; break; default: formattedQuery = searchInfo.query + QLatin1String("/rss"); break; }; QUrl url; TwitterApiAccount *theAccount = qobject_cast(searchInfo.account); Q_ASSERT(theAccount); if (searchInfo.option == ReferenceHashtag) { url = theAccount->apiUrl(); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1String("/search.atom")); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("q"), formattedQuery); if (!sinceStatusId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("since_id"), sinceStatusId); } int cntStr; if (count && count <= 100) { // GNU Social allows max 100 notices cntStr = count; } else { cntStr = 100; } urlQuery.addQueryItem(QLatin1String("rpp"), QString::number(cntStr)); if (page > 1) { urlQuery.addQueryItem(QLatin1String("page"), QString::number(page)); } url.setQuery(urlQuery); } else { url = QUrl(theAccount->apiUrl().url().remove(QLatin1String("/api"), Qt::CaseInsensitive)); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + (formattedQuery)); } return url; } void GNUSocialApiSearch::requestSearchResults(const SearchInfo &searchInfo, const QString &sinceStatusId, uint count, uint page) { qCDebug(CHOQOK); QUrl url = buildUrl(searchInfo, sinceStatusId, count, page); qCDebug(CHOQOK) << url; KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCCritical(CHOQOK) << "Cannot create an http GET request!"; return; } mSearchJobs[job] = searchInfo; connect(job, SIGNAL(result(KJob*)), this, SLOT(searchResultsReturned(KJob*))); job->start(); } void GNUSocialApiSearch::searchResultsReturned(KJob *job) { qCDebug(CHOQOK); if (job == 0) { qCDebug(CHOQOK) << "job is a null pointer"; Q_EMIT error(i18n("Unable to fetch search results.")); return; } SearchInfo info = mSearchJobs.take(job); if (job->error()) { qCCritical(CHOQOK) << "Error:" << job->errorString(); Q_EMIT error(i18n("Unable to fetch search results: %1", job->errorString())); return; } KIO::StoredTransferJob *jj = qobject_cast(job); QList postsList; if (info.option == ReferenceHashtag) { postsList = parseAtom(jj->data()); } else { postsList = parseRss(jj->data()); } qCDebug(CHOQOK) << "Emiting searchResultsReceived()"; Q_EMIT searchResultsReceived(info, postsList); } QString GNUSocialApiSearch::optionCode(int option) { return mSearchCode[option]; } QList< Choqok::Post * > GNUSocialApiSearch::parseAtom(const QByteArray &buffer) { QDomDocument document; QList statusList; document.setContent(buffer); QDomElement root = document.documentElement(); if (root.tagName() != QLatin1String("feed")) { qCDebug(CHOQOK) << "There is no feed element in Atom feed " << buffer.data(); return statusList; } QDomNode node = root.firstChild(); QString timeStr; while (!node.isNull()) { if (node.toElement().tagName() != QLatin1String("entry")) { node = node.nextSibling(); continue; } QDomNode entryNode = node.firstChild(); Choqok::Post *status = new Choqok::Post; status->isPrivate = false; while (!entryNode.isNull()) { QDomElement elm = entryNode.toElement(); if (elm.tagName() == QLatin1String("id")) { // Fomatting example: "tag:search.twitter.com,2005:1235016836" QString id; if (m_rId.exactMatch(elm.text())) { id = m_rId.cap(1); } /* sscanf( qPrintable( elm.text() ), "tag:search.twitter.com,%*d:%d", &id);*/ status->postId = id; } else if (elm.tagName() == QLatin1String("published")) { // Formatting example: "2009-02-21T19:42:39Z" // Need to extract date in similar fashion to dateFromString int year, month, day, hour, minute, second; sscanf(qPrintable(elm.text()), "%d-%d-%dT%d:%d:%d%*s", &year, &month, &day, &hour, &minute, &second); QDateTime recognized(QDate(year, month, day), QTime(hour, minute, second)); recognized.setTimeSpec(Qt::UTC); status->creationDateTime = recognized; } else if (elm.tagName() == QLatin1String("title")) { status->content = elm.text(); } else if (elm.tagName() == QLatin1String("link")) { if (elm.attribute(QLatin1String("rel")) == QLatin1String("related")) { - status->author.profileImageUrl = elm.attribute(QLatin1String("href")); + status->author.profileImageUrl = QUrl::fromUserInput(elm.attribute(QLatin1String("href"))); } else if (elm.attribute(QLatin1String("rel")) == QLatin1String("alternate")) { - status->link = elm.attribute(QLatin1String("href")); + status->link = QUrl::fromUserInput(elm.attribute(QLatin1String("href"))); } } else if (elm.tagName() == QLatin1String("author")) { QDomNode userNode = entryNode.firstChild(); while (!userNode.isNull()) { if (userNode.toElement().tagName() == QLatin1String("name")) { QString fullName = userNode.toElement().text(); int bracketPos = fullName.indexOf(QLatin1Char(' '), 0); QString screenName = fullName.left(bracketPos); QString name = fullName.right(fullName.size() - bracketPos - 2); name.chop(1); status->author.realName = name; status->author.userName = screenName; } userNode = userNode.nextSibling(); } } else if (elm.tagName() == QLatin1String("twitter:source")) { status->source = QUrl::fromPercentEncoding(elm.text().toLatin1()); } entryNode = entryNode.nextSibling(); } status->isFavorited = false; statusList.insert(0, status); node = node.nextSibling(); } return statusList; } QList< Choqok::Post * > GNUSocialApiSearch::parseRss(const QByteArray &buffer) { qCDebug(CHOQOK); QDomDocument document; QList statusList; document.setContent(buffer); QDomElement root = document.documentElement(); if (root.tagName() != QLatin1String("rdf:RDF")) { qCDebug(CHOQOK) << "There is no rdf:RDF element in RSS feed " << buffer.data(); return statusList; } QDomNode node = root.firstChild(); QString timeStr; while (!node.isNull()) { if (node.toElement().tagName() != QLatin1String("item")) { node = node.nextSibling(); continue; } Choqok::Post *status = new Choqok::Post; QDomAttr statusIdAttr = node.toElement().attributeNode(QLatin1String("rdf:about")); QString statusId; if (mIdRegExp.exactMatch(statusIdAttr.value())) { statusId = mIdRegExp.cap(1); } status->postId = statusId; QDomNode itemNode = node.firstChild(); while (!itemNode.isNull()) { if (itemNode.toElement().tagName() == QLatin1String("title")) { QString content = itemNode.toElement().text(); int nameSep = content.indexOf(QLatin1Char(':'), 0); QString screenName = content.left(nameSep); QString statusText = content.right(content.size() - nameSep - 2); status->author.userName = screenName; status->content = statusText; } else if (itemNode.toElement().tagName() == QLatin1String("dc:date")) { int year, month, day, hour, minute, second; sscanf(qPrintable(itemNode.toElement().text()), "%d-%d-%dT%d:%d:%d%*s", &year, &month, &day, &hour, &minute, &second); QDateTime recognized(QDate(year, month, day), QTime(hour, minute, second)); recognized.setTimeSpec(Qt::UTC); status->creationDateTime = recognized; } else if (itemNode.toElement().tagName() == QLatin1String("dc:creator")) { status->author.realName = itemNode.toElement().text(); } else if (itemNode.toElement().tagName() == QLatin1String("sioc:reply_of")) { QDomAttr userIdAttr = itemNode.toElement().attributeNode(QLatin1String("rdf:resource")); QString id; if (mIdRegExp.exactMatch(userIdAttr.value())) { id = mIdRegExp.cap(1); } status->replyToPostId = id; } else if (itemNode.toElement().tagName() == QLatin1String("statusnet:postIcon")) { QDomAttr imageAttr = itemNode.toElement().attributeNode(QLatin1String("rdf:resource")); - status->author.profileImageUrl = imageAttr.value(); + status->author.profileImageUrl = QUrl::fromUserInput(imageAttr.value()); } else if (itemNode.toElement().tagName() == QLatin1String("link")) { // QDomAttr imageAttr = itemNode.toElement().attributeNode( "rdf:resource" ); - status->link = itemNode.toElement().text(); + status->link = QUrl::fromUserInput(itemNode.toElement().text()); } else if (itemNode.toElement().tagName() == QLatin1String("sioc:has_discussion")) { status->conversationId = itemNode.toElement().attributeNode(QLatin1String("rdf:resource")).value(); } itemNode = itemNode.nextSibling(); } status->isPrivate = false; status->isFavorited = false; statusList.insert(0, status); node = node.nextSibling(); } return statusList; } diff --git a/helperlibs/twitterapihelper/twitterapimicroblog.cpp b/helperlibs/twitterapihelper/twitterapimicroblog.cpp index 0e94245d..52792173 100644 --- a/helperlibs/twitterapihelper/twitterapimicroblog.cpp +++ b/helperlibs/twitterapihelper/twitterapimicroblog.cpp @@ -1,1607 +1,1608 @@ /* 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); format = QLatin1String("json"); 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.%1"); timelineApiPath[QLatin1String("Reply")] = QLatin1String("/statuses/replies.%1"); timelineApiPath[QLatin1String("Inbox")] = QLatin1String("/direct_messages.%1"); timelineApiPath[QLatin1String("Outbox")] = QLatin1String("/direct_messages/sent.%1"); timelineApiPath[QLatin1String("Favorite")] = QLatin1String("/favorites/list.%1"); timelineApiPath[QLatin1String("ReTweets")] = QLatin1String("/statuses/retweets_of_me.%1"); timelineApiPath[QLatin1String("Public")] = QLatin1String("/statuses/public_timeline.%1"); 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, SIGNAL(triggered(bool)), SLOT(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); int count = groupList.count(); if (count) { Choqok::Post *st = 0; 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->replyToUserId = grp.readEntry("inReplyToUserId", QString()); st->isFavorited = grp.readEntry("favorited", false); st->replyToUserName = 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", QString()); - st->author.profileImageUrl = grp.readEntry("authorProfileImageUrl", 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->repeatedFromUsername = grp.readEntry("repeatedFrom", QString()); st->repeatedPostId = grp.readEntry("repeatedPostId", QString()); st->repeatedDateTime = grp.readEntry("repeatedDateTime", QDateTime()); st->conversationId = grp.readEntry("conversationId", QString()); - st->media = grp.readEntry("mediaUrl", QString()); + st->media = grp.readEntry("mediaUrl", QUrl()); st->quotedPost.postId = grp.readEntry("quotedPostId", QString()); - st->quotedPost.profileImageUrl = grp.readEntry("quotedProfileUrl", QString()); + st->quotedPost.profileImageUrl = grp.readEntry("quotedProfileUrl", QUrl()); st->quotedPost.content = grp.readEntry("quotedContent", QString()); st->quotedPost.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->replyToUserId); grp.writeEntry("favorited", post->isFavorited); grp.writeEntry("inReplyToUserName", post->replyToUserName); 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->repeatedFromUsername); 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.profileImageUrl); grp.writeEntry("quotedContent", post->quotedPost.content); grp.writeEntry("quotedUsername", post->quotedPost.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() + QStringLiteral("/statuses/update.%1").arg(format)); params.insert(QLatin1String("status"), QUrl::toPercentEncoding(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, SIGNAL(result(KJob*)), this, SLOT(slotCreatePost(KJob*))); job->start(); } else {///Direct message QString recipientScreenName = post->replyToUserName; QUrl url = account->apiUrl(); url.setPath(url.path() + QStringLiteral("/direct_messages/new.%1").arg(format)); params.insert(QLatin1String("user"), recipientScreenName.toLocal8Bit()); params.insert(QLatin1String("text"), QUrl::toPercentEncoding(post->content)); data = "user="; data += recipientScreenName.toLocal8Bit(); 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, SIGNAL(result(KJob*)), this, SLOT(slotCreatePost(KJob*))); 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.%2").arg(postId).arg(format)); 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 submitted successfully")); 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.%2").arg(post->postId).arg(format)); 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, SIGNAL(result(KJob*)), this, SLOT(slotFetchPost(KJob*))); 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.%2").arg(post->postId).arg(format)); } else { url.setPath(url.path() + QStringLiteral("/direct_messages/destroy/%1.%2").arg(post->postId).arg(format)); } 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, SIGNAL(result(KJob*)), this, SLOT(slotRemovePost(KJob*))); 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() + QStringLiteral("/favorites/create.%1").arg(format)); QUrl tmp(url); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("id"), postId); url.setQuery(urlQuery); QVariantMap params; params.insert(QLatin1String("id"), postId.toLatin1()); 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, tmp, QNetworkAccessManager::PostOperation, params))); mFavoriteMap[job] = postId; mJobsAccount[job] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotCreateFavorite(KJob*))); 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() + QStringLiteral("/favorites/destroy.%1").arg(format)); QUrl tmp(url); QUrlQuery tmpUrlQuery; tmpUrlQuery.addQueryItem(QLatin1String("id"), postId); url.setQuery(tmpUrlQuery); QVariantMap params; params.insert(QLatin1String("id"), postId.toLatin1()); 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, tmp, QNetworkAccessManager::PostOperation, params))); mFavoriteMap[job] = postId; mJobsAccount[job] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRemoveFavorite(KJob*))); 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() + QStringLiteral("/friends/list.%1").arg(format)); QUrl tmpUrl(url); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("cursor"), d->friendsCursor); urlQuery.addQueryItem(QLatin1String("count"), QLatin1String("200")); url.setQuery(urlQuery); QVariantMap params; params.insert(QLatin1String("cursor"), d->friendsCursor.toLatin1()); params.insert(QLatin1String("count"), QByteArray::number(200)); 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, tmpUrl, QNetworkAccessManager::GetOperation, params))); mJobsAccount[job] = theAccount; if (active) { connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRequestFriendsScreenNameActive(KJob*))); } else { connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRequestFriendsScreenNamePassive(KJob*))); } job->start(); Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Updating friends list for account %1...", theAccount->username())); } 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->username())); 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() + QStringLiteral("/followers/list.%1").arg(format)); QUrl tmpUrl(url); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("cursor"), d->followersCursor); urlQuery.addQueryItem(QLatin1String("count"), QLatin1String("200")); url.setQuery(urlQuery); QVariantMap params; params.insert(QLatin1String("cursor"), d->followersCursor.toLatin1()); params.insert(QLatin1String("count"), QByteArray::number(200)); 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, tmpUrl, QNetworkAccessManager::GetOperation, params))); mJobsAccount[job] = theAccount; if (active) { connect( job, SIGNAL( result( KJob* ) ), this, SLOT( slotRequestFollowersScreenNameActive(KJob*) ) ); } else { connect( job, SIGNAL( result( KJob* ) ), this, SLOT( slotRequestFollowersScreenNamePassive(KJob*) ) ); } job->start(); Choqok::UI::Global::mainWindow()->showStatusMessage(i18n("Updating followers list for account %1...", theAccount->username())); } 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->username()) ); 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].arg(format)); QUrl tmpUrl(url); QUrlQuery urlQuery; QVariantMap params; // 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); params.insert(QLatin1String("slug"), slug.toLatin1()); const QString owner = type.mid(1, type.indexOf(QLatin1String("/")) - 1); urlQuery.addQueryItem(QLatin1String("owner_screen_name"), owner); params.insert(QLatin1String("owner_screen_name"), owner.toLatin1()); } else { int countOfPost = Choqok::BehaviorSettings::countOfPosts(); if (!latestStatusId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("since_id"), latestStatusId); params.insert(QLatin1String("since_id"), latestStatusId.toLatin1()); countOfPost = 200; } urlQuery.addQueryItem(QLatin1String("count"), QString::number(countOfPost)); params.insert(QLatin1String("count"), QByteArray::number(countOfPost)); if (!maxId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("max_id"), maxId); params.insert(QLatin1String("max_id"), maxId.toLatin1()); } if (page) { urlQuery.addQueryItem(QLatin1String("page"), QString::number(page)); params.insert(QLatin1String("page"), QByteArray::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, tmpUrl, QNetworkAccessManager::GetOperation, params))); mRequestTimelineMap[job] = type; mJobsAccount[job] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRequestTimeline(KJob*))); 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->replyToUserId = repeatedPost->replyToUserId; post->replyToUserName = repeatedPost->replyToUserName; post->repeatedPostId = repeatedPost->postId; post->repeatedDateTime = repeatedPost->creationDateTime; if (Choqok::AppearanceSettings::showRetweetsInChoqokWay()) { post->repeatedFromUsername = repeatedPost->author.userName; } else { post->repeatedFromUsername = post->author.userName; post->author = repeatedPost->author; } if (!repeatedPost->quotedPost.content.isEmpty()) { post->quotedPost = repeatedPost->quotedPost; } } void TwitterApiMicroBlog::setQuotedPost(Choqok::Post* post, Choqok::Post* quotedPost) { post->quotedPost.profileImageUrl = quotedPost->author.profileImageUrl; post->quotedPost.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() + QStringLiteral("/friendships/create.%1").arg(format)); QUrl tmp(url); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); QVariantMap params; params.insert(QLatin1String("screen_name"), username.toLatin1()); 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, tmp, QNetworkAccessManager::PostOperation, params))); mJobsAccount[job] = theAccount; mFriendshipMap[ job ] = username; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotCreateFriendship(KJob*))); 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() + QStringLiteral("/friendships/destroy.%1").arg(format)); QUrl tmp(url); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); QVariantMap params; params.insert(QLatin1String("screen_name"), username.toLatin1()); 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, tmp, QNetworkAccessManager::PostOperation, params))); mJobsAccount[job] = theAccount; mFriendshipMap[ job ] = username; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotDestroyFriendship(KJob*))); 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() + QStringLiteral("/blocks/create.%1").arg(format)); QUrl tmp(url); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); QVariantMap params; params.insert(QLatin1String("screen_name"), username.toLatin1()); 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, tmp, QNetworkAccessManager::PostOperation, params))); mJobsAccount[job] = theAccount; mFriendshipMap[ job ] = username; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotBlockUser(KJob*))); 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() + QStringLiteral("/users/report_spam.%1").arg(format)); QUrl tmp(url); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); QVariantMap params; params.insert(QLatin1String("screen_name"), username.toLatin1()); 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, tmp, QNetworkAccessManager::PostOperation, params))); mJobsAccount[job] = theAccount; mFriendshipMap[ job ] = username; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotReportUser(KJob*))); 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->replyToUserId = var[QLatin1String("in_reply_to_user_id")].toString(); post->replyToUserName = 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")].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 = mediaMap[QLatin1String("media_url")].toString() + QLatin1String(":small"); + 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 = QString(); + 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->repeatedFromUsername.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, senderProfileImageUrl, - senderName, senderDescription, recipientProfileImageUrl, recipientName, recipientDescription; + 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")].toString(); + 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")].toString(); + 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->replyToUserId = recipientId; msg->replyToUserName = 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->replyToUserId = recipientId; msg->replyToUserName = 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(0, 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(0, 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.followersCount = map[QLatin1String("followers_count")].toUInt(); - u.homePageUrl = map[QLatin1String("url")].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")].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/helperlibs/twitterapihelper/twitterapipostwidget.cpp b/helperlibs/twitterapihelper/twitterapipostwidget.cpp index 5afd3ded..451186b6 100644 --- a/helperlibs/twitterapihelper/twitterapipostwidget.cpp +++ b/helperlibs/twitterapihelper/twitterapipostwidget.cpp @@ -1,321 +1,320 @@ /* 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 "twitterapipostwidget.h" #include #include #include #include #include #include #include "choqokappearancesettings.h" #include "mediamanager.h" #include "microblog.h" #include "textbrowser.h" #include "twitterapiaccount.h" #include "twitterapidebug.h" #include "twitterapimicroblog.h" #include "twitterapishowthread.h" const QIcon TwitterApiPostWidget::unFavIcon(Choqok::MediaManager::convertToGrayScale(QIcon::fromTheme(QLatin1String("rating")).pixmap(16))); class TwitterApiPostWidget::Private { public: Private(Choqok::Account *account) : isBasePostShowed(false) { mBlog = qobject_cast(account->microblog()); } QPushButton *btnFav; bool isBasePostShowed; TwitterApiMicroBlog *mBlog; }; TwitterApiPostWidget::TwitterApiPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) : PostWidget(account, post, parent), d(new Private(account)) { mainWidget()->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://thread")), QIcon::fromTheme(QLatin1String("go-top")).pixmap(10)); } TwitterApiPostWidget::~TwitterApiPostWidget() { delete d; } void TwitterApiPostWidget::initUi() { Choqok::UI::PostWidget::initUi(); QPushButton *btnRe = addButton(QLatin1String("btnReply"), i18nc("@info:tooltip", "Reply"), QLatin1String("edit-undo")); connect(btnRe, SIGNAL(clicked(bool)), SLOT(slotReply())); QMenu *menu = new QMenu(btnRe); btnRe->setMenu(menu); QAction *actRep = new QAction(QIcon::fromTheme(QLatin1String("edit-undo")), i18n("Reply to %1", currentPost()->author.userName), menu); menu->addAction(actRep); menu->setDefaultAction(actRep); connect(actRep, SIGNAL(triggered(bool)), SLOT(slotReply())); QAction *actWrite = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Write to %1", currentPost()->author.userName), menu); menu->addAction(actWrite); connect(actWrite, SIGNAL(triggered(bool)), SLOT(slotWriteTo())); if (!currentPost()->isPrivate) { QAction *actReplytoAll = new QAction(i18n("Reply to all"), menu); menu->addAction(actReplytoAll); connect(actReplytoAll, SIGNAL(triggered(bool)), SLOT(slotReplyToAll())); d->btnFav = addButton(QLatin1String("btnFavorite"), i18nc("@info:tooltip", "Favorite"), QLatin1String("rating")); d->btnFav->setCheckable(true); connect(d->btnFav, SIGNAL(clicked(bool)), SLOT(setFavorite())); updateFavStat(); } } QString TwitterApiPostWidget::generateSign() { QString sign = QStringLiteral("%1 - ").arg(getUsernameHyperlink(currentPost()->author)); // if (currentPost()->isPrivate) { sign += QLatin1String("%1"); if (currentPost()->replyToUserName.compare(currentAccount()->username(), Qt::CaseInsensitive) == 0) { sign.prepend(QLatin1String("From ")); } else { sign.prepend(QLatin1String("To ")); } } else { QDateTime time; if (currentPost()->repeatedDateTime.isNull()) { time = currentPost()->creationDateTime; } else { time = currentPost()->repeatedDateTime; } - sign += QStringLiteral("%3").arg(currentPost()->link) + sign += QStringLiteral("%3").arg(currentPost()->link.toDisplayString()) .arg(time.toString(Qt::DefaultLocaleLongDate)).arg(formatDateTime(time)); } if (!currentPost()->source.isEmpty()) { sign += QLatin1String(" - "); if (currentPost()->source == QLatin1String("ostatus") && !currentPost()->author.homePageUrl.isEmpty()) { - QUrl srcUrl(currentPost()->author.homePageUrl); sign += i18n("%2", - currentPost()->author.homePageUrl, - srcUrl.host()); + currentPost()->author.homePageUrl.toDisplayString(), + currentPost()->author.homePageUrl.host()); } else { sign += currentPost()->source; } } if (!currentPost()->isPrivate) { if (!currentPost()->replyToPostId.isEmpty()) { - QString link = currentAccount()->microblog()->postUrl(currentAccount(), currentPost()->replyToUserName, + QUrl link = currentAccount()->microblog()->postUrl(currentAccount(), currentPost()->replyToUserName, currentPost()->replyToPostId); QString showConMsg = i18n("Show Conversation"); QString threadlink; if (currentPost()->conversationId.isEmpty()) { threadlink = QLatin1String("thread://") + currentPost()->postId; } else { threadlink = QLatin1String("conversation://") + currentPost()->conversationId; } sign += QLatin1String(" - ") + i18n("in reply to @%4 %3", - currentPost()->replyToPostId, link, webIconText, currentPost()->replyToUserName) + QLatin1Char(' '); + currentPost()->replyToPostId, link.toDisplayString(), webIconText, currentPost()->replyToUserName) + QLatin1Char(' '); sign += QLatin1String(""); } //ReTweet detection if (!currentPost()->repeatedFromUsername.isEmpty()) { const QString retweet = QLatin1String("
") + d->mBlog->generateRepeatedByUserTooltip(QStringLiteral("%2") .arg(currentPost()->repeatedFromUsername) .arg(currentPost()->repeatedFromUsername)); sign.append(retweet); } } sign.prepend(QLatin1String("

")); sign.append(QLatin1String("

")); return sign; } QString TwitterApiPostWidget::getUsernameHyperlink(const Choqok::User &user) const { return QStringLiteral("%3") .arg(user.userName) .arg(user.description.isEmpty() ? user.realName : user.description.toHtmlEscaped()) .arg(user.userName); } void TwitterApiPostWidget::slotReply() { setReadWithSignal(); if (currentPost()->isPrivate) { TwitterApiAccount *account = qobject_cast(currentAccount()); d->mBlog->showDirectMessageDialog(account, currentPost()->author.userName); } else { QString replyto = QStringLiteral("@%1").arg(currentPost()->author.userName); QString postId = currentPost()->postId; QString username = currentPost()->author.userName; if (!currentPost()->repeatedFromUsername.isEmpty()) { replyto.prepend(QStringLiteral("@%1 ").arg(currentPost()->repeatedFromUsername)); postId = currentPost()->repeatedPostId; } Q_EMIT reply(replyto, postId, username); } } void TwitterApiPostWidget::slotWriteTo() { Q_EMIT reply(QStringLiteral("@%1").arg(currentPost()->author.userName), QString(), currentPost()->author.userName); } void TwitterApiPostWidget::slotReplyToAll() { QString txt = QStringLiteral("@%1").arg(currentPost()->author.userName); Q_EMIT reply(txt, currentPost()->postId, currentPost()->author.userName); } void TwitterApiPostWidget::setFavorite() { setReadWithSignal(); TwitterApiMicroBlog *mic = d->mBlog; if (currentPost()->isFavorited) { connect(mic, SIGNAL(favoriteRemoved(Choqok::Account*,QString)), this, SLOT(slotSetFavorite(Choqok::Account*,QString))); mic->removeFavorite(currentAccount(), currentPost()->postId); } else { connect(mic, SIGNAL(favoriteCreated(Choqok::Account*,QString)), this, SLOT(slotSetFavorite(Choqok::Account*,QString))); mic->createFavorite(currentAccount(), currentPost()->postId); } } void TwitterApiPostWidget::slotSetFavorite(Choqok::Account *theAccount, const QString &postId) { if (currentAccount() == theAccount && postId == currentPost()->postId) { qCDebug(CHOQOK) << postId; currentPost()->isFavorited = !currentPost()->isFavorited; updateFavStat(); disconnect(d->mBlog, SIGNAL(favoriteRemoved(Choqok::Account*,QString)), this, SLOT(slotSetFavorite(Choqok::Account*,QString))); disconnect(d->mBlog, SIGNAL(favoriteCreated(Choqok::Account*,QString)), this, SLOT(slotSetFavorite(Choqok::Account*,QString))); } } void TwitterApiPostWidget::updateFavStat() { if (currentPost()->isFavorited) { d->btnFav->setChecked(true); d->btnFav->setIcon(QIcon::fromTheme(QLatin1String("rating"))); } else { d->btnFav->setChecked(false); d->btnFav->setIcon(unFavIcon); } } void TwitterApiPostWidget::checkAnchor(const QUrl &url) { QString scheme = url.scheme(); if (scheme == QLatin1String("replyto")) { if (d->isBasePostShowed) { setContent(prepareStatus(currentPost()->content).replace(QLatin1String("isBasePostShowed = false; return; } else { connect(currentAccount()->microblog(), SIGNAL(postFetched(Choqok::Account*,Choqok::Post*)), this, SLOT(slotBasePostFetched(Choqok::Account*,Choqok::Post*))); Choqok::Post *ps = new Choqok::Post; ps->postId = url.host(); currentAccount()->microblog()->fetchPost(currentAccount(), ps); } } else if (scheme == QLatin1String("thread")) { TwitterApiShowThread *wd = new TwitterApiShowThread(currentAccount(), currentPost(), nullptr); wd->resize(this->width(), wd->height()); connect(wd, SIGNAL(forwardReply(QString,QString,QString)), this, SIGNAL(reply(QString,QString,QString))); connect(wd, SIGNAL(forwardResendPost(QString)), this, SIGNAL(resendPost(QString))); wd->show(); } else { Choqok::UI::PostWidget::checkAnchor(url); } } void TwitterApiPostWidget::slotBasePostFetched(Choqok::Account *theAccount, Choqok::Post *post) { if (theAccount == currentAccount() && post && post->postId == currentPost()->replyToPostId) { qCDebug(CHOQOK); disconnect(currentAccount()->microblog(), SIGNAL(postFetched(Choqok::Account*,Choqok::Post*)), this, SLOT(slotBasePostFetched(Choqok::Account*,Choqok::Post*))); if (d->isBasePostShowed) { return; } d->isBasePostShowed = true; QString color; if (Choqok::AppearanceSettings::isCustomUi()) { color = Choqok::AppearanceSettings::readForeColor().lighter().name(); } else { color = this->palette().dark().color().name(); } QString baseStatusText = QLatin1String("

"); baseStatusText += QLatin1String("") + post->author.userName + QLatin1String(" : "); baseStatusText += prepareStatus(post->content) + QLatin1String("

"); setContent(content().prepend(baseStatusText.replace(QLatin1String("owners < 1 ) delete post; } } void TwitterApiPostWidget::repeatPost() { setReadWithSignal(); QString postId; if (currentPost()->repeatedPostId.isEmpty()) { postId = currentPost()->postId; } else { postId = currentPost()->repeatedPostId; } auto q_answer = KMessageBox::questionYesNo(Choqok::UI::Global::mainWindow(), d->mBlog->repeatQuestion(), QString(), KStandardGuiItem::yes(), KStandardGuiItem::cancel(), QLatin1String("dontAskRepeatConfirm")); if ( q_answer == KMessageBox::Yes) { d->mBlog->repeatPost(currentAccount(), postId); } } diff --git a/helperlibs/twitterapihelper/twitterapiwhoiswidget.cpp b/helperlibs/twitterapihelper/twitterapiwhoiswidget.cpp index 32ee4e7b..a19d2613 100644 --- a/helperlibs/twitterapihelper/twitterapiwhoiswidget.cpp +++ b/helperlibs/twitterapihelper/twitterapiwhoiswidget.cpp @@ -1,426 +1,426 @@ /* 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 "twitterapiwhoiswidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "choqokappearancesettings.h" #include "choqoktools.h" #include "choqoktypes.h" #include "mediamanager.h" #include "microblog.h" #include "notifymanager.h" #include "twitterapiaccount.h" #include "twitterapidebug.h" #include "twitterapimicroblog.h" class TwitterApiWhoisWidget::Private { public: Private(TwitterApiAccount *account, const QString &userN) : currentAccount(account), waitFrame(0), job(0), username(userN) { mBlog = qobject_cast(account->microblog()); } QTextBrowser *wid; TwitterApiAccount *currentAccount; TwitterApiMicroBlog *mBlog; QFrame *waitFrame; QPointer job; Choqok::Post currentPost; QString username; QString errorMessage; QString followersCount; QString friendsCount; QString statusesCount; QString timeZone; QString imgActions; // bool isFollowing; }; TwitterApiWhoisWidget::TwitterApiWhoisWidget(TwitterApiAccount *theAccount, const QString &username, const Choqok::Post &post, QWidget *parent) : QFrame(parent), d(new Private(theAccount, username)) { qCDebug(CHOQOK); setAttribute(Qt::WA_DeleteOnClose); d->currentPost = post; loadUserInfo(theAccount, username); d->wid = new QTextBrowser(this); setFrameShape(StyledPanel); setFrameShadow(Sunken); d->wid->setFrameShape(QFrame::NoFrame); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(d->wid); this->setLayout(layout); this->setWindowFlags(Qt::Popup);// | Qt::FramelessWindowHint | Qt::Ta); d->wid->setOpenLinks(false); connect(d->wid, SIGNAL(anchorClicked(QUrl)), this, SLOT(checkAnchor(QUrl))); setupUi(); setActionImages(); } TwitterApiWhoisWidget::~TwitterApiWhoisWidget() { qCDebug(CHOQOK); delete d; } void TwitterApiWhoisWidget::loadUserInfo(TwitterApiAccount *theAccount, const QString &username) { qCDebug(CHOQOK); QString urlStr; QString user = username; if (user.contains(QLatin1Char('@'))) { QStringList lst = user.split(QLatin1Char('@')); if (lst.count() == 2) { //USER@HOST QString host = lst[1]; urlStr = QStringLiteral("https://%1/api").arg(host); user = lst[0]; } } else if (d->currentPost.source == QLatin1String("ostatus") && !d->currentPost.author.homePageUrl.isEmpty()) { - urlStr = d->currentPost.author.homePageUrl; + urlStr = d->currentPost.author.homePageUrl.toDisplayString(); if (urlStr.endsWith(user)) { int len = urlStr.length(); int userLen = user.length(); urlStr.remove(len - userLen, userLen); qCDebug(CHOQOK) << urlStr; } urlStr.append(QLatin1String("api")); } else { urlStr = theAccount->apiUrl().url(); } QUrl url(urlStr); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QStringLiteral("/users/show/%1.json").arg(user)); // qCDebug(CHOQOK) << url; KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (d->currentPost.source != QLatin1String("ostatus")) { job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(d->mBlog->authorizationHeader(theAccount, url, QNetworkAccessManager::GetOperation))); } d->job = job; connect(job, SIGNAL(result(KJob*)), SLOT(userInfoReceived(KJob*))); job->start(); } void TwitterApiWhoisWidget::userInfoReceived(KJob *job) { qCDebug(CHOQOK); if (job->error()) { qCCritical(CHOQOK) << "Job Error:" << job->errorString(); if (Choqok::UI::Global::mainWindow()->statusBar()) { Choqok::UI::Global::mainWindow()->statusBar()->showMessage(job->errorString()); } slotCancel(); return; } KIO::StoredTransferJob *stj = qobject_cast(job); // qCDebug(CHOQOK)<data(); const QJsonDocument json = QJsonDocument::fromJson(stj->data()); if (json.isNull()) { qCDebug(CHOQOK) << "JSON parsing failed! Data is:\n\t" << stj->data(); d->errorMessage = i18n("Cannot load user information."); updateHtml(); showForm(); return; } const QVariantMap map = json.toVariant().toMap(); QString timeStr; Choqok::Post post; d->errorMessage = map[QLatin1String("error")].toString(); if (d->errorMessage.isEmpty()) { //No Error post.author.realName = map[QLatin1String("name")].toString(); post.author.userName = map[QLatin1String("screen_name")].toString(); post.author.location = map[QLatin1String("location")].toString(); post.author.description = map[QLatin1String("description")].toString(); - post.author.profileImageUrl = map[QLatin1String("profile_image_url")].toString(); - post.author.homePageUrl = map[QLatin1String("url")].toString(); + post.author.profileImageUrl = map[QLatin1String("profile_image_url")].toUrl(); + post.author.homePageUrl = map[QLatin1String("url")].toUrl(); d->timeZone = map[QLatin1String("time_zone")].toString(); d->followersCount = map[QLatin1String("followers_count")].toString(); d->friendsCount = map[QLatin1String("friends_count")].toString(); d->statusesCount = map[QLatin1String("statuses_count")].toString(); QVariantMap var = map[QLatin1String("status")].toMap(); post.content = var[QLatin1String("text")].toString(); post.creationDateTime = d->mBlog->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.replyToUserId = var[QLatin1String("in_reply_to_user_id")].toString(); post.replyToUserName = var[QLatin1String("in_reply_to_screen_name")].toString(); post.source = var[QLatin1String("source")].toString(); d->currentPost = post; } updateHtml(); showForm(); QPixmap userAvatar = Choqok::MediaManager::self()->fetchImage(post.author.profileImageUrl, Choqok::MediaManager::Async); if (!userAvatar.isNull()) { d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("img://profileImage")), userAvatar); } else { - connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - this, SLOT(avatarFetched(QString,QPixmap))); + connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + this, SLOT(avatarFetched(QUrl,QPixmap))); connect(Choqok::MediaManager::self(), SIGNAL(fetchError(QString,QString)), this, SLOT(avatarFetchError(QString,QString))); } } -void TwitterApiWhoisWidget::avatarFetched(const QString &remoteUrl, const QPixmap &pixmap) +void TwitterApiWhoisWidget::avatarFetched(const QUrl &remoteUrl, const QPixmap &pixmap) { qCDebug(CHOQOK); if (remoteUrl == d->currentPost.author.profileImageUrl) { const QUrl url(QLatin1String("img://profileImage")); d->wid->document()->addResource(QTextDocument::ImageResource, url, pixmap); updateHtml(); - disconnect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - this, SLOT(avatarFetched(QString,QPixmap))); + disconnect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + this, SLOT(avatarFetched(QUrl,QPixmap))); disconnect(Choqok::MediaManager::self(), SIGNAL(fetchError(QString,QString)), this, SLOT(avatarFetchError(QString,QString))); } } -void TwitterApiWhoisWidget::avatarFetchError(const QString &remoteUrl, const QString &errMsg) +void TwitterApiWhoisWidget::avatarFetchError(const QUrl &remoteUrl, const QString &errMsg) { qCDebug(CHOQOK); Q_UNUSED(errMsg); if (remoteUrl == d->currentPost.author.profileImageUrl) { ///Avatar fetching is failed! but will not disconnect to get the img if it fetches later! const QUrl url(QLatin1String("img://profileImage")); d->wid->document()->addResource(QTextDocument::ImageResource, url, QIcon::fromTheme(QLatin1String("image-missing")).pixmap(48)); updateHtml(); } } void TwitterApiWhoisWidget::updateHtml() { qCDebug(CHOQOK); QString html; if (d->errorMessage.isEmpty()) { QString url = d->currentPost.author.homePageUrl.isEmpty() ? QString() - : QStringLiteral("%1").arg(d->currentPost.author.homePageUrl); + : QStringLiteral("%1").arg(d->currentPost.author.homePageUrl.toDisplayString()); QString mainTable = QString(QLatin1String("\ \
\
%1
\
%2

\ @%3 %4%5
\ %6
\ %7
")) .arg(d->imgActions) .arg(d->currentPost.author.realName.toHtmlEscaped()) .arg(d->currentPost.author.userName).arg(d->currentPost.author.location.toHtmlEscaped()) .arg(!d->timeZone.isEmpty() ? QLatin1Char('(') + d->timeZone + QLatin1Char(')') : QString()) .arg(d->currentPost.author.description) .arg(url); QString countTable = QString(QLatin1String("\ \ \ \
%1
") + i18nc("User posts", "Posts") + QLatin1String("
%2
") + i18nc("User friends", "Friends") + QLatin1String("
%3
") + i18nc("User followers" , "Followers") + QLatin1String("

")) .arg(d->statusesCount) .arg(d->friendsCount) .arg(d->followersCount); html = mainTable + countTable; if (!d->currentPost.content.isEmpty()) { html.append(QString(i18n("Last Status: %1
")).arg(d->currentPost.content)); } } else { html = i18n("

%1

", d->errorMessage); } d->wid->setHtml(html); } void TwitterApiWhoisWidget::showForm() { qCDebug(CHOQOK); QPoint pos = d->waitFrame->pos(); d->waitFrame->deleteLater(); d->wid->resize(320, 200); d->wid->document()->setTextWidth(width() - 2); d->wid->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); int h = d->wid->document()->size().toSize().height() + 10; d->wid->setMinimumHeight(h); d->wid->setMaximumHeight(h); this->resize(320, h + 4); int desktopHeight = QApplication::desktop()->height(); int desktopWidth = QApplication::desktop()->width(); if ((pos.x() + this->width()) > desktopWidth) { pos.setX(desktopWidth - width()); } if ((pos.y() + this->height()) > desktopHeight) { pos.setY(desktopHeight - height()); } move(pos); QWidget::show(); } void TwitterApiWhoisWidget::show(QPoint pos) { qCDebug(CHOQOK); d->waitFrame = new QFrame(this); d->waitFrame->setFrameShape(NoFrame); d->waitFrame->setWindowFlags(Qt::Popup); KAnimatedButton *waitButton = new KAnimatedButton; waitButton->setToolTip(i18n("Please wait...")); connect( waitButton, SIGNAL(clicked(bool)), SLOT(slotCancel()) ); waitButton->setAnimationPath(QLatin1String("process-working-kde")); waitButton->start(); QVBoxLayout *ly = new QVBoxLayout(d->waitFrame); ly->setSpacing(0); ly->setContentsMargins(0, 0, 0, 0); ly->addWidget(waitButton); d->waitFrame->move(pos - QPoint(15, 15)); d->waitFrame->show(); } void TwitterApiWhoisWidget::checkAnchor(const QUrl url) { qCDebug(CHOQOK); if (url.scheme() == QLatin1String("choqok")) { if (url.host() == QLatin1String("close")) { this->close(); } else if (url.host() == QLatin1String("subscribe")) { d->mBlog->createFriendship(d->currentAccount, d->username); connect(d->mBlog, SIGNAL(friendshipCreated(Choqok::Account*,QString)), SLOT(slotFriendshipCreated(Choqok::Account*,QString))); } else if (url.host() == QLatin1String("unsubscribe")) { d->mBlog->destroyFriendship(d->currentAccount, d->username); connect(d->mBlog, SIGNAL(friendshipDestroyed(Choqok::Account*,QString)), SLOT(slotFriendshipDestroyed(Choqok::Account*,QString))); } else if (url.host() == QLatin1String("block")) { d->mBlog->blockUser(d->currentAccount, d->username); // connect(d->mBlog, SIGNAL(userBlocked(Choqok::Account*,QString)), SLOT(slotUserBlocked(Choqok::Account*,QString))); } } else { Choqok::openUrl(url); close(); } } void TwitterApiWhoisWidget::setupUi() { qCDebug(CHOQOK); d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://close")), QIcon::fromTheme(QLatin1String("dialog-close")).pixmap(16)); QString style = QLatin1String("color: %1; background-color: %2"); if (Choqok::AppearanceSettings::isCustomUi()) { setStyleSheet(style.arg(Choqok::AppearanceSettings::readForeColor().name()) .arg(Choqok::AppearanceSettings::readBackColor().name())); } else { QPalette p = window()->palette(); setStyleSheet(style.arg(p.color(QPalette::WindowText).name()).arg(p.color(QPalette::Window).name())); } } void TwitterApiWhoisWidget::slotCancel() { qCDebug(CHOQOK); if (d->waitFrame) { d->waitFrame->deleteLater(); } if (d->job) { d->job->kill(); } this->close(); } void TwitterApiWhoisWidget::setActionImages() { d->imgActions.clear(); if (d->username.compare(d->currentAccount->username(), Qt::CaseInsensitive) != 0) { if (d->currentAccount->friendsList().contains(d->username, Qt::CaseInsensitive)) { d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://unsubscribe")), QIcon::fromTheme(QLatin1String("list-remove-user")).pixmap(16)); d->imgActions += QLatin1String(" "); } else { d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://subscribe")), QIcon::fromTheme(QLatin1String("list-add-user")).pixmap(16)); d->imgActions += QLatin1String(" "); } d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://block")), QIcon::fromTheme(QLatin1String("dialog-cancel")).pixmap(16)); d->imgActions += QLatin1String(""); } } void TwitterApiWhoisWidget::slotFriendshipCreated(Choqok::Account *theAccount, const QString &username) { if (theAccount == d->currentAccount && username == d->username) { setActionImages(); updateHtml(); } } void TwitterApiWhoisWidget::slotFriendshipDestroyed(Choqok::Account *theAccount, const QString &username) { if (theAccount == d->currentAccount && username == d->username) { setActionImages(); updateHtml(); } } // void TwitterApiWhoisWidget::slotUserBlocked(Choqok::Account* theAccount, const QString &username) // { // if(theAccount == d->currentAccount && username == d->username){ // Choqok::NotifyManager::success( i18n("Your posts are blocked for %1.", username) ); // } // } diff --git a/helperlibs/twitterapihelper/twitterapiwhoiswidget.h b/helperlibs/twitterapihelper/twitterapiwhoiswidget.h index 720eb52e..f08beb36 100644 --- a/helperlibs/twitterapihelper/twitterapiwhoiswidget.h +++ b/helperlibs/twitterapihelper/twitterapiwhoiswidget.h @@ -1,72 +1,72 @@ /* 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/ */ #ifndef TWITTERAPIWHOISWIDGET_H #define TWITTERAPIWHOISWIDGET_H #include #include #include "choqoktypes.h" #include "choqok_export.h" namespace Choqok { class Account; } class TwitterApiAccount; class KJob; class CHOQOK_HELPER_EXPORT TwitterApiWhoisWidget : public QFrame { Q_OBJECT public: TwitterApiWhoisWidget(TwitterApiAccount *theAccount, const QString &userName, const Choqok::Post &post, QWidget *parent = 0); ~TwitterApiWhoisWidget(); void show(QPoint pos); protected Q_SLOTS: void checkAnchor(const QUrl url); void userInfoReceived(KJob *job); void slotCancel(); - void avatarFetchError(const QString &remoteUrl, const QString &errMsg); - void avatarFetched(const QString &remoteUrl, const QPixmap &pixmap); + void avatarFetchError(const QUrl &remoteUrl, const QString &errMsg); + void avatarFetched(const QUrl &remoteUrl, const QPixmap &pixmap); void slotFriendshipCreated(Choqok::Account *, const QString &); void slotFriendshipDestroyed(Choqok::Account *, const QString &); // void slotUserBlocked(Choqok::Account*, const QString&); protected: void updateHtml(); void setActionImages(); // static const QString baseText; private: void setupUi(); void showForm(); void loadUserInfo(TwitterApiAccount *thAccount, const QString &username); class Private; Private *const d; }; #endif // TWITTERAPIWHOISWIDGET_H diff --git a/libchoqok/choqoktypes.h b/libchoqok/choqoktypes.h index 29015c1a..b857d860 100644 --- a/libchoqok/choqoktypes.h +++ b/libchoqok/choqoktypes.h @@ -1,117 +1,118 @@ /* 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/ */ #ifndef CHOQOKTYPES_H #define CHOQOKTYPES_H #include +#include #include "choqok_export.h" namespace Choqok { enum JobResult { Fail = 0, Success = 1 }; class CHOQOK_EXPORT User { public: User() : isProtected(false) {} User(const User& u) = default; User(User&& u) = default; virtual ~User() {} User& operator=(const User& u) = default; User& operator=(User&& u) = default; QString userId; QString realName; QString userName; QString location; QString description; - QString profileImageUrl; - QString homePageUrl; + QUrl profileImageUrl; + QUrl homePageUrl; bool isProtected; uint followersCount; }; class CHOQOK_EXPORT QuotedPost { public: QString username; - QString profileImageUrl; + QUrl profileImageUrl; QString postId; - QString content; + QString content; }; class CHOQOK_EXPORT Post { public: Post() : isFavorited(false), isPrivate(false), isError(false), isRead(false), owners(0) {} Post(const Post& u) = default; Post(Post&& u) = default; virtual ~Post() {} Post& operator=(const Post& u) = default; Post& operator=(Post&& u) = default; QDateTime creationDateTime; QString postId; - QString link; + QUrl link; QString content; QString source; QString replyToPostId; QString replyToUserId; bool isFavorited; QString replyToUserName; User author; QString type; bool isPrivate; bool isError; bool isRead; QString repeatedFromUsername; QString repeatedPostId; QDateTime repeatedDateTime; QString conversationId; - QString media; // first Image of Post, if available + QUrl media; // first Image of Post, if available QuotedPost quotedPost; unsigned int owners; // number of associated PostWidgets }; /** Describe an specific timeline, Should use by @ref MicroBlog */ class CHOQOK_EXPORT TimelineInfo { public: QString name; QString description; QString icon; }; } #endif diff --git a/libchoqok/mediamanager.cpp b/libchoqok/mediamanager.cpp index d63afc57..b4cab548 100644 --- a/libchoqok/mediamanager.cpp +++ b/libchoqok/mediamanager.cpp @@ -1,243 +1,242 @@ /* 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 "mediamanager.h" #include #include #include #include #include #include #include #include #include #include #include "choqokbehaviorsettings.h" #include "choqokuiglobal.h" #include "libchoqokdebug.h" #include "pluginmanager.h" #include "uploader.h" namespace Choqok { class MediaManager::Private { public: Private() : emoticons(KEmoticons().theme()), cache(QLatin1String("choqok-userimages"), 30000000), uploader(0) {} KEmoticonsTheme emoticons; KImageCache cache; - QHash queue; + QHash queue; QPixmap defaultImage; Uploader *uploader; }; MediaManager::MediaManager() : QObject(qApp), d(new Private) { d->defaultImage = QIcon::fromTheme(QLatin1String("image-loading")).pixmap(48); } MediaManager::~MediaManager() { delete d; mSelf = nullptr; } MediaManager *MediaManager::mSelf = nullptr; MediaManager *MediaManager::self() { if (!mSelf) { mSelf = new MediaManager; } return mSelf; } QPixmap &MediaManager::defaultImage() { return d->defaultImage; } QString MediaManager::parseEmoticons(const QString &text) { return d->emoticons.parseEmoticons(text, KEmoticonsTheme::DefaultParse, QStringList() << QLatin1String("(e)")); } -QPixmap MediaManager::fetchImage(const QString &remoteUrl, ReturnMode mode /*= Sync*/) +QPixmap MediaManager::fetchImage(const QUrl &remoteUrl, ReturnMode mode /*= Sync*/) { QPixmap p; - if (d->cache.findPixmap(remoteUrl, &p)) { + if (d->cache.findPixmap(remoteUrl.toDisplayString(), &p)) { Q_EMIT imageFetched(remoteUrl, p); } else if (mode == Async) { if (d->queue.values().contains(remoteUrl)) { ///The file is on the way, wait to download complete. return p; } - QUrl srcUrl(remoteUrl); - KIO::StoredTransferJob *job = KIO::storedGet(srcUrl, KIO::NoReload, KIO::HideProgressInfo) ; + KIO::StoredTransferJob *job = KIO::storedGet(remoteUrl, KIO::NoReload, KIO::HideProgressInfo) ; if (!job) { qCCritical(CHOQOK) << "Cannot create a FileCopyJob!"; QString errMsg = i18n("Cannot create a KDE Job. Please check your installation."); Q_EMIT fetchError(remoteUrl, errMsg); return p; } d->queue.insert(job, remoteUrl); connect(job, SIGNAL(result(KJob*)), this, SLOT(slotImageFetched(KJob*))); job->start(); } return p; } void MediaManager::slotImageFetched(KJob *job) { KIO::StoredTransferJob *baseJob = qobject_cast(job); - QString remote = d->queue.value(job); + QUrl remote = d->queue.value(job); d->queue.remove(job); int responseCode = 0; if (baseJob->metaData().contains(QStringLiteral("responsecode"))) { responseCode = baseJob->queryMetaData(QStringLiteral("responsecode")).toInt(); } if (job->error() || (responseCode > 399 && responseCode < 600)) { qCCritical(CHOQOK) << "Job error:" << job->error() << "\t" << job->errorString(); qCCritical(CHOQOK) << "HTTP response code" << responseCode; QString errMsg = i18n("Cannot download image from %1.", job->errorString()); Q_EMIT fetchError(remote, errMsg); } else { QPixmap p; if (p.loadFromData(baseJob->data())) { - d->cache.insertPixmap(remote, p); + d->cache.insertPixmap(remote.toDisplayString(), p); Q_EMIT imageFetched(remote, p); } else { qCCritical(CHOQOK) << "Cannot parse reply from " << baseJob->url().toDisplayString(); Q_EMIT fetchError(remote, i18n("The request failed. Cannot get image file.")); } } } void MediaManager::clearImageCache() { d->cache.clear(); } QPixmap MediaManager::convertToGrayScale(const QPixmap &pic) { QImage result = pic.toImage(); for (int y = 0; y < result.height(); ++y) { for (int x = 0; x < result.width(); ++x) { int pixel = result.pixel(x, y); int gray = qGray(pixel); int alpha = qAlpha(pixel); result.setPixel(x, y, qRgba(gray, gray, gray, alpha)); } } return QPixmap::fromImage(result); } void MediaManager::uploadMedium(const QUrl &localUrl, const QString &pluginId) { QString pId = pluginId; if (pId.isEmpty()) { pId = Choqok::BehaviorSettings::lastUsedUploaderPlugin(); } if (pId.isEmpty()) { Q_EMIT mediumUploadFailed(localUrl, i18n("No pluginId specified, And last used plugin is null.")); return; } if (!d->uploader) { Plugin *plugin = PluginManager::self()->loadPlugin(pId); d->uploader = qobject_cast(plugin); } else if (d->uploader->pluginName() != pId) { // qCDebug(CHOQOK)<<"CREATING A NEW UPLOADER OBJECT"; PluginManager::self()->unloadPlugin(d->uploader->pluginName()); Plugin *plugin = PluginManager::self()->loadPlugin(pId); d->uploader = qobject_cast(plugin); } if (!d->uploader) { return; } KIO::StoredTransferJob *picJob = KIO::storedGet(localUrl, KIO::Reload, KIO::HideProgressInfo); picJob->exec(); if (picJob->error()) { qCritical() << "Job error:" << picJob->errorString(); KMessageBox::detailedError(UI::Global::mainWindow(), i18n("Uploading medium failed: cannot read the medium file."), picJob->errorString()); return; } const QByteArray picData = picJob->data(); if (picData.count() == 0) { qCritical() << "Cannot read the media file, please check if it exists."; KMessageBox::error(UI::Global::mainWindow(), i18n("Uploading medium failed: cannot read the medium file.")); return; } connect(d->uploader, SIGNAL(mediumUploaded(QUrl,QString)), this, SIGNAL(mediumUploaded(QUrl,QString))); connect(d->uploader, SIGNAL(uploadingFailed(QUrl,QString)), this, SIGNAL(mediumUploadFailed(QUrl,QString))); const QMimeDatabase db; d->uploader->upload(localUrl, picData, db.mimeTypeForUrl(localUrl).name().toLocal8Bit()); } QByteArray MediaManager::createMultipartFormData(const QMap< QString, QByteArray > &formdata, const QList< QMap< QString, QByteArray > > &mediaFiles) { QByteArray newLine("\r\n"); QString formHeader(QLatin1String(newLine) + QLatin1String("Content-Disposition: form-data; name=\"%1\"")); QByteArray header("--AaB03x"); QByteArray footer("--AaB03x--"); QString fileHeader(QLatin1String(newLine) + QLatin1String("Content-Disposition: file; name=\"%1\"; filename=\"%2\"")); QByteArray data; data.append(header); if (!mediaFiles.isEmpty()) { QList< QMap< QString, QByteArray > >::const_iterator it1 = mediaFiles.constBegin(); QList< QMap< QString, QByteArray > >::const_iterator endIt1 = mediaFiles.constEnd(); for (; it1 != endIt1; ++it1) { data.append(fileHeader.arg(QLatin1String(it1->value(QLatin1String("name")).data())).arg(QLatin1String(it1->value(QLatin1String("filename")).data())).toUtf8()); data.append(newLine + "Content-Type: " + it1->value(QLatin1String("mediumType"))); data.append(newLine); data.append(newLine + it1->value(QLatin1String("medium"))); } } for (const QString &key: formdata.keys()) { data.append(newLine); data.append(header); data.append(formHeader.arg(key).toLatin1()); data.append(newLine); data.append(newLine + formdata.value(key)); } data.append(newLine); data.append(footer); return data; } } diff --git a/libchoqok/mediamanager.h b/libchoqok/mediamanager.h index b1aaa99b..6bb5a702 100644 --- a/libchoqok/mediamanager.h +++ b/libchoqok/mediamanager.h @@ -1,135 +1,135 @@ /* 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/ */ #ifndef MEDIAMANAGER_H #define MEDIAMANAGER_H #include #include #include #include #include "choqok_export.h" namespace KIO { class Job; } class KJob; namespace Choqok { /** @brief Media files manager! A simple and global way to fetch and cache images @author Mehrdad Momeny \ */ class CHOQOK_EXPORT MediaManager : public QObject { Q_OBJECT public: enum ReturnMode { Sync = 0, Async }; ~MediaManager(); static MediaManager *self(); /** * @brief Fetch an Image and cache it for later use. * * @param remoteUrl The URL of image to fetch * @param mode Return mode, if set to Sync and the image is not available in the cache the null pixmap will be returned. * if mode set to @ref Async and image is not available in the cache, the null pixmap will be returned * and then @ref MediaManager will fetch the image and emit @ref imageFetched() on success or * emit @ref fetchError() on error. * And if mode set to @ref Sync and image is not in the cache @ref MediaManager will not fetch it. * * @return return @ref QPixmap of requested image if exists in cache, otherwise null pixmap */ - QPixmap fetchImage(const QString &remoteUrl, ReturnMode mode = Sync); + QPixmap fetchImage(const QUrl &remoteUrl, ReturnMode mode = Sync); /** * @return KDE Default image */ QPixmap &defaultImage(); /** * @brief Parse a text for EmotIcons with kde default theme. */ QString parseEmoticons(const QString &text); static QPixmap convertToGrayScale(const QPixmap &pic); /** Upload medium at @p localUrl to @p pluginId service or to last used service when @p pluginId is empty. @see mediumUploaded() @see mediumUploadFailed() */ void uploadMedium(const QUrl &localUrl, const QString &pluginId = QString()); /** Create and return a byte array containing a multipart/form-data to send with HTTP POST request Boundary is AaB03x @param formdata are the "form-data" parts of data. This map knows as a list of name/value pairs @param mediaFiles are media files attached to form, each file stored in one QMap in list @note media file maps should contain these keys: name: The name of entry filename: the file name on server medium: contain the medium data loaded from disk! mediumType: type of medium file */ static QByteArray createMultipartFormData(const QMap &formdata, const QList< QMap > &mediaFiles); public Q_SLOTS: /** * @brief Clear image cache */ void clearImageCache(); Q_SIGNALS: - void fetchError(const QString &remoteUrl, const QString &errMsg); - void imageFetched(const QString &remoteUrl, const QPixmap &pixmap); + void fetchError(const QUrl &remoteUrl, const QString &errMsg); + void imageFetched(const QUrl &remoteUrl, const QPixmap &pixmap); void mediumUploaded(const QUrl &localUrl, const QString &remoteUrl); void mediumUploadFailed(const QUrl &localUrl, const QString &errorMessage); protected Q_SLOTS: void slotImageFetched(KJob *job); protected: MediaManager(); private: class Private; Private *const d; static MediaManager *mSelf; }; } #endif diff --git a/libchoqok/microblog.cpp b/libchoqok/microblog.cpp index 6349dcf9..9b2d29e4 100644 --- a/libchoqok/microblog.cpp +++ b/libchoqok/microblog.cpp @@ -1,233 +1,233 @@ /* 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 "microblog.h" #include #include #include #include "account.h" #include "accountmanager.h" #include "choqokbehaviorsettings.h" #include "composerwidget.h" #include "libchoqokdebug.h" #include "microblogwidget.h" #include "postwidget.h" #include "timelinewidget.h" namespace Choqok { class MicroBlog::Private { public: QString serviceName; QString homepage; QStringList timelineTypes; QTimer *saveTimelinesTimer; }; MicroBlog::MicroBlog(const QString &componentName, QObject *parent) : Plugin(componentName, parent), d(new Private) { qCDebug(CHOQOK); d->saveTimelinesTimer = new QTimer(this); d->saveTimelinesTimer->setInterval(BehaviorSettings::notifyInterval() * 60000); connect(d->saveTimelinesTimer, SIGNAL(timeout()), SIGNAL(saveTimelines())); connect(BehaviorSettings::self(), SIGNAL(configChanged()), this, SLOT(slotConfigChanged())); d->saveTimelinesTimer->start(); } MicroBlog::~MicroBlog() { qCDebug(CHOQOK); delete d; } QMenu *MicroBlog::createActionsMenu(Account *, QWidget *parent) { return new QMenu(parent); } QString MicroBlog::serviceName() const { return d->serviceName; } QString MicroBlog::homepageUrl() const { return d->homepage; } QString MicroBlog::errorString(ErrorType type) { switch (type) { case ServerError: return i18n("The server returned an error"); break; case CommunicationError: return i18n("Error on communication with server"); break; case ParsingError: return i18n("Error on parsing results"); break; case AuthenticationError: return i18n("Authentication error"); break; case NotSupportedError: return i18n("The server does not support this feature"); break; case OtherError: return i18n("Unknown error"); break; }; return QString(); } void MicroBlog::setServiceName(const QString &serviceName) { d->serviceName = serviceName; } void MicroBlog::setServiceHomepageUrl(const QString &homepage) { d->homepage = homepage; } QStringList MicroBlog::timelineNames() const { return d->timelineTypes; } void MicroBlog::setTimelineNames(const QStringList &types) { d->timelineTypes = types; } void MicroBlog::addTimelineName(const QString &name) { d->timelineTypes << name; } bool MicroBlog::isValidTimeline(const QString &timeline) { return d->timelineTypes.contains(timeline); } void MicroBlog::slotConfigChanged() { d->saveTimelinesTimer->setInterval(BehaviorSettings::notifyInterval() * 60000); } /// UI Objects: Account *MicroBlog::createNewAccount(const QString &alias) { Choqok::Account *acc = Choqok::AccountManager::self()->findAccount(alias); if (!acc) { return new Choqok::Account(this, alias); } else { return 0; } } UI::MicroBlogWidget *MicroBlog::createMicroBlogWidget(Account *account, QWidget *parent) { return new UI::MicroBlogWidget(account, parent); } UI::ComposerWidget *MicroBlog::createComposerWidget(Account *account, QWidget *parent) { return new UI::ComposerWidget(account, parent); } UI::TimelineWidget *MicroBlog::createTimelineWidget(Account *account, const QString &timelineName, QWidget *parent) { return new UI::TimelineWidget(account, timelineName, parent); } UI::PostWidget *MicroBlog::createPostWidget(Account *account, Choqok::Post *post, QWidget *parent) { return new UI::PostWidget(account, post, parent); } TimelineInfo *MicroBlog::timelineInfo(const QString &) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; return 0; } void MicroBlog::abortAllJobs(Account *) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; } void MicroBlog::abortCreatePost(Account *, Post *) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; } void MicroBlog::createPost(Account *, Post *) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; } void MicroBlog::fetchPost(Account *, Post *) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; } void MicroBlog::removePost(Account *, Post *) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; } void MicroBlog::updateTimelines(Account *) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; } QList< Post * > MicroBlog::loadTimeline(Account *, const QString &) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; return QList(); } void MicroBlog::saveTimeline(Account *, const QString &, const QList< UI::PostWidget * > &) { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; } -QString MicroBlog::postUrl(Account *, const QString &, const QString &) const +QUrl MicroBlog::postUrl(Account *, const QString &, const QString &) const { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; - return QString(); + return QUrl(); } QUrl MicroBlog::profileUrl(Account *, const QString &) const { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; return QUrl(); } } diff --git a/libchoqok/microblog.h b/libchoqok/microblog.h index c5fa8272..57abf132 100644 --- a/libchoqok/microblog.h +++ b/libchoqok/microblog.h @@ -1,329 +1,329 @@ /* 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/ */ #ifndef MICROBLOG_H #define MICROBLOG_H #include #include #include "account.h" #include "choqok_export.h" #include "choqoktypes.h" #include "choqokuiglobal.h" #include "plugin.h" class ChoqokEditAccountWidget; namespace Choqok { namespace UI { class PostWidget; class TimelineWidget; class MicroBlogWidget; class ComposerWidget; } /** \brief Base class for MicroBlog plugins Every MicroBlog plugin should subclass this they can subclass UI classes to use too, and implement: @ref createNewAccount() To create a new account for this microblog, can use @ref Choqok::Account or a subclass of it, @ref createEditAccountWidget() To create a widget/dialog to show to user for create/edit account, @ref createMicroBlogWidget() To create a @ref MicroBlogWidget to show on MainWindow @ref createTimelineWidget() To create a @ref TimelineWidget to show on MicroBlogWidget Additionally should implement this functions: @ref createPost() @ref abortCreatePost() @ref fetchPost() @ref removePost() @ref updateTimelines() @ref profileUrl() @author Mehrdad Momeny \ */ class CHOQOK_EXPORT MicroBlog : public Plugin { Q_OBJECT public: virtual ~MicroBlog(); /** @brief Return a KMenu contain microblog specific actions. Can use for search facilities, or other things Will add to a button on top of @ref MicroBlogWidget of account/microblog */ virtual QMenu *createActionsMenu(Account *theAccount, QWidget *parent = UI::Global::mainWindow()); /** Enumeration for possible errors. */ enum ErrorType { /** A server side error. */ ServerError, /** An error on communication with server */ CommunicationError, /** A parsing error. */ ParsingError, /** An error on authentication. */ AuthenticationError, /** An error where the method called is not supported by this object. */ NotSupportedError, /** Any other miscellaneous error. */ OtherError }; /** Enumeration for levels of errors. This could use for user feedback! For @ref Critical errors will show a messagebox, For @ref Normal will show a knotify. For @ref Low just show a message in statusBar */ enum ErrorLevel { Low = 0, Normal, Critical }; /** * @brief Create a new Account * * This method is called during the loading of the config file. * @param alias - the alias name to create the account with. * * you don't need to register the account to the AccountManager in this function. * * @return The new @ref Account object created by this function */ virtual Account *createNewAccount(const QString &alias); /** * @brief Create a new EditAccountWidget * * @return A new @ref ChoqokEditAccountWidget to be shown in the account part of the configurations. * * @param account is the Account to edit. If it's nullptr, then we create a new account * @param parent The parent of the 'to be returned' widget */ virtual ChoqokEditAccountWidget *createEditAccountWidget(Choqok::Account *account, QWidget *parent) = 0; /** * @brief Create a MicroBlogWidget for this Account * The returned MicroBlogWidget will show on Mainwindow. and manage of this microblog account will give to it * Every MicroBlog plugin should reimplement this. * * @return A new MicroBlogWidget to use. * * @param account account to use. * @param parent The parent of the 'to be returned' widget */ virtual UI::MicroBlogWidget *createMicroBlogWidget(Choqok::Account *account, QWidget *parent); /** * @brief Create a ComposerWidget to use in MicroBlogWidget * * @return A new ComposerWidget to use. * * @param account account to use. * @param parent The parent of the 'to be returned' widget */ virtual UI::ComposerWidget *createComposerWidget(Choqok::Account *account, QWidget *parent); /** * @brief Create a TimelineWidget to use in MicroBlogWidget * * @return A new TimelineWidget to use. * * @param account account to use. * @param timelineName Name of timeline * @param parent The parent of the 'to be returned' widget */ virtual UI::TimelineWidget *createTimelineWidget(Choqok::Account *account, const QString &timelineName, QWidget *parent); /** * @brief Create a PostWidget to contain one post in TimelineWidget * * @return A new PostWidget to use. * * @param account account to use. * @param post Post object. * @param parent The parent of the 'to be returned' widget */ virtual UI::PostWidget *createPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent); /** @brief Save a specific timeline! @Note Implementation of this is optional, i.e. One microblog may don't have timeline backup @see loadTimeline() */ virtual void saveTimeline(Choqok::Account *account, const QString &timelineName, const QList &timeline); /** @brief Load a specific timeline! @Note Implementation of this is optional, i.e. One microblog may don't have timeline backup @see saveTimeline() */ virtual QList loadTimeline(Choqok::Account *account, const QString &timelineName); /** \brief Create a new post @see postCreated() @see abortCreatePost() */ virtual void createPost(Choqok::Account *theAccount, Choqok::Post *post); /** \brief Abort all requests! */ virtual void abortAllJobs(Choqok::Account *theAccount); /** \brief Abort all createPost jobs \see abortAllJobs() */ virtual void abortCreatePost(Choqok::Account *theAccount, Choqok::Post *post = 0); /** \brief Fetch a post @see postFetched() */ virtual void fetchPost(Choqok::Account *theAccount, Choqok::Post *post); /** \brief Remove a post @see postRemoved() */ virtual void removePost(Choqok::Account *theAccount, Choqok::Post *post); /** Request to update all timelines of account! They will arrive in several signals! with timelineDataReceived() signal! @see timelineDataReceived() */ virtual void updateTimelines(Choqok::Account *theAccount); /** return Url to account page on service (Some kind of blog homepage) */ virtual QUrl profileUrl(Choqok::Account *account, const QString &username) const; /** Provide a Web link for post with id @p postId */ - virtual QString postUrl(Choqok::Account *account, const QString &username, const QString &postId) const; + virtual QUrl postUrl(Choqok::Account *account, const QString &username, const QString &postId) const; /** Return a list of timelines supported by this account! It will use to show timelines! and result of timelineDataReceived() signal will be based on these! @see timelineInfo() @see timelineDataReceived() */ QStringList timelineNames() const; /** Checks if @p timeline is valid for this blog! i.e. there is an entry for it at timelineTypes() list. @return True if the timeline is valid, false if not! */ bool isValidTimeline(const QString &timelineName); /** @Return information about an specific timeline */ virtual TimelineInfo *timelineInfo(const QString &timelineName); /** Return service homepage Url */ QString homepageUrl() const; /** Returns a user readable name of blog/service type! (e.g. Identica) */ QString serviceName() const; static QString errorString(ErrorType type); Q_SIGNALS: /** emit when data for a timeline received! @p type specifies the type of timeline as specifies in timelineTypes() */ void timelineDataReceived(Choqok::Account *theAccount, const QString &timelineName, QList data); /** emit when a post successfully created! */ void postCreated(Choqok::Account *theAccount, Choqok::Post *post); /** emit when a post successfully fetched! */ void postFetched(Choqok::Account *theAccount, Choqok::Post *post); /** emit when a post successfully removed! */ void postRemoved(Choqok::Account *theAccount, Choqok::Post *post); /** emit when an error occurred the @p errorMessage will specify the error. */ void error(Choqok::Account *theAccount, Choqok::MicroBlog::ErrorType error, const QString &errorMessage, Choqok::MicroBlog::ErrorLevel level = Normal); /** emit when an error occurred on Post manipulation. e.g. On Creation! */ void errorPost(Choqok::Account *theAccount, Choqok::Post *post, Choqok::MicroBlog::ErrorType error, const QString &errorMessage, Choqok::MicroBlog::ErrorLevel level = Normal); /** emit when microblog plugin is going to unload, and @ref Choqok::TimelineWidget should save their timelines */ void saveTimelines(); protected: MicroBlog(const QString &componentName, QObject *parent = 0); virtual void setTimelineNames(const QStringList &); void addTimelineName(const QString &); void setServiceName(const QString &); void setServiceHomepageUrl(const QString &); protected Q_SLOTS: void slotConfigChanged(); private: class Private; Private *const d; }; } #endif diff --git a/libchoqok/ui/postwidget.cpp b/libchoqok/ui/postwidget.cpp index 301fdfb1..30bf3540 100644 --- a/libchoqok/ui/postwidget.cpp +++ b/libchoqok/ui/postwidget.cpp @@ -1,726 +1,726 @@ /* 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 "postwidget.h" #include #include #include #include #include #include #include "choqokappearancesettings.h" #include "choqokbehaviorsettings.h" #include "choqoktools.h" #include "choqokuiglobal.h" #include "libchoqokdebug.h" #include "mediamanager.h" #include "quickpost.h" #include "timelinewidget.h" #include "textbrowser.h" #include "urlutils.h" static const int _15SECS = 15000; static const int _MINUTE = 60000; static const int _HOUR = 60 * _MINUTE; using namespace Choqok; using namespace Choqok::UI; class PostWidget::Private { public: Private(Account *account, Choqok::Post *post) : mCurrentPost(post), mCurrentAccount(account), dir(QLatin1String("ltr")), timeline(0) { mCurrentPost->owners++; if (!mCurrentPost->media.isEmpty()) { imageUrl = mCurrentPost->media; } } QGridLayout *buttonsLayout; QMap mUiButtons; // Post *mCurrentPost; Account *mCurrentAccount; // bool mRead; QTimer mTimer; //BEGIN UI contents: QString mSign; QString mContent; QString mProfileImage; QString mImage; - QString imageUrl; + QUrl imageUrl; QString dir; QPixmap originalImage; QString extraContents; //END UI contents; QStringList detectedUrls; TimelineWidget *timeline; static const QLatin1String resourceImageUrl; }; const QString mImageTemplate(QLatin1String("
")); const QLatin1String PostWidget::Private::resourceImageUrl("img://postImage"); const QString PostWidget::baseTextTemplate(QLatin1String("%6%5
%1

%2

%3
")); const QString PostWidget::baseStyle(QLatin1String("QTextBrowser {border: 1px solid rgb(150,150,150);\ border-radius:5px; color:%1; background-color:%2; %3}\ QPushButton{border:0px} QPushButton::menu-indicator{image:none;}")); const QString PostWidget::hrefTemplate(QLatin1String("%2")); const QRegExp PostWidget::dirRegExp(QLatin1String("(RT|RD)|(@([^\\s\\W]+))|(#([^\\s\\W]+))|(!([^\\s\\W]+))")); QString PostWidget::readStyle; QString PostWidget::unreadStyle; QString PostWidget::ownStyle; const QString PostWidget::webIconText(QLatin1String("☛")); PostWidget::PostWidget(Account *account, Choqok::Post *post, QWidget *parent/* = 0*/) : QWidget(parent), _mainWidget(new TextBrowser(this)), d(new Private(account, post)) { setAttribute(Qt::WA_DeleteOnClose); _mainWidget->setFrameShape(QFrame::NoFrame); if (isOwnPost()) { d->mCurrentPost->isRead = true; } d->mTimer.start(_MINUTE); connect(&d->mTimer, SIGNAL(timeout()), this, SLOT(updateUi())); connect(_mainWidget, SIGNAL(clicked(QMouseEvent*)), SLOT(mousePressEvent(QMouseEvent*))); connect(_mainWidget, SIGNAL(anchorClicked(QUrl)), this, SLOT(checkAnchor(QUrl))); d->timeline = qobject_cast(parent); setHeight(); } void PostWidget::checkAnchor(const QUrl &url) { if (url.scheme() == QLatin1String("choqok")) { if (url.host() == QLatin1String("showoriginalpost")) { setContent(prepareStatus(currentPost()->content).replace(QLatin1String("mCurrentPost->owners < 2) { delete d->mCurrentPost; } else { d->mCurrentPost->owners--; } delete d; } Account *PostWidget::currentAccount() { return d->mCurrentAccount; } QString PostWidget::generateSign() { QString ss = QStringLiteral("%1 - ").arg(getUsernameHyperlink(d->mCurrentPost->author)); QDateTime time; if (d->mCurrentPost->repeatedDateTime.isNull()) { time = d->mCurrentPost->creationDateTime; } else { time = d->mCurrentPost->repeatedDateTime; } - ss += QStringLiteral("%3").arg(d->mCurrentPost->link) + ss += QStringLiteral("%3").arg(d->mCurrentPost->link.toDisplayString()) .arg(time.toString(Qt::DefaultLocaleLongDate)).arg(formatDateTime(time)); if (!d->mCurrentPost->source.isEmpty()) { ss += QLatin1String(" - ") + d->mCurrentPost->source; } return ss; } QString PostWidget::getUsernameHyperlink(const Choqok::User &user) const { return QStringLiteral("%3") .arg(d->mCurrentAccount->microblog()->profileUrl(d->mCurrentAccount, user.userName).toDisplayString()) .arg(user.description.isEmpty() ? user.realName : user.description) .arg(user.userName); } void PostWidget::setupUi() { setLayout(new QVBoxLayout); layout()->setMargin(0); layout()->setContentsMargins(0, 0, 0, 0); layout()->addWidget(_mainWidget); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); _mainWidget->setFocusProxy(this); d->buttonsLayout = new QGridLayout(_mainWidget); d->buttonsLayout->setRowStretch(0, 100); d->buttonsLayout->setColumnStretch(5, 100); d->buttonsLayout->setMargin(0); d->buttonsLayout->setSpacing(0); _mainWidget->setLayout(d->buttonsLayout); connect(_mainWidget, SIGNAL(textChanged()), this, SLOT(setHeight())); } void PostWidget::initUi() { setupUi(); _mainWidget->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("img://profileImage")), MediaManager::self()->defaultImage()); if (isRemoveAvailable()) { QPushButton *btnRemove = addButton(QLatin1String("btnRemove"), i18nc("@info:tooltip", "Remove"), QLatin1String("edit-delete")); connect(btnRemove, SIGNAL(clicked(bool)), SLOT(removeCurrentPost())); } if (isResendAvailable()) { QPushButton *btnResend = addButton(QLatin1String("btnResend"), i18nc("@info:tooltip", "ReSend"), QLatin1String("retweet")); connect(btnResend, SIGNAL(clicked(bool)), SLOT(slotResendPost())); } d->mProfileImage = QLatin1String("mCurrentPost->author.realName + QLatin1String("\" width=\"48\" height=\"48\" />"); d->mContent = prepareStatus(d->mCurrentPost->content); d->mSign = generateSign(); setupAvatar(); fetchImage(); d->dir = getDirection(d->mCurrentPost->content); setUiStyle(); d->mContent.replace(QLatin1String("mContent.replace(QLatin1String("\n"), QLatin1String("
")); d->extraContents.replace(QLatin1String("
mSign.replace(QLatin1String("setHtml(baseTextTemplate.arg( d->mProfileImage, /*1*/ d->mContent, /*2*/ d->mSign, /*3*/ d->dir, /*4*/ d->mImage, /*5*/ d->extraContents /*6*/ )); } void PostWidget::setStyle(const QColor &color, const QColor &back, const QColor &read, const QColor &readBack, const QColor &own, const QColor &ownBack, const QFont &font) { QString fntStr = QLatin1String("font-family:\"") + font.family() + QLatin1String("\"; font-size:") + QString::number(font.pointSize()) + QLatin1String("pt;"); fntStr += (font.bold() ? QLatin1String(" font-weight:bold;") : QString()) + (font.italic() ? QLatin1String(" font-style:italic;") : QString()); unreadStyle = baseStyle.arg(getColorString(color), getColorString(back), fntStr); readStyle = baseStyle.arg(getColorString(read), getColorString(readBack), fntStr); ownStyle = baseStyle.arg(getColorString(own), getColorString(ownBack), fntStr); } QPushButton *PostWidget::addButton(const QString &objName, const QString &toolTip, const QString &icon) { return addButton(objName, toolTip, QIcon::fromTheme(icon)); } QPushButton *PostWidget::addButton(const QString &objName, const QString &toolTip, const QIcon &icon) { QPushButton *button = new QPushButton(icon, QString(), _mainWidget); button->setObjectName(objName); button->setToolTip(toolTip); button->setIconSize(QSize(16, 16)); button->setMinimumSize(QSize(20, 20)); button->setMaximumSize(QSize(20, 20)); button->setFlat(true); button->setVisible(false); button->setCursor(Qt::PointingHandCursor); d->mUiButtons.insert(objName, button); d->buttonsLayout->addWidget(button, 1, d->mUiButtons.count()); return button; } Post *PostWidget::currentPost() const { return d->mCurrentPost; } void PostWidget::setCurrentPost(Post *post) { d->mCurrentPost = post; } void PostWidget::setRead(bool read/* = true*/) { if (!read && !currentPost()->isRead && currentAccount()->username().compare(currentPost()->author.userName, Qt::CaseInsensitive) == 0) { d->mCurrentPost->isRead = true; ///Always Set own posts as read. setUiStyle(); } else if (currentPost()->isRead != read) { d->mCurrentPost->isRead = read; setUiStyle(); } } void PostWidget::setReadWithSignal() { if (!isRead()) { setRead(); Q_EMIT postReaded(); } } bool PostWidget::isRead() const { return currentPost()->isRead; } void PostWidget::setUiStyle() { if (isOwnPost()) { setStyleSheet(ownStyle); } else { if (currentPost()->isRead) { setStyleSheet(readStyle); } else { setStyleSheet(unreadStyle); } } setHeight(); } bool PostWidget::isOwnPost() { return currentAccount()->username().compare(currentPost()->author.userName, Qt::CaseInsensitive) == 0; } void PostWidget::setHeight() { _mainWidget->document()->setTextWidth(width() - 2); int h = _mainWidget->document()->size().toSize().height() + 2; setFixedHeight(h); } void PostWidget::closeEvent(QCloseEvent *event) { clearFocus(); if (!isRead()) { setReadWithSignal(); } Q_EMIT aboutClosing(currentPost()->postId, this); event->accept(); } void PostWidget::mousePressEvent(QMouseEvent *ev) { if (!isRead()) { setReadWithSignal(); } QWidget::mousePressEvent(ev); } void PostWidget::resizeEvent(QResizeEvent *event) { updatePostImage( event->size().width() ); setHeight(); updateUi(); QWidget::resizeEvent(event); } void PostWidget::enterEvent(QEvent *event) { for (QPushButton *btn: buttons()) { if (btn) { //A crash happens here :/ btn->show(); } } QWidget::enterEvent(event); } void PostWidget::leaveEvent(QEvent *event) { for (QPushButton *btn: buttons()) { if (btn) { btn->hide(); } } QWidget::enterEvent(event); } void PostWidget::updatePostImage(int width) { if ( !d->originalImage.isNull() ) { // TODO: Find a way to calculate the difference we need to subtract. width -= 76; QPixmap newPixmap = d->originalImage.scaledToWidth(width, Qt::SmoothTransformation); auto newW = newPixmap.width(); auto newH = newPixmap.height(); auto origW = d->originalImage.width(); auto origH = d->originalImage.height(); const QUrl url(d->resourceImageUrl); // only use scaled image if it's smaller than the original one if (newW <= origW && newH <= origH) { // never scale up d->mImage = mImageTemplate.arg(QString::number(newW), QString::number(newH), d->resourceImageUrl); _mainWidget->document()->addResource(QTextDocument::ImageResource, url, newPixmap); } else { d->mImage = mImageTemplate.arg(QString::number(origW), QString::number(origH), d->resourceImageUrl); _mainWidget->document()->addResource(QTextDocument::ImageResource, url, d->originalImage); } } } QString PostWidget::prepareStatus(const QString &txt) { QString text = removeTags(txt); d->detectedUrls = UrlUtils::detectUrls(text); for (const QString &url: d->detectedUrls) { QString httpUrl(url); if (!httpUrl.startsWith(QLatin1String("http"), Qt::CaseInsensitive) && !httpUrl.startsWith(QLatin1String("ftp"), Qt::CaseInsensitive)) { httpUrl.prepend(QLatin1String("http://")); text.replace(url, httpUrl); } text.replace(url, hrefTemplate.arg(httpUrl, url)); } text = UrlUtils::detectEmails(text); if (AppearanceSettings::isEmoticonsEnabled()) { text = MediaManager::self()->parseEmoticons(text); } return text; } QString PostWidget::removeTags(const QString &text) const { QString txt(text); txt.replace(QLatin1Char('<'), QLatin1String("<")); txt.replace(QLatin1Char('>'), QLatin1String(">")); return txt; } QLatin1String PostWidget::getDirection(QString txt) { txt.remove(dirRegExp); txt = txt.trimmed(); if (txt.isRightToLeft()) { return QLatin1String("rtl"); } else { return QLatin1String("ltr"); } } QString PostWidget::formatDateTime(const QDateTime &time) { if (!time.isValid()) { return tr("Invalid Time"); } auto seconds = time.secsTo(QDateTime::currentDateTime()); if (seconds <= 15) { d->mTimer.setInterval(_15SECS); return i18n("Just now"); } if (seconds <= 45) { d->mTimer.setInterval(_15SECS); return i18np("1 sec ago", "%1 secs ago", seconds); } auto minutes = (seconds - 45 + 59) / 60; if (minutes <= 45) { d->mTimer.setInterval(_MINUTE); return i18np("1 min ago", "%1 mins ago", minutes); } auto hours = (seconds - 45 * 60 + 3599) / 3600; if (hours <= 18) { d->mTimer.setInterval(_MINUTE * 15); return i18np("1 hour ago", "%1 hours ago", hours); } d->mTimer.setInterval(_HOUR); auto days = (seconds - 18 * 3600 + 24 * 3600 - 1) / (24 * 3600); return i18np("1 day ago", "%1 days ago", days); } void PostWidget::removeCurrentPost() { if (KMessageBox::warningYesNo(this, i18n("Are you sure you want to remove this post from the server?")) == KMessageBox::Yes) { connect(d->mCurrentAccount->microblog(), SIGNAL(postRemoved(Choqok::Account*,Choqok::Post*)), SLOT(slotCurrentPostRemoved(Choqok::Account*,Choqok::Post*))); connect(d->mCurrentAccount->microblog(), SIGNAL(errorPost(Choqok::Account*,Choqok::Post*,Choqok::MicroBlog::ErrorType,QString)), this, SLOT(slotPostError(Choqok::Account*,Choqok::Post*,Choqok::MicroBlog::ErrorType,QString))); setReadWithSignal(); d->mCurrentAccount->microblog()->removePost(d->mCurrentAccount, d->mCurrentPost); } } void PostWidget::slotCurrentPostRemoved(Account *theAccount, Post *post) { if (theAccount == currentAccount() && post == d->mCurrentPost) { this->close(); } } void PostWidget::slotResendPost() { QString text = generateResendText(); setReadWithSignal(); if ((BehaviorSettings::resendWithQuickPost() || currentAccount()->isReadOnly()) && Global::quickPostWidget()) { Global::quickPostWidget()->setText(text); } else { Q_EMIT resendPost(text); } } QString PostWidget::generateResendText() { if (BehaviorSettings::useCustomRT()) { return QString(BehaviorSettings::customRT()) + QLatin1String(" @") + currentPost()->author.userName + QLatin1String(": ") + currentPost()->content; } else { QChar re(0x267B); return QString(re) + QLatin1String(" @") + currentPost()->author.userName + QLatin1String(": ") + currentPost()->content; } } void PostWidget::fetchImage() { if (d->imageUrl.isEmpty()) { return; } QPixmap pix = MediaManager::self()->fetchImage(d->imageUrl, MediaManager::Async); if (!pix.isNull()) { slotImageFetched(d->imageUrl, pix); } else { - connect(MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - this, SLOT(slotImageFetched(QString,QPixmap))); + connect(MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + this, SLOT(slotImageFetched(QUrl,QPixmap))); } } -void PostWidget::slotImageFetched(const QString &remoteUrl, const QPixmap &pixmap) +void PostWidget::slotImageFetched(const QUrl &remoteUrl, const QPixmap &pixmap) { if (remoteUrl == d->imageUrl) { - disconnect(MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), this, SLOT(slotImageFetched(QString,QPixmap))); + disconnect(MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), this, SLOT(slotImageFetched(QUrl,QPixmap))); d->originalImage = pixmap; updatePostImage( width() ); updateUi(); } } void PostWidget::setupAvatar() { QPixmap pix = MediaManager::self()->fetchImage(d->mCurrentPost->author.profileImageUrl, MediaManager::Async); if (!pix.isNull()) { avatarFetched(d->mCurrentPost->author.profileImageUrl, pix); } else { - connect(MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - this, SLOT(avatarFetched(QString,QPixmap))); + connect(MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + this, SLOT(avatarFetched(QUrl,QPixmap))); connect(MediaManager::self(), SIGNAL(fetchError(QString,QString)), this, SLOT(avatarFetchError(QString,QString))); } } -void PostWidget::avatarFetched(const QString &remoteUrl, const QPixmap &pixmap) +void PostWidget::avatarFetched(const QUrl &remoteUrl, const QPixmap &pixmap) { if (remoteUrl == d->mCurrentPost->author.profileImageUrl) { const QUrl url(QLatin1String("img://profileImage")); _mainWidget->document()->addResource(QTextDocument::ImageResource, url, pixmap); updateUi(); - disconnect(MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - this, SLOT(avatarFetched(QString,QPixmap))); + disconnect(MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + this, SLOT(avatarFetched(QUrl,QPixmap))); disconnect(MediaManager::self(), SIGNAL(fetchError(QString,QString)), this, SLOT(avatarFetchError(QString,QString))); } } -void PostWidget::avatarFetchError(const QString &remoteUrl, const QString &errMsg) +void PostWidget::avatarFetchError(const QUrl &remoteUrl, const QString &errMsg) { Q_UNUSED(errMsg); if (remoteUrl == d->mCurrentPost->author.profileImageUrl) { ///Avatar fetching is failed! but will not disconnect to get the img if it fetches later! const QUrl url(QLatin1String("img://profileImage")); _mainWidget->document()->addResource(QTextDocument::ImageResource, url, QIcon::fromTheme(QLatin1String("image-missing")).pixmap(48)); updateUi(); } } QMap &PostWidget::buttons() { return d->mUiButtons; } void PostWidget::slotPostError(Account *theAccount, Choqok::Post *post, MicroBlog::ErrorType , const QString &errorMessage) { if (theAccount == currentAccount() && post == d->mCurrentPost) { qCDebug(CHOQOK) << errorMessage; disconnect(d->mCurrentAccount->microblog(), SIGNAL(postRemoved(Choqok::Account*,Choqok::Post*)), this, SLOT(slotCurrentPostRemoved(Choqok::Account*,Choqok::Post*))); disconnect(d->mCurrentAccount->microblog(), SIGNAL(errorPost(Account*,Post*,Choqok::MicroBlog::ErrorType,QString)), this, SLOT(slotPostError(Account*,Post*,Choqok::MicroBlog::ErrorType,QString))); } } QString PostWidget::avatarText() const { return d->mProfileImage; } void PostWidget::setAvatarText(const QString &text) { d->mProfileImage = text; updateUi(); } QString PostWidget::content() const { return d->mContent; } void PostWidget::setContent(const QString &content) { d->mContent = content; updateUi(); } QStringList PostWidget::urls() { return d->detectedUrls; } QString PostWidget::sign() const { return d->mSign; } void PostWidget::setSign(const QString &sign) { d->mSign = sign; updateUi(); } void PostWidget::deleteLater() { close(); } TextBrowser *PostWidget::mainWidget() { return _mainWidget; } void PostWidget::wheelEvent(QWheelEvent *event) { event->ignore(); } void PostWidget::addAction(QAction *action) { TextBrowser::addAction(action); } TimelineWidget *PostWidget::timelineWidget() const { return d->timeline; } class PostWidgetUserData::Private { public: Private(PostWidget *postwidget) : postWidget(postwidget) {} PostWidget *postWidget; }; PostWidgetUserData::PostWidgetUserData(PostWidget *postWidget) : QObjectUserData(), d(new Private(postWidget)) { } PostWidgetUserData::~PostWidgetUserData() { delete d; } PostWidget *PostWidgetUserData::postWidget() { return d->postWidget; } void PostWidgetUserData::setPostWidget(PostWidget *widget) { d->postWidget = widget; } QString PostWidget::getBaseStyle() { return baseStyle; } bool PostWidget::isRemoveAvailable() { return d->mCurrentAccount->username().compare(d->mCurrentPost->author.userName, Qt::CaseInsensitive) == 0; } bool PostWidget::isResendAvailable() { return d->mCurrentAccount->username().compare(d->mCurrentPost->author.userName, Qt::CaseInsensitive) != 0; } void PostWidget::setExtraContents(const QString& text) { d->extraContents = text; } QString PostWidget::extraContents() const { return d->extraContents; } diff --git a/libchoqok/ui/postwidget.h b/libchoqok/ui/postwidget.h index ef220b1b..94ae5788 100644 --- a/libchoqok/ui/postwidget.h +++ b/libchoqok/ui/postwidget.h @@ -1,258 +1,258 @@ /* 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/ */ #ifndef POSTWIDGET_H #define POSTWIDGET_H #include #include "account.h" #include "choqoktypes.h" #include "microblog.h" class QAction; class QPushButton; namespace Choqok { namespace UI { class TextBrowser; /** Post Widget! Attribute "Qt::WA_DeleteOnClose" is enabled at construtor! So please use close() for deleting an object, instead of deleteLater() or delete @author Mehrdad Momeny \ */ class CHOQOK_EXPORT PostWidget : public QWidget { Q_OBJECT Q_PROPERTY(bool read READ isRead) public: explicit PostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent = 0); virtual ~PostWidget(); Post *currentPost() const; virtual void setRead(bool read = true); /** Sets post widget as read, and emits postReaded() signal */ void setReadWithSignal(); virtual bool isRead() const; Account *currentAccount(); /** Setup UI elements @Note Should call from outside of class, To initialize drived class items too! */ virtual void initUi(); /** Set stylesheet data with new color data! to use later. @see setUiStyle() */ static void setStyle(const QColor &unreadColor, const QColor &unreadBack, const QColor &readColor, const QColor &readBack, const QColor &ownColor, const QColor &ownBack, const QFont &font); /** @brief Set current post @note Use with care! After changing current post, Don't forget to call @ref initUi() to update post UI. */ void setCurrentPost(Post *post); /** @brief Sets Post sign sign is the text that showed as sign of this post under post content. */ void setSign(const QString &sign); /** @return post sign sign is an html text that showed as sign of this post under post content. */ QString sign() const; /** @brief Sets post content Post content is an html text that showed as post text. */ void setContent(const QString &content); /** @return post content Post content is an html text that showed as post text. */ QString content() const; void deleteLater(); TextBrowser *mainWidget(); QStringList urls(); TimelineWidget *timelineWidget() const; /** * Plugins can add status specific actions and process them internally * */ static void addAction(QAction *action); static QString getBaseStyle(); public Q_SLOTS: /** Set Style sheet of widget to corresponding data-> @see setStyle() */ void setUiStyle(); Q_SIGNALS: /** Emit and contain text to resend. */ void resendPost(const QString &text); /** Emit when this post has been readed by pressing mouse on it, And to notify TimelineWidget about it. */ void postReaded(); /** Carry reply information, to reply to a post. */ void reply(const QString &txt, const QString &replyToId, const QString &replyToUsername); /** Emitted when this widget is about to close! postId and this returned! */ void aboutClosing(const QString &postId, PostWidget *widget); protected Q_SLOTS: virtual void checkAnchor(const QUrl &url); /** Set height of widget related to text contents */ virtual void setHeight(); /** Update UI after changes, such as timestamp */ virtual void updateUi(); /** Call microblog() to remove this post! */ virtual void removeCurrentPost(); /** Prepare text to send for resending this post. */ virtual void slotResendPost(); /** Internal slot to remove/close/destroy this post after bing deleted */ void slotCurrentPostRemoved(Choqok::Account *theAccount, Choqok::Post *post); virtual void slotPostError(Choqok::Account *theAccount, Choqok::Post *post, Choqok::MicroBlog::ErrorType error, const QString &errorMessage); - void avatarFetchError(const QString &remoteUrl, const QString &errMsg); - void avatarFetched(const QString &remoteUrl, const QPixmap &pixmap); + void avatarFetchError(const QUrl &remoteUrl, const QString &errMsg); + void avatarFetched(const QUrl &remoteUrl, const QPixmap &pixmap); - void slotImageFetched(const QString &remoteUrl, const QPixmap &pixmap); + void slotImageFetched(const QUrl &remoteUrl, const QPixmap &pixmap); virtual void mousePressEvent(QMouseEvent *ev) override; protected: virtual void setupUi(); virtual void closeEvent(QCloseEvent *event) override; virtual void setupAvatar(); virtual void fetchImage(); virtual void wheelEvent(QWheelEvent *) override; virtual void resizeEvent(QResizeEvent *event) override; virtual void enterEvent(QEvent *event) override; virtual void leaveEvent(QEvent *event) override; virtual QString prepareStatus(const QString &text); QLatin1String getDirection(QString text); virtual QString generateSign(); virtual QString formatDateTime(const QDateTime &time); virtual bool isResendAvailable() ; virtual bool isRemoveAvailable() ; virtual bool isOwnPost(); virtual QString removeTags(const QString &text) const; /** @brief Create and Add a new button to widget This function will add button to UI! @return added button, for some managements such as connect to a slot */ QPushButton *addButton(const QString &objName, const QString &toolTip, const QString &icon); QPushButton *addButton(const QString &objName, const QString &toolTip, const QIcon &icon); QMap &buttons(); QString getUsernameHyperlink(const Choqok::User &user) const; protected: TextBrowser *_mainWidget; static const QString baseStyle; static QString readStyle; static QString unreadStyle; static QString ownStyle; static const QString webIconText; static const QString hrefTemplate; static const QString baseTextTemplate; static const QRegExp dirRegExp; void setAvatarText(const QString &text); QString avatarText() const; void setExtraContents(const QString &text); QString extraContents() const; virtual QString generateResendText(); void updatePostImage(int width); private: class Private; Private *const d; }; class CHOQOK_EXPORT PostWidgetUserData : public QObjectUserData { public: PostWidgetUserData(PostWidget *postWidget); virtual ~PostWidgetUserData(); PostWidget *postWidget(); void setPostWidget(PostWidget *widget); private: class Private; Private *const d; }; } } #endif // POSTWIDGET_H diff --git a/microblogs/ocs/ocsmicroblog.cpp b/microblogs/ocs/ocsmicroblog.cpp index 27fdcd36..9bb3634e 100644 --- a/microblogs/ocs/ocsmicroblog.cpp +++ b/microblogs/ocs/ocsmicroblog.cpp @@ -1,310 +1,310 @@ /* 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, SIGNAL(defaultProvidersLoaded()), this, SLOT(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); int count = groupList.count(); if (count) { Choqok::Post *st = 0; 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", 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", QString()); - st->link = grp.readEntry("link", 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 0;//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, SIGNAL(finished(Attica::BaseJob*)), SLOT(slotCreatePost(Attica::BaseJob*))); 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, SIGNAL(finished(Attica::BaseJob*)), SLOT(slotTimelineLoaded(Attica::BaseJob*))); 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().toString(); + pst->link = act.link(); pst->isError = !act.isValid(); pst->author.userId = act.associatedPerson().id(); pst->author.userName = act.associatedPerson().id(); - pst->author.homePageUrl = act.associatedPerson().homepage(); + 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().toString(); + 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 0; } } 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 39a41139..d55a3ad8 100644 --- a/microblogs/pumpio/pumpiomicroblog.cpp +++ b/microblogs/pumpio/pumpiomicroblog.cpp @@ -1,1378 +1,1378 @@ /* 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, SIGNAL(triggered(bool)), this, SLOT(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 0; } } 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 0; } } 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"), QUrl::toPercentEncoding(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"), authorizationMetaData(acc, url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_createPostJobs[job] = post; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotCreatePost(KJob*))); 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"), authorizationMetaData(acc, url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_createPostJobs[job] = post; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotCreatePost(KJob*))); 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"), authorizationMetaData(acc, url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_uploadJobs[job] = post; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotUpload(KJob*))); 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.startsWith(acc->host())) { + 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, url, QNetworkAccessManager::GetOperation)); m_accountJobs[job] = acc; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFetchPost(KJob*))); 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"), authorizationMetaData(acc, url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_removePostJobs[job] = post; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRemovePost(KJob*))); 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); 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", 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", QString()); - st->author.homePageUrl = grp.readEntry("authorHomePageUrl", 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"), 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", QString()); st->replyToPostId = grp.readEntry("replyToPostId", QString()); st->replyToUserName = grp.readEntry("replyToUserName", QString()); st->replyToObjectType = grp.readEntry("replyToObjectType", QString()); list.append(st); } if (!list.isEmpty()) { setLastTimelineId(account, timelineName, list.last()->conversationId); } return list; } -QString PumpIOMicroBlog::postUrl(Choqok::Account *account, const QString &username, +QUrl PumpIOMicroBlog::postUrl(Choqok::Account *account, const QString &username, const QString &postId) const { Q_UNUSED(account); - return QString(postId).replace(QLatin1String("/api/"), QLatin1Char('/') + username + QLatin1Char('/')); + 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->replyToUserName); 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; QVariantMap oAuthParams; const QString lastActivityId(lastTimelineId(theAccount, timeline)); if (!lastActivityId.isEmpty()) { oAuthParams.insert(QLatin1String("count"), QByteArray::number(200)); query.addQueryItem(QLatin1String("count"), QString::number(200)); oAuthParams.insert(QLatin1String("since"), QUrl::toPercentEncoding(lastActivityId)); query.addQueryItem(QLatin1String("since"), lastActivityId); } else { oAuthParams.insert(QLatin1String("count"), QByteArray::number(Choqok::BehaviorSettings::countOfPosts())); 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"), authorizationMetaData(acc, url, QNetworkAccessManager::GetOperation, oAuthParams)); m_timelinesRequests[job] = timeline; m_accountJobs[job] = acc; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotUpdateTimeline(KJob*))); 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; QVariantMap oAuthParams; oAuthParams.insert(QLatin1String("count"), QByteArray::number(200)); query.addQueryItem(QLatin1String("count"), QString::number(200)); if (!acc->following().isEmpty()) { oAuthParams.insert(QLatin1String("since"), QUrl::toPercentEncoding(acc->following().last())); 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"), authorizationMetaData(acc, url, QNetworkAccessManager::GetOperation, oAuthParams)); m_accountJobs[job] = acc; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFollowing(KJob*))); 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; QVariantMap oAuthParams; oAuthParams.insert(QLatin1String("count"), QByteArray::number(200)); 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"), authorizationMetaData(acc, url, QNetworkAccessManager::GetOperation, oAuthParams)); m_accountJobs[job] = acc; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotLists(KJob*))); 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"), authorizationMetaData(acc, url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_shareJobs[job] = post; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotShare(KJob*))); 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"), authorizationMetaData(acc, url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_favoriteJobs[job] = post; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFavorite(KJob*))); 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 submitted successfully")); 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.", acc->username())); 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.", acc->username())); 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")); } } QString PumpIOMicroBlog::authorizationMetaData(PumpIOAccount *account, const QUrl &url, QNetworkAccessManager::Operation method, const QVariantMap &map) const { const QByteArray authorization = account->oAuth()->authorizationHeader(url, method, map); return QStringLiteral("Authorization: ") + QLatin1String(authorization); } void PumpIOMicroBlog::fetchReplies(Choqok::Account *theAccount, const QString &url) { PumpIOAccount *acc = qobject_cast(theAccount); if (acc) { if (!url.startsWith(acc->host())) { qCDebug(CHOQOK) << "You can only fetch replies from your host!"; return; } QUrl u(url); KIO::StoredTransferJob *job = KIO::storedGet(u, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCDebug(CHOQOK) << "Cannot create an http GET request!"; return; } job->addMetaData(QLatin1String("customHTTPHeader"), authorizationMetaData(acc, u, QNetworkAccessManager::GetOperation)); m_accountJobs[job] = acc; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFetchReplies(KJob*))); 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")].toString(); + 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")].toString(); + p->link = object[QLatin1String("id")].toUrl(); } else { - p->link = object[QLatin1String("pump_io")].toMap().value(QLatin1String("proxyURL")).toString(); + 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 QString homePageUrl = actor[QLatin1String("url")].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 QString profileImageUrl = actor[QLatin1String("image")].toMap().value(QLatin1String("url")).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 = homePageUrl + QLatin1String("images/default.png"); + p->author.profileImageUrl = QUrl::fromUserInput(homePageUrl.toDisplayString() + QLatin1String("images/default.png")); } else { - p->author.profileImageUrl = QStringLiteral("https://%1/images/default.png").arg(hostFromAcct(userId)); + 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")].toString(); } else { p->replies = replies[QLatin1String("pump_io")].toMap().value(QLatin1String("proxyURL")).toString(); } 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"), authorizationMetaData(acc, url, QNetworkAccessManager::PostOperation)); if (!job) { qCDebug(CHOQOK) << "Cannot create an http POST request!"; return; } m_accountJobs[job] = acc; m_updateJobs[job] = post; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotUpdatePost(KJob*))); 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" diff --git a/microblogs/pumpio/pumpiomicroblog.h b/microblogs/pumpio/pumpiomicroblog.h index d115add7..b75593a4 100644 --- a/microblogs/pumpio/pumpiomicroblog.h +++ b/microblogs/pumpio/pumpiomicroblog.h @@ -1,165 +1,165 @@ /* This file is part of Choqok, the KDE micro-blogging client Copyright (C) 2013-2014 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/ */ #ifndef PUMPIOMICROBLOG_H #define PUMPIOMICROBLOG_H #include #include "microblog.h" class QUrl; class KJob; class PumpIOAccount; class PumpIOPost; class PumpIOMicroBlog : public Choqok::MicroBlog { Q_OBJECT public: explicit PumpIOMicroBlog(QObject *parent, const QVariantList &args); virtual ~PumpIOMicroBlog(); virtual void abortAllJobs(Choqok::Account *theAccount) override; virtual void abortCreatePost(Choqok::Account *theAccount, Choqok::Post *post = 0) override; virtual void aboutToUnload() override; virtual QMenu *createActionsMenu(Choqok::Account *theAccount, QWidget *parent = Choqok::UI::Global::mainWindow()) override; virtual Choqok::UI::ComposerWidget *createComposerWidget(Choqok::Account *account, QWidget *parent) override; virtual ChoqokEditAccountWidget *createEditAccountWidget(Choqok::Account *account, QWidget *parent) override; virtual Choqok::UI::MicroBlogWidget *createMicroBlogWidget(Choqok::Account *account, QWidget *parent) override; virtual Choqok::Account *createNewAccount(const QString &alias) override; virtual void createPost(Choqok::Account *theAccount, Choqok::Post *post) override; virtual Choqok::UI::PostWidget *createPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) override; virtual void fetchPost(Choqok::Account *theAccount, Choqok::Post *post) override; virtual QList loadTimeline(Choqok::Account *account, const QString &timelineName) override; virtual void removePost(Choqok::Account *theAccount, Choqok::Post *post) override; - virtual QString postUrl(Choqok::Account *account, const QString &username, + virtual QUrl postUrl(Choqok::Account *account, const QString &username, const QString &postId) const override; virtual QUrl profileUrl(Choqok::Account *account, const QString &username) const override; virtual void saveTimeline(Choqok::Account *account, const QString &timelineName, const QList< Choqok::UI::PostWidget * > &timeline) override; virtual Choqok::TimelineInfo *timelineInfo(const QString &timelineName) override; virtual void updateTimelines(Choqok::Account *theAccount) override; void createPost(Choqok::Account *theAccount, Choqok::Post *post, const QVariantList &to, const QVariantList &cc = QVariantList()); void createPostWithMedia(Choqok::Account *theAccount, Choqok::Post *post, const QString &filePath); void createReply(Choqok::Account *theAccount, PumpIOPost *post); void fetchFollowing(Choqok::Account *theAccount); void fetchLists(Choqok::Account *theAccount); void share(Choqok::Account *theAccount, Choqok::Post *post); void toggleFavorite(Choqok::Account *theAccount, Choqok::Post *post); void fetchReplies(Choqok::Account *theAccount, const QString &url); static QString userNameFromAcct(const QString &acct); static QString hostFromAcct(const QString &acct); static const QString PublicCollection; Q_SIGNALS: void favorite(Choqok::Account *, Choqok::Post *); void followingFetched(Choqok::Account *); void listsFetched(Choqok::Account *); protected Q_SLOTS: void showDirectMessageDialog(); void slotCreatePost(KJob *job); void slotFavorite(KJob *job); void slotFetchPost(KJob *job); void slotFetchReplies(KJob *job); void slotFollowing(KJob *job); void slotLists(KJob *job); void slotRemovePost(KJob *job); void slotShare(KJob *job); void slotUpdatePost(KJob *job); void slotUpdateTimeline(KJob *job); void slotUpload(KJob *job); protected: static const QString inboxActivity; static const QString outboxActivity; QString authorizationMetaData(PumpIOAccount *account, const QUrl &url, QNetworkAccessManager::Operation method, const QVariantMap &map = QVariantMap()) const; QString lastTimelineId(Choqok::Account *theAccount, const QString &timeline) const; Choqok::Post *readPost(const QVariantMap &var, Choqok::Post *post); QList readTimeline(const QByteArray &buffer); void setLastTimelineId(Choqok::Account *theAccount, const QString &timeline, const QString &id); void setTimelinesInfo(); void updatePost(Choqok::Account *theAccount, Choqok::Post *post); QMap m_accountJobs; QMap m_createPostJobs; QMap m_favoriteJobs; QMap m_removePostJobs; QMap m_shareJobs; QMap m_uploadJobs; QMap m_updateJobs; QMap m_timelinesInfos; QHash > m_timelinesLatestIds; QHash m_timelinesPaths; QMap m_timelinesRequests; private: class Private; Private *const d; }; #endif // PUMPIOMICROBLOG_H diff --git a/microblogs/pumpio/pumpiopostwidget.cpp b/microblogs/pumpio/pumpiopostwidget.cpp index 36b601b9..5daebbe8 100644 --- a/microblogs/pumpio/pumpiopostwidget.cpp +++ b/microblogs/pumpio/pumpiopostwidget.cpp @@ -1,278 +1,278 @@ /* 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 "pumpiopostwidget.h" #include #include #include #include #include "mediamanager.h" #include "textbrowser.h" #include "pumpioaccount.h" #include "pumpiodebug.h" #include "pumpiomicroblog.h" #include "pumpiopost.h" #include "pumpioshowthread.h" const QIcon PumpIOPostWidget::unFavIcon(Choqok::MediaManager::convertToGrayScale(QIcon::fromTheme(QLatin1String("rating")).pixmap(16))); class PumpIOPostWidget::Private { public: QPushButton *btnFavorite; QPushButton *btnReply; }; PumpIOPostWidget::PumpIOPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent): PostWidget(account, post, parent), d(new Private) { mainWidget()->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://thread")), QIcon::fromTheme(QLatin1String("go-top")).pixmap(10)); } PumpIOPostWidget::~PumpIOPostWidget() { delete d; } void PumpIOPostWidget::checkAnchor(const QUrl &url) { if (url.scheme() == QLatin1String("thread")) { PumpIOShowThread *thread = new PumpIOShowThread(currentAccount(), currentPost()); connect(thread, SIGNAL(forwardReply(QString,QString,QString)), this, SIGNAL(reply(QString,QString,QString))); thread->resize(this->width(), thread->height() * 3); thread->show(); } else { Choqok::UI::PostWidget::checkAnchor(url); } } QString PumpIOPostWidget::generateSign() { QString ss; PumpIOPost *post = dynamic_cast(currentPost()); PumpIOAccount *account = qobject_cast(currentAccount()); PumpIOMicroBlog *microblog = qobject_cast(account->microblog()); if (post) { ss += QStringLiteral("%1 - ").arg(getUsernameHyperlink(currentPost()->author)); QDateTime time; if (currentPost()->repeatedDateTime.isNull()) { time = currentPost()->creationDateTime; } else { time = currentPost()->repeatedDateTime; } - ss += QStringLiteral("%3").arg(currentPost()->link) + ss += QStringLiteral("%3").arg(currentPost()->link.toDisplayString()) .arg(time.toString(Qt::DefaultLocaleLongDate)).arg(formatDateTime(time)); if (!post->source.isEmpty()) { ss += QLatin1String(" - ") + post->source; } const QRegExp followers(QLatin1String("/api/user/\\w+/followers")); if (!post->to.isEmpty()) { ss += QLatin1String(" - "); ss += i18n("To:") + QLatin1Char(' '); for (const QString &id: post->to) { if (id == PumpIOMicroBlog::PublicCollection) { ss += i18n("Public") + QLatin1String(", "); } else if (followers.indexIn(id) != -1) { ss += QLatin1String("") + i18n("Followers") + QLatin1String(", "); } else if (id == QLatin1String("acct:") + account->webfingerID()) { ss += i18n("You") + QLatin1String(", "); } else { ss += QLatin1String("profileUrl(account, post->author.userName).toDisplayString() + QLatin1String("\">") + PumpIOMicroBlog::userNameFromAcct(id) + QLatin1String(", "); } } if (ss.endsWith(QLatin1String(", "))) { ss.chop(2); } } if (!post->cc.isEmpty()) { ss += QLatin1String(" - "); ss += i18n("CC:") + QLatin1Char(' '); for (const QString &id: post->cc) { if (id == PumpIOMicroBlog::PublicCollection) { ss += i18n("Public") + QLatin1String(", "); } else if (followers.indexIn(id) != -1) { ss += QLatin1String("") + i18n("Followers") + QLatin1String(", "); } else if (id == QLatin1String("acct:") + account->webfingerID()) { ss += i18n("You") + QLatin1String(", "); } else { ss += QLatin1String("profileUrl(account, post->author.userName).toDisplayString() + QLatin1String("\">") + PumpIOMicroBlog::userNameFromAcct(id) + QLatin1String(", "); } } if (ss.endsWith(QLatin1String(", "))) { ss.chop(2); } } if (!post->shares.isEmpty()) { ss += QLatin1String(" - "); ss += i18n("Shared by:") + QLatin1Char(' '); for (const QString &id: post->shares) { if (id == QLatin1String("acct:") + account->webfingerID()) { ss += i18n("You") + QLatin1String(", "); } else { ss += QLatin1String("profileUrl(account, post->author.userName).toDisplayString() + QLatin1String("\">") + PumpIOMicroBlog::userNameFromAcct(id) + QLatin1String(", "); } } if (ss.endsWith(QLatin1String(", "))) { ss.chop(2); } } ss += QLatin1String(" "); } else { qCDebug(CHOQOK) << "post is not a PumpIOPost!"; } return ss; } QString PumpIOPostWidget::getUsernameHyperlink(const Choqok::User &user) const { return QStringLiteral("%3") - .arg(user.homePageUrl) + .arg(user.homePageUrl.toDisplayString()) .arg(user.description.isEmpty() ? user.realName : user.description.toHtmlEscaped()) .arg(user.userName); } void PumpIOPostWidget::initUi() { Choqok::UI::PostWidget::initUi(); if (isResendAvailable()) { buttons().value(QLatin1String("btnResend"))->setToolTip(i18nc("@info:tooltip", "Share")); } if (isReplyAvailable()) { d->btnReply = addButton(QLatin1String("btnReply"), i18nc("@info:tooltip", "Reply"), QLatin1String("edit-undo")); QMenu *replyMenu = new QMenu(d->btnReply); QAction *replyToAct = new QAction(QIcon::fromTheme(QLatin1String("edit-undo")), i18n("Reply to %1", currentPost()->author.userName), replyMenu); replyMenu->addAction(replyToAct); connect(replyToAct, SIGNAL(triggered(bool)), SLOT(slotReplyTo())); connect(d->btnReply, SIGNAL(clicked(bool)), SLOT(slotReplyTo())); } d->btnFavorite = addButton(QLatin1String("btnFavorite"), i18nc("@info:tooltip", "Like"), QLatin1String("rating")); d->btnFavorite->setCheckable(true); connect(d->btnFavorite, SIGNAL(clicked(bool)), this, SLOT(toggleFavorite())); updateFavStat(); } void PumpIOPostWidget::toggleFavorite() { qCDebug(CHOQOK); setReadWithSignal(); PumpIOMicroBlog *microBlog = qobject_cast(currentAccount()->microblog()); connect(microBlog, SIGNAL(favorite(Choqok::Account*,Choqok::Post*)), this, SLOT(slotToggleFavorite(Choqok::Account*,Choqok::Post*))); microBlog->toggleFavorite(currentAccount(), currentPost()); } void PumpIOPostWidget::slotToggleFavorite(Choqok::Account *, Choqok::Post *) { qCDebug(CHOQOK); updateFavStat(); } void PumpIOPostWidget::slotPostError(Choqok::Account *theAccount, Choqok::Post *post, Choqok::MicroBlog::ErrorType error, const QString &errorMessage) { Q_UNUSED(error) qCDebug(CHOQOK); if (theAccount == currentAccount() && post == currentPost()) { qCDebug(CHOQOK) << errorMessage; disconnect(currentAccount()->microblog(), SIGNAL(postRemoved(Choqok::Account*,Choqok::Post*)), this, SLOT(slotCurrentPostRemoved(Choqok::Account*,Choqok::Post*))); disconnect(currentAccount()->microblog(), SIGNAL(errorPost(Choqok::Account*,Choqok::Post*,Choqok::MicroBlog::ErrorType,QString,Choqok::MicroBlog::ErrorLevel)), this, SLOT(slotPostError(Choqok::Account*,Choqok::Post*,Choqok::MicroBlog::ErrorType,QString))); } } void PumpIOPostWidget::slotResendPost() { qCDebug(CHOQOK); setReadWithSignal(); PumpIOMicroBlog *microBlog = qobject_cast(currentAccount()->microblog()); microBlog->share(currentAccount(), currentPost()); } bool PumpIOPostWidget::isReplyAvailable() { return (currentPost()->type != QLatin1String("comment")); } bool PumpIOPostWidget::isResendAvailable() { return PostWidget::isResendAvailable() && (currentPost()->type != QLatin1String("comment")); } void PumpIOPostWidget::slotReplyTo() { qCDebug(CHOQOK); setReadWithSignal(); PumpIOPost *post = dynamic_cast(currentPost()); if (post->type == QLatin1String("comment")) { Q_EMIT reply(post->replyToPostId, post->replyToUserName, post->replyToObjectType); } else { Q_EMIT reply(post->postId, PumpIOMicroBlog::userNameFromAcct(post->author.userId), post->type); } } void PumpIOPostWidget::updateFavStat() { d->btnFavorite->setChecked(currentPost()->isFavorited); if (currentPost()->isFavorited) { d->btnFavorite->setIcon(QIcon::fromTheme(QLatin1String("rating"))); } else { d->btnFavorite->setIcon(unFavIcon); } } diff --git a/microblogs/twitter/twittermicroblog.cpp b/microblogs/twitter/twittermicroblog.cpp index 22418b2a..f4435fbe 100644 --- a/microblogs/twitter/twittermicroblog.cpp +++ b/microblogs/twitter/twittermicroblog.cpp @@ -1,589 +1,589 @@ /* 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 "twittermicroblog.h" #include #include #include #include #include #include #include #include #include "account.h" #include "accountmanager.h" #include "choqokappearancesettings.h" #include "choqokbehaviorsettings.h" #include "choqoktypes.h" #include "composerwidget.h" #include "editaccountwidget.h" #include "mediamanager.h" #include "postwidget.h" #include "timelinewidget.h" #include "twitterapimicroblogwidget.h" #include "twitteraccount.h" #include "twittercomposerwidget.h" #include "twitterdebug.h" #include "twitterdmessagedialog.h" #include "twittereditaccount.h" #include "twitterlistdialog.h" #include "twitterpostwidget.h" #include "twittersearch.h" #include "twittertimelinewidget.h" K_PLUGIN_FACTORY_WITH_JSON(TwitterMicroBlogFactory, "choqok_twitter.json", registerPlugin < TwitterMicroBlog > ();) TwitterMicroBlog::TwitterMicroBlog(QObject *parent, const QVariantList &) : TwitterApiMicroBlog(QLatin1String("choqok_twitter"), parent) { qCDebug(CHOQOK); setServiceName(QLatin1String("Twitter")); setServiceHomepageUrl(QLatin1String("https://twitter.com/")); timelineApiPath[QLatin1String("Reply")] = QLatin1String("/statuses/mentions_timeline.%1"); setTimelineInfos(); } void TwitterMicroBlog::setTimelineInfos() { // hange description of replies to mentions Choqok::TimelineInfo *t = mTimelineInfos[QLatin1String("Reply")]; t->name = i18nc("Timeline Name", "Mentions"); t->description = i18nc("Timeline description", "Mentions of you"); } TwitterMicroBlog::~TwitterMicroBlog() { qCDebug(CHOQOK); } Choqok::Account *TwitterMicroBlog::createNewAccount(const QString &alias) { TwitterAccount *acc = qobject_cast(Choqok::AccountManager::self()->findAccount(alias)); if (!acc) { return new TwitterAccount(this, alias); } else { return 0; } } ChoqokEditAccountWidget *TwitterMicroBlog::createEditAccountWidget(Choqok::Account *account, QWidget *parent) { qCDebug(CHOQOK); TwitterAccount *acc = qobject_cast(account); if (acc || !account) { return new TwitterEditAccountWidget(this, acc, parent); } else { qCDebug(CHOQOK) << "Account passed here is not a TwitterAccount!"; return nullptr; } } Choqok::UI::MicroBlogWidget *TwitterMicroBlog::createMicroBlogWidget(Choqok::Account *account, QWidget *parent) { return new TwitterApiMicroBlogWidget(account, parent); } Choqok::UI::TimelineWidget *TwitterMicroBlog::createTimelineWidget(Choqok::Account *account, const QString &timelineName, QWidget *parent) { return new TwitterTimelineWidget(account, timelineName, parent); } Choqok::UI::PostWidget *TwitterMicroBlog::createPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) { return new TwitterPostWidget(account, post, parent); } Choqok::UI::ComposerWidget *TwitterMicroBlog::createComposerWidget(Choqok::Account *account, QWidget *parent) { return new TwitterComposerWidget(account, parent); } QUrl TwitterMicroBlog::profileUrl(Choqok::Account *account, const QString &username) const { Q_UNUSED(account) return QUrl::fromUserInput(QStringLiteral("https://twitter.com/%1").arg(username)); } -QString TwitterMicroBlog::postUrl(Choqok::Account *, const QString &username, +QUrl TwitterMicroBlog::postUrl(Choqok::Account *, const QString &username, const QString &postId) const { - return QStringLiteral("https://twitter.com/%1/status/%2").arg(username).arg(postId); + return QUrl::fromUserInput(QStringLiteral("https://twitter.com/%1/status/%2").arg(username).arg(postId)); } TwitterApiSearch *TwitterMicroBlog::searchBackend() { if (!mSearchBackend) { mSearchBackend = new TwitterSearch(this); } return mSearchBackend; } void TwitterMicroBlog::createPostWithAttachment(Choqok::Account *theAccount, Choqok::Post *post, const QString &mediumToAttach) { if (mediumToAttach.isEmpty()) { TwitterApiMicroBlog::createPost(theAccount, post); } else { const QUrl picUrl = QUrl::fromUserInput(mediumToAttach); KIO::StoredTransferJob *picJob = KIO::storedGet(picUrl, KIO::Reload, KIO::HideProgressInfo); picJob->exec(); if (picJob->error()) { qCCritical(CHOQOK) << "Job error:" << picJob->errorString(); KMessageBox::detailedError(Choqok::UI::Global::mainWindow(), i18n("Uploading medium failed: cannot read the medium file."), picJob->errorString()); return; } const QByteArray picData = picJob->data(); if (picData.count() == 0) { qCCritical(CHOQOK) << "Cannot read the media file, please check if it exists."; KMessageBox::error(Choqok::UI::Global::mainWindow(), i18n("Uploading medium failed: cannot read the medium file.")); return; } TwitterAccount *account = qobject_cast(theAccount); QUrl url = account->uploadUrl(); url.setPath(url.path() + QStringLiteral("/statuses/update_with_media.%1").arg(format)); const QMimeDatabase db; QByteArray fileContentType = db.mimeTypeForUrl(picUrl).name().toUtf8(); QMap formdata; formdata[QLatin1String("status")] = post->content.toUtf8(); if (!post->replyToPostId.isEmpty()) { formdata[QLatin1String("in_reply_to_status_id")] = post->replyToPostId.toLatin1(); } formdata[QLatin1String("source")] = QCoreApplication::applicationName().toLatin1(); QMap mediafile; mediafile[QLatin1String("name")] = "media[]"; mediafile[QLatin1String("filename")] = picUrl.fileName().toUtf8(); mediafile[QLatin1String("mediumType")] = fileContentType; mediafile[QLatin1String("medium")] = picData; QList< QMap > listMediafiles; listMediafiles.append(mediafile); QByteArray data = Choqok::MediaManager::createMultipartFormData(formdata, listMediafiles); KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo) ; if (!job) { qCCritical(CHOQOK) << "Cannot create a http POST request!"; return; } job->addMetaData(QStringLiteral("content-type"), QStringLiteral("Content-Type: multipart/form-data; boundary=AaB03x")); job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); mCreatePostMap[ job ] = post; mJobsAccount[job] = theAccount; connect(job, SIGNAL(result(KJob*)), SLOT(slotCreatePost(KJob*))); job->start(); } } void TwitterMicroBlog::verifyCredentials(TwitterAccount *theAccount) { qCDebug(CHOQOK); QUrl url = theAccount->apiUrl(); url.setPath(url.path() + QStringLiteral("/account/verify_credentials.json")); 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(theAccount, url, QNetworkAccessManager::GetOperation))); mJobsAccount[ job ] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFetchVerifyCredentials(KJob*))); job->start(); } void TwitterMicroBlog::slotFetchVerifyCredentials(KJob *job) { if (!job) { qCWarning(CHOQOK) << "NULL Job returned"; return; } TwitterAccount *theAccount = qobject_cast(mJobsAccount.take(job)); if (job->error()) { qCDebug(CHOQOK) << "Job Error:" << job->errorString(); Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, i18n("Verify credentials failed. %1", job->errorString()), Low); } else { KIO::StoredTransferJob *stj = qobject_cast (job); const QJsonDocument json = QJsonDocument::fromJson(stj->data()); if (!json.isNull()) { theAccount->setUsername(json.object()[QLatin1String("screen_name")].toString()); theAccount->setUserId(json.object()[QLatin1String("id_str")].toString()); } } } void TwitterMicroBlog::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())); } TwitterDMessageDialog *dmsg = new TwitterDMessageDialog(theAccount, Choqok::UI::Global::mainWindow()); if (!toUsername.isEmpty()) { dmsg->setTo(toUsername); } dmsg->show(); } QString TwitterMicroBlog::generateRepeatedByUserTooltip(const QString &username) { if (Choqok::AppearanceSettings::showRetweetsInChoqokWay()) { return i18n("Retweet of %1", username); } else { return i18n("Retweeted by %1", username); } } QString TwitterMicroBlog::repeatQuestion() { return i18n("Retweet to your followers?"); } QMenu *TwitterMicroBlog::createActionsMenu(Choqok::Account *theAccount, QWidget *parent) { QMenu *menu = TwitterApiMicroBlog::createActionsMenu(theAccount, parent); QAction *lists = new QAction(i18n("Add User List..."), menu); lists->setData(theAccount->alias()); connect(lists, SIGNAL(triggered(bool)), SLOT(showListDialog())); menu->addAction(lists); return menu; } void TwitterMicroBlog::showListDialog(TwitterApiAccount *theAccount) { if (!theAccount) { QAction *act = qobject_cast(sender()); theAccount = qobject_cast( Choqok::AccountManager::self()->findAccount(act->data().toString())); } QPointer listDlg = new TwitterListDialog(theAccount, Choqok::UI::Global::mainWindow()); listDlg->show(); } void TwitterMicroBlog::fetchUserLists(TwitterAccount *theAccount, const QString &username) { qCDebug(CHOQOK); if (!theAccount) { return; } QUrl url = theAccount->apiUrl(); url.setPath(url.path() + QStringLiteral("/lists/ownerships.%1").arg(format)); QUrl url_for_oauth(url);//we need base URL (without params) to make OAuth signature with it! QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("screen_name"), username); url.setQuery(urlQuery); QVariantMap params; params.insert(QLatin1String("screen_name"), username.toLocal8Bit()); KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo) ; if (!job) { qCCritical(CHOQOK) << "TwitterMicroBlog::loadUserLists: Cannot create an http GET request!"; return; } job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(authorizationHeader(theAccount, url_for_oauth, QNetworkAccessManager::GetOperation, params))); mFetchUsersListMap[ job ] = username; mJobsAccount[ job ] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFetchUserLists(KJob*))); job->start(); } void TwitterMicroBlog::slotFetchUserLists(KJob *job) { qCDebug(CHOQOK); if (!job) { qCWarning(CHOQOK) << "NULL Job returned"; return; } QString username = mFetchUsersListMap.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 %1's lists failed. %2", username, job->errorString()), Critical); } else { KIO::StoredTransferJob *stj = qobject_cast (job); QByteArray buffer = stj->data(); QList list = readUserListsFromJson(theAccount, buffer); if (list.isEmpty()) { qCDebug(CHOQOK) << buffer; QString errorMsg; errorMsg = checkForError(buffer); if (errorMsg.isEmpty()) { KMessageBox::information(choqokMainWindow, i18n("There is no list record for user %1", username)); } else { Q_EMIT error(theAccount, ServerError, errorMsg, Critical); } } else { Q_EMIT userLists(theAccount, username, list); } } } Choqok::Post *TwitterMicroBlog::readDirectMessage(Choqok::Account *theAccount, const QVariantMap &var) { qCDebug(CHOQOK); Choqok::Post *post = TwitterApiMicroBlog::readDirectMessage(theAccount, var); if (!post) { qCCritical(CHOQOK) << "post is NULL!"; return 0; } post->postId = var[QLatin1String("id_str")].toString(); return post; } void TwitterMicroBlog::addListTimeline(TwitterAccount *theAccount, const QString &username, const QString &listname) { qCDebug(CHOQOK); QStringList tms = theAccount->timelineNames(); QString name = QStringLiteral("@%1/%2").arg(username).arg(listname); tms.append(name); addTimelineName(name); theAccount->setTimelineNames(tms); theAccount->writeConfig(); QString url = QLatin1String("/lists/statuses"); timelineApiPath[name] = url + QLatin1String(".%1"); updateTimelines(theAccount); } // TODO: Change to new API void TwitterMicroBlog::setListTimelines(TwitterAccount *theAccount, const QStringList &lists) { qCDebug(CHOQOK) << lists; QStringList tms = theAccount->timelineNames(); for (const QString &name: lists) { tms.append(name); addTimelineName(name); QString url = QLatin1String("/lists/statuses"); timelineApiPath[name] = url + QLatin1String(".%1"); } tms.removeDuplicates(); theAccount->setTimelineNames(tms); } Choqok::TimelineInfo *TwitterMicroBlog::timelineInfo(const QString &timelineName) { if (timelineName.startsWith(QLatin1Char('@'))) { if (mListsInfo.contains(timelineName)) { return mListsInfo.value(timelineName); } else { Choqok::TimelineInfo *info = new Choqok::TimelineInfo; info->description = info->name = timelineName; info->icon = QLatin1String("format-list-unordered"); mListsInfo.insert(timelineName, info); return info; } } else { return TwitterApiMicroBlog::timelineInfo(timelineName); } } QList< Twitter::List > TwitterMicroBlog::readUserListsFromJson(Choqok::Account *theAccount, QByteArray buffer) { QList twitterList; const QJsonDocument json = QJsonDocument::fromJson(buffer); if (!json.isNull()) { const QVariantMap map = json.toVariant().toMap(); if (map.contains(QLatin1String("lists"))) { for (const QVariant &list: map[QLatin1String("lists")].toList()) { twitterList.append(readListFromJsonMap(theAccount, list.toMap())); } } } return twitterList; } Twitter::List TwitterMicroBlog::readListFromJsonMap(Choqok::Account *theAccount, QVariantMap map) { Twitter::List l; l.author = readUser(theAccount, map[QLatin1String("user")].toMap()); l.description = map[QLatin1String("description")].toString(); l.fullname = map[QLatin1String("full_name")].toString(); l.isFollowing = map[QLatin1String("following")].toBool(); l.listId = map[QLatin1String("id")].toString(); l.memberCount = map[QLatin1String("member_count")].toInt(); l.mode = (map[QLatin1String("mode")].toString() == QLatin1String("public") ? Twitter::Public : Twitter::Private); l.name = map[QLatin1String("name")].toString(); l.slug = map[QLatin1String("slug")].toString(); l.subscriberCount = map[QLatin1String("subscriber_count")].toInt(); l.uri = map[QLatin1String("uri")].toString(); return l; } Choqok::Post *TwitterMicroBlog::readPost(Choqok::Account *account, const QVariantMap &var, Choqok::Post *post) { if (!post) { qCCritical(CHOQOK) << "TwitterMicroBlog::readPost: post is NULL!"; return 0; } post = TwitterApiMicroBlog::readPost(account, var, post); post->postId = var[QLatin1String("id_str")].toString(); post->replyToPostId = var[QLatin1String("in_reply_to_status_id_str")].toString(); post->replyToUserId = var[QLatin1String("in_reply_to_user_id_str")].toString(); // Support for extended tweet_mode if (var.contains(QLatin1String("full_text"))) { post->content = var[QLatin1String("full_text")].toString(); } //postId is changed, regenerate link url post->link = postUrl(account, post->author.userName, post->postId); QVariantMap userMap = var[QLatin1String("user")].toMap(); post->author.userId = userMap[QLatin1String("id_str")].toString(); return post; } void TwitterMicroBlog::fetchPost(Choqok::Account *theAccount, Choqok::Post *post) { qCDebug(CHOQOK); if (!post || post->postId.isEmpty()) { return; } TwitterAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + QStringLiteral("/statuses/show/%1.%2").arg(post->postId).arg(format)); QUrl tmpUrl(url); QUrlQuery urlQuery; urlQuery.addQueryItem(QLatin1String("tweet_mode"), QLatin1String("extended")); url.setQuery(urlQuery); 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, tmpUrl, QNetworkAccessManager::GetOperation))); mFetchPostMap[ job ] = post; mJobsAccount[ job ] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFetchPost(KJob*))); job->start(); } void TwitterMicroBlog::requestTimeLine(Choqok::Account *theAccount, QString type, QString latestStatusId, int page, QString maxId) { qCDebug(CHOQOK); TwitterAccount *account = qobject_cast(theAccount); QUrl url = account->apiUrl(); url.setPath(url.path() + timelineApiPath[type].arg(format)); QUrl tmpUrl(url); QUrlQuery urlQuery; QVariantMap params; // 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); params.insert(QLatin1String("slug"), slug.toLatin1()); const QString owner = type.mid(1, type.indexOf(QLatin1String("/")) - 1); urlQuery.addQueryItem(QLatin1String("owner_screen_name"), owner); params.insert(QLatin1String("owner_screen_name"), owner.toLatin1()); } else { int countOfPost = Choqok::BehaviorSettings::countOfPosts(); if (!latestStatusId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("since_id"), latestStatusId); params.insert(QLatin1String("since_id"), latestStatusId.toLatin1()); countOfPost = 200; } urlQuery.addQueryItem(QLatin1String("count"), QString::number(countOfPost)); params.insert(QLatin1String("count"), QByteArray::number(countOfPost)); if (!maxId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("max_id"), maxId); params.insert(QLatin1String("max_id"), maxId.toLatin1()); } if (page) { urlQuery.addQueryItem(QLatin1String("page"), QString::number(page)); params.insert(QLatin1String("page"), QByteArray::number(page)); } } urlQuery.addQueryItem(QLatin1String("tweet_mode"), QLatin1String("extended")); params.insert(QLatin1String("tweet_mode"), QLatin1String("extended")); 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, tmpUrl, QNetworkAccessManager::GetOperation, params))); mRequestTimelineMap[job] = type; mJobsAccount[job] = theAccount; connect(job, SIGNAL(result(KJob*)), this, SLOT(slotRequestTimeline(KJob*))); job->start(); } #include "twittermicroblog.moc" diff --git a/microblogs/twitter/twittermicroblog.h b/microblogs/twitter/twittermicroblog.h index 4d079b22..66d8c9bc 100644 --- a/microblogs/twitter/twittermicroblog.h +++ b/microblogs/twitter/twittermicroblog.h @@ -1,107 +1,107 @@ /* 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/ */ #ifndef TWITTERMICROBLOGPLUGIN_H #define TWITTERMICROBLOGPLUGIN_H #include #include "twitterapimicroblog.h" #include "twitterlist.h" class TwitterAccount; class TwitterSearch; class ChoqokEditAccountWidget; class KJob; /** @author Mehrdad Momeny \ */ class TwitterMicroBlog : public TwitterApiMicroBlog { Q_OBJECT public: TwitterMicroBlog(QObject *parent, const QVariantList &args); ~TwitterMicroBlog(); virtual Choqok::Account *createNewAccount(const QString &alias) override; virtual ChoqokEditAccountWidget *createEditAccountWidget(Choqok::Account *account, QWidget *parent) override; virtual Choqok::UI::MicroBlogWidget *createMicroBlogWidget(Choqok::Account *account, QWidget *parent) override; virtual Choqok::UI::TimelineWidget *createTimelineWidget(Choqok::Account *account, const QString &timelineName, QWidget *parent) override; virtual Choqok::UI::PostWidget *createPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) override; virtual Choqok::UI::ComposerWidget *createComposerWidget(Choqok::Account *account, QWidget *parent) override; - virtual QString postUrl(Choqok::Account *account, const QString &username, const QString &postId) const override; + virtual QUrl postUrl(Choqok::Account *account, const QString &username, const QString &postId) const override; virtual QUrl profileUrl(Choqok::Account *account, const QString &username) const override; virtual void fetchPost(Choqok::Account *theAccount, Choqok::Post *post) override; virtual TwitterApiSearch *searchBackend() override; virtual QString generateRepeatedByUserTooltip(const QString &username) override; virtual QString repeatQuestion() override; virtual QMenu *createActionsMenu(Choqok::Account *theAccount, QWidget *parent = Choqok::UI::Global::mainWindow()) override; void fetchUserLists(TwitterAccount *theAccount, const QString &username); void addListTimeline(TwitterAccount *theAccount, const QString &username, const QString &listname); void setListTimelines(TwitterAccount *theAccount, const QStringList &lists); virtual Choqok::TimelineInfo *timelineInfo(const QString &timelineName) override; void createPostWithAttachment(Choqok::Account *theAccount, Choqok::Post *post, const QString &mediumToAttach = QString()); void verifyCredentials(TwitterAccount *theAccount); Q_SIGNALS: void userLists(Choqok::Account *theAccount, const QString &username, QList lists); public Q_SLOTS: virtual void showDirectMessageDialog(TwitterApiAccount *theAccount = 0, const QString &toUsername = QString()) override; protected Q_SLOTS: void showListDialog(TwitterApiAccount *theAccount = 0); void slotFetchUserLists(KJob *job); void slotFetchVerifyCredentials(KJob *job); protected: virtual void requestTimeLine(Choqok::Account *theAccount, QString timelineName, QString sincePostId, int page = 1, QString maxId = QString()) override; using TwitterApiMicroBlog::readDirectMessage; virtual Choqok::Post *readDirectMessage(Choqok::Account *theAccount, const QVariantMap &var) override; using TwitterApiMicroBlog::readPost; virtual Choqok::Post *readPost(Choqok::Account *account, const QVariantMap &var, Choqok::Post *post) override; void setTimelineInfos() override; QList readUserListsFromJson(Choqok::Account *theAccount, QByteArray buffer); Twitter::List readListFromJsonMap(Choqok::Account *theAccount, QVariantMap map); QMap mFetchUsersListMap; private: QPointer mSearchBackend; QMap mListsInfo; }; #endif diff --git a/microblogs/twitter/twitterpostwidget.cpp b/microblogs/twitter/twitterpostwidget.cpp index d466bddd..62a604f6 100644 --- a/microblogs/twitter/twitterpostwidget.cpp +++ b/microblogs/twitter/twitterpostwidget.cpp @@ -1,300 +1,300 @@ /* 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 "twitterpostwidget.h" #include #include #include #include #include "choqokbehaviorsettings.h" #include "choqoktools.h" #include "mediamanager.h" #include "textbrowser.h" #include "twitterapiaccount.h" #include "twitterapimicroblog.h" #include "twitterapiwhoiswidget.h" #include "twittersearch.h" const QRegExp TwitterPostWidget::mTwitterUserRegExp(QLatin1String("([\\s\\W]|^)@([a-z0-9_]+){1,20}"), Qt::CaseInsensitive); const QRegExp TwitterPostWidget::mTwitterTagRegExp(QLatin1String("([\\s]|^)#([\\w_\\.\\-]+)"), Qt::CaseInsensitive); const QString TwitterPostWidget::mQuotedTextBase(QLatin1String("")); const QUrl TwitterPostWidget::mQuotedAvatarResourceUrl(QLatin1String("img://quotedProfileImage")); TwitterPostWidget::TwitterPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent): TwitterApiPostWidget(account, post, parent) { } TwitterPostWidget::~TwitterPostWidget() { } void TwitterPostWidget::initUi() { TwitterApiPostWidget::initUi(); if ( ! currentPost()->quotedPost.content.isEmpty() ) { if( !setupQuotedAvatar() ){ _mainWidget->document()->addResource(QTextDocument::ImageResource, mQuotedAvatarResourceUrl, Choqok::MediaManager::self()->defaultImage()); } auto dir = getDirection(currentPost()->quotedPost.content); auto text = prepareStatus(currentPost()->quotedPost.content); QString user = QString(QLatin1String("%1")).arg(currentPost()->quotedPost.username); QString quoteText = mQuotedTextBase.arg(text, dir, user, QLatin1String("background-color:%1;")); setExtraContents(quoteText.arg(getBackgroundColor())); updateUi(); } QPushButton *btn = buttons().value(QLatin1String("btnResend")); if (btn) { QMenu *menu = new QMenu(btn); QAction *resend = new QAction(i18n("Manual ReSend"), menu); connect(resend, SIGNAL(triggered(bool)), SLOT(slotResendPost())); QAction *repeat = new QAction(i18n("Retweet"), menu); repeat->setToolTip(i18n("Retweet post using API")); connect(repeat, SIGNAL(triggered(bool)), SLOT(repeatPost())); // If person protects their acc, we will use simple adding RT before message if (!currentPost()->author.isProtected) { menu->addAction(repeat); } menu->addAction(resend); btn->setMenu(menu); } } QString TwitterPostWidget::prepareStatus(const QString &text) { QString res = TwitterApiPostWidget::prepareStatus(text); res.replace(mTwitterUserRegExp, QLatin1String("\\1@\\2")); res.replace(mTwitterTagRegExp, QLatin1String("\\1#\\2")); return res; } bool TwitterPostWidget::isRemoveAvailable() { if (currentAccount()->username().compare(currentPost()->author.userName, Qt::CaseInsensitive) == 0) { return true; } else if (currentPost()->isPrivate) { return true; } else { return false; } } void TwitterPostWidget::slotReplyToAll() { QStringList nicks; nicks.append(currentPost()->author.userName); QString txt = QStringLiteral("@%1 ").arg(currentPost()->author.userName); int pos = 0; while ((pos = mTwitterUserRegExp.indexIn(currentPost()->content, pos)) != -1) { if (mTwitterUserRegExp.cap(2).toLower() != currentAccount()->username() && mTwitterUserRegExp.cap(2).toLower() != currentPost()->author.userName && !nicks.contains(mTwitterUserRegExp.cap(2).toLower())) { nicks.append(mTwitterUserRegExp.cap(2)); txt += QStringLiteral("@%1 ").arg(mTwitterUserRegExp.cap(2)); } pos += mTwitterUserRegExp.matchedLength(); } txt.chop(1); Q_EMIT reply(txt, currentPost()->postId, currentPost()->author.userName); } void TwitterPostWidget::checkAnchor(const QUrl &url) { QString scheme = url.scheme(); TwitterApiMicroBlog *blog = qobject_cast(currentAccount()->microblog()); TwitterApiAccount *account = qobject_cast(currentAccount()); if (scheme == QLatin1String("tag")) { blog->searchBackend()->requestSearchResults(currentAccount(), QUrl::fromAce(url.host().toUtf8()), (int)TwitterSearch::ReferenceHashtag); } else if (scheme == QLatin1String("user")) { QMenu menu; QAction *info = new QAction(QIcon::fromTheme(QLatin1String("user-identity")), i18nc("Who is user", "Who is %1", url.host()), &menu); QAction *from = new QAction(QIcon::fromTheme(QLatin1String("edit-find-user")), i18nc("Posts from user", "Posts from %1", url.host()), &menu); QAction *to = new QAction(QIcon::fromTheme(QLatin1String("meeting-attending")), i18nc("Replies to user", "Replies to %1", url.host()), &menu); QAction *cont = new QAction(QIcon::fromTheme(QLatin1String("user-properties")), i18nc("Including user name", "Including %1", url.host()), &menu); QAction *openInBrowser = new QAction(QIcon::fromTheme(QLatin1String("applications-internet")), i18nc("Open profile page in browser", "Open profile in browser"), &menu); from->setData(TwitterSearch::FromUser); to->setData(TwitterSearch::ToUser); cont->setData(TwitterSearch::ReferenceUser); menu.addAction(info); menu.addAction(from); menu.addAction(to); menu.addAction(cont); menu.addAction(openInBrowser); //Subscribe/UnSubscribe/Block bool isSubscribe = false; QString accountUsername = currentAccount()->username().toLower(); QString postUsername = url.host().toLower(); QAction *subscribe = 0, *block = 0, *replyTo = 0, *dMessage = 0, *reportSpam = 0; if (accountUsername != postUsername) { menu.addSeparator(); QMenu *actionsMenu = menu.addMenu(QIcon::fromTheme(QLatin1String("applications-system")), i18n("Actions")); replyTo = new QAction(QIcon::fromTheme(QLatin1String("edit-undo")), i18nc("Write a message to user attention", "Write to %1", url.host()), actionsMenu); actionsMenu->addAction(replyTo); if (account->friendsList().contains(url.host(), Qt::CaseInsensitive)) { dMessage = new QAction(QIcon::fromTheme(QLatin1String("mail-message-new")), i18nc("Send direct message to user", "Send private message to %1", url.host()), actionsMenu); actionsMenu->addAction(dMessage); isSubscribe = false;//It's UnSubscribe subscribe = new QAction(QIcon::fromTheme(QLatin1String("list-remove-user")), i18nc("Unfollow user", "Unfollow %1", url.host()), actionsMenu); } else { isSubscribe = true; subscribe = new QAction(QIcon::fromTheme(QLatin1String("list-add-user")), i18nc("Follow user", "Follow %1", url.host()), actionsMenu); } block = new QAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18nc("Block user", "Block %1", url.host()), actionsMenu); reportSpam = new QAction(QIcon::fromTheme(QLatin1String("irc-voice")), i18nc("Report user", "Report %1 as spam", url.host()), actionsMenu); actionsMenu->addAction(subscribe); actionsMenu->addAction(block); actionsMenu->addAction(reportSpam); } QAction *ret = menu.exec(QCursor::pos()); if (ret == 0) { return; } if (ret == info) { TwitterApiWhoisWidget *wd = new TwitterApiWhoisWidget(account, url.host(), *currentPost(), this); wd->show(QCursor::pos()); return; } else if (ret == subscribe) { if (isSubscribe) { blog->createFriendship(currentAccount(), url.host()); } else { blog->destroyFriendship(currentAccount(), url.host()); } return; } else if (ret == block) { blog->blockUser(currentAccount(), url.host()); return; } else if (ret == reportSpam) { blog->reportUserAsSpam(currentAccount(), url.host()); return; } else if (ret == openInBrowser) { Choqok::openUrl(currentAccount()->microblog()->profileUrl(currentAccount(), url.host())); return; } else if (ret == replyTo) { Q_EMIT reply(QStringLiteral("@%1").arg(url.host()), QString(), url.host()); return; } else if (ret == dMessage) { blog->showDirectMessageDialog(account, url.host()); return; } int type = ret->data().toInt(); blog->searchBackend()->requestSearchResults(currentAccount(), url.host(), type, QString(), Choqok::BehaviorSettings::countOfPosts()); } else { TwitterApiPostWidget::checkAnchor(url); } } bool TwitterPostWidget::setupQuotedAvatar() { QPixmap pix = Choqok::MediaManager::self()->fetchImage(currentPost()->quotedPost.profileImageUrl, Choqok::MediaManager::Async); if (!pix.isNull()) { quotedAvatarFetched(currentPost()->quotedPost.profileImageUrl, pix); return true; } else { - connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - this, SLOT(quotedAvatarFetched(QString,QPixmap))); + connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + this, SLOT(quotedAvatarFetched(QUrl,QPixmap))); connect(Choqok::MediaManager::self(), SIGNAL(fetchError(QString,QString)), this, SLOT(quotedAvatarFetchError(QString,QString))); return false; } } -void TwitterPostWidget::quotedAvatarFetched(const QString &remoteUrl, const QPixmap &pixmap) +void TwitterPostWidget::quotedAvatarFetched(const QUrl &remoteUrl, const QPixmap &pixmap) { if (remoteUrl == currentPost()->quotedPost.profileImageUrl) { _mainWidget->document()->addResource(QTextDocument::ImageResource, mQuotedAvatarResourceUrl, pixmap); - disconnect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - this, SLOT(quotedAvatarFetched(QString,QPixmap))); + disconnect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + this, SLOT(quotedAvatarFetched(QUrl,QPixmap))); disconnect(Choqok::MediaManager::self(), SIGNAL(fetchError(QString,QString)), this, SLOT(quotedAvatarFetchError(QString,QString))); } } -void TwitterPostWidget::quotedAvatarFetchError(const QString &remoteUrl, const QString &errMsg) +void TwitterPostWidget::quotedAvatarFetchError(const QUrl &remoteUrl, const QString &errMsg) { Q_UNUSED(errMsg); if (remoteUrl == currentPost()->quotedPost.profileImageUrl) { ///Avatar fetching is failed! but will not disconnect to get the img if it fetches later! _mainWidget->document()->addResource(QTextDocument::ImageResource, mQuotedAvatarResourceUrl, QIcon::fromTheme(QLatin1String("image-missing")).pixmap(40)); } } QString TwitterPostWidget::getBackgroundColor() { QString style = styleSheet(); QLatin1String str{ "background-color:rgb(" }; int idx = style.indexOf(str); if(idx != -1){ idx += str.size(); int endIdx = style.indexOf(QLatin1String(");"), idx); if( endIdx != -1 ){ QStringList rgb = style.mid(idx, endIdx-idx).split(QLatin1Char(',')); if( rgb.size() == 3 ){ return QString(QLatin1String("#%1%2%3")).arg( rgb[0].toInt() - 20, 2, 16, QLatin1Char('0') ) .arg( rgb[1].toInt() - 20, 2, 16, QLatin1Char('0') ) .arg( rgb[2].toInt() - 20, 2, 16, QLatin1Char('0') ); } } } return QLatin1String("#ffffff"); } diff --git a/microblogs/twitter/twitterpostwidget.h b/microblogs/twitter/twitterpostwidget.h index 136e2eea..dc44f428 100644 --- a/microblogs/twitter/twitterpostwidget.h +++ b/microblogs/twitter/twitterpostwidget.h @@ -1,57 +1,57 @@ /* 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/ */ #ifndef TWITTERPOSTWIDGET_H #define TWITTERPOSTWIDGET_H #include "twitterapipostwidget.h" class TwitterPostWidget : public TwitterApiPostWidget { Q_OBJECT public: TwitterPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent = 0); ~TwitterPostWidget(); virtual void initUi() override; protected Q_SLOTS: void slotReplyToAll() override; - void quotedAvatarFetched(const QString &remoteUrl, const QPixmap &pixmap); - void quotedAvatarFetchError(const QString &remoteUrl, const QString &errMsg); + void quotedAvatarFetched(const QUrl &remoteUrl, const QPixmap &pixmap); + void quotedAvatarFetchError(const QUrl &remoteUrl, const QString &errMsg); protected: QString prepareStatus(const QString &text) override; void checkAnchor(const QUrl &url) override; bool isRemoveAvailable() override; bool setupQuotedAvatar(); static const QRegExp mTwitterUserRegExp; static const QRegExp mTwitterTagRegExp; static const QString mQuotedTextBase; static const QUrl mQuotedAvatarResourceUrl; private: QString getBackgroundColor(); }; #endif // TWITTERPOSTWIDGET_H diff --git a/microblogs/twitter/twittersearch.cpp b/microblogs/twitter/twittersearch.cpp index 5d51e94e..f241924c 100644 --- a/microblogs/twitter/twittersearch.cpp +++ b/microblogs/twitter/twittersearch.cpp @@ -1,208 +1,208 @@ /* 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 "twittersearch.h" #include #include #include #include "twitterapimicroblog.h" #include "twitteraccount.h" #include "twitterdebug.h" const QRegExp TwitterSearch::m_rId(QLatin1String("tag:search.twitter.com,[0-9]+:([0-9]+)")); TwitterSearch::TwitterSearch(QObject *parent): TwitterApiSearch(parent) { qCDebug(CHOQOK); mSearchCode[CustomSearch].clear(); mSearchCode[ToUser] = QLatin1String("to:"); mSearchCode[FromUser] = QLatin1String("from:"); mSearchCode[ReferenceUser] = QLatin1Char('@'); mSearchCode[ReferenceHashtag] = QLatin1Char('#'); mI18nSearchCode[CustomSearch].clear(); mI18nSearchCode[ReferenceUser] = QLatin1Char('@'); mI18nSearchCode[ReferenceHashtag] = QLatin1Char('#'); mI18nSearchCode[ToUser] = i18nc("Posts sent to user", "To:"); mI18nSearchCode[FromUser] = i18nc("Posts from user, Sent by user", "From:"); mSearchTypes[CustomSearch].first = i18n("Custom Search"); mSearchTypes[CustomSearch].second = true; mSearchTypes[ToUser].first = i18nc("Tweets are Twitter posts", "Tweets To This User"); mSearchTypes[ToUser].second = true; mSearchTypes[FromUser].first = i18nc("Tweets are Twitter posts", "Tweets From This User"); mSearchTypes[FromUser].second = true; mSearchTypes[ReferenceUser].first = i18nc("Tweets are Twitter posts", "Tweets Including This Username"); mSearchTypes[ReferenceUser].second = true; mSearchTypes[ReferenceHashtag].first = i18nc("Tweets are Twitter posts", "Tweets Including This Hashtag"); mSearchTypes[ReferenceHashtag].second = true; } void TwitterSearch::requestSearchResults(const SearchInfo &searchInfo, const QString &sinceStatusId, uint count, uint page) { Q_UNUSED(page) qCDebug(CHOQOK); TwitterAccount *account = qobject_cast< TwitterAccount * >(searchInfo.account); QUrl url = account->apiUrl(); QUrlQuery urlQuery; QVariantMap param; const QString query = searchInfo.query; if (searchInfo.option == TwitterSearch::FromUser) { url.setPath(url.path() + QLatin1String("/statuses/user_timeline.json")); urlQuery.addQueryItem(QLatin1String("screen_name"), query); param.insert(QLatin1String("screen_name"), query.toLocal8Bit()); } else { url.setPath(url.path() + QLatin1String("/search/tweets.json")); const QByteArray formattedQuery(QUrl::toPercentEncoding(mSearchCode[searchInfo.option] + query)); urlQuery.addQueryItem(QLatin1String("q"), QString::fromLatin1(formattedQuery)); param.insert(QLatin1String("q"), formattedQuery); } if (!sinceStatusId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("since_id"), sinceStatusId); param.insert(QLatin1String("since_id"), sinceStatusId.toLocal8Bit()); } int cntStr; if (count && count <= 100) { // Twitter API specifies a max count of 100 cntStr = count; } else { cntStr = 100; } urlQuery.addQueryItem(QLatin1String("tweet_mode"), QLatin1String("extended")); param.insert(QLatin1String("tweet_mode"), QLatin1String("extended")); urlQuery.addQueryItem(QLatin1String("count"), QString::number(cntStr)); param.insert(QLatin1String("count"), QString::number(cntStr).toLocal8Bit()); const QUrl tmpUrl(url); url.setQuery(urlQuery); qCDebug(CHOQOK) << url; KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); if (!job) { qCCritical(CHOQOK) << "Cannot create an http GET request!"; return; } TwitterApiMicroBlog *microblog = qobject_cast(account->microblog()); job->addMetaData(QStringLiteral("customHTTPHeader"), QStringLiteral("Authorization: ") + QLatin1String(microblog->authorizationHeader(account, tmpUrl, QNetworkAccessManager::GetOperation, param))); mSearchJobs[job] = searchInfo; connect(job, SIGNAL(result(KJob*)), this, SLOT(searchResultsReturned(KJob*))); job->start(); } void TwitterSearch::searchResultsReturned(KJob *job) { qCDebug(CHOQOK); if (!job) { qCDebug(CHOQOK) << "job is a null pointer"; Q_EMIT error(i18n("Unable to fetch search results.")); return; } const SearchInfo info = mSearchJobs.take(job); QList postsList; if (job->error()) { qCCritical(CHOQOK) << "Error:" << job->errorString(); Q_EMIT error(i18n("Unable to fetch search results: %1", job->errorString())); } else { KIO::StoredTransferJob *jj = qobject_cast(job); const QJsonDocument json = QJsonDocument::fromJson(jj->data()); if (!json.isNull()) { if (info.option == TwitterSearch::FromUser) { for (const QVariant elem: json.toVariant().toList()) { postsList.prepend(readStatusesFromJsonMap(elem.toMap())); } } else { const QVariantMap map = json.toVariant().toMap(); if (map.contains(QLatin1String("statuses"))) { for (const QVariant elem: map[QLatin1String("statuses")].toList()) { postsList.prepend(readStatusesFromJsonMap(elem.toMap())); } } } } } Q_EMIT searchResultsReceived(info, postsList); } Choqok::Post *TwitterSearch::readStatusesFromJsonMap(const QVariantMap &var) { Choqok::Post *post = new Choqok::Post; post->content = var[QLatin1String("text")].toString(); // Support for extended tweet_mode if (var.contains(QLatin1String("full_text"))) { post->content = var[QLatin1String("full_text")].toString(); } //%*s %s %d %d:%d:%d %d %d post->creationDateTime = dateFromString(var[QLatin1String("created_at")].toString()); post->postId = var[QLatin1String("id")].toString(); post->source = var[QLatin1String("source")].toString(); QVariantMap userMap = var[QLatin1String("user")].toMap(); post->author.realName = userMap[QLatin1String("name")].toString(); post->author.userName = userMap[QLatin1String("screen_name")].toString(); - post->author.profileImageUrl = userMap[QLatin1String("profile_image_url")].toString(); + post->author.profileImageUrl = userMap[QLatin1String("profile_image_url")].toUrl(); post->isPrivate = false; post->isFavorited = false; post->replyToPostId = var[QLatin1String("in_reply_to_status_id_str")].toString(); post->replyToUserName = var[QLatin1String("in_reply_to_screen_name")].toString(); - post->link = QStringLiteral("https://twitter.com/%1/status/%2").arg(post->author.userName).arg(post->postId); + post->link = QUrl::fromUserInput(QStringLiteral("https://twitter.com/%1/status/%2").arg(post->author.userName).arg(post->postId)); return post; } QString TwitterSearch::optionCode(int option) { return mI18nSearchCode[option]; } TwitterSearch::~TwitterSearch() { } diff --git a/plugins/imagepreview/imagepreview.cpp b/plugins/imagepreview/imagepreview.cpp index 732b455f..67071c29 100644 --- a/plugins/imagepreview/imagepreview.cpp +++ b/plugins/imagepreview/imagepreview.cpp @@ -1,166 +1,166 @@ /* 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 "imagepreview.h" #include #include #include "choqokuiglobal.h" #include "mediamanager.h" #include "postwidget.h" #include "textbrowser.h" K_PLUGIN_FACTORY_WITH_JSON(ImagePreviewFactory, "choqok_imagepreview.json", registerPlugin < ImagePreview > ();) const QRegExp ImagePreview::mImgLyRegExp(QLatin1String("(http://img.ly/[^\\s<>\"]+[^!,\\.\\s<>'\"\\]])")); const QRegExp ImagePreview::mTwitgooRegExp(QLatin1String("(http://(([a-zA-Z0-9]+\\.)?)twitgoo.com/[^\\s<>\"]+[^!,\\.\\s<>'\"\\]])")); const QRegExp ImagePreview::mPumpIORegExp(QLatin1String("(https://([a-zA-Z0-9]+\\.)?[a-zA-Z0-9]+\\.[a-zA-Z]+/uploads/\\w+/\\d{4}/\\d{1,2}/\\d{1,2}/\\w+)(\\.[a-zA-Z]{3,4})")); ImagePreview::ImagePreview(QObject *parent, const QList< QVariant > &) : Choqok::Plugin(QLatin1String("choqok_imagepreview"), parent), state(Stopped) { connect(Choqok::UI::Global::SessionManager::self(), SIGNAL(newPostWidgetAdded(Choqok::UI::PostWidget*,Choqok::Account*,QString)), this, SLOT(slotAddNewPostWidget(Choqok::UI::PostWidget*))); } ImagePreview::~ImagePreview() { } void ImagePreview::slotAddNewPostWidget(Choqok::UI::PostWidget *newWidget) { postsQueue.enqueue(newWidget); if (state == Stopped) { state = Running; QTimer::singleShot(1000, this, SLOT(startParsing())); } } void ImagePreview::startParsing() { int i = 8; while (!postsQueue.isEmpty() && i > 0) { parse(postsQueue.dequeue()); --i; } if (postsQueue.isEmpty()) { state = Stopped; } else { QTimer::singleShot(500, this, SLOT(startParsing())); } } void ImagePreview::parse(Choqok::UI::PostWidget *postToParse) { if (!postToParse) { return; } int pos = 0; QStringList ImgLyRedirectList; QStringList TwitgooRedirectList; QStringList PumpIORedirectList; QString content = postToParse->currentPost()->content; //Img.ly; http://img.ly/api/docs pos = 0; while ((pos = mImgLyRegExp.indexIn(content, pos)) != -1) { pos += mImgLyRegExp.matchedLength(); ImgLyRedirectList << mImgLyRegExp.cap(0); } for (const QString &url: ImgLyRedirectList) { connect(Choqok::MediaManager::self(), - SIGNAL(imageFetched(QString,QPixmap)), - SLOT(slotImageFetched(QString,QPixmap))); - QString ImgLyUrl = QStringLiteral("http://img.ly/show/thumb%1").arg(QString(url).remove(QLatin1String("http://img.ly"))); + SIGNAL(imageFetched(QUrl,QPixmap)), + SLOT(slotImageFetched(QUrl,QPixmap))); + QUrl ImgLyUrl = QUrl::fromUserInput(QStringLiteral("http://img.ly/show/thumb%1").arg(QString(url).remove(QLatin1String("http://img.ly")))); mParsingList.insert(ImgLyUrl, postToParse); mBaseUrlMap.insert(ImgLyUrl, url); Choqok::MediaManager::self()->fetchImage(ImgLyUrl, Choqok::MediaManager::Async); } //Twitgoo; http://twitgoo.com/docs/TwitgooHelp.htm pos = 0; while ((pos = mTwitgooRegExp.indexIn(content, pos)) != -1) { pos += mTwitgooRegExp.matchedLength(); TwitgooRedirectList << mTwitgooRegExp.cap(0); } for (const QString &url: TwitgooRedirectList) { connect(Choqok::MediaManager::self(), - SIGNAL(imageFetched(QString,QPixmap)), - SLOT(slotImageFetched(QString,QPixmap))); - QString TwitgooUrl = url + QLatin1String("/thumb"); + SIGNAL(imageFetched(QUrl,QPixmap)), + SLOT(slotImageFetched(QUrl,QPixmap))); + QUrl TwitgooUrl = QUrl::fromUserInput(url + QLatin1String("/thumb")); mParsingList.insert(TwitgooUrl, postToParse); mBaseUrlMap.insert(TwitgooUrl, url); Choqok::MediaManager::self()->fetchImage(TwitgooUrl, Choqok::MediaManager::Async); } //PumpIO pos = 0; QString baseUrl; QString imageExtension; while ((pos = mPumpIORegExp.indexIn(content, pos)) != -1) { pos += mPumpIORegExp.matchedLength(); PumpIORedirectList << mPumpIORegExp.cap(0); baseUrl = mPumpIORegExp.cap(1); imageExtension = mPumpIORegExp.cap(mPumpIORegExp.capturedTexts().length() - 1); } for (const QString &url: PumpIORedirectList) { - connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - SLOT(slotImageFetched(QString,QPixmap))); - const QString pumpIOUrl = baseUrl + QLatin1String("_thumb") + imageExtension; + connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + SLOT(slotImageFetched(QUrl,QPixmap))); + const QUrl pumpIOUrl = QUrl::fromUserInput(baseUrl + QLatin1String("_thumb") + imageExtension); mParsingList.insert(pumpIOUrl, postToParse); mBaseUrlMap.insert(pumpIOUrl, url); Choqok::MediaManager::self()->fetchImage(pumpIOUrl, Choqok::MediaManager::Async); } } -void ImagePreview::slotImageFetched(const QString &remoteUrl, const QPixmap &pixmap) +void ImagePreview::slotImageFetched(const QUrl &remoteUrl, const QPixmap &pixmap) { Choqok::UI::PostWidget *postToParse = mParsingList.take(remoteUrl); QString baseUrl = mBaseUrlMap.take(remoteUrl); if (!postToParse) { return; } QString content = postToParse->content(); QUrl imgU(remoteUrl); imgU.setScheme(QLatin1String("img")); // imgUrl.replace("http://","img://"); QPixmap pix = pixmap; if (pixmap.width() > 200) { pix = pixmap.scaledToWidth(200); } else if (pixmap.height() > 200) { pix = pixmap.scaledToHeight(200); } postToParse->mainWidget()->document()->addResource(QTextDocument::ImageResource, imgU, pix); content.replace(QRegExp(QLatin1Char('>') + baseUrl + QLatin1Char('<')), QStringLiteral("><")); postToParse->setContent(content); } #include "imagepreview.moc" diff --git a/plugins/imagepreview/imagepreview.h b/plugins/imagepreview/imagepreview.h index 6e9166a4..7e0c3078 100644 --- a/plugins/imagepreview/imagepreview.h +++ b/plugins/imagepreview/imagepreview.h @@ -1,67 +1,67 @@ /* 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/ */ #ifndef IMAGEPREVIEW_H #define IMAGEPREVIEW_H #include #include #include #include "plugin.h" namespace Choqok { namespace UI { class PostWidget; } } class ImagePreview : public Choqok::Plugin { Q_OBJECT public: ImagePreview(QObject *parent, const QList< QVariant > &args); ~ImagePreview(); protected Q_SLOTS: void slotAddNewPostWidget(Choqok::UI::PostWidget *newWidget); void startParsing(); - void slotImageFetched(const QString &remoteUrl, const QPixmap &pixmap); + void slotImageFetched(const QUrl &remoteUrl, const QPixmap &pixmap); private: enum ParserState { Running = 0, Stopped }; ParserState state; void parse(Choqok::UI::PostWidget *postToParse); QQueue< QPointer > postsQueue; - QMap > mParsingList;//remoteUrl, Post - QMap mBaseUrlMap;//remoteUrl, BaseUrl + QMap > mParsingList;//remoteUrl, Post + QMap mBaseUrlMap;//remoteUrl, BaseUrl static const QRegExp mImgLyRegExp; static const QRegExp mTwitgooRegExp; static const QRegExp mPumpIORegExp; }; #endif // IMAGEPREVIEW_H diff --git a/plugins/imstatus/imstatus.cpp b/plugins/imstatus/imstatus.cpp index d2831d94..49e96c80 100644 --- a/plugins/imstatus/imstatus.cpp +++ b/plugins/imstatus/imstatus.cpp @@ -1,89 +1,89 @@ /* This file is part of Choqok, the KDE micro-blogging client Copyright (C) 2010-2012 Andrey Esin 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 "imstatus.h" #include #include #include "choqokuiglobal.h" #include "quickpost.h" #include "imqdbus.h" #include "imstatussettings.h" K_PLUGIN_FACTORY_WITH_JSON(IMStatusFactory, "choqok_imstatus.json", registerPlugin < IMStatus > ();) class IMStatusPrivate { public: IMStatusPrivate() {} IMQDBus *im; }; IMStatus::IMStatus(QObject *parent, const QList &) : Choqok::Plugin(QLatin1String("choqok_imstatus"), parent), d(new IMStatusPrivate()) { QTimer::singleShot(500, this, SLOT(update())); d->im = new IMQDBus(this); } IMStatus::~IMStatus() { delete d; } void IMStatus::update() { if (Choqok::UI::Global::quickPostWidget() != 0) { connect(Choqok::UI::Global::quickPostWidget(), SIGNAL(newPostSubmitted(Choqok::JobResult,Choqok::Post*)), this, SLOT(slotIMStatus(Choqok::JobResult,Choqok::Post*))); } else { QTimer::singleShot(500, this, SLOT(update())); } } void IMStatus::slotIMStatus(Choqok::JobResult res, Choqok::Post *newPost) { if (res == Choqok::Success) { IMStatusSettings::self()->load(); QString statusMessage = IMStatusSettings::templtate(); statusMessage.replace(QLatin1String("%status%"), newPost->content, Qt::CaseInsensitive); statusMessage.replace(QLatin1String("%username%"), newPost->author.userName, Qt::CaseInsensitive); statusMessage.replace(QLatin1String("%fullname%"), newPost->author.realName, Qt::CaseInsensitive); statusMessage.replace(QLatin1String("%time%"), newPost->creationDateTime.toString(QLatin1String("hh:mm:ss")), Qt::CaseInsensitive); - statusMessage.replace(QLatin1String("%url%"), newPost->link, Qt::CaseInsensitive); + statusMessage.replace(QLatin1String("%url%"), newPost->link.toDisplayString(), Qt::CaseInsensitive); statusMessage.replace(QLatin1String("%client%"), QLatin1String("Choqok"), Qt::CaseInsensitive); if (!IMStatusSettings::repeat() && !newPost->repeatedFromUsername.isEmpty()) { return; } if (!IMStatusSettings::reply() && !newPost->replyToUserName.isEmpty()) { return; } d->im->updateStatusMessage(IMStatusSettings::imclient(), statusMessage); } } #include "imstatus.moc" diff --git a/plugins/shorteners/goo_gl/goo_gl.cpp b/plugins/shorteners/goo_gl/goo_gl.cpp index f5611dd6..e3f7419a 100644 --- a/plugins/shorteners/goo_gl/goo_gl.cpp +++ b/plugins/shorteners/goo_gl/goo_gl.cpp @@ -1,76 +1,76 @@ /* This file is part of Choqok, the KDE micro-blogging client Copyright (C) 2010-2012 Andrey Esin 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 "goo_gl.h" #include #include #include #include #include #include "notifymanager.h" K_PLUGIN_FACTORY_WITH_JSON(Goo_glFactory, "choqok_goo_gl.json", registerPlugin < Goo_gl > ();) Goo_gl::Goo_gl(QObject *parent, const QVariantList &) : Choqok::Shortener(QLatin1String("choqok_goo_gl"), parent) { } Goo_gl::~Goo_gl() { } QString Goo_gl::shorten(const QString &url) { QVariantMap req; req.insert(QLatin1String("longUrl"), url); const QByteArray json = QJsonDocument::fromVariant(req).toJson(); - KIO::StoredTransferJob *job = KIO::storedHttpPost(json, QUrl(QLatin1String("https://www.googleapis.com/urlshortener/v1/url")), KIO::HideProgressInfo) ; + KIO::StoredTransferJob *job = KIO::storedHttpPost(json, QUrl::fromUserInput(QLatin1String("https://www.googleapis.com/urlshortener/v1/url")), KIO::HideProgressInfo) ; if (!job) { Choqok::NotifyManager::error(i18n("Error when creating job"), i18n("Goo.gl Error")); return url; } job->addMetaData(QLatin1String("content-type"), QLatin1String("Content-Type: application/json")); job->exec(); if (!job->error()) { const QJsonDocument json = QJsonDocument::fromJson(job->data()); if (!json.isNull()) { const QVariantMap map = json.toVariant().toMap(); const QVariantMap error = map[QLatin1String("error")].toMap(); if (!error.isEmpty()) { Choqok::NotifyManager::error(error[QLatin1String("message")].toString(), i18n("Goo.gl Error")); return url; } return map[ QLatin1String("id") ].toString(); } Choqok::NotifyManager::error(i18n("Malformed response"), i18n("Goo.gl Error")); } else { Choqok::NotifyManager::error(i18n("Cannot create a short URL.\n%1", job->errorString()), i18n("Goo.gl Error")); } return url; } #include "goo_gl.moc" diff --git a/plugins/videopreview/videopreview.cpp b/plugins/videopreview/videopreview.cpp index 2d2f9fdf..3a37ddfe 100644 --- a/plugins/videopreview/videopreview.cpp +++ b/plugins/videopreview/videopreview.cpp @@ -1,272 +1,274 @@ /* This file is part of Choqok, the KDE micro-blogging client Based on the imagepreview extension Copyright (C) 2010-2012 Emanuele Bigiarini 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 "videopreview.h" #include #include #include #include #include #include #include #include #include "choqokuiglobal.h" #include "postwidget.h" #include "notifymanager.h" #include "mediamanager.h" #include "textbrowser.h" #include "shortenmanager.h" K_PLUGIN_FACTORY_WITH_JSON(VideoPreviewFactory, "choqok_videopreview.json", registerPlugin < VideoPreview > ();) const QRegExp VideoPreview::mYouTuRegExp(QLatin1String("(https?://youtu.[^\\s<>\"]+[^!,\\.\\s<>'\\\"\\]])")); const QRegExp VideoPreview::mYouTubeRegExp(QLatin1String("(https?://www.youtube.[^\\s<>\"]+[^!,\\.\\s<>'\\\"\\]])")); const QRegExp VideoPreview::mVimeoRegExp(QLatin1String("(https?://(.+)?vimeo.com/(.+)[&]?)")); const QRegExp VideoPreview::mYouTuCode(QLatin1String("youtu.(.+)/(.+)[?&]?")); VideoPreview::VideoPreview(QObject *parent, const QList< QVariant > &) : Choqok::Plugin(QLatin1String("choqok_videopreview"), parent) , state(Stopped) { connect(Choqok::UI::Global::SessionManager::self(), SIGNAL(newPostWidgetAdded(Choqok::UI::PostWidget*,Choqok::Account*,QString)), this, SLOT(slotAddNewPostWidget(Choqok::UI::PostWidget*))); connect(Choqok::ShortenManager::self(), SIGNAL(newUnshortenedUrl(Choqok::UI::PostWidget*,QUrl,QUrl)), this, SLOT(slotNewUnshortenedUrl(Choqok::UI::PostWidget*,QUrl,QUrl))); } VideoPreview::~VideoPreview() { } void VideoPreview::slotAddNewPostWidget(Choqok::UI::PostWidget *newWidget) { postsQueue.enqueue(newWidget); if (state == Stopped) { state = Running; QTimer::singleShot(1000, this, SLOT(startParsing())); } } void VideoPreview::slotNewUnshortenedUrl(Choqok::UI::PostWidget *widget, const QUrl &fromUrl, const QUrl &toUrl) { Q_UNUSED(fromUrl) if (mYouTubeRegExp.indexIn(toUrl.toDisplayString()) != -1) { QUrl thisurl(mYouTubeRegExp.cap(0)); QUrlQuery thisurlQuery(thisurl); - QString thumbUrl = parseYoutube(thisurlQuery.queryItemValue(QLatin1String("v")), widget); - connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - SLOT(slotImageFetched(QString,QPixmap))); + QUrl thumbUrl = parseYoutube(thisurlQuery.queryItemValue(QLatin1String("v")), widget); + connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + SLOT(slotImageFetched(QUrl,QPixmap))); Choqok::MediaManager::self()->fetchImage(thumbUrl, Choqok::MediaManager::Async); } else if (mVimeoRegExp.indexIn(toUrl.toDisplayString()) != -1) { - QString thumbUrl = parseVimeo(mVimeoRegExp.cap(3), widget); - connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), - SLOT(slotImageFetched(QString,QPixmap))); + QUrl thumbUrl = parseVimeo(mVimeoRegExp.cap(3), widget); + connect(Choqok::MediaManager::self(), SIGNAL(imageFetched(QUrl,QPixmap)), + SLOT(slotImageFetched(QUrl,QPixmap))); Choqok::MediaManager::self()->fetchImage(thumbUrl, Choqok::MediaManager::Async); } } void VideoPreview::startParsing() { int i = 8; while (!postsQueue.isEmpty() && i > 0) { parse(postsQueue.dequeue()); --i; } if (postsQueue.isEmpty()) { state = Stopped; } else { QTimer::singleShot(500, this, SLOT(startParsing())); } } void VideoPreview::parse(QPointer postToParse) { if (!postToParse) { return; } int pos = 0; int pos1 = 0; int pos2 = 0; int pos3 = 0; - QStringList thumbList; + QList thumbList; QString content = postToParse->currentPost()->content; while (((pos1 = mYouTuRegExp.indexIn(content, pos)) != -1) | ((pos2 = mYouTubeRegExp.indexIn(content, pos)) != -1) | ((pos3 = mVimeoRegExp.indexIn(content, pos)) != -1)) { if (pos1 >= 0) { pos = pos1 + mYouTuRegExp.matchedLength(); if (mYouTuCode.indexIn(mYouTuRegExp.cap(0)) != -1) { thumbList << parseYoutube(mYouTuCode.cap(2), postToParse); } } else if (pos2 >= 0) { pos = pos2 + mYouTubeRegExp.matchedLength(); QUrl thisurl(mYouTubeRegExp.cap(0)); QUrlQuery thisurlQuery(thisurl); thumbList << parseYoutube(thisurlQuery.queryItemValue(QLatin1String("v")), postToParse); } else if (pos3 >= 0) { pos = pos3 + mVimeoRegExp.matchedLength(); thumbList << parseVimeo(mVimeoRegExp.cap(3), postToParse); } } - for (const QString &thumb_url: thumbList) { + for (const QUrl &thumb_url: thumbList) { connect(Choqok::MediaManager::self(), - SIGNAL(imageFetched(QString,QPixmap)), - SLOT(slotImageFetched(QString,QPixmap))); + SIGNAL(imageFetched(QUrl,QPixmap)), + SLOT(slotImageFetched(QUrl,QPixmap))); Choqok::MediaManager::self()->fetchImage(thumb_url, Choqok::MediaManager::Async); } } -QString VideoPreview::parseYoutube(QString videoid, QPointer< Choqok::UI::PostWidget > postToParse) +QUrl VideoPreview::parseYoutube(QString videoid, QPointer< Choqok::UI::PostWidget > postToParse) { QString youtubeUrl = QStringLiteral("https://gdata.youtube.com/feeds/api/videos/%1").arg(videoid); QUrl th_url(youtubeUrl); KIO::StoredTransferJob *job = KIO::storedGet(th_url, KIO::NoReload, KIO::HideProgressInfo); KJobWidgets::setWindow(job, Choqok::UI::Global::mainWindow()); - QString title, description, thumb_url; + QString title, description; + QUrl thumb_url; job->exec(); if (!job->error()) { QDomDocument document; document.setContent(job->data()); QDomElement root = document.documentElement(); if (!root.isNull()) { QDomElement node; node = root.firstChildElement(QLatin1String("title")); if (!node.isNull()) { title = QString(node.text()); } node = root.firstChildElement(QLatin1String("media:group")); node = node.firstChildElement(QLatin1String("media:description")); if (!node.isNull()) { description = QString(node.text()); } node = node.nextSiblingElement(QLatin1String("media:thumbnail")); if (!node.isNull()) { - thumb_url = QString(node.attributeNode(QLatin1String("url")).value()); + thumb_url = QUrl::fromUserInput(node.attributeNode(QLatin1String("url")).value()); } } description = description.left(70); mParsingList.insert(thumb_url, postToParse); mBaseUrlMap.insert(thumb_url, QLatin1String("https://www.youtube.com/watch?v=") + videoid); mTitleVideoMap.insert(thumb_url, title); mDescriptionVideoMap.insert(thumb_url, description); } else { qCritical() << "Youtube XML response is NULL!"; } return thumb_url; } -QString VideoPreview::parseVimeo(QString videoid, QPointer< Choqok::UI::PostWidget > postToParse) +QUrl VideoPreview::parseVimeo(QString videoid, QPointer< Choqok::UI::PostWidget > postToParse) { QString vimeoUrl = QStringLiteral("https://vimeo.com/api/v2/video/%1.xml").arg(videoid); QUrl th_url(vimeoUrl); QEventLoop loop; KIO::StoredTransferJob *job = KIO::storedGet(th_url, KIO::NoReload, KIO::HideProgressInfo); KJobWidgets::setWindow(job, Choqok::UI::Global::mainWindow()); - QString title, description, thumb_url; + QString title, description; + QUrl thumb_url; job->exec(); if (!job->error()) { QDomDocument document; document.setContent(job->data()); QDomElement root = document.documentElement(); if (!root.isNull()) { QDomElement videotag; videotag = root.firstChildElement(QLatin1String("video")); if (!videotag.isNull()) { QDomElement node; node = videotag.firstChildElement(QLatin1String("title")); if (!node.isNull()) { title = QString(node.text()); } node = videotag.firstChildElement(QLatin1String("description")); if (!node.isNull()) { description = QString(node.text()); } node = videotag.firstChildElement(QLatin1String("thumbnail_small")); if (!node.isNull()) { - thumb_url = QString(node.text()); + thumb_url = QUrl::fromUserInput(node.text()); } } } description = description.left(70); mParsingList.insert(thumb_url, postToParse); mBaseUrlMap.insert(thumb_url, QLatin1String("https://vimeo.com/") + videoid); mTitleVideoMap.insert(thumb_url, title); mDescriptionVideoMap.insert(thumb_url, description); } else { qCritical() << "Vimeo XML response is NULL!"; } return thumb_url; } -void VideoPreview::slotImageFetched(const QString &remoteUrl, const QPixmap &pixmap) +void VideoPreview::slotImageFetched(const QUrl &remoteUrl, const QPixmap &pixmap) { Choqok::UI::PostWidget *postToParse = mParsingList.take(remoteUrl); QString baseUrl = mBaseUrlMap.take(remoteUrl); QString title = mTitleVideoMap.take(remoteUrl); QString description = mDescriptionVideoMap.take(remoteUrl); if (!postToParse) { return; } QString content = postToParse->content(); QUrl imgU(remoteUrl); imgU.setScheme(QLatin1String("img")); postToParse->mainWidget()->document()->addResource(QTextDocument::ImageResource, imgU, pixmap); QString temp(QLatin1String("
")); temp.append(QLatin1String("")); temp.append(QLatin1String("") + description + QLatin1String("
") + title + QLatin1String("
")); content.append(temp); postToParse->setContent(content); } #include "videopreview.moc" diff --git a/plugins/videopreview/videopreview.h b/plugins/videopreview/videopreview.h index 220a7da9..4bb90514 100644 --- a/plugins/videopreview/videopreview.h +++ b/plugins/videopreview/videopreview.h @@ -1,82 +1,82 @@ /* This file is part of Choqok, the KDE micro-blogging client Copyright (C) 2008-2012 Mehrdad Momeny Copyright (C) 2010-2012 Emanuele Bigiarini 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/ */ #ifndef VIDEOPREVIEW_H #define VIDEOPREVIEW_H #include #include #include #include #include #include #include #include #include "plugin.h" namespace Choqok { namespace UI { class PostWidget; } } class VideoPreview : public Choqok::Plugin { Q_OBJECT public: VideoPreview(QObject *parent, const QList< QVariant > &args); ~VideoPreview(); protected Q_SLOTS: void slotAddNewPostWidget(Choqok::UI::PostWidget *newWidget); void startParsing(); - void slotImageFetched(const QString &remoteUrl, const QPixmap &pixmap); + void slotImageFetched(const QUrl &remoteUrl, const QPixmap &pixmap); void slotNewUnshortenedUrl(Choqok::UI::PostWidget *widget, const QUrl &fromUrl, const QUrl &toUrl); private: enum ParserState { Running = 0, Stopped }; ParserState state; void parse(QPointer< Choqok::UI::PostWidget > postToParse); - QString parseYoutube(QString videoid , QPointer< Choqok::UI::PostWidget > postToParse); - QString parseVimeo(QString videoid , QPointer< Choqok::UI::PostWidget > postToParse); + QUrl parseYoutube(QString videoid , QPointer< Choqok::UI::PostWidget > postToParse); + QUrl parseVimeo(QString videoid , QPointer< Choqok::UI::PostWidget > postToParse); QQueue< QPointer > postsQueue; - QMap > mParsingList;//remoteUrl, Post - QMap mBaseUrlMap;//remoteUrl, BaseUrl - QMap mTitleVideoMap;//remoteUrl, TitleVideo - QMap mDescriptionVideoMap;//remoteUrl, DescriptionVideo + QMap > mParsingList;//remoteUrl, Post + QMap mBaseUrlMap;//remoteUrl, BaseUrl + QMap mTitleVideoMap;//remoteUrl, TitleVideo + QMap mDescriptionVideoMap;//remoteUrl, DescriptionVideo static const QRegExp mYouTubeRegExp; static const QRegExp mYouTuRegExp; static const QRegExp mVimeoRegExp; static const QRegExp mYouTuCode; //static const QRegExp mYFrogRegExp; }; #endif //VIDEOPREVIEW_H