1 /*
2     This file is part of Choqok, the KDE micro-blogging client
3 
4     Based on the imagepreview extension
5     Copyright (C) 2010-2012 Emanuele Bigiarini <pulmro@gmail.com>
6     Copyright (C) 2008-2012 Mehrdad Momeny <mehrdad.momeny@gmail.com>
7 
8     This program is free software; you can redistribute it and/or
9     modify it under the terms of the GNU General Public License as
10     published by the Free Software Foundation; either version 2 of
11     the License or (at your option) version 3 or any later version
12     accepted by the membership of KDE e.V. (or its successor approved
13     by the membership of KDE e.V.), which shall act as a proxy
14     defined in Section 14 of version 3 of the license.
15 
16     This program is distributed in the hope that it will be useful,
17     but WITHOUT ANY WARRANTY; without even the implied warranty of
18     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19     GNU General Public License for more details.
20 
21     You should have received a copy of the GNU General Public License
22     along with this program; if not, see http://www.gnu.org/licenses/
23 
24 */
25 #include "videopreview.h"
26 
27 #include <QDebug>
28 #include <QDomDocument>
29 #include <QDomElement>
30 #include <QEventLoop>
31 #include <QTimer>
32 
33 #include <KIO/StoredTransferJob>
34 #include <KJobWidgets>
35 #include <KPluginFactory>
36 
37 #include "choqokuiglobal.h"
38 #include "postwidget.h"
39 #include "notifymanager.h"
40 #include "mediamanager.h"
41 #include "textbrowser.h"
42 #include "shortenmanager.h"
43 
44 K_PLUGIN_FACTORY_WITH_JSON(VideoPreviewFactory, "choqok_videopreview.json",
45                            registerPlugin < VideoPreview > ();)
46 
47 const QRegExp VideoPreview::mYouTuRegExp(QLatin1String("(https?://youtu.[^\\s<>\"]+[^!,\\.\\s<>'\\\"\\]])"));
48 const QRegExp VideoPreview::mYouTubeRegExp(QLatin1String("(https?://www.youtube.[^\\s<>\"]+[^!,\\.\\s<>'\\\"\\]])"));
49 const QRegExp VideoPreview::mVimeoRegExp(QLatin1String("(https?://(.+)?vimeo.com/(.+)[&]?)"));
50 
51 const QRegExp VideoPreview::mYouTuCode(QLatin1String("youtu.(.+)/(.+)[?&]?"));
52 
VideoPreview(QObject * parent,const QList<QVariant> &)53 VideoPreview::VideoPreview(QObject *parent, const QList< QVariant > &)
54     : Choqok::Plugin(QLatin1String("choqok_videopreview"), parent)
55     , state(Stopped)
56 {
57     connect(Choqok::UI::Global::SessionManager::self(), &Choqok::UI::Global::SessionManager::newPostWidgetAdded,
58             this, &VideoPreview::slotAddNewPostWidget);
59     connect(Choqok::ShortenManager::self(), &Choqok::ShortenManager::newUnshortenedUrl,
60             this, &VideoPreview::slotNewUnshortenedUrl);
61 }
62 
~VideoPreview()63 VideoPreview::~VideoPreview()
64 {
65 
66 }
67 
slotAddNewPostWidget(Choqok::UI::PostWidget * newWidget)68 void VideoPreview::slotAddNewPostWidget(Choqok::UI::PostWidget *newWidget)
69 {
70     postsQueue.enqueue(newWidget);
71     if (state == Stopped) {
72         state = Running;
73         QTimer::singleShot(1000, this, SLOT(startParsing()));
74     }
75 }
76 
slotNewUnshortenedUrl(Choqok::UI::PostWidget * widget,const QUrl & fromUrl,const QUrl & toUrl)77 void VideoPreview::slotNewUnshortenedUrl(Choqok::UI::PostWidget *widget, const QUrl &fromUrl, const QUrl &toUrl)
78 {
79     Q_UNUSED(fromUrl)
80     if (mYouTubeRegExp.indexIn(toUrl.toDisplayString()) != -1) {
81         QUrl thisurl(mYouTubeRegExp.cap(0));
82         QUrlQuery thisurlQuery(thisurl);
83         QUrl thumbUrl = parseYoutube(thisurlQuery.queryItemValue(QLatin1String("v")), widget);
84         connect(Choqok::MediaManager::self(), &Choqok::MediaManager::imageFetched,
85                 this, &VideoPreview::slotImageFetched);
86         Choqok::MediaManager::self()->fetchImage(thumbUrl, Choqok::MediaManager::Async);
87     } else if (mVimeoRegExp.indexIn(toUrl.toDisplayString()) != -1) {
88         QUrl thumbUrl = parseVimeo(mVimeoRegExp.cap(3), widget);
89         connect(Choqok::MediaManager::self(), &Choqok::MediaManager::imageFetched,
90                 this, &VideoPreview::slotImageFetched);
91         Choqok::MediaManager::self()->fetchImage(thumbUrl, Choqok::MediaManager::Async);
92     }
93 
94 }
95 
startParsing()96 void VideoPreview::startParsing()
97 {
98     int i = 8;
99     while (!postsQueue.isEmpty() && i > 0) {
100         parse(postsQueue.dequeue());
101         --i;
102     }
103 
104     if (postsQueue.isEmpty()) {
105         state = Stopped;
106     } else {
107         QTimer::singleShot(500, this, SLOT(startParsing()));
108     }
109 }
110 
parse(QPointer<Choqok::UI::PostWidget> postToParse)111 void VideoPreview::parse(QPointer<Choqok::UI::PostWidget> postToParse)
112 {
113     if (!postToParse) {
114         return;
115     }
116     int pos = 0;
117     int pos1 = 0;
118     int pos2 = 0;
119     int pos3 = 0;
120     QList<QUrl> thumbList;
121 
122     QString content = postToParse->currentPost()->content;
123 
124     while (((pos1 = mYouTuRegExp.indexIn(content, pos)) != -1) |
125             ((pos2 = mYouTubeRegExp.indexIn(content, pos)) != -1) |
126             ((pos3 = mVimeoRegExp.indexIn(content, pos)) != -1)) {
127 
128         if (pos1 >= 0) {
129             pos = pos1 + mYouTuRegExp.matchedLength();
130             if (mYouTuCode.indexIn(mYouTuRegExp.cap(0)) != -1) {
131                 thumbList << parseYoutube(mYouTuCode.cap(2), postToParse);
132             }
133         } else if (pos2 >= 0) {
134             pos = pos2 + mYouTubeRegExp.matchedLength();
135             QUrl thisurl(mYouTubeRegExp.cap(0));
136             QUrlQuery thisurlQuery(thisurl);
137             thumbList << parseYoutube(thisurlQuery.queryItemValue(QLatin1String("v")), postToParse);
138         } else if (pos3 >= 0) {
139             pos = pos3 + mVimeoRegExp.matchedLength();
140             thumbList << parseVimeo(mVimeoRegExp.cap(3), postToParse);
141         }
142     }
143 
144     for (const QUrl &thumb_url: thumbList) {
145         connect(Choqok::MediaManager::self(), &Choqok::MediaManager::imageFetched,
146                 this, &VideoPreview::slotImageFetched);
147 
148         Choqok::MediaManager::self()->fetchImage(thumb_url, Choqok::MediaManager::Async);
149     }
150 
151 }
152 
parseYoutube(QString videoid,QPointer<Choqok::UI::PostWidget> postToParse)153 QUrl VideoPreview::parseYoutube(QString videoid, QPointer< Choqok::UI::PostWidget > postToParse)
154 {
155     QString youtubeUrl = QStringLiteral("https://gdata.youtube.com/feeds/api/videos/%1").arg(videoid);
156     QUrl th_url(youtubeUrl);
157     KIO::StoredTransferJob *job = KIO::storedGet(th_url, KIO::NoReload, KIO::HideProgressInfo);
158     KJobWidgets::setWindow(job, Choqok::UI::Global::mainWindow());
159     QString title, description;
160     QUrl thumb_url;
161 
162     job->exec();
163     if (!job->error()) {
164         QDomDocument document;
165         document.setContent(job->data());
166         QDomElement root = document.documentElement();
167         if (!root.isNull()) {
168             QDomElement node;
169             node = root.firstChildElement(QLatin1String("title"));
170             if (!node.isNull()) {
171                 title = QString(node.text());
172             }
173             node = root.firstChildElement(QLatin1String("media:group"));
174             node = node.firstChildElement(QLatin1String("media:description"));
175             if (!node.isNull()) {
176                 description = QString(node.text());
177             }
178 
179             node = node.nextSiblingElement(QLatin1String("media:thumbnail"));
180             if (!node.isNull()) {
181                 thumb_url = QUrl::fromUserInput(node.attributeNode(QLatin1String("url")).value());
182             }
183         }
184 
185         description = description.left(70);
186 
187         mParsingList.insert(thumb_url, postToParse);
188         mBaseUrlMap.insert(thumb_url, QLatin1String("https://www.youtube.com/watch?v=") + videoid);
189         mTitleVideoMap.insert(thumb_url, title);
190         mDescriptionVideoMap.insert(thumb_url, description);
191     } else {
192         qCritical() << "Youtube XML response is NULL!";
193     }
194 
195     return thumb_url;
196 }
197 
parseVimeo(QString videoid,QPointer<Choqok::UI::PostWidget> postToParse)198 QUrl VideoPreview::parseVimeo(QString videoid, QPointer< Choqok::UI::PostWidget > postToParse)
199 {
200     QString vimeoUrl = QStringLiteral("https://vimeo.com/api/v2/video/%1.xml").arg(videoid);
201     QUrl th_url(vimeoUrl);
202     QEventLoop loop;
203     KIO::StoredTransferJob *job = KIO::storedGet(th_url, KIO::NoReload, KIO::HideProgressInfo);
204     KJobWidgets::setWindow(job, Choqok::UI::Global::mainWindow());
205     QString title, description;
206     QUrl thumb_url;
207 
208     job->exec();
209     if (!job->error()) {
210         QDomDocument document;
211         document.setContent(job->data());
212         QDomElement root = document.documentElement();
213         if (!root.isNull()) {
214             QDomElement videotag;
215             videotag = root.firstChildElement(QLatin1String("video"));
216             if (!videotag.isNull()) {
217                 QDomElement node;
218                 node = videotag.firstChildElement(QLatin1String("title"));
219                 if (!node.isNull()) {
220                     title = QString(node.text());
221                 }
222                 node = videotag.firstChildElement(QLatin1String("description"));
223                 if (!node.isNull()) {
224                     description = QString(node.text());
225                 }
226                 node = videotag.firstChildElement(QLatin1String("thumbnail_small"));
227                 if (!node.isNull()) {
228                     thumb_url = QUrl::fromUserInput(node.text());
229                 }
230             }
231         }
232         description = description.left(70);
233 
234         mParsingList.insert(thumb_url, postToParse);
235         mBaseUrlMap.insert(thumb_url, QLatin1String("https://vimeo.com/") + videoid);
236         mTitleVideoMap.insert(thumb_url, title);
237         mDescriptionVideoMap.insert(thumb_url, description);
238     } else {
239         qCritical() << "Vimeo XML response is NULL!";
240     }
241 
242     return thumb_url;
243 }
244 
slotImageFetched(const QUrl & remoteUrl,const QPixmap & pixmap)245 void VideoPreview::slotImageFetched(const QUrl &remoteUrl, const QPixmap &pixmap)
246 {
247     Choqok::UI::PostWidget *postToParse = mParsingList.take(remoteUrl);
248     QString baseUrl = mBaseUrlMap.take(remoteUrl);
249     QString title = mTitleVideoMap.take(remoteUrl);
250     QString description = mDescriptionVideoMap.take(remoteUrl);
251 
252     if (!postToParse) {
253         return;
254     }
255     QString content = postToParse->content();
256     QUrl imgU(remoteUrl);
257     imgU.setScheme(QLatin1String("img"));
258     postToParse->mainWidget()->document()->addResource(QTextDocument::ImageResource, imgU, pixmap);
259 
260     QString temp(QLatin1String("<br/><table><tr><td rowspan=2><img align='left' height=64 src='")
261                  + imgU.toDisplayString() + QLatin1String("' /></td>"));
262     temp.append(QLatin1String("<td><a href='") + baseUrl + QLatin1String("' title='") + baseUrl + QLatin1String("'><b>") + title + QLatin1String("</b></a></td></tr>"));
263     temp.append(QLatin1String("<tr><font size=\"-1\">") + description + QLatin1String("</font></tr></table>"));
264 
265     content.append(temp);
266     postToParse->setContent(content);
267 }
268 
269 #include "videopreview.moc"
270