]> git.sur5r.net Git - minitube/blobdiff - src/yt/ytjs/ytjschannelsource.cpp
Update upstream source from tag 'upstream/3.8'
[minitube] / src / yt / ytjs / ytjschannelsource.cpp
diff --git a/src/yt/ytjs/ytjschannelsource.cpp b/src/yt/ytjs/ytjschannelsource.cpp
new file mode 100644 (file)
index 0000000..f09bbab
--- /dev/null
@@ -0,0 +1,167 @@
+#include "ytjschannelsource.h"
+
+#include "mainwindow.h"
+#include "searchparams.h"
+#include "video.h"
+#include "ytsearch.h"
+
+#include "js.h"
+
+namespace {
+
+int parseDuration(const QString &s) {
+    static const QTime zeroTime(0, 0);
+    QTime time = QTime::fromString(s, QStringLiteral("hh:mm:ss"));
+    return zeroTime.secsTo(time);
+}
+
+QString parseChannelId(const QString &channelUrl) {
+    int pos = channelUrl.lastIndexOf('/');
+    if (pos >= 0) return channelUrl.mid(pos + 1);
+    return QString();
+}
+
+QDateTime parsePublishedText(const QString &s) {
+    int num = 0;
+    const auto parts = s.splitRef(' ');
+    for (const auto &part : parts) {
+        num = part.toInt();
+        if (num > 0) break;
+    }
+    if (num == 0) return QDateTime();
+
+    auto now = QDateTime::currentDateTimeUtc();
+    if (s.contains("hour")) {
+        return now.addSecs(-num * 3600);
+    } else if (s.contains("day")) {
+        return now.addDays(-num);
+    } else if (s.contains("week")) {
+        return now.addDays(-num * 7);
+    } else if (s.contains("month")) {
+        return now.addMonths(-num);
+    } else if (s.contains("year")) {
+        return now.addDays(-num * 365);
+    }
+    return QDateTime();
+}
+
+} // namespace
+
+YTJSChannelSource::YTJSChannelSource(SearchParams *searchParams, QObject *parent)
+    : VideoSource(parent), searchParams(searchParams) {}
+
+void YTJSChannelSource::loadVideos(int max, int startIndex) {
+    aborted = false;
+
+    QString channelId = searchParams->channelId();
+
+    QString sortBy;
+    switch (searchParams->sortBy()) {
+    case SearchParams::SortByNewest:
+        sortBy = "newest";
+        break;
+    case SearchParams::SortByViewCount:
+        sortBy = "oldest";
+        break;
+    case SearchParams::SortByRating:
+        sortBy = "popular";
+        break;
+    }
+
+    if (startIndex <= 1) continuation.clear();
+
+    auto &js = JS::instance();
+
+    js.callFunction(new JSResult(this), "channelVideos", {channelId, sortBy, continuation})
+            .onJson([this](auto &doc) {
+                auto obj = doc.object();
+
+                qDebug() << doc.toJson();
+
+                continuation = obj["continuation"].toString();
+
+                const auto items = obj["items"].toArray();
+                QVector<Video *> videos;
+                videos.reserve(items.size());
+
+                for (const auto &i : items) {
+                    QString type = i["type"].toString();
+                    if (type != "video") continue;
+
+                    Video *video = new Video();
+
+                    QString id = i["videoId"].toString();
+                    video->setId(id);
+
+                    QString title = i["title"].toString();
+                    video->setTitle(title);
+
+                    QString desc = i["description"].toString();
+                    if (desc.isEmpty()) desc = i["desc"].toString();
+                    video->setDescription(desc);
+
+                    const auto thumbs = i["videoThumbnails"].toArray();
+                    for (const auto &thumbObj : thumbs) {
+                        QString url = thumbObj["url"].toString();
+                        int width = thumbObj["width"].toInt();
+                        if (width >= 336)
+                            video->setLargeThumbnailUrl(url);
+                        else if (width >= 246)
+                            video->setMediumThumbnailUrl(url);
+                        else if (width >= 168)
+                            video->setThumbnailUrl(url);
+                    }
+
+                    int views = i["viewCount"].toInt();
+                    video->setViewCount(views);
+
+                    int duration = i["lengthSeconds"].toInt();
+                    video->setDuration(duration);
+
+                    auto published = parsePublishedText(i["publishedText"].toString());
+                    if (published.isValid()) video->setPublished(published);
+
+                    QString channelName = i["author"].toString();
+                    if (channelName != name) {
+                        this->name = channelName;
+                        emit nameChanged(name);
+                    }
+                    video->setChannelTitle(channelName);
+                    QString channelId = i["authorId"].toString();
+                    video->setChannelId(channelId);
+
+                    videos << video;
+                }
+
+                emit gotVideos(videos);
+                emit finished(videos.size());
+            })
+            .onError([this, &js, max, startIndex](auto &msg) {
+                static int retries = 0;
+                if (retries < 3) {
+                    qDebug() << "Retrying...";
+                    auto nam = js.getEngine().networkAccessManager();
+                    nam->clearAccessCache();
+                    nam->setCookieJar(new QNetworkCookieJar());
+                    QTimer::singleShot(0, this,
+                                       [this, max, startIndex] { loadVideos(max, startIndex); });
+                    retries++;
+                } else {
+                    emit error(msg);
+                }
+            });
+}
+
+QString YTJSChannelSource::getName() {
+    return name;
+}
+
+const QList<QAction *> &YTJSChannelSource::getActions() {
+    static const QList<QAction *> channelActions = {
+            MainWindow::instance()->getAction("subscribeChannel")};
+    if (searchParams->channelId().isEmpty()) {
+        static const QList<QAction *> noActions;
+        return noActions;
+    }
+    return channelActions;
+}