File indexing completed on 2024-04-28 04:55:39
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 "twitterapiwhoiswidget.h" 0010 0011 #include <QApplication> 0012 #include <QDesktopWidget> 0013 #include <QIcon> 0014 #include <QJsonDocument> 0015 #include <QPoint> 0016 #include <QPointer> 0017 #include <QStatusBar> 0018 #include <QTextBrowser> 0019 #include <QUrl> 0020 #include <QVBoxLayout> 0021 0022 #include <KAnimatedButton> 0023 #include <KIO/Job> 0024 #include <KLocalizedString> 0025 0026 #include "choqokappearancesettings.h" 0027 #include "choqoktools.h" 0028 #include "choqoktypes.h" 0029 #include "mediamanager.h" 0030 #include "microblog.h" 0031 #include "notifymanager.h" 0032 #include "twitterapiaccount.h" 0033 #include "twitterapidebug.h" 0034 #include "twitterapimicroblog.h" 0035 0036 class TwitterApiWhoisWidget::Private 0037 { 0038 public: 0039 Private(TwitterApiAccount *account, const QString &userN) 0040 : currentAccount(account), waitFrame(nullptr), job(nullptr), username(userN) 0041 { 0042 mBlog = qobject_cast<TwitterApiMicroBlog *>(account->microblog()); 0043 } 0044 QTextBrowser *wid; 0045 TwitterApiAccount *currentAccount; 0046 TwitterApiMicroBlog *mBlog; 0047 QFrame *waitFrame; 0048 QPointer<KJob> job; 0049 Choqok::Post currentPost; 0050 QString username; 0051 QString errorMessage; 0052 0053 QString followersCount; 0054 QString friendsCount; 0055 QString statusesCount; 0056 QString timeZone; 0057 QString imgActions; 0058 // bool isFollowing; 0059 }; 0060 0061 TwitterApiWhoisWidget::TwitterApiWhoisWidget(TwitterApiAccount *theAccount, const QString &username, 0062 const Choqok::Post &post, QWidget *parent) 0063 : QFrame(parent), d(new Private(theAccount, username)) 0064 { 0065 qCDebug(CHOQOK); 0066 setAttribute(Qt::WA_DeleteOnClose); 0067 d->currentPost = post; 0068 loadUserInfo(theAccount, username); 0069 0070 d->wid = new QTextBrowser(this); 0071 setFrameShape(StyledPanel); 0072 setFrameShadow(Sunken); 0073 0074 d->wid->setFrameShape(QFrame::NoFrame); 0075 QVBoxLayout *layout = new QVBoxLayout(this); 0076 layout->setContentsMargins(0, 0, 0, 0); 0077 layout->addWidget(d->wid); 0078 this->setLayout(layout); 0079 this->setWindowFlags(Qt::Popup);// | Qt::FramelessWindowHint | Qt::Ta); 0080 d->wid->setOpenLinks(false); 0081 connect(d->wid, &QTextBrowser::anchorClicked, this, &TwitterApiWhoisWidget::checkAnchor); 0082 setupUi(); 0083 setActionImages(); 0084 } 0085 0086 TwitterApiWhoisWidget::~TwitterApiWhoisWidget() 0087 { 0088 qCDebug(CHOQOK); 0089 delete d; 0090 } 0091 0092 void TwitterApiWhoisWidget::loadUserInfo(TwitterApiAccount *theAccount, const QString &username) 0093 { 0094 qCDebug(CHOQOK); 0095 QString urlStr; 0096 QString user = username; 0097 if (user.contains(QLatin1Char('@'))) { 0098 QStringList lst = user.split(QLatin1Char('@')); 0099 if (lst.count() == 2) { //USER@HOST 0100 QString host = lst[1]; 0101 urlStr = QStringLiteral("https://%1/api").arg(host); 0102 user = lst[0]; 0103 } 0104 } else if (d->currentPost.source == QLatin1String("ostatus") && !d->currentPost.author.homePageUrl.isEmpty()) { 0105 urlStr = d->currentPost.author.homePageUrl.toDisplayString(); 0106 if (urlStr.endsWith(user)) { 0107 int len = urlStr.length(); 0108 int userLen = user.length(); 0109 urlStr.remove(len - userLen, userLen); 0110 qCDebug(CHOQOK) << urlStr; 0111 } 0112 urlStr.append(QLatin1String("api")); 0113 } else { 0114 urlStr = theAccount->apiUrl().url(); 0115 } 0116 QUrl url(urlStr); 0117 0118 url = url.adjusted(QUrl::StripTrailingSlash); 0119 url.setPath(url.path() + QStringLiteral("/users/show/%1.json").arg(user)); 0120 0121 // qCDebug(CHOQOK) << url; 0122 0123 KIO::StoredTransferJob *job = KIO::storedGet(url, KIO::Reload, KIO::HideProgressInfo); 0124 if (d->currentPost.source != QLatin1String("ostatus")) { 0125 job->addMetaData(QStringLiteral("customHTTPHeader"), 0126 QStringLiteral("Authorization: ") + 0127 QLatin1String(d->mBlog->authorizationHeader(theAccount, url, QNetworkAccessManager::GetOperation))); 0128 } 0129 d->job = job; 0130 connect(job, &KIO::StoredTransferJob::result, this, &TwitterApiWhoisWidget::userInfoReceived); 0131 job->start(); 0132 } 0133 0134 void TwitterApiWhoisWidget::userInfoReceived(KJob *job) 0135 { 0136 qCDebug(CHOQOK); 0137 if (job->error()) { 0138 qCCritical(CHOQOK) << "Job Error:" << job->errorString(); 0139 if (Choqok::UI::Global::mainWindow()->statusBar()) { 0140 Choqok::UI::Global::mainWindow()->statusBar()->showMessage(job->errorString()); 0141 } 0142 slotCancel(); 0143 return; 0144 } 0145 KIO::StoredTransferJob *stj = qobject_cast<KIO::StoredTransferJob *>(job); 0146 // qCDebug(CHOQOK)<<stj->data(); 0147 const QJsonDocument json = QJsonDocument::fromJson(stj->data()); 0148 if (json.isNull()) { 0149 qCDebug(CHOQOK) << "JSON parsing failed! Data is:\n\t" << stj->data(); 0150 d->errorMessage = i18n("Cannot load user information."); 0151 updateHtml(); 0152 showForm(); 0153 return; 0154 } 0155 0156 const QVariantMap map = json.toVariant().toMap(); 0157 0158 QString timeStr; 0159 Choqok::Post post; 0160 d->errorMessage = map[QLatin1String("error")].toString(); 0161 if (d->errorMessage.isEmpty()) { //No Error 0162 post.author.realName = map[QLatin1String("name")].toString(); 0163 post.author.userName = map[QLatin1String("screen_name")].toString(); 0164 post.author.location = map[QLatin1String("location")].toString(); 0165 post.author.description = map[QLatin1String("description")].toString(); 0166 post.author.profileImageUrl = map[QLatin1String("profile_image_url")].toUrl(); 0167 post.author.homePageUrl = map[QLatin1String("url")].toUrl(); 0168 d->timeZone = map[QLatin1String("time_zone")].toString(); 0169 d->followersCount = map[QLatin1String("followers_count")].toString(); 0170 d->friendsCount = map[QLatin1String("friends_count")].toString(); 0171 d->statusesCount = map[QLatin1String("statuses_count")].toString(); 0172 QVariantMap var = map[QLatin1String("status")].toMap(); 0173 post.content = var[QLatin1String("text")].toString(); 0174 post.creationDateTime = d->mBlog->dateFromString(var[QLatin1String("created_at")].toString()); 0175 post.isFavorited = var[QLatin1String("favorited")].toBool(); 0176 post.postId = var[QLatin1String("id")].toString(); 0177 post.replyToPostId = var[QLatin1String("in_reply_to_status_id")].toString(); 0178 post.replyToUser.userId = var[QLatin1String("in_reply_to_user_id")].toString(); 0179 post.replyToUser.userName = var[QLatin1String("in_reply_to_screen_name")].toString(); 0180 post.source = var[QLatin1String("source")].toString(); 0181 d->currentPost = post; 0182 } 0183 0184 updateHtml(); 0185 showForm(); 0186 0187 QPixmap userAvatar = Choqok::MediaManager::self()->fetchImage(post.author.profileImageUrl, 0188 Choqok::MediaManager::Async); 0189 0190 if (!userAvatar.isNull()) { 0191 d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("img://profileImage")), 0192 userAvatar); 0193 } else { 0194 connect(Choqok::MediaManager::self(), &Choqok::MediaManager::imageFetched, 0195 this, &TwitterApiWhoisWidget::avatarFetched); 0196 connect(Choqok::MediaManager::self(), &Choqok::MediaManager::fetchError, 0197 this, &TwitterApiWhoisWidget::avatarFetchError); 0198 } 0199 } 0200 0201 void TwitterApiWhoisWidget::avatarFetched(const QUrl &remoteUrl, const QPixmap &pixmap) 0202 { 0203 qCDebug(CHOQOK); 0204 if (remoteUrl == d->currentPost.author.profileImageUrl) { 0205 const QUrl url(QLatin1String("img://profileImage")); 0206 d->wid->document()->addResource(QTextDocument::ImageResource, url, pixmap); 0207 updateHtml(); 0208 disconnect(Choqok::MediaManager::self(), &Choqok::MediaManager::imageFetched, 0209 this, &TwitterApiWhoisWidget::avatarFetched); 0210 disconnect(Choqok::MediaManager::self(), &Choqok::MediaManager::fetchError, 0211 this, &TwitterApiWhoisWidget::avatarFetchError); 0212 } 0213 } 0214 0215 void TwitterApiWhoisWidget::avatarFetchError(const QUrl &remoteUrl, const QString &errMsg) 0216 { 0217 qCDebug(CHOQOK); 0218 Q_UNUSED(errMsg); 0219 if (remoteUrl == d->currentPost.author.profileImageUrl) { 0220 ///Avatar fetching is failed! but will not disconnect to get the img if it fetches later! 0221 const QUrl url(QLatin1String("img://profileImage")); 0222 d->wid->document()->addResource(QTextDocument::ImageResource, url, QIcon::fromTheme(QLatin1String("image-missing")).pixmap(48)); 0223 updateHtml(); 0224 } 0225 } 0226 0227 void TwitterApiWhoisWidget::updateHtml() 0228 { 0229 qCDebug(CHOQOK); 0230 QString html; 0231 if (d->errorMessage.isEmpty()) { 0232 QString url = d->currentPost.author.homePageUrl.isEmpty() ? QString() 0233 : QStringLiteral("<a title='%1' href='%1'>%1</a>").arg(d->currentPost.author.homePageUrl.toDisplayString()); 0234 0235 QString mainTable = QString(QLatin1String("<table width='100%'><tr>\ 0236 <td width=49><img width=48 height=48 src='img://profileImage'/>\ 0237 <center><table width='100%' cellpadding='3'><tr>%1</tr></table></center></td>\ 0238 <td><table width='100%'><tr><td><font size=5><b>%2</b></font></td>\ 0239 <td><a href='choqok://close'><img src='icon://close' title='") + i18n("Close") + QLatin1String("' align='right' /></a></td></tr></table><br/>\ 0240 <b>@%3</b> <font size=3>%4</font><font size=2>%5</font><br/>\ 0241 <i>%6</i><br/>\ 0242 <font size=3>%7</font></td></tr></table>")) 0243 .arg(d->imgActions) 0244 .arg(d->currentPost.author.realName.toHtmlEscaped()) 0245 .arg(d->currentPost.author.userName).arg(d->currentPost.author.location.toHtmlEscaped()) 0246 .arg(!d->timeZone.isEmpty() ? QLatin1Char('(') + d->timeZone + QLatin1Char(')') : QString()) 0247 .arg(d->currentPost.author.description) 0248 .arg(url); 0249 0250 QString countTable = QString(QLatin1String("<table><tr>\ 0251 <td><b>%1</b><br/>") + i18nc("User posts", "Posts") + QLatin1String("</td>\ 0252 <td><b>%2</b><br/>") + i18nc("User friends", "Friends") + QLatin1String("</td>\ 0253 <td><b>%3</b><br/>") + i18nc("User followers" , "Followers") + QLatin1String("</td>\ 0254 </tr></table><br/>")) 0255 .arg(d->statusesCount) 0256 .arg(d->friendsCount) 0257 .arg(d->followersCount); 0258 0259 html = mainTable + countTable; 0260 0261 if (!d->currentPost.content.isEmpty()) { 0262 html.append(i18n("<table><tr><b>Last Status:</b> %1</tr></table>", d->currentPost.content)); 0263 } 0264 0265 } else { 0266 html = i18n("<h3>%1</h3>", d->errorMessage); 0267 } 0268 d->wid->setHtml(html); 0269 } 0270 0271 void TwitterApiWhoisWidget::showForm() 0272 { 0273 qCDebug(CHOQOK); 0274 QPoint pos = d->waitFrame->pos(); 0275 d->waitFrame->deleteLater(); 0276 d->wid->resize(320, 200); 0277 d->wid->document()->setTextWidth(width() - 2); 0278 d->wid->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0279 int h = d->wid->document()->size().toSize().height() + 10; 0280 d->wid->setMinimumHeight(h); 0281 d->wid->setMaximumHeight(h); 0282 this->resize(320, h + 4); 0283 int desktopHeight = QApplication::desktop()->height(); 0284 int desktopWidth = QApplication::desktop()->width(); 0285 if ((pos.x() + this->width()) > desktopWidth) { 0286 pos.setX(desktopWidth - width()); 0287 } 0288 if ((pos.y() + this->height()) > desktopHeight) { 0289 pos.setY(desktopHeight - height()); 0290 } 0291 move(pos); 0292 QWidget::show(); 0293 } 0294 0295 void TwitterApiWhoisWidget::show(QPoint pos) 0296 { 0297 qCDebug(CHOQOK); 0298 d->waitFrame = new QFrame(this); 0299 d->waitFrame->setFrameShape(NoFrame); 0300 d->waitFrame->setWindowFlags(Qt::Popup); 0301 KAnimatedButton *waitButton = new KAnimatedButton; 0302 waitButton->setToolTip(i18n("Please wait...")); 0303 connect(waitButton, &KAnimatedButton::clicked, this, &TwitterApiWhoisWidget::slotCancel); 0304 waitButton->setAnimationPath(QLatin1String("process-working-kde")); 0305 waitButton->start(); 0306 0307 QVBoxLayout *ly = new QVBoxLayout(d->waitFrame); 0308 ly->setSpacing(0); 0309 ly->setContentsMargins(0, 0, 0, 0); 0310 ly->addWidget(waitButton); 0311 0312 d->waitFrame->move(pos - QPoint(15, 15)); 0313 d->waitFrame->show(); 0314 } 0315 0316 void TwitterApiWhoisWidget::checkAnchor(const QUrl url) 0317 { 0318 qCDebug(CHOQOK); 0319 if (url.scheme() == QLatin1String("choqok")) { 0320 if (url.host() == QLatin1String("close")) { 0321 this->close(); 0322 } else if (url.host() == QLatin1String("subscribe")) { 0323 d->mBlog->createFriendship(d->currentAccount, d->username); 0324 connect(d->mBlog, &TwitterApiMicroBlog::friendshipCreated, 0325 this, &TwitterApiWhoisWidget::slotFriendshipCreated); 0326 } else if (url.host() == QLatin1String("unsubscribe")) { 0327 d->mBlog->destroyFriendship(d->currentAccount, d->username); 0328 connect(d->mBlog, &TwitterApiMicroBlog::friendshipDestroyed, 0329 this, &TwitterApiWhoisWidget::slotFriendshipDestroyed); 0330 } else if (url.host() == QLatin1String("block")) { 0331 d->mBlog->blockUser(d->currentAccount, d->username); 0332 // connect(d->mBlog, SIGNAL(userBlocked(Choqok::Account*,QString)), SLOT(slotUserBlocked(Choqok::Account*,QString))); 0333 } 0334 } else { 0335 Choqok::openUrl(url); 0336 close(); 0337 } 0338 } 0339 0340 void TwitterApiWhoisWidget::setupUi() 0341 { 0342 qCDebug(CHOQOK); 0343 d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://close")), 0344 QIcon::fromTheme(QLatin1String("dialog-close")).pixmap(16)); 0345 0346 QString style = QLatin1String("color: %1; background-color: %2"); 0347 if (Choqok::AppearanceSettings::isCustomUi()) { 0348 setStyleSheet(style.arg(Choqok::AppearanceSettings::readForeColor().name()) 0349 .arg(Choqok::AppearanceSettings::readBackColor().name())); 0350 } else { 0351 QPalette p = window()->palette(); 0352 setStyleSheet(style.arg(p.color(QPalette::WindowText).name()).arg(p.color(QPalette::Window).name())); 0353 } 0354 } 0355 0356 void TwitterApiWhoisWidget::slotCancel() 0357 { 0358 qCDebug(CHOQOK); 0359 if (d->waitFrame) { 0360 d->waitFrame->deleteLater(); 0361 } 0362 if (d->job) { 0363 d->job->kill(); 0364 } 0365 this->close(); 0366 } 0367 0368 void TwitterApiWhoisWidget::setActionImages() 0369 { 0370 d->imgActions.clear(); 0371 if (d->username.compare(d->currentAccount->username(), Qt::CaseInsensitive) != 0) { 0372 if (d->currentAccount->friendsList().contains(d->username, Qt::CaseInsensitive)) { 0373 d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://unsubscribe")), 0374 QIcon::fromTheme(QLatin1String("list-remove-user")).pixmap(16)); 0375 d->imgActions += QLatin1String("<a href='choqok://unsubscribe'><img src='icon://unsubscribe' title='") + 0376 i18n("Unsubscribe") + QLatin1String("'></a> "); 0377 } else { 0378 d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://subscribe")), 0379 QIcon::fromTheme(QLatin1String("list-add-user")).pixmap(16)); 0380 d->imgActions += QLatin1String("<a href='choqok://subscribe'><img src='icon://subscribe' title='") + 0381 i18n("Subscribe") + QLatin1String("'></a> "); 0382 } 0383 0384 d->wid->document()->addResource(QTextDocument::ImageResource, QUrl(QLatin1String("icon://block")), 0385 QIcon::fromTheme(QLatin1String("dialog-cancel")).pixmap(16)); 0386 d->imgActions += QLatin1String("<a href='choqok://block'><img src='icon://block' title='") + i18n("Block") + QLatin1String("'></a>"); 0387 } 0388 } 0389 0390 void TwitterApiWhoisWidget::slotFriendshipCreated(Choqok::Account *theAccount, const QString &username) 0391 { 0392 if (theAccount == d->currentAccount && username == d->username) { 0393 setActionImages(); 0394 updateHtml(); 0395 } 0396 } 0397 0398 void TwitterApiWhoisWidget::slotFriendshipDestroyed(Choqok::Account *theAccount, const QString &username) 0399 { 0400 if (theAccount == d->currentAccount && username == d->username) { 0401 setActionImages(); 0402 updateHtml(); 0403 } 0404 } 0405 0406 // void TwitterApiWhoisWidget::slotUserBlocked(Choqok::Account* theAccount, const QString &username) 0407 // { 0408 // if(theAccount == d->currentAccount && username == d->username){ 0409 // Choqok::NotifyManager::success( i18n("Your posts are blocked for %1.", username) ); 0410 // } 0411 // } 0412 0413 #include "moc_twitterapiwhoiswidget.cpp"