File indexing completed on 2025-02-23 04:35:15

0001 // SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
0002 // SPDX-License-Identifier: GPL-3.0-or-later
0003 
0004 #include "videosource.h"
0005 
0006 #include <QFutureWatcher>
0007 
0008 #include <invidious/invidiousapi.h>
0009 #include <peertube/peertubeapi.h>
0010 #include <piped/pipedapi.h>
0011 #include <qt6keychain/keychain.h>
0012 
0013 VideoSource::VideoSource(const QString &key, QObject *parent)
0014     : QObject(parent)
0015     , m_config(key)
0016     , m_key(key)
0017 {
0018     createApi();
0019 
0020     auto loop = new QEventLoop();
0021 
0022     auto job = new QKeychain::ReadPasswordJob(QStringLiteral("PlasmaTube"));
0023     job->setKey(cookieKey());
0024     job->start();
0025 
0026     QString value;
0027 
0028     QObject::connect(job, &QKeychain::ReadPasswordJob::finished, [loop, job, &value](QKeychain::Job *) {
0029         value = job->textData();
0030         loop->quit();
0031     });
0032 
0033     loop->exec();
0034 
0035     if (!value.isEmpty()) {
0036         m_cookie = value;
0037         setApiCookie();
0038     }
0039 
0040     fetchPreferences();
0041     fetchHistory();
0042     fetchSubscriptions();
0043 }
0044 
0045 QString VideoSource::uuid() const
0046 {
0047     return m_key;
0048 }
0049 
0050 QString VideoSource::url() const
0051 {
0052     return m_config.url();
0053 }
0054 
0055 void VideoSource::setUrl(const QString &url)
0056 {
0057     if (m_config.url() != url) {
0058         m_api->setApiHost(url);
0059         m_config.setUrl(url);
0060         m_config.save();
0061         Q_EMIT urlChanged();
0062     }
0063 }
0064 
0065 VideoSource::Type VideoSource::type() const
0066 {
0067     return static_cast<VideoSource::Type>(m_config.type());
0068 }
0069 
0070 void VideoSource::setType(const VideoSource::Type value)
0071 {
0072     if (static_cast<VideoSource::Type>(m_config.type()) != value) {
0073         m_config.setType(static_cast<int>(value));
0074         m_config.save();
0075         Q_EMIT typeChanged();
0076 
0077         createApi();
0078     }
0079 }
0080 
0081 bool VideoSource::loggedIn() const
0082 {
0083     return !m_cookie.isEmpty() && !username().isEmpty();
0084 }
0085 
0086 void VideoSource::logOut()
0087 {
0088     auto cookieDeleteJob = new QKeychain::DeletePasswordJob{QStringLiteral("PlasmaTube"), this};
0089     cookieDeleteJob->setKey(cookieKey());
0090     cookieDeleteJob->start();
0091 
0092     setUsername(QStringLiteral(""));
0093     m_cookie.clear();
0094 
0095     Q_EMIT credentialsChanged();
0096 }
0097 
0098 QString VideoSource::username() const
0099 {
0100     return m_config.username();
0101 }
0102 
0103 void VideoSource::setUsername(const QString &username)
0104 {
0105     if (m_config.username() != username) {
0106         m_config.setUsername(username);
0107         m_config.save();
0108         Q_EMIT usernameChanged();
0109     }
0110 }
0111 
0112 void VideoSource::setCookie(const QString &cookie)
0113 {
0114     m_cookie = cookie;
0115 
0116     auto cookieJob = new QKeychain::WritePasswordJob{QStringLiteral("PlasmaTube"), this};
0117     cookieJob->setKey(cookieKey());
0118     cookieJob->setTextData(cookie);
0119     cookieJob->start();
0120 
0121     setApiCookie();
0122 
0123     Q_EMIT credentialsChanged();
0124 }
0125 
0126 QString VideoSource::cookie() const
0127 {
0128     return m_cookie;
0129 }
0130 
0131 QInvidious::Preferences VideoSource::preferences()
0132 {
0133     return m_preferences;
0134 }
0135 
0136 void VideoSource::setPreferences(const QInvidious::Preferences &preferences)
0137 {
0138     if (!loggedIn()) {
0139         return;
0140     }
0141 
0142     m_api->setPreferences(preferences);
0143     m_preferences = preferences;
0144     Q_EMIT preferencesChanged();
0145 }
0146 
0147 void VideoSource::fetchPreferences()
0148 {
0149     if (!loggedIn()) {
0150         m_finishedLoading = true;
0151         Q_EMIT finishedLoading();
0152         return;
0153     }
0154 
0155     auto *watcher = new QFutureWatcher<QInvidious::PreferencesResult>();
0156     connect(watcher, &QFutureWatcherBase::finished, this, [this, watcher] {
0157         auto result = watcher->result();
0158 
0159         if (const auto prefs = std::get_if<QInvidious::Preferences>(&result)) {
0160             m_preferences = *prefs;
0161             Q_EMIT preferencesChanged();
0162         }
0163 
0164         m_finishedLoading = true;
0165         Q_EMIT finishedLoading();
0166 
0167         watcher->deleteLater();
0168     });
0169     watcher->setFuture(m_api->requestPreferences());
0170 }
0171 
0172 bool VideoSource::hasFinishedLoading() const
0173 {
0174     return m_finishedLoading;
0175 }
0176 
0177 QInvidious::AbstractApi *VideoSource::api() const
0178 {
0179     return m_api;
0180 }
0181 
0182 void VideoSource::createApi()
0183 {
0184     switch (type()) {
0185     case Type::Invidious:
0186         m_api = new QInvidious::InvidiousApi(new QNetworkAccessManager(this), this);
0187         break;
0188     case Type::PeerTube:
0189         m_api = new QInvidious::PeerTubeApi(new QNetworkAccessManager(this), this);
0190         break;
0191     case Type::Piped:
0192         m_api = new QInvidious::PipedApi(new QNetworkAccessManager(this), this);
0193         break;
0194     }
0195     connect(m_api, &QInvidious::AbstractApi::credentialsChanged, this, &VideoSource::credentialsChanged);
0196     m_api->setApiHost(m_config.url());
0197 }
0198 
0199 void VideoSource::setApiCookie()
0200 {
0201     m_api->setCredentials(QInvidious::Credentials(username(), m_cookie));
0202 }
0203 
0204 QString VideoSource::cookieKey()
0205 {
0206 #ifdef PLASMATUBE_FLATPAK
0207     return QStringLiteral("%1-flatpak-cookie").arg(m_key);
0208 #else
0209     return QStringLiteral("%1-cookie").arg(m_key);
0210 #endif
0211 }
0212 
0213 std::optional<bool> VideoSource::isSubscribedToChannel(const QString &jid) const
0214 {
0215     if (m_subscriptions.has_value()) {
0216         return m_subscriptions->contains(jid);
0217     }
0218     return std::nullopt;
0219 }
0220 
0221 void VideoSource::fetchSubscriptions()
0222 {
0223     if (!loggedIn()) {
0224         return;
0225     }
0226 
0227     auto *watcher = new QFutureWatcher<QInvidious::SubscriptionsResult>();
0228     connect(watcher, &QFutureWatcherBase::finished, this, [this, watcher] {
0229         auto result = watcher->result();
0230 
0231         if (const auto subscriptions = std::get_if<QList<QString>>(&result)) {
0232             setSubscriptions(*subscriptions);
0233         } else if (const auto error = std::get_if<QInvidious::Error>(&result)) {
0234             qDebug() << "Fetching subscriptions:" << error->first << error->second;
0235             // Q_EMIT errorOccurred(error->second);
0236         }
0237 
0238         watcher->deleteLater();
0239     });
0240     watcher->setFuture(m_api->requestSubscriptions());
0241 }
0242 
0243 void VideoSource::setSubscriptions(const QList<QString> &subscriptions)
0244 {
0245     m_subscriptions = subscriptions;
0246     Q_EMIT subscriptionsChanged();
0247 }
0248 
0249 std::optional<QList<QString>> &VideoSource::subscriptions()
0250 {
0251     return m_subscriptions;
0252 }
0253 
0254 bool VideoSource::isVideoWatched(const QString &videoId)
0255 {
0256     return m_watchedVideos.contains(videoId);
0257 }
0258 
0259 void VideoSource::markVideoWatched(const QString &videoId)
0260 {
0261     if (!m_watchedVideos.contains(videoId) && loggedIn()) {
0262         m_watchedVideos.push_back(videoId);
0263         m_api->markWatched(videoId);
0264     }
0265 }
0266 
0267 void VideoSource::markVideoUnwatched(const QString &videoId)
0268 {
0269     if (m_watchedVideos.contains(videoId) && loggedIn()) {
0270         m_watchedVideos.removeAll(videoId);
0271         m_api->markUnwatched(videoId);
0272     }
0273 }
0274 
0275 void VideoSource::fetchHistory(qint32 page)
0276 {
0277     if (!loggedIn()) {
0278         return;
0279     }
0280 
0281     if (page == 1) {
0282         m_watchedVideos.clear();
0283     }
0284 
0285     auto *watcher = new QFutureWatcher<QInvidious::HistoryResult>();
0286     connect(watcher, &QFutureWatcherBase::finished, this, [this, watcher, page] {
0287         auto result = watcher->result();
0288 
0289         if (const auto history = std::get_if<QList<QString>>(&result)) {
0290             if (!history->isEmpty()) {
0291                 m_watchedVideos.append(*history);
0292 
0293                 fetchHistory(page + 1);
0294             }
0295         }
0296 
0297         watcher->deleteLater();
0298     });
0299     watcher->setFuture(m_api->requestHistory(page));
0300 }
0301 
0302 void VideoSource::addToPlaylist(const QString &plid, const QString &videoId)
0303 {
0304     m_api->addVideoToPlaylist(plid, videoId);
0305 }
0306 
0307 bool VideoSource::supportsPopularPage() const
0308 {
0309     return m_api->supportsFeature(QInvidious::AbstractApi::PopularPage);
0310 }
0311 
0312 bool VideoSource::supportsTrendingCategories() const
0313 {
0314     return m_api->supportsFeature(QInvidious::AbstractApi::TrendingCategories);
0315 }
0316 
0317 #include "moc_videosource.cpp"