1 #include "ytjschannelsource.h"
3 #include "mainwindow.h"
4 #include "searchparams.h"
12 int parseDuration(const QString &s) {
13 static const QTime zeroTime(0, 0);
14 QTime time = QTime::fromString(s, QStringLiteral("hh:mm:ss"));
15 return zeroTime.secsTo(time);
18 QString parseChannelId(const QString &channelUrl) {
19 int pos = channelUrl.lastIndexOf('/');
20 if (pos >= 0) return channelUrl.mid(pos + 1);
24 QDateTime parsePublishedText(const QString &s) {
26 const auto parts = s.splitRef(' ');
27 for (const auto &part : parts) {
31 if (num == 0) return QDateTime();
33 auto now = QDateTime::currentDateTimeUtc();
34 if (s.contains("hour")) {
35 return now.addSecs(-num * 3600);
36 } else if (s.contains("day")) {
37 return now.addDays(-num);
38 } else if (s.contains("week")) {
39 return now.addDays(-num * 7);
40 } else if (s.contains("month")) {
41 return now.addMonths(-num);
42 } else if (s.contains("year")) {
43 return now.addDays(-num * 365);
50 YTJSChannelSource::YTJSChannelSource(SearchParams *searchParams, QObject *parent)
51 : VideoSource(parent), searchParams(searchParams) {}
53 void YTJSChannelSource::loadVideos(int max, int startIndex) {
56 QString channelId = searchParams->channelId();
59 switch (searchParams->sortBy()) {
60 case SearchParams::SortByNewest:
63 case SearchParams::SortByViewCount:
66 case SearchParams::SortByRating:
71 if (startIndex <= 1) continuation.clear();
73 auto &js = JS::instance();
75 js.callFunction(new JSResult(this), "channelVideos", {channelId, sortBy, continuation})
76 .onJson([this](auto &doc) {
77 auto obj = doc.object();
79 qDebug() << doc.toJson();
81 continuation = obj["continuation"].toString();
83 const auto items = obj["items"].toArray();
84 QVector<Video *> videos;
85 videos.reserve(items.size());
87 for (const auto &i : items) {
88 QString type = i["type"].toString();
89 if (type != "video") continue;
91 Video *video = new Video();
93 QString id = i["videoId"].toString();
96 QString title = i["title"].toString();
97 video->setTitle(title);
99 QString desc = i["description"].toString();
100 if (desc.isEmpty()) desc = i["desc"].toString();
101 video->setDescription(desc);
103 const auto thumbs = i["videoThumbnails"].toArray();
104 for (const auto &thumbObj : thumbs) {
105 QString url = thumbObj["url"].toString();
106 int width = thumbObj["width"].toInt();
108 video->setLargeThumbnailUrl(url);
109 else if (width >= 246)
110 video->setMediumThumbnailUrl(url);
111 else if (width >= 168)
112 video->setThumbnailUrl(url);
115 int views = i["viewCount"].toInt();
116 video->setViewCount(views);
118 int duration = i["lengthSeconds"].toInt();
119 video->setDuration(duration);
121 auto published = parsePublishedText(i["publishedText"].toString());
122 if (published.isValid()) video->setPublished(published);
124 QString channelName = i["author"].toString();
125 if (channelName != name) {
126 this->name = channelName;
127 emit nameChanged(name);
129 video->setChannelTitle(channelName);
130 QString channelId = i["authorId"].toString();
131 video->setChannelId(channelId);
136 emit gotVideos(videos);
137 emit finished(videos.size());
139 .onError([this, &js, max, startIndex](auto &msg) {
140 static int retries = 0;
142 qDebug() << "Retrying...";
143 auto nam = js.getEngine().networkAccessManager();
144 nam->clearAccessCache();
145 nam->setCookieJar(new QNetworkCookieJar());
146 QTimer::singleShot(0, this,
147 [this, max, startIndex] { loadVideos(max, startIndex); });
155 QString YTJSChannelSource::getName() {
159 const QList<QAction *> &YTJSChannelSource::getActions() {
160 static const QList<QAction *> channelActions = {
161 MainWindow::instance()->getAction("subscribeChannel")};
162 if (searchParams->channelId().isEmpty()) {
163 static const QList<QAction *> noActions;
166 return channelActions;