1 /*
2     This file is part of Choqok, the KDE micro-blogging client
3 
4     Copyright (C) 2008-2012 Mehrdad Momeny <mehrdad.momeny@gmail.com>
5 
6     This program is free software; you can redistribute it and/or
7     modify it under the terms of the GNU General Public License as
8     published by the Free Software Foundation; either version 2 of
9     the License or (at your option) version 3 or any later version
10     accepted by the membership of KDE e.V. (or its successor approved
11     by the membership of KDE e.V.), which shall act as a proxy
12     defined in Section 14 of version 3 of the license.
13 
14     This program is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with this program; if not, see http://www.gnu.org/licenses/
21 
22 */
23 
24 #include "twitterpostwidget.h"
25 
26 #include <QAction>
27 #include <QMenu>
28 #include <QPushButton>
29 
30 #include <KLocalizedString>
31 
32 #include "choqokbehaviorsettings.h"
33 #include "choqoktools.h"
34 #include "mediamanager.h"
35 #include "textbrowser.h"
36 
37 #include "twitterapiaccount.h"
38 #include "twitterapimicroblog.h"
39 #include "twitterapiwhoiswidget.h"
40 
41 #include "twittersearch.h"
42 
43 const QRegExp TwitterPostWidget::mTwitterUserRegExp(QLatin1String("([\\s\\W]|^)@([a-z0-9_]+){1,20}"), Qt::CaseInsensitive);
44 const QRegExp TwitterPostWidget::mTwitterTagRegExp(QLatin1String("([\\s]|^)#([\\w_\\.\\-]+)"), Qt::CaseInsensitive);
45 const QString TwitterPostWidget::mQuotedTextBase(QLatin1String("<div style=\"padding-top:5px;padding-bottom:3px;\"><table style=\"%4 border-style:groove;\" border=\"1\"><tr><td style=\"padding-top:4px;padding-bottom:4px;padding-left:3px;padding-right:3px;\"><table> <tr><td width=\"40\" style=\"padding-right:5px;\"><img src=\"img://quotedProfileImage\" width=\"40\" height=\"40\" /></td><td dir=\"%2\"><p><b><a>%3:</b>&nbsp;%1</p></td></tr></table></td></tr></table></div>"));
46 const QUrl TwitterPostWidget::mQuotedAvatarResourceUrl(QLatin1String("img://quotedProfileImage"));
TwitterPostWidget(Choqok::Account * account,Choqok::Post * post,QWidget * parent)47 TwitterPostWidget::TwitterPostWidget(Choqok::Account *account, Choqok::Post *post, QWidget *parent): TwitterApiPostWidget(account, post, parent)
48 {
49 }
50 
~TwitterPostWidget()51 TwitterPostWidget::~TwitterPostWidget()
52 {
53 }
54 
initUi()55 void TwitterPostWidget::initUi()
56 {
57     TwitterApiPostWidget::initUi();
58 
59     if ( ! currentPost()->quotedPost.content.isEmpty() ) {
60         if( !setupQuotedAvatar() ){
61             _mainWidget->document()->addResource(QTextDocument::ImageResource, mQuotedAvatarResourceUrl,
62                                                  Choqok::MediaManager::self()->defaultImage());
63         }
64 
65         auto dir = getDirection(currentPost()->quotedPost.content);
66         auto text = prepareStatus(currentPost()->quotedPost.content);
67         QString user = QStringLiteral("<a href='user://%1'>%1</a>").arg(currentPost()->quotedPost.user.userName);
68         QString quoteText = mQuotedTextBase.arg(text, dir, user, QLatin1String("background-color:%1;"));
69         setExtraContents(quoteText.arg(getBackgroundColor()));
70         updateUi();
71     }
72 
73     QPushButton *btn = buttons().value(QLatin1String("btnResend"));
74 
75     if (btn) {
76         QMenu *menu = new QMenu(btn);
77         QAction *resend = new QAction(i18n("Manual ReSend"), menu);
78         connect(resend, &QAction::triggered, this, &TwitterPostWidget::slotResendPost);
79         QAction *repeat = new QAction(i18n("Retweet"), menu);
80         repeat->setToolTip(i18n("Retweet post using API"));
81         connect(repeat, &QAction::triggered, this, &TwitterPostWidget::repeatPost);
82         // If person protects their acc, we will use simple adding RT before message
83         if (!currentPost()->author.isProtected) {
84             menu->addAction(repeat);
85         }
86         menu->addAction(resend);
87         btn->setMenu(menu);
88     }
89 }
90 
prepareStatus(const QString & text)91 QString TwitterPostWidget::prepareStatus(const QString &text)
92 {
93     QString res = TwitterApiPostWidget::prepareStatus(text);
94     res.replace(mTwitterUserRegExp, QLatin1String("\\1@<a href='user://\\2'>\\2</a>"));
95     res.replace(mTwitterTagRegExp, QLatin1String("\\1#<a href='tag://\\2'>\\2</a>"));
96     return res;
97 }
98 
isRemoveAvailable()99 bool TwitterPostWidget::isRemoveAvailable()
100 {
101     if (currentAccount()->username().compare(currentPost()->author.userName, Qt::CaseInsensitive) == 0) {
102         return true;
103     } else if (currentPost()->isPrivate) {
104         return true;
105     } else {
106         return false;
107     }
108 }
109 
slotReplyToAll()110 void TwitterPostWidget::slotReplyToAll()
111 {
112     QStringList nicks;
113     nicks.append(currentPost()->author.userName);
114 
115     QString txt = QStringLiteral("@%1 ").arg(currentPost()->author.userName);
116 
117     int pos = 0;
118     while ((pos = mTwitterUserRegExp.indexIn(currentPost()->content, pos)) != -1) {
119         if (mTwitterUserRegExp.cap(2).toLower() != currentAccount()->username() &&
120                 mTwitterUserRegExp.cap(2).toLower() != currentPost()->author.userName &&
121                 !nicks.contains(mTwitterUserRegExp.cap(2).toLower())) {
122             nicks.append(mTwitterUserRegExp.cap(2));
123             txt += QStringLiteral("@%1 ").arg(mTwitterUserRegExp.cap(2));
124         }
125         pos += mTwitterUserRegExp.matchedLength();
126     }
127 
128     txt.chop(1);
129 
130     Q_EMIT reply(txt, currentPost()->postId, currentPost()->author.userName);
131 }
132 
checkAnchor(const QUrl & url)133 void TwitterPostWidget::checkAnchor(const QUrl &url)
134 {
135     QString scheme = url.scheme();
136     TwitterApiMicroBlog *blog = qobject_cast<TwitterApiMicroBlog *>(currentAccount()->microblog());
137     TwitterApiAccount *account = qobject_cast<TwitterApiAccount *>(currentAccount());
138     if (scheme == QLatin1String("tag")) {
139         blog->searchBackend()->requestSearchResults(currentAccount(),
140                 QUrl::fromAce(url.host().toUtf8()),
141                 (int)TwitterSearch::ReferenceHashtag);
142     } else if (scheme == QLatin1String("user")) {
143         QMenu menu;
144         QAction *info = new QAction(QIcon::fromTheme(QLatin1String("user-identity")), i18nc("Who is user", "Who is %1", url.host()),
145                                     &menu);
146         QAction *from = new QAction(QIcon::fromTheme(QLatin1String("edit-find-user")), i18nc("Posts from user", "Posts from %1", url.host()),
147                                     &menu);
148         QAction *to = new QAction(QIcon::fromTheme(QLatin1String("meeting-attending")), i18nc("Replies to user", "Replies to %1",
149                                   url.host()),
150                                   &menu);
151         QAction *cont = new QAction(QIcon::fromTheme(QLatin1String("user-properties")), i18nc("Including user name", "Including %1",
152                                     url.host()),
153                                     &menu);
154         QAction *openInBrowser = new QAction(QIcon::fromTheme(QLatin1String("internet-services")),
155                                              i18nc("Open profile page in browser",
156                                                      "Open profile in browser"), &menu);
157         from->setData(TwitterSearch::FromUser);
158         to->setData(TwitterSearch::ToUser);
159         cont->setData(TwitterSearch::ReferenceUser);
160         menu.addAction(info);
161         menu.addAction(from);
162         menu.addAction(to);
163         menu.addAction(cont);
164         menu.addAction(openInBrowser);
165 
166         //Subscribe/UnSubscribe/Block
167         bool isSubscribe = false;
168         QString accountUsername = currentAccount()->username().toLower();
169         QString postUsername = url.host().toLower();
170         QAction *subscribe = nullptr, *block = nullptr, *replyTo = nullptr, *dMessage = nullptr, *reportSpam = nullptr;
171         if (accountUsername != postUsername) {
172             menu.addSeparator();
173             QMenu *actionsMenu = menu.addMenu(QIcon::fromTheme(QLatin1String("applications-system")), i18n("Actions"));
174             replyTo = new QAction(QIcon::fromTheme(QLatin1String("edit-undo")), i18nc("Write a message to user attention", "Write to %1",
175                                   url.host()), actionsMenu);
176             actionsMenu->addAction(replyTo);
177             if (account->friendsList().contains(url.host(),
178                                                 Qt::CaseInsensitive)) {
179                 dMessage = new QAction(QIcon::fromTheme(QLatin1String("mail-message-new")), i18nc("Send direct message to user",
180                                        "Send private message to %1",
181                                        url.host()), actionsMenu);
182                 actionsMenu->addAction(dMessage);
183                 isSubscribe = false;//It's UnSubscribe
184                 subscribe = new QAction(QIcon::fromTheme(QLatin1String("list-remove-user")),
185                                         i18nc("Unfollow user",
186                                               "Unfollow %1", url.host()), actionsMenu);
187             } else {
188                 isSubscribe = true;
189                 subscribe = new QAction(QIcon::fromTheme(QLatin1String("list-add-user")),
190                                         i18nc("Follow user",
191                                               "Follow %1", url.host()), actionsMenu);
192             }
193             block = new QAction(QIcon::fromTheme(QLatin1String("dialog-cancel")),
194                                 i18nc("Block user",
195                                       "Block %1", url.host()), actionsMenu);
196             reportSpam = new QAction(QIcon::fromTheme(QLatin1String("irc-voice")),
197                                      i18nc("Report user",
198                                            "Report %1 as spam", url.host()), actionsMenu);
199 
200             actionsMenu->addAction(subscribe);
201             actionsMenu->addAction(block);
202             actionsMenu->addAction(reportSpam);
203         }
204 
205         QAction *ret = menu.exec(QCursor::pos());
206         if (ret == nullptr) {
207             return;
208         }
209         if (ret == info) {
210             TwitterApiWhoisWidget *wd = new TwitterApiWhoisWidget(account, url.host(),  *currentPost(), this);
211             wd->show(QCursor::pos());
212             return;
213         } else if (ret == subscribe) {
214             if (isSubscribe) {
215                 blog->createFriendship(currentAccount(), url.host());
216             } else {
217                 blog->destroyFriendship(currentAccount(), url.host());
218             }
219             return;
220         } else if (ret == block) {
221             blog->blockUser(currentAccount(), url.host());
222             return;
223         } else if (ret == reportSpam) {
224             blog->reportUserAsSpam(currentAccount(), url.host());
225             return;
226         } else if (ret == openInBrowser) {
227             Choqok::openUrl(currentAccount()->microblog()->profileUrl(currentAccount(), url.host()));
228             return;
229         } else if (ret == replyTo) {
230             Q_EMIT reply(QStringLiteral("@%1").arg(url.host()), QString(), url.host());
231             return;
232         } else if (ret == dMessage) {
233             blog->showDirectMessageDialog(account, url.host());
234             return;
235         }
236         int type = ret->data().toInt();
237         blog->searchBackend()->requestSearchResults(currentAccount(),
238                 url.host(), type, QString(), Choqok::BehaviorSettings::countOfPosts());
239     } else {
240         TwitterApiPostWidget::checkAnchor(url);
241     }
242 }
243 
setupQuotedAvatar()244 bool TwitterPostWidget::setupQuotedAvatar()
245 {
246     QPixmap pix = Choqok::MediaManager::self()->fetchImage(currentPost()->quotedPost.user.profileImageUrl,
247                                                            Choqok::MediaManager::Async);
248     if (!pix.isNull()) {
249         quotedAvatarFetched(currentPost()->quotedPost.user.profileImageUrl, pix);
250         return true;
251     } else {
252         connect(Choqok::MediaManager::self(), &Choqok::MediaManager::imageFetched,
253                 this, &TwitterPostWidget::quotedAvatarFetched);
254         connect(Choqok::MediaManager::self(), &Choqok::MediaManager::fetchError,
255                 this, &TwitterPostWidget::quotedAvatarFetchError);
256         return false;
257     }
258 }
259 
quotedAvatarFetched(const QUrl & remoteUrl,const QPixmap & pixmap)260 void TwitterPostWidget::quotedAvatarFetched(const QUrl &remoteUrl, const QPixmap &pixmap)
261 {
262     if (remoteUrl == currentPost()->quotedPost.user.profileImageUrl) {
263         _mainWidget->document()->addResource(QTextDocument::ImageResource, mQuotedAvatarResourceUrl, pixmap);
264         disconnect(Choqok::MediaManager::self(), &Choqok::MediaManager::imageFetched,
265                    this, &TwitterPostWidget::quotedAvatarFetched);
266         disconnect(Choqok::MediaManager::self(), &Choqok::MediaManager::fetchError,
267                    this, &TwitterPostWidget::quotedAvatarFetchError);
268     }
269 }
270 
quotedAvatarFetchError(const QUrl & remoteUrl,const QString & errMsg)271 void TwitterPostWidget::quotedAvatarFetchError(const QUrl &remoteUrl, const QString &errMsg)
272 {
273     Q_UNUSED(errMsg);
274     if (remoteUrl == currentPost()->quotedPost.user.profileImageUrl) {
275         ///Avatar fetching is failed! but will not disconnect to get the img if it fetches later!
276         _mainWidget->document()->addResource(QTextDocument::ImageResource, mQuotedAvatarResourceUrl,
277                                              QIcon::fromTheme(QLatin1String("image-missing")).pixmap(40));
278     }
279 }
280 
getBackgroundColor()281 QString TwitterPostWidget::getBackgroundColor()
282 {
283     QString style = styleSheet();
284     QLatin1String str{ "background-color:rgb(" };
285     int idx = style.indexOf(str);
286     if(idx != -1){
287         idx += str.size();
288         int endIdx = style.indexOf(QLatin1String(");"), idx);
289         if( endIdx != -1 ){
290             QStringList rgb = style.mid(idx, endIdx-idx).split(QLatin1Char(','));
291             if( rgb.size() == 3 ){
292                 return QStringLiteral("#%1%2%3").arg( rgb[0].toInt() - 20, 2, 16, QLatin1Char('0') )
293                                                 .arg( rgb[1].toInt() - 20, 2, 16, QLatin1Char('0') )
294                                                 .arg( rgb[2].toInt() - 20, 2, 16, QLatin1Char('0') );
295             }
296         }
297     }
298 
299     return QLatin1String("#ffffff");
300 }
301