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"
35 #include "compatibility/qurlqueryhelper.h"
38 NetworkAccess* http();
39 QHash<QString, QAction*>* globalActions();
44 QDateTime RFC3339fromString(const QString &s) {
45 return QDateTime::fromString(s, "yyyy-MM-ddThh:mm:ssZ");
48 QString RFC3339toString(const QDateTime &dt) {
49 return dt.toString("yyyy-MM-ddThh:mm:ssZ");
54 YTSearch::YTSearch(SearchParams *searchParams, QObject *parent) :
55 PaginatedVideoSource(parent),
56 searchParams(searchParams) {
57 searchParams->setParent(this);
62 void YTSearch::loadVideos(int max, int startIndex) {
65 QUrl url = YT3::instance().method("search");
67 QUrlQueryHelper urlHelper(url);
68 urlHelper.addQueryItem("part", "snippet");
69 urlHelper.addQueryItem("type", "video");
70 urlHelper.addQueryItem("maxResults", QString::number(max));
73 if (maybeReloadToken(max, startIndex)) return;
74 urlHelper.addQueryItem("pageToken", nextPageToken);
77 // TODO interesting params
78 // urlHelper.addQueryItem("videoSyndicated", "true");
79 // urlHelper.addQueryItem("regionCode", "IT");
80 // urlHelper.addQueryItem("videoType", "movie");
82 if (!searchParams->keywords().isEmpty()) {
83 if (searchParams->keywords().startsWith("http://") ||
84 searchParams->keywords().startsWith("https://")) {
85 urlHelper.addQueryItem("q", YTSearch::videoIdFromUrl(searchParams->keywords()));
86 } else urlHelper.addQueryItem("q", searchParams->keywords());
89 if (!searchParams->channelId().isEmpty())
90 urlHelper.addQueryItem("channelId", searchParams->channelId());
92 switch (searchParams->sortBy()) {
93 case SearchParams::SortByNewest:
94 urlHelper.addQueryItem("order", "date");
96 case SearchParams::SortByViewCount:
97 urlHelper.addQueryItem("order", "viewCount");
99 case SearchParams::SortByRating:
100 urlHelper.addQueryItem("order", "rating");
104 switch (searchParams->duration()) {
105 case SearchParams::DurationShort:
106 urlHelper.addQueryItem("videoDuration", "short");
108 case SearchParams::DurationMedium:
109 urlHelper.addQueryItem("videoDuration", "medium");
111 case SearchParams::DurationLong:
112 urlHelper.addQueryItem("videoDuration", "long");
116 switch (searchParams->time()) {
117 case SearchParams::TimeToday:
118 urlHelper.addQueryItem("publishedAfter", RFC3339toString(QDateTime::currentDateTimeUtc().addSecs(-60*60*24)));
120 case SearchParams::TimeWeek:
121 urlHelper.addQueryItem("publishedAfter", RFC3339toString(QDateTime::currentDateTimeUtc().addSecs(-60*60*24*7)));
123 case SearchParams::TimeMonth:
124 urlHelper.addQueryItem("publishedAfter", RFC3339toString(QDateTime::currentDateTimeUtc().addSecs(-60*60*24*30)));
128 if (searchParams->publishedAfter()) {
129 urlHelper.addQueryItem("publishedAfter", RFC3339toString(QDateTime::fromTime_t(searchParams->publishedAfter()).toUTC()));
132 switch (searchParams->quality()) {
133 case SearchParams::QualityHD:
134 urlHelper.addQueryItem("videoDefinition", "high");
141 // qWarning() << "YT3 search" << url.toString();
142 QObject *reply = The::http()->get(url);
143 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
144 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
147 void YTSearch::parseResults(QByteArray data) {
150 YT3ListParser parser(data);
151 QList<Video*> videos = parser.getVideos();
152 suggestions = parser.getSuggestions();
154 bool tryingWithNewToken = setPageToken(parser.getNextPageToken());
155 if (tryingWithNewToken) return;
157 if (name.isEmpty() && !searchParams->channelId().isEmpty()) {
158 if (!videos.isEmpty()) {
159 name = videos.first()->channelTitle();
161 emit nameChanged(name);
165 emit gotVideos(videos);
166 emit finished(videos.size());
168 loadVideoDetails(videos);
173 void YTSearch::loadVideos(int max, int startIndex) {
176 QUrl url("http://gdata.youtube.com/feeds/api/videos/");
178 QUrlQueryHelper urlHelper(url);
180 urlHelper.addQueryItem("v", "2");
181 urlHelper.addQueryItem("max-results", QString::number(max));
182 urlHelper.addQueryItem("start-index", QString::number(startIndex));
184 if (!searchParams->keywords().isEmpty()) {
185 if (searchParams->keywords().startsWith("http://") ||
186 searchParams->keywords().startsWith("https://")) {
187 urlHelper.addQueryItem("q", YTSearch::videoIdFromUrl(searchParams->keywords()));
188 } else urlHelper.addQueryItem("q", searchParams->keywords());
191 if (!searchParams->channelId().isEmpty())
192 urlHelper.addQueryItem("author", searchParams->channelId());
194 switch (searchParams->sortBy()) {
195 case SearchParams::SortByNewest:
196 urlHelper.addQueryItem("orderby", "published");
198 case SearchParams::SortByViewCount:
199 urlHelper.addQueryItem("orderby", "viewCount");
201 case SearchParams::SortByRating:
202 urlHelper.addQueryItem("orderby", "rating");
206 switch (searchParams->duration()) {
207 case SearchParams::DurationShort:
208 urlHelper.addQueryItem("duration", "short");
210 case SearchParams::DurationMedium:
211 urlHelper.addQueryItem("duration", "medium");
213 case SearchParams::DurationLong:
214 urlHelper.addQueryItem("duration", "long");
218 switch (searchParams->time()) {
219 case SearchParams::TimeToday:
220 urlHelper.addQueryItem("time", "today");
222 case SearchParams::TimeWeek:
223 urlHelper.addQueryItem("time", "this_week");
225 case SearchParams::TimeMonth:
226 urlHelper.addQueryItem("time", "this_month");
230 switch (searchParams->quality()) {
231 case SearchParams::QualityHD:
232 urlHelper.addQueryItem("hd", "true");
237 QObject *reply = The::http()->get(url);
238 connect(reply, SIGNAL(data(QByteArray)), SLOT(parseResults(QByteArray)));
239 connect(reply, SIGNAL(error(QNetworkReply*)), SLOT(requestError(QNetworkReply*)));
242 void YTSearch::parseResults(QByteArray data) {
245 YTFeedReader reader(data);
246 QList<Video*> videos = reader.getVideos();
247 suggestions = reader.getSuggestions();
249 if (name.isEmpty() && !searchParams->channelId().isEmpty()) {
250 if (videos.isEmpty()) name = searchParams->channelId();
252 name = videos.first()->channelTitle();
253 // also grab the userId
254 userId = videos.first()->channelId();
256 emit nameChanged(name);
259 emit gotVideos(videos);
260 emit finished(videos.size());
265 void YTSearch::abort() {
269 const QStringList & YTSearch::getSuggestions() {
273 QString YTSearch::getName() {
274 if (!name.isEmpty()) return name;
275 if (!searchParams->keywords().isEmpty()) return searchParams->keywords();
279 void YTSearch::requestError(QNetworkReply *reply) {
280 qWarning() << reply->errorString();
281 emit error(reply->errorString());
284 QString YTSearch::videoIdFromUrl(const QString &url) {
285 QRegExp re = QRegExp("^.*[\\?&]v=([^&#]+).*$");
286 if (re.exactMatch(url)) return re.cap(1);
287 re = QRegExp("^.*://.*/([^&#\\?]+).*$");
288 if (re.exactMatch(url)) return re.cap(1);
292 QList<QAction*> YTSearch::getActions() {
293 QList<QAction*> channelActions;
294 if (searchParams->channelId().isEmpty())
295 return channelActions;
296 channelActions << The::globalActions()->value("subscribe-channel");
297 return channelActions;