Qobuz: Fix authentication and add automatic credential fetching

Qobuz API now requires intent=stream parameter for stream URL requests,
and the app_secret must be extracted using the Spoofbuz decoding method
from bundle.js rather than plain-text values.

Changes:
- Add intent=stream parameter to stream URL requests
- Add QobuzCredentialFetcher class to extract credentials from web player
- Add "Fetch Credentials" button to Qobuz settings page
- Decode obfuscated app secrets using seed/timezone/info/extras method

This fixes "Invalid Request Signature" errors that prevented playback.
This commit is contained in:
Rob Stanfield
2025-12-17 18:18:14 -08:00
committed by Jonas Kvinge
parent 2cd9498469
commit 2bb0dbada2
8 changed files with 397 additions and 1 deletions

View File

@@ -38,6 +38,7 @@
#include "core/settings.h"
#include "widgets/loginstatewidget.h"
#include "qobuz/qobuzservice.h"
#include "qobuz/qobuzcredentialfetcher.h"
#include "constants/qobuzsettings.h"
using namespace Qt::Literals::StringLiterals;
@@ -46,13 +47,15 @@ using namespace QobuzSettings;
QobuzSettingsPage::QobuzSettingsPage(SettingsDialog *dialog, const SharedPtr<QobuzService> service, QWidget *parent)
: SettingsPage(dialog, parent),
ui_(new Ui::QobuzSettingsPage),
service_(service) {
service_(service),
credential_fetcher_(nullptr) {
ui_->setupUi(this);
setWindowIcon(IconLoader::Load(u"qobuz"_s, true, 0, 32));
QObject::connect(ui_->button_login, &QPushButton::clicked, this, &QobuzSettingsPage::LoginClicked);
QObject::connect(ui_->login_state, &LoginStateWidget::LogoutClicked, this, &QobuzSettingsPage::LogoutClicked);
QObject::connect(ui_->button_fetch_credentials, &QPushButton::clicked, this, &QobuzSettingsPage::FetchCredentialsClicked);
QObject::connect(this, &QobuzSettingsPage::Login, &*service_, &StreamingService::LoginWithCredentials);
@@ -186,3 +189,40 @@ void QobuzSettingsPage::LoginFailure(const QString &failure_reason) {
QMessageBox::warning(this, tr("Authentication failed"), failure_reason);
}
void QobuzSettingsPage::FetchCredentialsClicked() {
ui_->button_fetch_credentials->setEnabled(false);
ui_->button_fetch_credentials->setText(tr("Fetching..."));
if (!credential_fetcher_) {
credential_fetcher_ = new QobuzCredentialFetcher(service_->network(), this);
QObject::connect(credential_fetcher_, &QobuzCredentialFetcher::CredentialsFetched, this, &QobuzSettingsPage::CredentialsFetched);
QObject::connect(credential_fetcher_, &QobuzCredentialFetcher::CredentialsFetchError, this, &QobuzSettingsPage::CredentialsFetchError);
}
credential_fetcher_->FetchCredentials();
}
void QobuzSettingsPage::CredentialsFetched(const QString &app_id, const QString &app_secret) {
ui_->app_id->setText(app_id);
ui_->app_secret->setText(app_secret);
ui_->checkbox_base64_secret->setChecked(false);
ui_->button_fetch_credentials->setEnabled(true);
ui_->button_fetch_credentials->setText(tr("Fetch Credentials"));
QMessageBox::information(this, tr("Credentials fetched"), tr("App ID and secret have been successfully fetched from the Qobuz web player."));
}
void QobuzSettingsPage::CredentialsFetchError(const QString &error) {
ui_->button_fetch_credentials->setEnabled(true);
ui_->button_fetch_credentials->setText(tr("Fetch Credentials"));
QMessageBox::warning(this, tr("Credential fetch failed"), error);
}

View File

@@ -30,6 +30,7 @@ class QShowEvent;
class QEvent;
class SettingsDialog;
class QobuzService;
class QobuzCredentialFetcher;
class Ui_QobuzSettingsPage;
class QobuzSettingsPage : public SettingsPage {
@@ -55,10 +56,14 @@ class QobuzSettingsPage : public SettingsPage {
void LogoutClicked();
void LoginSuccess();
void LoginFailure(const QString &failure_reason);
void FetchCredentialsClicked();
void CredentialsFetched(const QString &app_id, const QString &app_secret);
void CredentialsFetchError(const QString &error);
private:
Ui_QobuzSettingsPage *ui_;
const SharedPtr<QobuzService> service_;
QobuzCredentialFetcher *credential_fetcher_;
};
#endif // QOBUZSETTINGSPAGE_H

View File

@@ -115,6 +115,16 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="button_fetch_credentials">
<property name="text">
<string>Fetch Credentials</string>
</property>
<property name="toolTip">
<string>Automatically fetch app ID and secret from Qobuz web player</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>