Add optional oauth authentication for tidal

This commit is contained in:
Jonas Kvinge
2019-06-09 19:29:25 +02:00
parent 85a0748ad9
commit c0c1457073
16 changed files with 766 additions and 399 deletions

View File

@@ -20,20 +20,29 @@
#include "config.h"
#include <QObject>
#include <QStandardPaths>
#include <QMimeDatabase>
#include <QFile>
#include <QDir>
#include <QList>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QJsonValue>
#include <QJsonObject>
#include <QXmlStreamReader>
#include "core/logging.h"
#include "core/network.h"
#include "core/song.h"
#include "settings/tidalsettingspage.h"
#include "tidalservice.h"
#include "tidalbaserequest.h"
#include "tidalstreamurlrequest.h"
TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, QObject *parent)
: TidalBaseRequest(service, network, parent),
service_(service),
reply_(nullptr),
original_url_(original_url),
song_id_(original_url.path().toInt()),
@@ -67,6 +76,10 @@ void TidalStreamURLRequest::LoginComplete(bool success, QString error) {
void TidalStreamURLRequest::Process() {
if (!authenticated()) {
if (oauth()) {
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, tr("Not authenticated."));
return;
}
need_login_ = true;
emit TryLogin();
return;
@@ -81,7 +94,7 @@ void TidalStreamURLRequest::Cancel() {
reply_->abort();
}
else {
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, "Cancelled.");
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, tr("Cancelled."));
}
}
@@ -90,16 +103,36 @@ void TidalStreamURLRequest::GetStreamURL() {
++tries_;
ParamList parameters;
parameters << Param("soundQuality", quality());
if (reply_) {
disconnect(reply_, 0, nullptr, 0);
if (reply_->isRunning()) reply_->abort();
reply_->deleteLater();
}
reply_ = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id_), parameters);
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
ParamList params;
switch (stream_url_method()) {
case TidalSettingsPage::StreamUrlMethod_StreamUrl:
params << Param("soundQuality", quality());
reply_ = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id_), params);
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
break;
case TidalSettingsPage::StreamUrlMethod_UrlPostPaywall:
params << Param("audioquality", quality());
params << Param("playbackmode", "STREAM");
params << Param("assetpresentation", "FULL");
params << Param("urlusagemode", "STREAM");
reply_ = CreateRequest(QString("tracks/%1/urlpostpaywall").arg(song_id_), params);
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
break;
case TidalSettingsPage::StreamUrlMethod_PlaybackInfoPostPaywall:
params << Param("audioquality", quality());
params << Param("playbackmode", "STREAM");
params << Param("assetpresentation", "FULL");
reply_ = CreateRequest(QString("tracks/%1/playbackinfopostpaywall").arg(song_id_), params);
connect(reply_, SIGNAL(finished()), this, SLOT(StreamURLReceived()));
break;
}
}
@@ -123,26 +156,125 @@ void TidalStreamURLRequest::StreamURLReceived() {
}
reply_ = nullptr;
qLog(Debug) << "Tidal:" << data;
QJsonObject json_obj = ExtractJsonObj(data, error);
if (json_obj.isEmpty()) {
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
if (!json_obj.contains("url") || !json_obj.contains("codec")) {
error = Error("Invalid Json reply, stream missing url or codec.", json_obj);
if (!json_obj.contains("trackId")) {
error = Error("Invalid Json reply, stream missing trackId.", json_obj);
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
int track_id(json_obj["trackId"].toInt());
if (track_id != song_id_) {
error = Error("Incorrect track ID returned.", json_obj);
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
QUrl new_url(json_obj["url"].toString());
QString codec(json_obj["codec"].toString().toLower());
Song::FileType filetype(Song::FiletypeByExtension(codec));
if (filetype == Song::FileType_Unknown) {
qLog(Debug) << "Tidal: Unknown codec" << codec;
filetype = Song::FileType_Stream;
Song::FileType filetype(Song::FileType_Unknown);
if (json_obj.contains("codec") || json_obj.contains("codecs")) {
QString codec;
if (json_obj.contains("codec")) codec = json_obj["codec"].toString().toLower();
if (json_obj.contains("codecs")) codec = json_obj["codecs"].toString().toLower();
filetype = Song::FiletypeByExtension(codec);
if (filetype == Song::FileType_Unknown) {
qLog(Debug) << "Tidal: Unknown codec" << codec;
filetype = Song::FileType_Stream;
}
}
emit StreamURLFinished(original_url_, new_url, filetype, QString());
QList<QUrl> urls;
if (json_obj.contains("manifest")) {
QString manifest(json_obj["manifest"].toString());
QByteArray data_manifest = QByteArray::fromBase64(manifest.toUtf8());
qLog(Debug) << "Tidal:" << data_manifest;
QXmlStreamReader xml_reader(data_manifest);
if (!xml_reader.hasError()) {
QString filepath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/tidalstreams";
QString filename = "tidal-" + QString::number(song_id_) + ".xml";
if (!QDir().mkpath(filepath)) {
error = Error(QString("Failed to create directory %1.").arg(filepath), json_obj);
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
QUrl url("file://" + filepath + "/" + filename);
QFile file(url.toLocalFile());
if (file.exists())
file.remove();
if (!file.open(QIODevice::WriteOnly)) {
error = Error(QString("Failed to open file %1 for writing.").arg(url.toLocalFile()), json_obj);
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
file.write(data_manifest);
file.close();
urls << url;
}
else {
json_obj = ExtractJsonObj(data_manifest, error);
if (json_obj.isEmpty()) {
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
if (!json_obj.contains("mimeType")) {
error = Error("Invalid Json reply, stream url reply manifest is missing mimeType.", json_obj);
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
QString mimetype = json_obj["mimeType"].toString();
QMimeDatabase mimedb;
for (QString suffix : mimedb.mimeTypeForName(mimetype.toUtf8()).suffixes()) {
filetype = Song::FiletypeByExtension(suffix);
if (filetype != Song::FileType_Unknown) break;
}
if (filetype == Song::FileType_Unknown) {
qLog(Debug) << "Tidal: Unknown mimetype" << mimetype;
filetype = Song::FileType_Stream;
}
}
}
if (json_obj.contains("urls")) {
QJsonValue json_urls = json_obj["urls"];
if (!json_urls.isArray()) {
error = Error("Invalid Json reply, urls is not an array.", json_urls);
emit StreamURLFinished(original_url_, original_url_, Song::FileType_Stream, error);
return;
}
QJsonArray json_array_urls = json_urls.toArray();
for (const QJsonValue &value : json_array_urls) {
urls << QUrl(value.toString());
}
}
else if (json_obj.contains("url")) {
QUrl new_url(json_obj["url"].toString());
urls << new_url;
}
if (urls.isEmpty()) {
error = Error("Missing stream urls.", json_obj);
emit StreamURLFinished(original_url_, original_url_, filetype);
return;
}
emit StreamURLFinished(original_url_, urls.first(), filetype);
}