File indexing completed on 2024-05-05 04:57:28

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 "twittersearch.h"
0010 
0011 #include <QJsonDocument>
0012 
0013 #include <KIO/StoredTransferJob>
0014 #include <KLocalizedString>
0015 
0016 #include "twitterapimicroblog.h"
0017 
0018 #include "twitteraccount.h"
0019 #include "twitterdebug.h"
0020 
0021 const QRegExp TwitterSearch::m_rId(QLatin1String("tag:search.twitter.com,[0-9]+:([0-9]+)"));
0022 
0023 TwitterSearch::TwitterSearch(QObject *parent): TwitterApiSearch(parent)
0024 {
0025     qCDebug(CHOQOK);
0026     mSearchCode[CustomSearch].clear();
0027     mSearchCode[ToUser] = QLatin1String("to:");
0028     mSearchCode[FromUser] = QLatin1String("from:");
0029     mSearchCode[ReferenceUser] = QLatin1Char('@');
0030     mSearchCode[ReferenceHashtag] = QLatin1Char('#');
0031 
0032     mI18nSearchCode[CustomSearch].clear();
0033     mI18nSearchCode[ReferenceUser] = QLatin1Char('@');
0034     mI18nSearchCode[ReferenceHashtag] = QLatin1Char('#');
0035     mI18nSearchCode[ToUser] = i18nc("Posts sent to user", "To:");
0036     mI18nSearchCode[FromUser] = i18nc("Posts from user, Sent by user", "From:");
0037 
0038     mSearchTypes[CustomSearch].first = i18n("Custom Search");
0039     mSearchTypes[CustomSearch].second = true;
0040 
0041     mSearchTypes[ToUser].first = i18nc("Tweets are Twitter posts",  "Tweets To This User");
0042     mSearchTypes[ToUser].second = true;
0043 
0044     mSearchTypes[FromUser].first = i18nc("Tweets are Twitter posts", "Tweets From This User");
0045     mSearchTypes[FromUser].second = true;
0046 
0047     mSearchTypes[ReferenceUser].first = i18nc("Tweets are Twitter posts", "Tweets Including This Username");
0048     mSearchTypes[ReferenceUser].second = true;
0049 
0050     mSearchTypes[ReferenceHashtag].first = i18nc("Tweets are Twitter posts", "Tweets Including This Hashtag");
0051     mSearchTypes[ReferenceHashtag].second = true;
0052 }
0053 
0054 void TwitterSearch::requestSearchResults(const SearchInfo &searchInfo,
0055         const QString &sinceStatusId, uint count, uint page)
0056 {
0057     Q_UNUSED(page)
0058     qCDebug(CHOQOK);
0059 
0060     TwitterAccount *account = qobject_cast< TwitterAccount * >(searchInfo.account);
0061     QUrl url = account->apiUrl();
0062 
0063     QUrlQuery urlQuery;
0064 
0065     const QString query = searchInfo.query;
0066     if (searchInfo.option == TwitterSearch::FromUser) {
0067         url.setPath(url.path() + QLatin1String("/statuses/user_timeline.json"));
0068 
0069         urlQuery.addQueryItem(QLatin1String("screen_name"), query);
0070     } else {
0071         url.setPath(url.path() + QLatin1String("/search/tweets.json"));
0072 
0073         const QByteArray formattedQuery(QUrl::toPercentEncoding(mSearchCode[searchInfo.option] + query));
0074         urlQuery.addQueryItem(QLatin1String("q"), QString::fromLatin1(formattedQuery));
0075     }
0076 
0077     if (!sinceStatusId.isEmpty()) {
0078         urlQuery.addQueryItem(QLatin1String("since_id"), sinceStatusId);
0079     }
0080 
0081     int cntStr;
0082     if (count && count <= 100) { // Twitter API specifies a max count of 100
0083         cntStr = count;
0084     } else {
0085         cntStr = 100;
0086     }
0087     urlQuery.addQueryItem(QLatin1String("tweet_mode"), QLatin1String("extended"));
0088     urlQuery.addQueryItem(QLatin1String("count"), QString::number(cntStr));
0089 
0090     url.setQuery(urlQuery);
0091 
0092     qCDebug(CHOQOK) << url;
0093     KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo);
0094     if (!job) {
0095         qCCritical(CHOQOK) << "Cannot create an http GET request!";
0096         return;
0097     }
0098 
0099     TwitterApiMicroBlog *microblog = qobject_cast<TwitterApiMicroBlog *>(account->microblog());
0100 
0101     job->addMetaData(QStringLiteral("customHTTPHeader"),
0102                      QStringLiteral("Authorization: ") +
0103                      QLatin1String(microblog->authorizationHeader(account, url, QNetworkAccessManager::GetOperation)));
0104 
0105     mSearchJobs[job] = searchInfo;
0106     connect(job, &KIO::StoredTransferJob::result, this, &TwitterSearch::searchResultsReturned);
0107     job->start();
0108 }
0109 
0110 void TwitterSearch::searchResultsReturned(KJob *job)
0111 {
0112     qCDebug(CHOQOK);
0113     if (!job) {
0114         qCDebug(CHOQOK) << "job is a null pointer";
0115         Q_EMIT error(i18n("Unable to fetch search results."));
0116         return;
0117     }
0118 
0119     const SearchInfo info = mSearchJobs.take(job);
0120     QList<Choqok::Post *> postsList;
0121     if (job->error()) {
0122         qCCritical(CHOQOK) << "Error:" << job->errorString();
0123         Q_EMIT error(i18n("Unable to fetch search results: %1", job->errorString()));
0124     } else {
0125         KIO::StoredTransferJob *jj = qobject_cast<KIO::StoredTransferJob *>(job);
0126         const QJsonDocument json = QJsonDocument::fromJson(jj->data());
0127 
0128         if (!json.isNull()) {
0129             if (info.option == TwitterSearch::FromUser) {
0130                 for (const QVariant &elem: json.toVariant().toList()) {
0131                     postsList.prepend(readStatusesFromJsonMap(elem.toMap()));
0132                 }
0133             } else {
0134                 const QVariantMap map = json.toVariant().toMap();
0135 
0136                 if (map.contains(QLatin1String("statuses"))) {
0137                     for (const QVariant &elem: map[QLatin1String("statuses")].toList()) {
0138                         postsList.prepend(readStatusesFromJsonMap(elem.toMap()));
0139                     }
0140                 }
0141             }
0142         }
0143     }
0144 
0145     Q_EMIT searchResultsReceived(info, postsList);
0146 }
0147 
0148 Choqok::Post *TwitterSearch::readStatusesFromJsonMap(const QVariantMap &var)
0149 {
0150     Choqok::Post *post = new Choqok::Post;
0151 
0152     post->content = var[QLatin1String("text")].toString();
0153 
0154     // Support for extended tweet_mode
0155     if (var.contains(QLatin1String("full_text"))) {
0156         post->content = var[QLatin1String("full_text")].toString();
0157     }
0158 
0159     //%*s %s %d %d:%d:%d %d %d
0160     post->creationDateTime = dateFromString(var[QLatin1String("created_at")].toString());
0161     post->postId = var[QLatin1String("id")].toString();
0162     post->source = var[QLatin1String("source")].toString();
0163     QVariantMap userMap = var[QLatin1String("user")].toMap();
0164     post->author.realName = userMap[QLatin1String("name")].toString();
0165     post->author.userName = userMap[QLatin1String("screen_name")].toString();
0166     post->author.profileImageUrl = userMap[QLatin1String("profile_image_url")].toUrl();
0167     post->isPrivate = false;
0168     post->isFavorited = false;
0169     post->replyToPostId = var[QLatin1String("in_reply_to_status_id_str")].toString();
0170     post->replyToUser.userName = var[QLatin1String("in_reply_to_screen_name")].toString();
0171 
0172     post->link =  QUrl::fromUserInput(QStringLiteral("https://twitter.com/%1/status/%2").arg(post->author.userName).arg(post->postId));
0173 
0174     return post;
0175 }
0176 
0177 QString TwitterSearch::optionCode(int option)
0178 {
0179     return mI18nSearchCode[option];
0180 }
0181 
0182 TwitterSearch::~TwitterSearch()
0183 {
0184 }
0185 
0186 #include "moc_twittersearch.cpp"