3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
6 Minitube is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 Minitube is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with Minitube. If not, see <http://www.gnu.org/licenses/>.
22 #include "constants.h"
23 #include "networkaccess.h"
24 #include "searchparams.h"
26 #include "ytchannel.h"
30 #include "yt3listparser.h"
31 #include "datautils.h"
33 #include "ytfeedreader.h"
37 NetworkAccess* http();
38 QHash<QString, QAction*>* globalActions();
43 QDateTime RFC3339fromString(const QString &s) {
44 return QDateTime::fromString(s, "yyyy-MM-ddThh:mm:ssZ");
47 QString RFC3339toString(const QDateTime &dt) {
48 return dt.toString("yyyy-MM-ddThh:mm:ssZ");
53 YTSearch::YTSearch(SearchParams *searchParams, QObject *parent) :
54 PaginatedVideoSource(parent),
55 searchParams(searchParams) {
56 searchParams->setParent(this);
61 void YTSearch::loadVideos(int max, int startIndex) {
64 QUrl url = YT3::instance().method("search");
66 #if QT_VERSION >= 0x050000
72 url.addQueryItem("part", "snippet");
73 url.addQueryItem("type", "video");
75 url.addQueryItem("maxResults", QString::number(max));
78 if (maybeReloadToken(max, startIndex)) return;
79 url.addQueryItem("pageToken", nextPageToken);
82 // TODO interesting params
83 // url.addQueryItem("videoSyndicated", "true");
84 // url.addQueryItem("regionCode", "IT");
85 // url.addQueryItem("videoType", "movie");
87 if (!searchParams->keywords().isEmpty()) {
88 if (searchParams->keywords().startsWith("http://") ||
89 searchParams->keywords().startsWith("https://")) {
90 url.addQueryItem("q", YTSearch::videoIdFromUrl(searchParams->keywords()));
91 } else url.addQueryItem("q", searchParams->keywords());
94 if (!searchParams->channelId().isEmpty())
95 url.addQueryItem("channelId", searchParams->channelId());
97 switch (searchParams->sortBy()) {
98 case SearchParams::SortByNewest:
99 url.addQueryItem("order", "date");
101 case SearchParams::SortByViewCount:
102 url.addQueryItem("order", "viewCount");
104 case SearchParams::SortByRating:
105 url.addQueryItem("order", "rating");
109 switch (searchParams->duration()) {
110 case SearchParams::DurationShort:
111 url.addQueryItem("videoDuration", "short");
113 case SearchParams::DurationMedium:
114 url.addQueryItem("videoDuration", "medium");
116 case SearchParams::DurationLong:
117 url.addQueryItem("videoDuration", "long");
121 switch (searchParams->time()) {
122 case SearchParams::TimeToday:
123 url.addQueryItem("publishedAfter", RFC3339toString(QDateTime::currentDateTimeUtc().addSecs(-60*60*24)));
125 case SearchParams::TimeWeek:
126 url.addQueryItem("publishedAfter", RFC3339toString(QDateTime::currentDateTimeUtc().addSecs(-60*60*24*7)));
128 case SearchParams::TimeMonth:
129 url.addQueryItem("publishedAfter", RFC3339toString(QDateTime::currentDateTimeUtc().addSecs(-60*60*24*30)));
133 if (searchParams->publishedAfter()) {
134 url.addQueryItem("publishedAfter", RFC3339toString(QDateTime::fromTime_t(searchParams->publishedAfter()).toUTC()));
137 switch (searchParams->quality()) {
138 case SearchParams::QualityHD:
139 url.addQueryItem("videoDefinition", "high");
143 #if QT_VERSION >= 0x050000
150 // qWarning() << "YT3 search" << url.toString();
151 QObject *reply = The::http()->get(url);
152 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
153 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
156 void YTSearch::parseResults(QByteArray data) {
159 YT3ListParser parser(data);
160 QList<Video*> videos = parser.getVideos();
161 suggestions = parser.getSuggestions();
163 bool tryingWithNewToken = setPageToken(parser.getNextPageToken());
164 if (tryingWithNewToken) return;
166 if (name.isEmpty() && !searchParams->channelId().isEmpty()) {
167 if (!videos.isEmpty()) {
168 name = videos.first()->channelTitle();
170 emit nameChanged(name);
174 emit gotVideos(videos);
175 emit finished(videos.size());
177 loadVideoDetails(videos);
182 void YTSearch::loadVideos(int max, int startIndex) {
185 QUrl url("http://gdata.youtube.com/feeds/api/videos/");
186 #if QT_VERSION >= 0x050000
192 url.addQueryItem("v", "2");
193 url.addQueryItem("max-results", QString::number(max));
194 url.addQueryItem("start-index", QString::number(startIndex));
196 if (!searchParams->keywords().isEmpty()) {
197 if (searchParams->keywords().startsWith("http://") ||
198 searchParams->keywords().startsWith("https://")) {
199 url.addQueryItem("q", YTSearch::videoIdFromUrl(searchParams->keywords()));
200 } else url.addQueryItem("q", searchParams->keywords());
203 if (!searchParams->channelId().isEmpty())
204 url.addQueryItem("author", searchParams->channelId());
206 switch (searchParams->sortBy()) {
207 case SearchParams::SortByNewest:
208 url.addQueryItem("orderby", "published");
210 case SearchParams::SortByViewCount:
211 url.addQueryItem("orderby", "viewCount");
213 case SearchParams::SortByRating:
214 url.addQueryItem("orderby", "rating");
218 switch (searchParams->duration()) {
219 case SearchParams::DurationShort:
220 url.addQueryItem("duration", "short");
222 case SearchParams::DurationMedium:
223 url.addQueryItem("duration", "medium");
225 case SearchParams::DurationLong:
226 url.addQueryItem("duration", "long");
230 switch (searchParams->time()) {
231 case SearchParams::TimeToday:
232 url.addQueryItem("time", "today");
234 case SearchParams::TimeWeek:
235 url.addQueryItem("time", "this_week");
237 case SearchParams::TimeMonth:
238 url.addQueryItem("time", "this_month");
242 switch (searchParams->quality()) {
243 case SearchParams::QualityHD:
244 url.addQueryItem("hd", "true");
248 #if QT_VERSION >= 0x050000
252 QObject *reply = The::http()->get(url);
253 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
254 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
257 void YTSearch::parseResults(QByteArray data) {
260 YTFeedReader reader(data);
261 QList<Video*> videos = reader.getVideos();
262 suggestions = reader.getSuggestions();
264 if (name.isEmpty() && !searchParams->channelId().isEmpty()) {
265 if (videos.isEmpty()) name = searchParams->channelId();
267 name = videos.first()->channelTitle();
268 // also grab the userId
269 userId = videos.first()->channelId();
271 emit nameChanged(name);
274 emit gotVideos(videos);
275 emit finished(videos.size());
280 void YTSearch::abort() {
284 const QStringList & YTSearch::getSuggestions() {
288 QString YTSearch::getName() {
289 if (!name.isEmpty()) return name;
290 if (!searchParams->keywords().isEmpty()) return searchParams->keywords();
294 void YTSearch::requestError(QNetworkReply *reply) {
295 qWarning() << reply->errorString();
296 emit error(reply->errorString());
299 QString YTSearch::videoIdFromUrl(QString url) {
300 QRegExp re = QRegExp("^.*[\\?&]v=([^&#]+).*$");
301 if (re.exactMatch(url)) return re.cap(1);
302 re = QRegExp("^.*://.*/([^&#\\?]+).*$");
303 if (re.exactMatch(url)) return re.cap(1);
307 QList<QAction*> YTSearch::getActions() {
308 QList<QAction*> channelActions;
309 if (searchParams->channelId().isEmpty())
310 return channelActions;
311 channelActions << The::globalActions()->value("subscribe-channel");
312 return channelActions;