1 #include "ytjssearch.h"
3 #include "mainwindow.h"
4 #include "searchparams.h"
11 int parseDuration(const QString &s) {
12 const auto parts = s.splitRef(':');
15 for (auto i = parts.crbegin(); i != parts.crend(); ++i) {
19 secs += i->toInt() * 60;
21 secs += i->toInt() * 60 * 60;
28 QString parseChannelId(const QString &channelUrl) {
29 int pos = channelUrl.lastIndexOf('/');
30 if (pos >= 0) return channelUrl.mid(pos + 1);
36 YTJSSearch::YTJSSearch(SearchParams *searchParams, QObject *parent)
37 : VideoSource(parent), searchParams(searchParams) {}
39 void YTJSSearch::loadVideos(int max, int startIndex) {
40 auto &ytjs = YTJS::instance();
41 if (!ytjs.isInitialized()) {
42 QTimer::singleShot(500, this, [this, max, startIndex] { loadVideos(max, startIndex); });
45 auto &engine = ytjs.getEngine();
49 auto function = engine.evaluate("search");
50 if (!function.isCallable()) {
51 qWarning() << function.toString() << " is not callable";
52 emit error(function.toString());
57 if (!searchParams->keywords().isEmpty()) {
58 if (searchParams->keywords().startsWith("http://") ||
59 searchParams->keywords().startsWith("https://")) {
60 q = YTSearch::videoIdFromUrl(searchParams->keywords());
62 q = searchParams->keywords();
67 QJSValue options = engine.newObject();
69 if (startIndex > 1 && !nextpageRef.isEmpty()) options.setProperty("nextpageRef", nextpageRef);
70 options.setProperty("limit", max);
72 switch (searchParams->safeSearch()) {
73 case SearchParams::None:
74 options.setProperty("safeSearch", false);
76 case SearchParams::Strict:
77 options.setProperty("safeSearch", true);
83 auto filterMap = engine.evaluate("new Map()");
84 auto jsMapSet = filterMap.property("set");
85 auto addFilter = [&filterMap, &jsMapSet](QString name, QString value) {
86 jsMapSet.callWithInstance(filterMap, {name, value});
89 addFilter("Type", "Video");
91 switch (searchParams->sortBy()) {
92 case SearchParams::SortByNewest:
93 addFilter("Sort by", "Upload date");
95 case SearchParams::SortByViewCount:
96 addFilter("Sort by", "View count");
98 case SearchParams::SortByRating:
99 addFilter("Sort by", "Rating");
103 switch (searchParams->duration()) {
104 case SearchParams::DurationShort:
105 addFilter("Duration", "Short");
107 case SearchParams::DurationMedium:
108 case SearchParams::DurationLong:
109 addFilter("Duration", "Long");
113 switch (searchParams->time()) {
114 case SearchParams::TimeToday:
115 addFilter("Upload date", "Today");
117 case SearchParams::TimeWeek:
118 addFilter("Upload date", "This week");
120 case SearchParams::TimeMonth:
121 addFilter("Upload date", "This month");
125 switch (searchParams->quality()) {
126 case SearchParams::QualityHD:
127 addFilter("Features", "HD");
129 case SearchParams::Quality4K:
130 addFilter("Features", "4K");
132 case SearchParams::QualityHDR:
133 addFilter("Features", "HDR");
137 auto handler = new ResultHandler;
138 connect(handler, &ResultHandler::error, this, &VideoSource::error);
139 connect(handler, &ResultHandler::data, this, [this](const QJsonDocument &doc) {
142 auto obj = doc.object();
144 nextpageRef = obj["nextpageRef"].toString();
146 const auto items = obj["items"].toArray();
147 QVector<Video *> videos;
148 videos.reserve(items.size());
150 for (const auto &i : items) {
151 QString type = i["type"].toString();
152 if (type != "video") continue;
154 Video *video = new Video();
156 QString id = YTSearch::videoIdFromUrl(i["link"].toString());
159 QString title = i["title"].toString();
160 video->setTitle(title);
162 QString desc = i["description"].toString();
163 video->setDescription(desc);
165 QString thumb = i["thumbnail"].toString();
166 video->setThumbnailUrl(thumb);
168 int views = i["views"].toInt();
169 video->setViewCount(views);
171 int duration = parseDuration(i["duration"].toString());
172 video->setDuration(duration);
174 auto authorObj = i["author"];
175 QString channelName = authorObj["name"].toString();
176 video->setChannelTitle(channelName);
177 QString channelId = parseChannelId(authorObj["ref"].toString());
178 video->setChannelId(channelId);
183 emit gotVideos(videos);
184 emit finished(videos.size());
186 QJSValue h = engine.newQObject(handler);
187 auto value = function.call({h, q, options, filterMap});
188 if (ytjs.checkError(value)) emit error(value.toString());
191 QString YTJSSearch::getName() {
192 if (!name.isEmpty()) return name;
193 if (!searchParams->keywords().isEmpty()) return searchParams->keywords();
197 const QList<QAction *> &YTJSSearch::getActions() {
198 static const QList<QAction *> channelActions = {
199 MainWindow::instance()->getAction("subscribeChannel")};
200 if (searchParams->channelId().isEmpty()) {
201 static const QList<QAction *> noActions;
204 return channelActions;