]> git.sur5r.net Git - minitube/blob - src/ytjs/ytjssearch.cpp
c5a5031ff1a87adb511333748d78a26f1dcf09cd
[minitube] / src / ytjs / ytjssearch.cpp
1 #include "ytjssearch.h"
2
3 #include "mainwindow.h"
4 #include "searchparams.h"
5 #include "video.h"
6 #include "ytjs.h"
7 #include "ytsearch.h"
8
9 namespace {
10
11 int parseDuration(const QString &s) {
12     const auto parts = s.splitRef(':');
13     int secs = 0;
14     int p = 0;
15     for (auto i = parts.crbegin(); i != parts.crend(); ++i) {
16         if (p == 0) {
17             secs = i->toInt();
18         } else if (p == 1) {
19             secs += i->toInt() * 60;
20         } else if (p == 2) {
21             secs += i->toInt() * 60 * 60;
22         }
23         p++;
24     }
25     return secs;
26 }
27
28 QString parseChannelId(const QString &channelUrl) {
29     int pos = channelUrl.lastIndexOf('/');
30     if (pos >= 0) return channelUrl.mid(pos + 1);
31     return QString();
32 }
33
34 } // namespace
35
36 YTJSSearch::YTJSSearch(SearchParams *searchParams, QObject *parent)
37     : VideoSource(parent), searchParams(searchParams) {}
38
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); });
43         return;
44     }
45     auto &engine = ytjs.getEngine();
46
47     aborted = false;
48
49     auto function = engine.evaluate("search");
50     if (!function.isCallable()) {
51         qWarning() << function.toString() << " is not callable";
52         emit error(function.toString());
53         return;
54     }
55
56     QString q;
57     if (!searchParams->keywords().isEmpty()) {
58         if (searchParams->keywords().startsWith("http://") ||
59             searchParams->keywords().startsWith("https://")) {
60             q = YTSearch::videoIdFromUrl(searchParams->keywords());
61         } else
62             q = searchParams->keywords();
63     }
64
65     // Options
66
67     QJSValue options = engine.newObject();
68
69     if (startIndex > 1 && !nextpageRef.isEmpty()) options.setProperty("nextpageRef", nextpageRef);
70     options.setProperty("limit", max);
71
72     switch (searchParams->safeSearch()) {
73     case SearchParams::None:
74         options.setProperty("safeSearch", false);
75         break;
76     case SearchParams::Strict:
77         options.setProperty("safeSearch", true);
78         break;
79     }
80
81     // Filters
82
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});
87     };
88
89     addFilter("Type", "Video");
90
91     switch (searchParams->sortBy()) {
92     case SearchParams::SortByNewest:
93         addFilter("Sort by", "Upload date");
94         break;
95     case SearchParams::SortByViewCount:
96         addFilter("Sort by", "View count");
97         break;
98     case SearchParams::SortByRating:
99         addFilter("Sort by", "Rating");
100         break;
101     }
102
103     switch (searchParams->duration()) {
104     case SearchParams::DurationShort:
105         addFilter("Duration", "Short");
106         break;
107     case SearchParams::DurationMedium:
108     case SearchParams::DurationLong:
109         addFilter("Duration", "Long");
110         break;
111     }
112
113     switch (searchParams->time()) {
114     case SearchParams::TimeToday:
115         addFilter("Upload date", "Today");
116         break;
117     case SearchParams::TimeWeek:
118         addFilter("Upload date", "This week");
119         break;
120     case SearchParams::TimeMonth:
121         addFilter("Upload date", "This month");
122         break;
123     }
124
125     switch (searchParams->quality()) {
126     case SearchParams::QualityHD:
127         addFilter("Features", "HD");
128         break;
129     case SearchParams::Quality4K:
130         addFilter("Features", "4K");
131         break;
132     case SearchParams::QualityHDR:
133         addFilter("Features", "HDR");
134         break;
135     }
136
137     auto handler = new ResultHandler;
138     connect(handler, &ResultHandler::error, this, &VideoSource::error);
139     connect(handler, &ResultHandler::data, this, [this](const QJsonDocument &doc) {
140         if (aborted) return;
141
142         auto obj = doc.object();
143
144         nextpageRef = obj["nextpageRef"].toString();
145
146         const auto items = obj["items"].toArray();
147         QVector<Video *> videos;
148         videos.reserve(items.size());
149
150         for (const auto &i : items) {
151             QString type = i["type"].toString();
152             if (type != "video") continue;
153
154             Video *video = new Video();
155
156             QString id = YTSearch::videoIdFromUrl(i["link"].toString());
157             video->setId(id);
158
159             QString title = i["title"].toString();
160             video->setTitle(title);
161
162             QString desc = i["description"].toString();
163             video->setDescription(desc);
164
165             QString thumb = i["thumbnail"].toString();
166             video->setThumbnailUrl(thumb);
167
168             int views = i["views"].toInt();
169             video->setViewCount(views);
170
171             int duration = parseDuration(i["duration"].toString());
172             video->setDuration(duration);
173
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);
179
180             videos << video;
181         }
182
183         emit gotVideos(videos);
184         emit finished(videos.size());
185     });
186     QJSValue h = engine.newQObject(handler);
187     auto value = function.call({h, q, options, filterMap});
188     if (ytjs.checkError(value)) emit error(value.toString());
189 }
190
191 QString YTJSSearch::getName() {
192     if (!name.isEmpty()) return name;
193     if (!searchParams->keywords().isEmpty()) return searchParams->keywords();
194     return QString();
195 }
196
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;
202         return noActions;
203     }
204     return channelActions;
205 }