File indexing completed on 2024-05-05 04:57:14
0001 /* 0002 This file is part of Choqok, the KDE micro-blogging client 0003 0004 SPDX-FileCopyrightText: 2008-2012 Mehrdad Momeny <mehrdad.momeny@gmail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0007 */ 0008 0009 #include "gnusocialapimicroblog.h" 0010 0011 #include <QJsonArray> 0012 #include <QJsonDocument> 0013 #include <QJsonObject> 0014 #include <QMimeDatabase> 0015 0016 #include <KIO/StoredTransferJob> 0017 #include <KJobWidgets> 0018 #include <KLocalizedString> 0019 #include <KMessageBox> 0020 0021 #include "account.h" 0022 #include "accountmanager.h" 0023 #include "choqokappearancesettings.h" 0024 #include "composerwidget.h" 0025 #include "editaccountwidget.h" 0026 #include "mediamanager.h" 0027 #include "microblogwidget.h" 0028 #include "postwidget.h" 0029 #include "timelinewidget.h" 0030 0031 #include "twitterapimicroblogwidget.h" 0032 #include "twitterapipostwidget.h" 0033 #include "twitterapitimelinewidget.h" 0034 0035 #include "gnusocialapiaccount.h" 0036 #include "gnusocialapicomposerwidget.h" 0037 #include "gnusocialapidebug.h" 0038 #include "gnusocialapidmessagedialog.h" 0039 #include "gnusocialapipostwidget.h" 0040 #include "gnusocialapisearch.h" 0041 0042 GNUSocialApiMicroBlog::GNUSocialApiMicroBlog(const QString &componentName, QObject *parent = nullptr) 0043 : TwitterApiMicroBlog(componentName, parent), friendsPage(1) 0044 { 0045 qCDebug(CHOQOK); 0046 setServiceName(QLatin1String("GNU social")); 0047 mTimelineInfos[QLatin1String("ReTweets")]->name = i18nc("Timeline name", "Repeated"); 0048 mTimelineInfos[QLatin1String("ReTweets")]->description = i18nc("Timeline description", "Your posts that were repeated by others"); 0049 } 0050 0051 GNUSocialApiMicroBlog::~GNUSocialApiMicroBlog() 0052 { 0053 qCDebug(CHOQOK); 0054 } 0055 0056 Choqok::Account *GNUSocialApiMicroBlog::createNewAccount(const QString &alias) 0057 { 0058 GNUSocialApiAccount *acc = qobject_cast<GNUSocialApiAccount *>(Choqok::AccountManager::self()->findAccount(alias)); 0059 if (!acc) { 0060 return new GNUSocialApiAccount(this, alias); 0061 } else { 0062 return nullptr; 0063 } 0064 } 0065 0066 Choqok::UI::MicroBlogWidget *GNUSocialApiMicroBlog::createMicroBlogWidget(Choqok::Account *account, QWidget *parent) 0067 { 0068 return new TwitterApiMicroBlogWidget(account, parent); 0069 } 0070 0071 Choqok::UI::TimelineWidget *GNUSocialApiMicroBlog::createTimelineWidget(Choqok::Account *account, 0072 const QString &timelineName, QWidget *parent) 0073 { 0074 return new TwitterApiTimelineWidget(account, timelineName, parent); 0075 } 0076 0077 Choqok::UI::PostWidget *GNUSocialApiMicroBlog::createPostWidget(Choqok::Account *account, 0078 Choqok::Post *post, QWidget *parent) 0079 { 0080 return new GNUSocialApiPostWidget(account, post, parent); 0081 } 0082 0083 Choqok::UI::ComposerWidget *GNUSocialApiMicroBlog::createComposerWidget(Choqok::Account *account, QWidget *parent) 0084 { 0085 return new GNUSocialApiComposerWidget(account, parent); 0086 } 0087 0088 Choqok::Post *GNUSocialApiMicroBlog::readPost(Choqok::Account *account, const QVariantMap &var, Choqok::Post *post) 0089 { 0090 if (!post) { 0091 qCCritical(CHOQOK) << "post is NULL!"; 0092 return nullptr; 0093 } 0094 0095 if (var[QLatin1String("source")].toString().compare(QLatin1String("linkback")) == 0) { 0096 // Skip linkback statuses 0097 return nullptr; 0098 } 0099 0100 post = TwitterApiMicroBlog::readPost(account, var, post); 0101 0102 QUrl profileUrl = var[QLatin1String("user")].toMap()[QLatin1String("statusnet_profile_url")].toUrl(); 0103 post->author.homePageUrl = profileUrl; 0104 0105 const QVariantMap retweeted = var[QLatin1String("retweeted_status")].toMap(); 0106 0107 if (!retweeted.isEmpty()) { 0108 profileUrl = retweeted[QLatin1String("user")].toMap()[QLatin1String("statusnet_profile_url")].toUrl(); 0109 post->repeatedFromUser.homePageUrl = profileUrl; 0110 } 0111 0112 if (var.contains(QLatin1String("uri"))) { 0113 post->link = var[QLatin1String("uri")].toUrl(); 0114 } else if (var.contains(QLatin1String("external_url"))) { 0115 post->link = var[QLatin1String("external_url")].toUrl(); 0116 } else { 0117 if (retweeted.contains(QLatin1String("uri"))) { 0118 post->link = var[QLatin1String("uri")].toUrl(); 0119 } else { 0120 // Last try, compone the url. However this only works for GNU Social instances. 0121 post->link = QUrl::fromUserInput(QStringLiteral("%1://%2/notice/%3") 0122 .arg(profileUrl.scheme()).arg(profileUrl.host()).arg(post->postId)); 0123 } 0124 } 0125 0126 return post; 0127 } 0128 0129 QUrl GNUSocialApiMicroBlog::profileUrl(Choqok::Account *account, const QString &username) const 0130 { 0131 if (username.contains(QLatin1Char('@'))) { 0132 const QStringList lst = username.split(QLatin1Char('@'), QString::SkipEmptyParts); 0133 0134 if (lst.count() == 2) { 0135 return QUrl::fromUserInput(QStringLiteral("https://%1/%2").arg(lst[1]).arg(lst[0])); 0136 } else { 0137 return QUrl(); 0138 } 0139 } else { 0140 GNUSocialApiAccount *acc = qobject_cast<GNUSocialApiAccount *>(account); 0141 0142 QUrl url(acc->host()); 0143 url = url.adjusted(QUrl::StripTrailingSlash); 0144 url.setPath(QLatin1Char('/') + username); 0145 0146 return url; 0147 } 0148 } 0149 0150 QUrl GNUSocialApiMicroBlog::postUrl(Choqok::Account *account, const QString &username, 0151 const QString &postId) const 0152 { 0153 Q_UNUSED(username) 0154 TwitterApiAccount *acc = qobject_cast<TwitterApiAccount *>(account); 0155 if (acc) { 0156 QUrl url(acc->homepageUrl()); 0157 url.setPath(url.path() + QStringLiteral("/notice/%1").arg(postId)); 0158 return url; 0159 } else { 0160 return QUrl(); 0161 } 0162 } 0163 0164 TwitterApiSearch *GNUSocialApiMicroBlog::searchBackend() 0165 { 0166 if (!mSearchBackend) { 0167 mSearchBackend = new GNUSocialApiSearch(this); 0168 } 0169 return mSearchBackend; 0170 } 0171 0172 void GNUSocialApiMicroBlog::createPostWithAttachment(Choqok::Account *theAccount, Choqok::Post *post, 0173 const QString &mediumToAttach) 0174 { 0175 if (mediumToAttach.isEmpty()) { 0176 TwitterApiMicroBlog::createPost(theAccount, post); 0177 } else { 0178 const QUrl picUrl = QUrl::fromUserInput(mediumToAttach); 0179 KIO::StoredTransferJob *picJob = KIO::storedGet(picUrl, KIO::Reload, KIO::HideProgressInfo); 0180 picJob->exec(); 0181 if (picJob->error()) { 0182 qCCritical(CHOQOK) << "Job error:" << picJob->errorString(); 0183 KMessageBox::detailedError(Choqok::UI::Global::mainWindow(), 0184 i18n("Uploading medium failed: cannot read the medium file."), 0185 picJob->errorString()); 0186 return; 0187 } 0188 const QByteArray picData = picJob->data(); 0189 if (picData.count() == 0) { 0190 qCCritical(CHOQOK) << "Cannot read the media file, please check if it exists."; 0191 KMessageBox::error(Choqok::UI::Global::mainWindow(), 0192 i18n("Uploading medium failed: cannot read the medium file.")); 0193 return; 0194 } 0195 ///Documentation: http://identi.ca/notice/17779990 0196 TwitterApiAccount *account = qobject_cast<TwitterApiAccount *>(theAccount); 0197 QUrl url = account->apiUrl(); 0198 url.setPath(url.path() + QLatin1String("/statuses/update.json")); 0199 const QMimeDatabase db; 0200 QByteArray fileContentType = db.mimeTypeForUrl(picUrl).name().toUtf8(); 0201 0202 QMap<QString, QByteArray> formdata; 0203 formdata[QLatin1String("status")] = post->content.toUtf8(); 0204 formdata[QLatin1String("in_reply_to_status_id")] = post->replyToPostId.toLatin1(); 0205 formdata[QLatin1String("source")] = QCoreApplication::applicationName().toLatin1(); 0206 0207 QMap<QString, QByteArray> mediafile; 0208 mediafile[QLatin1String("name")] = "media"; 0209 mediafile[QLatin1String("filename")] = picUrl.fileName().toUtf8(); 0210 mediafile[QLatin1String("mediumType")] = fileContentType; 0211 mediafile[QLatin1String("medium")] = picData; 0212 QList< QMap<QString, QByteArray> > listMediafiles; 0213 listMediafiles.append(mediafile); 0214 0215 QByteArray data = Choqok::MediaManager::createMultipartFormData(formdata, listMediafiles); 0216 0217 KIO::StoredTransferJob *job = KIO::storedHttpPost(data, url, KIO::HideProgressInfo) ; 0218 if (!job) { 0219 qCCritical(CHOQOK) << "Cannot create a http POST request!"; 0220 return; 0221 } 0222 job->addMetaData(QStringLiteral("content-type"), 0223 QStringLiteral("Content-Type: multipart/form-data; boundary=AaB03x")); 0224 job->addMetaData(QStringLiteral("customHTTPHeader"), 0225 QStringLiteral("Authorization: ") + 0226 QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::PostOperation))); 0227 mCreatePostMap[ job ] = post; 0228 mJobsAccount[job] = theAccount; 0229 connect(job, &KIO::StoredTransferJob::result, this, &GNUSocialApiMicroBlog::slotCreatePost); 0230 job->start(); 0231 } 0232 } 0233 0234 QString GNUSocialApiMicroBlog::generateRepeatedByUserTooltip(const QString &username) 0235 { 0236 if (Choqok::AppearanceSettings::showRetweetsInChoqokWay()) { 0237 return i18n("Repeat of %1", username); 0238 } else { 0239 return i18n("Repeated by %1", username); 0240 } 0241 } 0242 0243 QString GNUSocialApiMicroBlog::repeatQuestion() 0244 { 0245 return i18n("Repeat this notice?"); 0246 } 0247 0248 void GNUSocialApiMicroBlog::listFriendsUsername(TwitterApiAccount *theAccount, bool active) 0249 { 0250 Q_UNUSED(active); 0251 friendsList.clear(); 0252 if (theAccount) { 0253 doRequestFriendsScreenName(theAccount, 1); 0254 } 0255 } 0256 0257 QStringList GNUSocialApiMicroBlog::readFriendsScreenName(Choqok::Account *theAccount, const QByteArray &buffer) 0258 { 0259 QStringList list; 0260 const QJsonDocument json = QJsonDocument::fromJson(buffer); 0261 if (!json.isNull()) { 0262 for (const QJsonValue &u: json.array()) { 0263 const QJsonObject user = u.toObject(); 0264 0265 if (user.contains(QStringLiteral("statusnet_profile_url"))) { 0266 list.append(user.value(QLatin1String("statusnet_profile_url")).toString()); 0267 } 0268 } 0269 } else { 0270 QString err = i18n("Retrieving the friends list failed. The data returned from the server is corrupted."); 0271 qCDebug(CHOQOK) << "JSON parse error:the buffer is: \n" << buffer; 0272 Q_EMIT error(theAccount, ParsingError, err, Critical); 0273 } 0274 return list; 0275 } 0276 0277 void GNUSocialApiMicroBlog::requestFriendsScreenName(TwitterApiAccount *theAccount, bool active) 0278 { 0279 Q_UNUSED(active); 0280 doRequestFriendsScreenName(theAccount, 1); 0281 } 0282 0283 void GNUSocialApiMicroBlog::showDirectMessageDialog(TwitterApiAccount *theAccount, const QString &toUsername) 0284 { 0285 qCDebug(CHOQOK); 0286 if (!theAccount) { 0287 QAction *act = qobject_cast<QAction *>(sender()); 0288 theAccount = qobject_cast<TwitterApiAccount *>(Choqok::AccountManager::self()->findAccount(act->data().toString())); 0289 } 0290 GNUSocialApiDMessageDialog *dmsg = new GNUSocialApiDMessageDialog(theAccount, Choqok::UI::Global::mainWindow()); 0291 if (!toUsername.isEmpty()) { 0292 dmsg->setTo(toUsername); 0293 } 0294 dmsg->show(); 0295 } 0296 0297 void GNUSocialApiMicroBlog::doRequestFriendsScreenName(TwitterApiAccount *theAccount, int page) 0298 { 0299 qCDebug(CHOQOK); 0300 TwitterApiAccount *account = qobject_cast<TwitterApiAccount *>(theAccount); 0301 QUrl url = account->apiUrl(); 0302 url = url.adjusted(QUrl::StripTrailingSlash); 0303 url.setPath(url.path() + QLatin1String("/statuses/friends.json")); 0304 0305 if (page > 1) { 0306 QUrlQuery urlQuery; 0307 urlQuery.addQueryItem(QLatin1String("page"), QString::number(page)); 0308 url.setQuery(urlQuery); 0309 } 0310 0311 KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo) ; 0312 if (!job) { 0313 qCDebug(CHOQOK) << "Cannot create an http GET request!"; 0314 return; 0315 } 0316 job->addMetaData(QStringLiteral("customHTTPHeader"), 0317 QStringLiteral("Authorization: ") + 0318 QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::GetOperation))); 0319 mJobsAccount[job] = theAccount; 0320 connect(job, &KIO::StoredTransferJob::result, this, &GNUSocialApiMicroBlog::slotRequestFriendsScreenName); 0321 job->start(); 0322 } 0323 0324 void GNUSocialApiMicroBlog::slotRequestFriendsScreenName(KJob *job) 0325 { 0326 qCDebug(CHOQOK); 0327 TwitterApiAccount *theAccount = qobject_cast<TwitterApiAccount *>(mJobsAccount.take(job)); 0328 if (job->error()) { 0329 Q_EMIT error(theAccount, ServerError, i18n("Friends list for account %1 could not be updated:\n%2", 0330 theAccount->username(), job->errorString()), Normal); 0331 return; 0332 } 0333 KIO::StoredTransferJob *stJob = qobject_cast<KIO::StoredTransferJob *>(job); 0334 QStringList newList = readFriendsScreenName(theAccount, stJob->data()); 0335 friendsList << newList; 0336 if (newList.count() == 100) { 0337 doRequestFriendsScreenName(theAccount, ++friendsPage); 0338 } else { 0339 friendsList.removeDuplicates(); 0340 theAccount->setFriendsList(friendsList); 0341 Q_EMIT friendsUsernameListed(theAccount, friendsList); 0342 } 0343 } 0344 0345 void GNUSocialApiMicroBlog::fetchConversation(Choqok::Account *theAccount, const QString &conversationId) 0346 { 0347 qCDebug(CHOQOK); 0348 if (conversationId.isEmpty()) { 0349 return; 0350 } 0351 TwitterApiAccount *account = qobject_cast<TwitterApiAccount *>(theAccount); 0352 QUrl url = account->apiUrl(); 0353 url.setPath(QStringLiteral("/statusnet/conversation/%1.json").arg(conversationId)); 0354 0355 KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo) ; 0356 if (!job) { 0357 qCDebug(CHOQOK) << "Cannot create an http GET request!"; 0358 return; 0359 } 0360 job->addMetaData(QStringLiteral("customHTTPHeader"), 0361 QStringLiteral("Authorization: ") + 0362 QLatin1String(authorizationHeader(account, url, QNetworkAccessManager::GetOperation))); 0363 mFetchConversationMap[ job ] = conversationId; 0364 mJobsAccount[ job ] = theAccount; 0365 connect(job, &KIO::StoredTransferJob::result, this, &GNUSocialApiMicroBlog::slotFetchConversation); 0366 job->start(); 0367 } 0368 0369 QString GNUSocialApiMicroBlog::usernameFromProfileUrl(const QString &profileUrl) 0370 { 0371 // Remove the initial slash from path 0372 return QUrl(profileUrl).path().remove(0, 1); 0373 } 0374 0375 QString GNUSocialApiMicroBlog::hostFromProfileUrl(const QString &profileUrl) 0376 { 0377 return QUrl(profileUrl).host(); 0378 } 0379 0380 void GNUSocialApiMicroBlog::slotFetchConversation(KJob *job) 0381 { 0382 qCDebug(CHOQOK); 0383 if (!job) { 0384 qCWarning(CHOQOK) << "NULL Job returned"; 0385 return; 0386 } 0387 QList<Choqok::Post *> posts; 0388 QString conversationId = mFetchConversationMap.take(job); 0389 Choqok::Account *theAccount = mJobsAccount.take(job); 0390 if (job->error()) { 0391 qCDebug(CHOQOK) << "Job Error:" << job->errorString(); 0392 Q_EMIT error(theAccount, Choqok::MicroBlog::CommunicationError, 0393 i18n("Fetching conversation failed. %1", job->errorString()), Normal); 0394 } else { 0395 KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob *> (job); 0396 //if(format=="json"){ 0397 posts = readTimeline(theAccount, stj->data()); 0398 //} else { 0399 // posts = readTimelineFromXml ( theAccount, stj->data() ); 0400 //} 0401 if (!posts.isEmpty()) { 0402 Q_EMIT conversationFetched(theAccount, conversationId, posts); 0403 } 0404 } 0405 } 0406 0407 #include "moc_gnusocialapimicroblog.cpp"