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"