File indexing completed on 2025-01-05 04:29:53

0001 /**
0002  * SPDX-FileCopyrightText: 2021 Bart De Vries <bart@mogwai.be>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 #include "gpodder.h"
0008 
0009 #include <QDateTime>
0010 #include <QJsonArray>
0011 #include <QJsonDocument>
0012 #include <QJsonObject>
0013 #include <QNetworkReply>
0014 #include <QNetworkRequest>
0015 #include <QSqlQuery>
0016 #include <QUrl>
0017 
0018 #include "database.h"
0019 #include "fetcher.h"
0020 #include "sync/gpodder/devicerequest.h"
0021 #include "sync/gpodder/episodeactionrequest.h"
0022 #include "sync/gpodder/loginrequest.h"
0023 #include "sync/gpodder/logoutrequest.h"
0024 #include "sync/gpodder/subscriptionrequest.h"
0025 #include "sync/gpodder/syncrequest.h"
0026 #include "sync/gpodder/updatedevicerequest.h"
0027 #include "sync/gpodder/updatesyncrequest.h"
0028 #include "sync/gpodder/uploadepisodeactionrequest.h"
0029 #include "sync/gpodder/uploadsubscriptionrequest.h"
0030 #include "sync/sync.h"
0031 #include "sync/syncutils.h"
0032 #include "synclogging.h"
0033 
0034 GPodder::GPodder(const QString &username, const QString &password, QObject *parent)
0035     : QObject(parent)
0036     , m_username(username)
0037     , m_password(password)
0038 {
0039 }
0040 
0041 GPodder::GPodder(const QString &username, const QString &password, const QString &hostname, const SyncUtils::Provider provider, QObject *parent)
0042     : QObject(parent)
0043     , m_username(username)
0044     , m_password(password)
0045     , m_hostname(hostname)
0046     , m_provider(provider)
0047 {
0048 }
0049 
0050 LoginRequest *GPodder::login()
0051 {
0052     QString url = QStringLiteral("%1/api/2/auth/%2/login.json").arg(baseUrl()).arg(m_username);
0053     QNetworkRequest request((QUrl(url)));
0054     request.setTransferTimeout();
0055     addAuthentication(request);
0056 
0057     QNetworkReply *reply = Fetcher::instance().post(request, QByteArray());
0058     LoginRequest *loginRequest = new LoginRequest(m_provider, reply, this);
0059     return loginRequest;
0060 }
0061 
0062 LogoutRequest *GPodder::logout()
0063 {
0064     QString url = QStringLiteral("%1/api/2/auth/%2/logout.json").arg(baseUrl()).arg(m_username);
0065     QNetworkRequest request((QUrl(url)));
0066     request.setTransferTimeout();
0067     addAuthentication(request);
0068 
0069     QNetworkReply *reply = Fetcher::instance().post(request, QByteArray());
0070     LogoutRequest *logoutRequest = new LogoutRequest(m_provider, reply, this);
0071     return logoutRequest;
0072 }
0073 
0074 DeviceRequest *GPodder::getDevices()
0075 {
0076     QString url = QStringLiteral("%1/api/2/devices/%2.json").arg(baseUrl()).arg(m_username);
0077     QNetworkRequest request((QUrl(url)));
0078     request.setTransferTimeout();
0079     addAuthentication(request);
0080 
0081     QNetworkReply *reply = Fetcher::instance().get(request);
0082     DeviceRequest *deviceRequest = new DeviceRequest(m_provider, reply, this);
0083     return deviceRequest;
0084 }
0085 
0086 UpdateDeviceRequest *GPodder::updateDevice(const QString &id, const QString &caption, const QString &type)
0087 {
0088     QString url = QStringLiteral("%1/api/2/devices/%2/%3.json").arg(baseUrl()).arg(m_username).arg(id);
0089     QNetworkRequest request((QUrl(url)));
0090     request.setTransferTimeout();
0091     addAuthentication(request);
0092 
0093     QJsonObject deviceObject;
0094     deviceObject.insert(QStringLiteral("caption"), caption);
0095     deviceObject.insert(QStringLiteral("type"), type);
0096     QJsonDocument json(deviceObject);
0097 
0098     QByteArray data = json.toJson(QJsonDocument::Compact);
0099 
0100     QNetworkReply *reply = Fetcher::instance().post(request, data);
0101 
0102     UpdateDeviceRequest *updateDeviceRequest = new UpdateDeviceRequest(m_provider, reply, this);
0103     return updateDeviceRequest;
0104 }
0105 SyncRequest *GPodder::getSyncStatus()
0106 {
0107     QString url = QStringLiteral("%1/api/2/sync-devices/%2.json").arg(baseUrl()).arg(m_username);
0108     QNetworkRequest request((QUrl(url)));
0109     request.setTransferTimeout();
0110     addAuthentication(request);
0111 
0112     QNetworkReply *reply = Fetcher::instance().get(request);
0113     SyncRequest *syncRequest = new SyncRequest(m_provider, reply, this);
0114     return syncRequest;
0115 }
0116 
0117 UpdateSyncRequest *GPodder::updateSyncStatus(const QVector<QStringList> &syncedDevices, const QStringList &unsyncedDevices)
0118 {
0119     QString url = QStringLiteral("%1/api/2/sync-devices/%2.json").arg(baseUrl()).arg(m_username);
0120     QNetworkRequest request((QUrl(url)));
0121     request.setTransferTimeout();
0122     addAuthentication(request);
0123 
0124     QJsonArray syncGroupArray;
0125     for (QStringList syncGroup : syncedDevices) {
0126         syncGroupArray.push_back(QJsonArray::fromStringList(syncGroup));
0127     }
0128     QJsonArray unsyncArray = QJsonArray::fromStringList(unsyncedDevices);
0129 
0130     QJsonObject uploadObject;
0131     uploadObject.insert(QStringLiteral("synchronize"), syncGroupArray);
0132     uploadObject.insert(QStringLiteral("stop-synchronize"), unsyncArray);
0133     QJsonDocument json(uploadObject);
0134 
0135     QByteArray data = json.toJson(QJsonDocument::Compact);
0136 
0137     QNetworkReply *reply = Fetcher::instance().post(request, data);
0138 
0139     UpdateSyncRequest *updateSyncRequest = new UpdateSyncRequest(m_provider, reply, this);
0140     return updateSyncRequest;
0141 }
0142 
0143 SubscriptionRequest *GPodder::getSubscriptionChanges(const qulonglong &oldtimestamp, const QString &device)
0144 {
0145     QString url;
0146     if (m_provider == SyncUtils::Provider::GPodderNextcloud) {
0147         url = QStringLiteral("%1/index.php/apps/gpoddersync/subscriptions?since=%2").arg(baseUrl()).arg(QString::number(oldtimestamp));
0148     } else {
0149         url = QStringLiteral("%1/api/2/subscriptions/%2/%3.json?since=%4").arg(baseUrl()).arg(m_username).arg(device).arg(QString::number(oldtimestamp));
0150     }
0151     QNetworkRequest request((QUrl(url)));
0152     request.setTransferTimeout();
0153     addAuthentication(request);
0154 
0155     QNetworkReply *reply = Fetcher::instance().get(request);
0156 
0157     SubscriptionRequest *subscriptionRequest = new SubscriptionRequest(m_provider, reply, this);
0158     return subscriptionRequest;
0159 }
0160 
0161 UploadSubscriptionRequest *GPodder::uploadSubscriptionChanges(const QStringList &add, const QStringList &remove, const QString &device)
0162 {
0163     QString url;
0164     if (m_provider == SyncUtils::Provider::GPodderNextcloud) {
0165         url = QStringLiteral("%1/index.php/apps/gpoddersync/subscription_change/create").arg(baseUrl());
0166     } else {
0167         url = QStringLiteral("%1/api/2/subscriptions/%2/%3.json").arg(baseUrl()).arg(m_username).arg(device);
0168     }
0169     QNetworkRequest request((QUrl(url)));
0170     request.setTransferTimeout();
0171     addAuthentication(request);
0172 
0173     QJsonArray addArray = QJsonArray::fromStringList(add);
0174     QJsonArray removeArray = QJsonArray::fromStringList(remove);
0175 
0176     QJsonObject uploadObject;
0177     uploadObject.insert(QStringLiteral("add"), addArray);
0178     uploadObject.insert(QStringLiteral("remove"), removeArray);
0179     QJsonDocument json(uploadObject);
0180 
0181     QByteArray data = json.toJson(QJsonDocument::Compact);
0182 
0183     QNetworkReply *reply = Fetcher::instance().post(request, data);
0184 
0185     UploadSubscriptionRequest *uploadSubscriptionRequest = new UploadSubscriptionRequest(m_provider, reply, this);
0186     return uploadSubscriptionRequest;
0187 }
0188 
0189 EpisodeActionRequest *GPodder::getEpisodeActions(const qulonglong &timestamp, bool aggregated)
0190 {
0191     QString url;
0192     if (m_provider == SyncUtils::Provider::GPodderNextcloud) {
0193         url = QStringLiteral("%1/index.php/apps/gpoddersync/episode_action?since=%2").arg(baseUrl()).arg(QString::number(timestamp));
0194     } else {
0195         url = QStringLiteral("%1/api/2/episodes/%2.json?since=%3&aggregated=%4")
0196                   .arg(baseUrl())
0197                   .arg(m_username)
0198                   .arg(QString::number(timestamp))
0199                   .arg(aggregated ? QStringLiteral("true") : QStringLiteral("false"));
0200     }
0201     QNetworkRequest request((QUrl(url)));
0202     request.setTransferTimeout();
0203     addAuthentication(request);
0204 
0205     QNetworkReply *reply = Fetcher::instance().get(request);
0206 
0207     EpisodeActionRequest *episodeActionRequest = new EpisodeActionRequest(m_provider, reply, this);
0208     return episodeActionRequest;
0209 }
0210 
0211 UploadEpisodeActionRequest *GPodder::uploadEpisodeActions(const QVector<SyncUtils::EpisodeAction> &episodeActions)
0212 {
0213     QString url;
0214     if (m_provider == SyncUtils::Provider::GPodderNextcloud) {
0215         url = QStringLiteral("%1/index.php/apps/gpoddersync/episode_action/create").arg(baseUrl());
0216     } else {
0217         url = QStringLiteral("%1/api/2/episodes/%2.json").arg(baseUrl()).arg(m_username);
0218     }
0219     QNetworkRequest request((QUrl(url)));
0220     request.setTransferTimeout();
0221     addAuthentication(request);
0222 
0223     QJsonArray actionArray;
0224     for (SyncUtils::EpisodeAction episodeAction : episodeActions) {
0225         QJsonObject actionObject;
0226         actionObject.insert(QStringLiteral("podcast"), episodeAction.podcast);
0227         if (!episodeAction.url.isEmpty()) {
0228             actionObject.insert(QStringLiteral("episode"), episodeAction.url);
0229         } else if (!episodeAction.id.isEmpty()) {
0230             QSqlQuery query;
0231             query.prepare(QStringLiteral("SELECT url FROM Enclosures WHERE id=:id;"));
0232             query.bindValue(QStringLiteral(":id"), episodeAction.id);
0233             Database::instance().execute(query);
0234             if (!query.next()) {
0235                 qCDebug(kastsSync) << "cannot find episode with id:" << episodeAction.id;
0236                 continue;
0237             } else {
0238                 actionObject.insert(QStringLiteral("episode"), query.value(QStringLiteral("url")).toString());
0239             }
0240         }
0241         actionObject.insert(QStringLiteral("guid"), episodeAction.id);
0242         actionObject.insert(QStringLiteral("device"), episodeAction.device);
0243         actionObject.insert(QStringLiteral("action"), episodeAction.action);
0244 
0245         QString dateTime = QDateTime::fromSecsSinceEpoch(episodeAction.timestamp).toUTC().toString(Qt::ISODate);
0246         // Qt::ISODate adds "Z" to the end of the string; cut it off since
0247         // GPodderNextcloud cannot handle it
0248         dateTime.chop(1);
0249         actionObject.insert(QStringLiteral("timestamp"), dateTime);
0250         if (episodeAction.action == QStringLiteral("play")) {
0251             actionObject.insert(QStringLiteral("started"), static_cast<qint64>(episodeAction.started));
0252             actionObject.insert(QStringLiteral("position"), static_cast<qint64>(episodeAction.position));
0253             actionObject.insert(QStringLiteral("total"), static_cast<qint64>(episodeAction.total));
0254         }
0255         actionArray.push_back(actionObject);
0256     }
0257     QJsonDocument json(actionArray);
0258 
0259     QByteArray data = json.toJson(QJsonDocument::Compact);
0260 
0261     QNetworkReply *reply = Fetcher::instance().post(request, data);
0262 
0263     UploadEpisodeActionRequest *uploadEpisodeActionRequest = new UploadEpisodeActionRequest(m_provider, reply, this);
0264     return uploadEpisodeActionRequest;
0265 }
0266 
0267 QString GPodder::baseUrl()
0268 {
0269     return m_hostname;
0270 }
0271 
0272 void GPodder::addAuthentication(QNetworkRequest &request)
0273 {
0274     QByteArray headerData = "Basic " + QString(m_username + QStringLiteral(":") + m_password).toLocal8Bit().toBase64();
0275     request.setRawHeader("Authorization", headerData);
0276 }