diff --git a/helperlibs/gnusocialapihelper/gnusocialapimicroblog.cpp b/helperlibs/gnusocialapihelper/gnusocialapimicroblog.cpp index d4c1e7d2..b280708e 100644 --- a/helperlibs/gnusocialapihelper/gnusocialapimicroblog.cpp +++ b/helperlibs/gnusocialapihelper/gnusocialapimicroblog.cpp @@ -1,444 +1,434 @@ /* 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; } post = TwitterApiMicroBlog::readPost(account, var, post); + post->author.homePageUrl = var[QLatin1String("user")].toMap()[QLatin1String("statusnet_profile_url")].toString(); + if (var.contains(QLatin1String("external_url"))) { post->link = var[QLatin1String("external_url")].toString(); } else { QVariantMap userMap; if (var[QLatin1String("repeated")].toBool()) { userMap = var[QLatin1String("retweeted_status")].toMap()[QLatin1String("user")].toMap(); } else { userMap = var[QLatin1String("user")].toMap(); } 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); } return post; } -QString GNUSocialApiMicroBlog::profileUrl(Choqok::Account *account, const QString &username) const +QUrl GNUSocialApiMicroBlog::profileUrl(Choqok::Account *account, const Choqok::User &user) const { - if (username.contains(QLatin1Char('@'))) { - const QStringList lst = username.split(QLatin1Char('@'), QString::SkipEmptyParts); - - if (lst.count() == 2) { - return QStringLiteral("https://%1/%2").arg(lst[1]).arg(lst[0]); - } - } - - TwitterApiAccount *acc = qobject_cast(account); - if (acc) { - return QString(acc->homepageUrl().toDisplayString() + QLatin1Char('/') + username) ; - } else { - return QString(); - } + Q_UNUSED(account) + return QUrl(user.homePageUrl); } QString 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(); } else { return QString(); } } 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, QOAuth::POST))); 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)); QOAuth::ParamMap params; if (page > 1) { params.insert("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, QOAuth::GET, 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, QOAuth::GET))); 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 d9cb6824..6cc0cc98 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 QString profileUrl(Choqok::Account *account, const QString &username) const override; + virtual QUrl profileUrl(Choqok::Account *account, const Choqok::User &user) const override; virtual QString 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/gnusocialapipostwidget.cpp b/helperlibs/gnusocialapihelper/gnusocialapipostwidget.cpp index 1a826692..6111ef28 100644 --- a/helperlibs/gnusocialapihelper/gnusocialapipostwidget.cpp +++ b/helperlibs/gnusocialapihelper/gnusocialapipostwidget.cpp @@ -1,314 +1,309 @@ /* 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 "gnusocialapipostwidget.h" #include #include #include #include #include "choqokbehaviorsettings.h" #include "choqoktools.h" #include "notifymanager.h" #include "quickpost.h" #include "twitterapiaccount.h" #include "twitterapimicroblog.h" #include "twitterapiwhoiswidget.h" #include "gnusocialapiaccount.h" #include "gnusocialapiconversationtimelinewidget.h" #include "gnusocialapidebug.h" #include "gnusocialapimicroblog.h" #include "gnusocialapisearch.h" const QRegExp GNUSocialApiPostWidget::mGroupRegExp(QLatin1String("([\\s]|^)!([a-z0-9]+){1,64}"), Qt::CaseInsensitive); const QRegExp GNUSocialApiPostWidget::mGNUSocialApiUserRegExp(QLatin1String("([\\s\\W]|^)@([a-z0-9_]+){1,64}(?!(@))"), Qt::CaseInsensitive); const QRegExp GNUSocialApiPostWidget::mGNUSocialApiHashRegExp(QLatin1String("([\\s]|^)#([\\w_\\.\\-]+)"), Qt::CaseInsensitive); const QString subdomains = QLatin1String("(([a-z0-9-_]\\.)?)"); const QString dname = QLatin1String("(([a-z0-9-\\x0080-\\xFFFF]){1,63}\\.)+"); const QString zone = QLatin1String("((a[cdefgilmnoqrstuwxz])|(b[abdefghijlmnorstvwyz])|(c[acdfghiklmnoruvxyz])|(d[ejkmoz])|(e[ceghrstu])|\ (f[ijkmor])|(g[abdefghilmnpqrstuwy])|(h[kmnrtu])|(i[delmnoqrst])|(j[emop])|(k[eghimnprwyz])|(l[abcikrstuvy])|\ (m[acdefghklmnopqrstuvwxyz])|(n[acefgilopruz])|(om)|(p[aefghklnrstwy])|(qa)|(r[eosuw])|(s[abcdeghijklmnortuvyz])|\ (t[cdfghjkmnoprtvwz])|(u[agksyz])|(v[aceginu])|(w[fs])|(ye)|(z[amrw])\ |(asia|com|info|net|org|biz|name|pro|aero|cat|coop|edu|jobs|mobi|museum|tel|travel|gov|int|mil|local)|(中国)|(公司)|(网络)|(صر)|(امارات)|(рф))"); const QString domain = QLatin1Char('(') + subdomains + dname + zone + QLatin1Char(')'); const QRegExp GNUSocialApiPostWidget::mStatusNetUserRegExp(QLatin1String("([\\s\\W]|^)@(([a-z0-9]+){1,64}@") + domain + QLatin1Char(')'), Qt::CaseInsensitive); class GNUSocialApiPostWidget::Private { public: Private(Choqok::Account *theAccount) { account = qobject_cast(theAccount); mBlog = qobject_cast(account->microblog()); } GNUSocialApiAccount *account; GNUSocialApiMicroBlog *mBlog; QString tmpUsername; }; GNUSocialApiPostWidget::GNUSocialApiPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent) : TwitterApiPostWidget(account, post, parent), d(new Private(account)) { } void GNUSocialApiPostWidget::initUi() { TwitterApiPostWidget::initUi(); 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("Repeat"), menu); repeat->setToolTip(i18n("Repeat post using API")); connect(repeat, SIGNAL(triggered(bool)), SLOT(repeatPost())); menu->addAction(repeat); menu->addAction(resend); btn->setMenu(menu); } } GNUSocialApiPostWidget::~GNUSocialApiPostWidget() { delete d; } void GNUSocialApiPostWidget::slotReplyToAll() { QStringList nicks; nicks.append(currentPost()->author.userName); QString txt = QStringLiteral("@%1 ").arg(currentPost()->author.userName); int pos = 0; while ((pos = mGNUSocialApiUserRegExp.indexIn(currentPost()->content, pos)) != -1) { if (mGNUSocialApiUserRegExp.cap(2).toLower() != currentAccount()->username() && mGNUSocialApiUserRegExp.cap(2).toLower() != currentPost()->author.userName && !nicks.contains(mGNUSocialApiUserRegExp.cap(2).toLower())) { nicks.append(mGNUSocialApiUserRegExp.cap(2)); txt += QStringLiteral("@%1 ").arg(mGNUSocialApiUserRegExp.cap(2)); } pos += mGNUSocialApiUserRegExp.matchedLength(); } txt.chop(1); Q_EMIT reply(txt, currentPost()->postId, currentPost()->author.userName); } QString GNUSocialApiPostWidget::prepareStatus(const QString &text) { QString res = TwitterApiPostWidget::prepareStatus(text); res.replace(mStatusNetUserRegExp, QLatin1String("\\1@\\2")); res.replace(mGNUSocialApiUserRegExp, QLatin1String("\\1@\\2")); res.replace(mGroupRegExp, QLatin1String("\\1!\\2")); res.replace(mGNUSocialApiHashRegExp, QLatin1String("\\1#\\2")); return res; } QString GNUSocialApiPostWidget::generateSign() { return TwitterApiPostWidget::generateSign(); } void GNUSocialApiPostWidget::checkAnchor(const QUrl &url) { QString scheme = url.scheme(); QAction *ret; if (scheme == QLatin1String("tag")) { QString unpcode = QUrl::fromAce(url.host().toUtf8()); unpcode.remove(QLatin1Char('.')); unpcode.remove(QLatin1Char('-')); unpcode.remove(QLatin1Char('_')); QMenu menu; QAction *search = new QAction(QIcon::fromTheme(QLatin1String("system-search")), i18n("Search for %1", unpcode), &menu); QAction *openInBrowser = new QAction(QIcon::fromTheme(QLatin1String("applications-internet")), i18n("Open tag page in browser"), &menu); menu.addAction(search); menu.addAction(openInBrowser); menu.setDefaultAction(search); ret = menu.exec(QCursor::pos()); if (ret == search) { d->mBlog->searchBackend()->requestSearchResults(currentAccount(), unpcode, GNUSocialApiSearch::ReferenceHashtag); } else if (ret == openInBrowser) { Choqok::openUrl(QUrl(d->account->homepageUrl().toDisplayString() + QLatin1String("/tag/") + unpcode)); } } else if (scheme == QLatin1String("group")) { QMenu menu; QAction *search = new QAction(QIcon::fromTheme(QLatin1String("system-search")), i18n("Show latest group posts"), &menu); QAction *openInBrowser = new QAction(QIcon::fromTheme(QLatin1String("applications-internet")), i18n("Open group page in browser"), &menu); menu.addAction(search); menu.addAction(openInBrowser); menu.setDefaultAction(search); ret = menu.exec(QCursor::pos()); if (ret == search) { d->mBlog->searchBackend()->requestSearchResults(currentAccount(), url.host(), GNUSocialApiSearch::ReferenceGroup); } else if (ret == openInBrowser) { Choqok::openUrl(QUrl(d->account->homepageUrl().toDisplayString() + QLatin1String("/group/") + url.host())); } } else if (scheme == QLatin1String("user")) { QString username = (url.userName().isEmpty() ? QString() : QStringLiteral("%1@").arg(url.userName())) + url.host(); QMenu menu; QAction *info = new QAction(QIcon::fromTheme(QLatin1String("user-identity")), i18nc("Who is user", "Who is %1", username), &menu); QAction *from = new QAction(QIcon::fromTheme(QLatin1String("edit-find-user")), i18nc("Posts from user", "Posts from %1", username), &menu); QAction *to = new QAction(QIcon::fromTheme(QLatin1String("meeting-attending")), i18nc("Replies to user", "Replies to %1", username), &menu); QAction *openInBrowser = new QAction(QIcon::fromTheme(QLatin1String("applications-internet")), i18nc("Open profile page in browser", "Open profile in browser"), &menu); menu.addAction(info); if (currentPost()->source != QLatin1String("ostatus")) { menu.addAction(from); menu.addAction(to); from->setData(GNUSocialApiSearch::FromUser); to->setData(GNUSocialApiSearch::ToUser); } menu.addAction(openInBrowser); //Subscribe/UnSubscribe/Block bool isSubscribe = false; QString accountUsername = d->account->username().toLower(); QString postUsername = username.toLower(); QAction *subscribe = 0, *block = 0, *replyTo = 0, *dMessage = 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", username), actionsMenu); actionsMenu->addAction(replyTo); if (d->account->friendsList().contains(username)) { dMessage = new QAction(QIcon::fromTheme(QLatin1String("mail-message-new")), i18nc("Send direct message to user", "Send private message to %1", username), actionsMenu); actionsMenu->addAction(dMessage); isSubscribe = false;//It's UnSubscribe subscribe = new QAction(QIcon::fromTheme(QLatin1String("list-remove-user")), i18nc("Unsubscribe from user", "Unsubscribe from %1", username), actionsMenu); } else { isSubscribe = true; subscribe = new QAction(QIcon::fromTheme(QLatin1String("list-add-user")), i18nc("Subscribe to user", "Subscribe to %1", username), actionsMenu); } block = new QAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18nc("Block user", "Block %1", username), actionsMenu); if (currentPost()->source != QLatin1String("ostatus")) { actionsMenu->addAction(subscribe); actionsMenu->addAction(block); } } ret = menu.exec(QCursor::pos()); if (ret == 0) { return; } if (ret == info) { TwitterApiWhoisWidget *wd = new TwitterApiWhoisWidget(d->account, username, *currentPost(), this); wd->show(QCursor::pos()); return; } else if (ret == subscribe) { if (isSubscribe) { d->mBlog->createFriendship(d->account, username); } else { d->mBlog->destroyFriendship(d->account, username); } return; } else if (ret == block) { d->mBlog->blockUser(d->account, username); return; } else if (ret == openInBrowser) { - qCDebug(CHOQOK) << url << username; - if (username == currentPost()->author.userName && !currentPost()->author.homePageUrl.isEmpty()) { - Choqok::openUrl(QUrl(currentPost()->author.homePageUrl)); - } else { - Choqok::openUrl(QUrl(currentAccount()->microblog()->profileUrl(currentAccount(), username))); - } + Choqok::openUrl(QUrl(currentAccount()->microblog()->profileUrl(currentAccount(), currentPost()->author))); return; } else if (ret == replyTo) { Q_EMIT reply(QStringLiteral("@%1").arg(username), QString(), username); return; } else if (ret == dMessage) { d->mBlog->showDirectMessageDialog(d->account, username); return; } int type = ret->data().toInt(); d->mBlog->searchBackend()->requestSearchResults(currentAccount(), url.host(), type); } else if (scheme == QLatin1String("conversation")) { GNUSocialApiConversationTimelineWidget *tm = new GNUSocialApiConversationTimelineWidget(currentAccount(), url.host()); connect(tm, SIGNAL(forwardReply(QString,QString,QString)), this, SIGNAL(reply(QString,QString,QString))); connect(tm, SIGNAL(forwardResendPost(QString)), this, SIGNAL(resendPost(QString))); tm->show(); } else { TwitterApiPostWidget::checkAnchor(url); } } void GNUSocialApiPostWidget::slotResendPost() { QString text = generateResendText(); if (d->account->isChangeExclamationMark()) { int index = 0; while (true) { index = mGroupRegExp.indexIn(text, index); if (index != -1) { text.replace(index + 1, 1, d->account->changeExclamationMarkToText()); } else { break; } } } if ((Choqok::BehaviorSettings::resendWithQuickPost() || currentAccount()->isReadOnly()) && Choqok::UI::Global::quickPostWidget()) { Choqok::UI::Global::quickPostWidget()->setText(text); } else { Q_EMIT resendPost(text); } } diff --git a/helperlibs/twitterapihelper/twitterapimicroblog.cpp b/helperlibs/twitterapihelper/twitterapimicroblog.cpp index bd37a6e8..b25eca50 100644 --- a/helperlibs/twitterapihelper/twitterapimicroblog.cpp +++ b/helperlibs/twitterapihelper/twitterapimicroblog.cpp @@ -1,1605 +1,1607 @@ /* 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 #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.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->quotedPost.postId = grp.readEntry("quotedPostId", QString()); st->quotedPost.profileImageUrl = grp.readEntry("quotedProfileUrl", QString()); 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; QOAuth::ParamMap 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("status", QUrl::toPercentEncoding(post->content)); if (!post->replyToPostId.isEmpty()) { params.insert("in_reply_to_status_id", post->replyToPostId.toLocal8Bit()); } data = "status="; data += QUrl::toPercentEncoding(post->content); if (!post->replyToPostId.isEmpty()) { data += "&in_reply_to_status_id="; data += post->replyToPostId.toLocal8Bit(); } 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, QOAuth::POST, 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("user", recipientScreenName.toLocal8Bit()); params.insert("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, QOAuth::POST, 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, QOAuth::POST))); 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, QOAuth::GET))); 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, QOAuth::POST))); 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); QOAuth::ParamMap params; params.insert("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, QOAuth::POST, 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); QOAuth::ParamMap params; params.insert("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, QOAuth::POST, 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); QOAuth::ParamMap params; params.insert("cursor", d->friendsCursor.toLatin1()); params.insert("count", QStringLiteral("200").toLatin1()); 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, QOAuth::GET, 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); QOAuth::ParamMap params; params.insert("cursor", d->followersCursor.toLatin1()); params.insert("count", QStringLiteral("200").toLatin1()); 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, QOAuth::GET, 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; QOAuth::ParamMap 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("slug", slug.toLatin1()); const QString owner = type.mid(1, type.indexOf(QLatin1String("/")) - 1); urlQuery.addQueryItem(QLatin1String("owner_screen_name"), owner); params.insert("owner_screen_name", owner.toLatin1()); } else { int countOfPost = Choqok::BehaviorSettings::countOfPosts(); if (!latestStatusId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("since_id"), latestStatusId); params.insert("since_id", latestStatusId.toLatin1()); countOfPost = 200; } urlQuery.addQueryItem(QLatin1String("count"), QString::number(countOfPost)); params.insert("count", QByteArray::number(countOfPost)); if (!maxId.isEmpty()) { urlQuery.addQueryItem(QLatin1String("max_id"), maxId); params.insert("max_id", maxId.toLatin1()); } if (page) { urlQuery.addQueryItem(QLatin1String("page"), QString::number(page)); params.insert("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, QOAuth::GET, 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, QOAuth::HttpMethod method, QOAuth::ParamMap params) { QByteArray auth; if (theAccount->usingOAuth()) { auth = theAccount->oauthInterface()->createParametersString(requestUrl.url(), method, theAccount->oauthToken(), theAccount->oauthTokenSecret(), QOAuth::HMAC_SHA1, params, QOAuth::ParseForHeaderArguments); } 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); QOAuth::ParamMap params; params.insert("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, QOAuth::POST, 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); QOAuth::ParamMap params; params.insert("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, QOAuth::POST, 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); QOAuth::ParamMap params; params.insert("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, QOAuth::POST, 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); QOAuth::ParamMap params; params.insert("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, QOAuth::POST, 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()) { postList.prepend(readPost(theAccount, list.toMap(), new Choqok::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(); 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"); QVariantMap sizes = mediaMap[QLatin1String("sizes")].toMap(); QVariantMap w = sizes[QLatin1String("small")].toMap(); } else { post->media = QString(); } 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; 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(); senderName = sender[QLatin1String("name")].toString(); senderDescription = sender[QLatin1String("description")].toString(); QVariantMap recipient = var[QLatin1String("recipient")].toMap(); recipientProfileImageUrl = recipient[QLatin1String("profile_image_url")].toString(); 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.isProtected = map[QLatin1String("protected")].toBool(); u.location = map[QLatin1String("location")].toString(); u.profileImageUrl = map[QLatin1String("profile_image_url")].toString(); 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/libchoqok/microblog.cpp b/libchoqok/microblog.cpp index 9e3d0b91..20a0270f 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 { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; return QString(); } -QString MicroBlog::profileUrl(Account *, const QString &) const +QUrl MicroBlog::profileUrl(Account *, const Choqok::User &) const { qCWarning(CHOQOK) << "MicroBlog Plugin should implement this!"; - return QString(); + return QUrl(); } } diff --git a/libchoqok/microblog.h b/libchoqok/microblog.h index 18157579..c8ecdcf6 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 QString profileUrl(Choqok::Account *account, const QString &username) const; + virtual QUrl profileUrl(Choqok::Account *account, const Choqok::User &user) const; /** Provide a Web link for post with id @p postId */ virtual QString 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 65fb4c5b..705b1d51 100644 --- a/libchoqok/ui/postwidget.cpp +++ b/libchoqok/ui/postwidget.cpp @@ -1,730 +1,730 @@ /* 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; 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 = QString(QLatin1String("%1 - ")).arg(getUsernameHyperlink(d->mCurrentPost->author.userName, d->mCurrentPost->author.description)); + QString ss = QString(QLatin1String("%1 - ")).arg(getUsernameHyperlink(d->mCurrentPost->author)); if (d->mCurrentPost->repeatedDateTime.isNull()) { ss += QLatin1String("mCurrentPost->link + QLatin1String("\" title=\"") + d->mCurrentPost->creationDateTime.toString(Qt::DefaultLocaleLongDate) + QLatin1String("\">%1"); } else { ss += QLatin1String("mCurrentPost->link + QLatin1String("\" title=\"") + d->mCurrentPost->repeatedDateTime.toString(Qt::DefaultLocaleLongDate) + QLatin1String("\">%1"); } if (!d->mCurrentPost->source.isEmpty()) { ss += QLatin1String(" - ") + d->mCurrentPost->source; } return ss; } -QString PostWidget::getUsernameHyperlink(const QString& username, const QString& userDesc) +QString PostWidget::getUsernameHyperlink(const Choqok::User &user) const { - return QLatin1String("mCurrentAccount->microblog()->profileUrl(d->mCurrentAccount, user).toDisplayString() + QLatin1String("' title=\"") + - (userDesc.isEmpty() ? username : userDesc) + - QLatin1String("\">") + username + QLatin1String(""); + (user.description.isEmpty() ? user.userName : user.description) + + QLatin1String("\">") + user.userName + QLatin1String(""); } 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("mCurrentPost->repeatedDateTime.isNull()) { time = d->mCurrentPost->creationDateTime; } else { time = d->mCurrentPost->repeatedDateTime; } _mainWidget->setHtml(baseTextTemplate.arg( d->mProfileImage, /*1*/ d->mContent, /*2*/ d->mSign.arg(formatDateTime(time)), /*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))); } } void PostWidget::slotImageFetched(const QString &remoteUrl, const QPixmap &pixmap) { if (remoteUrl == d->imageUrl) { disconnect(MediaManager::self(), SIGNAL(imageFetched(QString,QPixmap)), this, SLOT(slotImageFetched(QString,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(fetchError(QString,QString)), this, SLOT(avatarFetchError(QString,QString))); } } void PostWidget::avatarFetched(const QString &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(fetchError(QString,QString)), this, SLOT(avatarFetchError(QString,QString))); } } void PostWidget::avatarFetchError(const QString &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 5f1117fb..ef220b1b 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 slotImageFetched(const QString &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 QString& username, const QString& userDesc); - + 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 1323cc35..138aa421 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.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->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->isError = !act.isValid(); pst->author.userId = act.associatedPerson().id(); pst->author.userName = act.associatedPerson().id(); pst->author.homePageUrl = 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.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; }; } } -QString OCSMicroblog::profileUrl(Choqok::Account *account, const QString &username) const +QUrl OCSMicroblog::profileUrl(Choqok::Account *account, const Choqok::User &user) const { OCSAccount *acc = qobject_cast(account); if (acc->providerUrl().host().contains(QLatin1String("opendesktop.org"))) { - return QStringLiteral("https://opendesktop.org/usermanager/search.php?username=%1").arg(username); + return QUrl::fromUserInput(QStringLiteral("https://opendesktop.org/usermanager/search.php?username=%1").arg(user.userName)); } - return QString(); + return QUrl(); } void OCSMicroblog::aboutToUnload() { Q_EMIT saveTimelines(); } #include "ocsmicroblog.moc" diff --git a/microblogs/ocs/ocsmicroblog.h b/microblogs/ocs/ocsmicroblog.h index 9a542fee..aebb24e6 100644 --- a/microblogs/ocs/ocsmicroblog.h +++ b/microblogs/ocs/ocsmicroblog.h @@ -1,80 +1,80 @@ /* 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/ */ #ifndef OCSMICROBLOG_H #define OCSMICROBLOG_H #include "microblog.h" #include class OCSAccount; namespace Attica { class ProviderManager; class BaseJob; } class OCSMicroblog : public Choqok::MicroBlog { Q_OBJECT public: OCSMicroblog(QObject *parent, const QVariantList &args); ~OCSMicroblog(); virtual ChoqokEditAccountWidget *createEditAccountWidget(Choqok::Account *account, QWidget *parent) override; virtual void createPost(Choqok::Account *theAccount, Choqok::Post *post) override; virtual void abortCreatePost(Choqok::Account *theAccount, Choqok::Post *post = 0) override; virtual void fetchPost(Choqok::Account *theAccount, Choqok::Post *post) override; virtual void removePost(Choqok::Account *theAccount, Choqok::Post *post) override; virtual void saveTimeline(Choqok::Account *account, const QString &timelineName, const QList< Choqok::UI::PostWidget * > &timeline) override; virtual QList< Choqok::Post * > loadTimeline(Choqok::Account *account, const QString &timelineName) override; virtual Choqok::Account *createNewAccount(const QString &alias) override; virtual void updateTimelines(Choqok::Account *theAccount) override; virtual Choqok::TimelineInfo *timelineInfo(const QString &timelineName) override; - virtual QString profileUrl(Choqok::Account *account, const QString &username) const override; + virtual QUrl profileUrl(Choqok::Account *account, const Choqok::User &user) const override; Attica::ProviderManager *providerManager(); bool isOperational(); virtual void aboutToUnload() override; Q_SIGNALS: void initialized(); protected Q_SLOTS: void slotTimelineLoaded(Attica::BaseJob *); void slotCreatePost(Attica::BaseJob *); void slotDefaultProvidersLoaded(); private: enum Task {Update}; QList parseActivityList(const Attica::Activity::List &list); Attica::ProviderManager *mProviderManager; QMap mJobsAccount; QMap mJobsPost; QMultiMap scheduledTasks; bool mIsOperational; }; #endif // OCSMICROBLOG_H diff --git a/microblogs/pumpio/pumpiomicroblog.cpp b/microblogs/pumpio/pumpiomicroblog.cpp index b077fdf2..06b98caa 100644 --- a/microblogs/pumpio/pumpiomicroblog.cpp +++ b/microblogs/pumpio/pumpiomicroblog.cpp @@ -1,1365 +1,1361 @@ /* 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() + QLatin1Char('/') + (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, QOAuth::POST)); 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() + QLatin1Char('/') + (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, QOAuth::POST)); 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, QOAuth::POST)); 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())) { 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, QOAuth::GET)); 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() + QLatin1Char('/') + (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, QOAuth::POST)); 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->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->type = grp.readEntry("type", QString()); st->media = grp.readEntry("media"), QString(); 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, const QString &postId) const { Q_UNUSED(account); return QString(postId).replace(QLatin1String("/api/"), QLatin1Char('/') + username + QLatin1Char('/')); } -QString PumpIOMicroBlog::profileUrl(Choqok::Account *account, const QString &username) const +QUrl PumpIOMicroBlog::profileUrl(Choqok::Account *account, const Choqok::User &user) const { Q_UNUSED(account) - if (username.contains(QLatin1String("acct:"))) { - return QStringLiteral("https://%1/%2").arg(hostFromAcct(username)).arg(userNameFromAcct(username)); - } else { - return username; - } + return QUrl(user.homePageUrl); } 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() + QLatin1Char('/') + (m_timelinesPaths[timeline].arg(acc->username()))); QOAuth::ParamMap oAuthParams; const QString lastActivityId(lastTimelineId(theAccount, timeline)); if (!lastActivityId.isEmpty()) { oAuthParams.insert("count", QByteArray::number(200)); oAuthParams.insert("since", QUrl::toPercentEncoding(lastActivityId)); } else { oAuthParams.insert("count", QByteArray::number(Choqok::BehaviorSettings::countOfPosts())); } 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, QOAuth::GET, 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())); QOAuth::ParamMap oAuthParams; oAuthParams.insert("count", QByteArray::number(200)); if (!acc->following().isEmpty()) { oAuthParams.insert("since", QUrl::toPercentEncoding(acc->following().last())); } 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, QOAuth::GET, 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())); QOAuth::ParamMap oAuthParams; oAuthParams.insert("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(QLatin1String("customHTTPHeader"), authorizationMetaData(acc, url, QOAuth::GET, 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() + QLatin1Char('/') + (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, QOAuth::POST)); 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() + QLatin1Char('/') + (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, QOAuth::POST)); 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, const QOAuth::HttpMethod &method, const QOAuth::ParamMap ¶mMap) const { const QByteArray authorization = account->oAuth()->createParametersString(url.url(), method, account->token().toLocal8Bit(), account->tokenSecret().toLocal8Bit(), QOAuth::HMAC_SHA1, paramMap, QOAuth::ParseForHeaderArguments); 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, QOAuth::GET)); 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->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(); } else { p->link = object[QLatin1String("pump_io")].toMap().value(QLatin1String("proxyURL")).toString(); } 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(); 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(); if (!profileImageUrl.isEmpty()) { p->author.profileImageUrl = profileImageUrl; } else if (actor[QLatin1String("objectType")].toString() == QLatin1String("service")) { p->author.profileImageUrl = homePageUrl + QLatin1String("images/default.png"); } else { p->author.profileImageUrl = 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() + QLatin1Char('/') + (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, QOAuth::POST)); 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 fc10063b..5c56217a 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 "QtOAuth/qoauth_namespace.h" #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, const QString &postId) const override; - virtual QString profileUrl(Choqok::Account *account, const QString &username) const override; + virtual QUrl profileUrl(Choqok::Account *account, const Choqok::User &user) 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, const QOAuth::HttpMethod &method, const QOAuth::ParamMap &map = QOAuth::ParamMap()) 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 a985c21c..2ecb9379 100644 --- a/microblogs/pumpio/pumpiopostwidget.cpp +++ b/microblogs/pumpio/pumpiopostwidget.cpp @@ -1,269 +1,269 @@ /* 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) { if (post->author.userName != account->username()) { - ss += QLatin1String("profileUrl(account, post->author.homePageUrl) + ss += QLatin1String("profileUrl(account, post->author).toDisplayString() + QLatin1String("\" title=\"") + post->author.realName + QLatin1String("\">") + post->author.userName + QLatin1String(" - "); } ss += QLatin1String("postUrl(account, post->author.userName, post->postId) + QLatin1String("\" title=\"") + post->creationDateTime.toString(Qt::DefaultLocaleLongDate) + QLatin1String("\">%1"); 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, id) + ss += QLatin1String("profileUrl(account, post->author).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, id) + ss += QLatin1String("profileUrl(account, post->author).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, id) + ss += QLatin1String("profileUrl(account, post->author).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; } 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 093c7e7e..0ec75530 100644 --- a/microblogs/twitter/twittermicroblog.cpp +++ b/microblogs/twitter/twittermicroblog.cpp @@ -1,442 +1,443 @@ /* 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 "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); } -QString TwitterMicroBlog::profileUrl(Choqok::Account *, const QString &username) const +QUrl TwitterMicroBlog::profileUrl(Choqok::Account *account, const Choqok::User &user) const { - return QStringLiteral("https://twitter.com/#!/%1").arg(username); + Q_UNUSED(account) + return QUrl::fromUserInput(QStringLiteral("https://twitter.com/%1").arg(user.userName)); } QString TwitterMicroBlog::postUrl(Choqok::Account *, const QString &username, const QString &postId) const { return 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, QOAuth::POST))); mCreatePostMap[ job ] = post; mJobsAccount[job] = theAccount; connect(job, SIGNAL(result(KJob*)), SLOT(slotCreatePost(KJob*))); job->start(); } } 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); QOAuth::ParamMap params; params.insert("screen_name", username.toLatin1()); 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, QOAuth::GET, 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(); //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; } #include "twittermicroblog.moc" diff --git a/microblogs/twitter/twittermicroblog.h b/microblogs/twitter/twittermicroblog.h index 986c993d..ba3bf6b2 100644 --- a/microblogs/twitter/twittermicroblog.h +++ b/microblogs/twitter/twittermicroblog.h @@ -1,99 +1,99 @@ /* 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 QString profileUrl(Choqok::Account *account, const QString &username) const override; + virtual QUrl profileUrl(Choqok::Account *account, const Choqok::User &user) const 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()); 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); protected: 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 0bef3622..a0e3af18 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(QUrl(currentAccount()->microblog()->profileUrl(currentAccount(), url.host()))); + Choqok::openUrl(QUrl(currentAccount()->microblog()->profileUrl(currentAccount(), currentPost()->author))); 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(fetchError(QString,QString)), this, SLOT(quotedAvatarFetchError(QString,QString))); return false; } } void TwitterPostWidget::quotedAvatarFetched(const QString &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(fetchError(QString,QString)), this, SLOT(quotedAvatarFetchError(QString,QString))); } } void TwitterPostWidget::quotedAvatarFetchError(const QString &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"); }