1 #include "ytjschannelsource.h"
3 #include "mainwindow.h"
4 #include "searchparams.h"
11 int parseDuration(const QString &s) {
12 static const QTime zeroTime(0, 0);
13 QTime time = QTime::fromString(s, QStringLiteral("hh:mm:ss"));
14 return zeroTime.secsTo(time);
17 QString parseChannelId(const QString &channelUrl) {
18 int pos = channelUrl.lastIndexOf('/');
19 if (pos >= 0) return channelUrl.mid(pos + 1);
23 QDateTime parsePublishedText(const QString &s) {
25 const auto parts = s.splitRef(' ');
26 for (const auto &part : parts) {
30 if (num == 0) return QDateTime();
32 auto now = QDateTime::currentDateTimeUtc();
33 if (s.contains("day")) {
34 return now.addDays(-num);
35 } else if (s.contains("week")) {
36 return now.addDays(-num * 7);
37 } else if (s.contains("month")) {
38 return now.addMonths(-num);
39 } else if (s.contains("year")) {
40 return now.addDays(-num * 365);
47 YTJSChannelSource::YTJSChannelSource(SearchParams *searchParams, QObject *parent)
48 : VideoSource(parent), searchParams(searchParams) {}
50 void YTJSChannelSource::loadVideos(int max, int startIndex) {
51 auto &ytjs = YTJS::instance();
52 if (!ytjs.isInitialized()) {
53 QTimer::singleShot(500, this, [this, max, startIndex] { loadVideos(max, startIndex); });
56 auto &engine = ytjs.getEngine();
60 auto function = engine.evaluate("channelVideos");
61 if (!function.isCallable()) {
62 qWarning() << function.toString() << " is not callable";
63 emit error(function.toString());
67 QString channelId = searchParams->channelId();
70 switch (searchParams->sortBy()) {
71 case SearchParams::SortByNewest:
74 case SearchParams::SortByViewCount:
77 case SearchParams::SortByRating:
82 if (startIndex <= 1) continuation.clear();
84 auto handler = new ResultHandler;
85 connect(handler, &ResultHandler::error, this, &VideoSource::error);
86 connect(handler, &ResultHandler::data, this, [this](const QJsonDocument &doc) {
87 auto obj = doc.object();
89 qDebug() << doc.toJson();
91 continuation = obj["continuation"].toString();
93 const auto items = obj["items"].toArray();
94 QVector<Video *> videos;
95 videos.reserve(items.size());
97 for (const auto &i : items) {
98 QString type = i["type"].toString();
99 if (type != "video") continue;
101 Video *video = new Video();
103 QString id = i["videoId"].toString();
106 QString title = i["title"].toString();
107 video->setTitle(title);
109 QString desc = i["description"].toString();
110 if (desc.isEmpty()) desc = i["desc"].toString();
111 video->setDescription(desc);
113 const auto thumbs = i["videoThumbnails"].toArray();
114 for (const auto &thumbObj : thumbs) {
115 QString url = thumbObj["url"].toString();
116 int width = thumbObj["width"].toInt();
118 video->setLargeThumbnailUrl(url);
119 else if (width >= 246)
120 video->setMediumThumbnailUrl(url);
121 else if (width >= 168)
122 video->setThumbnailUrl(url);
125 int views = i["viewCount"].toInt();
126 video->setViewCount(views);
128 int duration = i["lengthSeconds"].toInt();
129 video->setDuration(duration);
131 auto published = parsePublishedText(i["publishedText"].toString());
132 if (published.isValid()) video->setPublished(published);
134 QString channelName = i["author"].toString();
135 if (channelName != name) {
136 this->name = channelName;
137 emit nameChanged(name);
139 video->setChannelTitle(channelName);
140 QString channelId = i["authorId"].toString();
141 video->setChannelId(channelId);
146 emit gotVideos(videos);
147 emit finished(videos.size());
149 QJSValue h = engine.newQObject(handler);
150 auto value = function.call({h, channelId, sortBy, continuation});
151 ytjs.checkError(value);
154 QString YTJSChannelSource::getName() {
158 const QList<QAction *> &YTJSChannelSource::getActions() {
159 static const QList<QAction *> channelActions = {
160 MainWindow::instance()->getAction("subscribeChannel")};
161 if (searchParams->channelId().isEmpty()) {
162 static const QList<QAction *> noActions;
165 return channelActions;