Use an undocumented sky.fm/di.fm api to handle premium account logins, allowing us to remove dodgy code to scrape login information for each service individually
This commit is contained in:
parent
8e314ef4d8
commit
63140f83cf
@ -132,6 +132,7 @@ set(SOURCES
|
||||
globalsearch/tooltipactionwidget.cpp
|
||||
globalsearch/tooltipresultwidget.cpp
|
||||
|
||||
internet/digitallyimportedclient.cpp
|
||||
internet/digitallyimportedservice.cpp
|
||||
internet/digitallyimportedservicebase.cpp
|
||||
internet/digitallyimportedsettingspage.cpp
|
||||
@ -381,6 +382,7 @@ set(HEADERS
|
||||
globalsearch/tooltipactionwidget.h
|
||||
globalsearch/tooltipresultwidget.h
|
||||
|
||||
internet/digitallyimportedclient.h
|
||||
internet/digitallyimportedservicebase.h
|
||||
internet/digitallyimportedsettingspage.h
|
||||
internet/groovesharksearchplaylisttype.h
|
||||
|
85
src/internet/digitallyimportedclient.cpp
Normal file
85
src/internet/digitallyimportedclient.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "digitallyimportedclient.h"
|
||||
#include "core/network.h"
|
||||
|
||||
#include <qjson/parser.h>
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
|
||||
// The API used here is undocumented - it was reverse engineered by watching
|
||||
// calls made by the sky.fm android app:
|
||||
// https://market.android.com/details?id=com.audioaddict.sky
|
||||
|
||||
const char* DigitallyImportedClient::kApiUsername = "ephemeron";
|
||||
const char* DigitallyImportedClient::kApiPassword = "dayeiph0ne@pp";
|
||||
|
||||
const char* DigitallyImportedClient::kAuthUrl =
|
||||
"http://api.audioaddict.com/%1/premium/auth";
|
||||
|
||||
|
||||
DigitallyImportedClient::DigitallyImportedClient(const QString& service_name, QObject* parent)
|
||||
: QObject(parent),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
service_name_(service_name)
|
||||
{
|
||||
}
|
||||
|
||||
QNetworkReply* DigitallyImportedClient::Auth(const QString& username,
|
||||
const QString& password) {
|
||||
QNetworkRequest req(QUrl(QString(kAuthUrl).arg(service_name_)));
|
||||
req.setRawHeader("Authorization",
|
||||
"Basic " + QString("%1:%2").arg(kApiUsername, kApiPassword)
|
||||
.toAscii().toBase64());
|
||||
|
||||
QByteArray postdata = "username=" + QUrl::toPercentEncoding(username) +
|
||||
"&password=" + QUrl::toPercentEncoding(password);
|
||||
|
||||
return network_->post(req, postdata);
|
||||
}
|
||||
|
||||
DigitallyImportedClient::AuthReply
|
||||
DigitallyImportedClient::ParseAuthReply(QNetworkReply* reply) const {
|
||||
AuthReply ret;
|
||||
ret.success_ = false;
|
||||
ret.error_reason_ = tr("Unknown error");
|
||||
|
||||
QJson::Parser parser;
|
||||
QVariantMap data = parser.parse(reply).toMap();
|
||||
|
||||
if (data.value("status", QString()).toString() != "OK" ||
|
||||
!data.contains("user")) {
|
||||
ret.error_reason_ = data.value("reason", ret.error_reason_).toString();
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVariantMap user = data["user"].toMap();
|
||||
if (!user.contains("first_name") ||
|
||||
!user.contains("last_name") ||
|
||||
!user.contains("expires") ||
|
||||
!user.contains("listen_hash"))
|
||||
return ret;
|
||||
|
||||
ret.success_ = true;
|
||||
ret.first_name_ = user["first_name"].toString();
|
||||
ret.last_name_ = user["last_name"].toString();
|
||||
ret.expires_ = QDateTime::fromString(user["expires"].toString(), Qt::ISODate);
|
||||
ret.listen_hash_ = user["listen_hash"].toString();
|
||||
return ret;
|
||||
}
|
59
src/internet/digitallyimportedclient.h
Normal file
59
src/internet/digitallyimportedclient.h
Normal file
@ -0,0 +1,59 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2011, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DIGITALLYIMPORTEDCLIENT_H
|
||||
#define DIGITALLYIMPORTEDCLIENT_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
class DigitallyImportedClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DigitallyImportedClient(const QString& service_name, QObject* parent = 0);
|
||||
|
||||
static const char* kApiUsername;
|
||||
static const char* kApiPassword;
|
||||
static const char* kAuthUrl;
|
||||
|
||||
struct AuthReply {
|
||||
bool success_;
|
||||
|
||||
// Set if success_ == false
|
||||
QString error_reason_;
|
||||
|
||||
// Set if success_ == true
|
||||
QString first_name_;
|
||||
QString last_name_;
|
||||
QDateTime expires_;
|
||||
QString listen_hash_;
|
||||
};
|
||||
|
||||
QNetworkReply* Auth(const QString& username, const QString& password);
|
||||
AuthReply ParseAuthReply(QNetworkReply* reply) const;
|
||||
|
||||
private:
|
||||
QNetworkAccessManager* network_;
|
||||
|
||||
QString service_name_;
|
||||
};
|
||||
|
||||
#endif // DIGITALLYIMPORTEDCLIENT_H
|
@ -23,42 +23,10 @@
|
||||
#include <QNetworkReply>
|
||||
|
||||
DigitallyImportedService::DigitallyImportedService(InternetModel* model, QObject* parent)
|
||||
: DigitallyImportedServiceBase(
|
||||
"DigitallyImported", "Digitally Imported", QUrl("http://www.di.fm"),
|
||||
"di.fm", QUrl("http://listen.di.fm"), "digitallyimported",
|
||||
":/providers/digitallyimported.png", model, parent)
|
||||
: DigitallyImportedServiceBase("DigitallyImported", model, parent)
|
||||
{
|
||||
playlists_ = QList<Playlist>()
|
||||
<< Playlist(false, "http://listen.di.fm/public3/%1.pls")
|
||||
<< Playlist(true, "http://www.di.fm/listen/%1/premium.pls")
|
||||
<< Playlist(false, "http://listen.di.fm/public2/%1.pls")
|
||||
<< Playlist(true, "http://www.di.fm/listen/%1/64k.pls")
|
||||
<< Playlist(true, "http://www.di.fm/listen/%1/128k.pls")
|
||||
<< Playlist(false, "http://listen.di.fm/public5/%1.asx")
|
||||
<< Playlist(true, "http://www.di.fm/listen/%1/64k.asx")
|
||||
<< Playlist(true, "http://www.di.fm/listen/%1/128k.asx");
|
||||
Init("Digitally Imported", QUrl("http://www.di.fm"),
|
||||
"di.fm", QUrl("http://listen.di.fm"), "digitallyimported",
|
||||
":/providers/digitallyimported.png", "di");
|
||||
}
|
||||
|
||||
void DigitallyImportedService::ReloadSettings() {
|
||||
DigitallyImportedServiceBase::ReloadSettings();
|
||||
|
||||
QNetworkCookieJar* cookies = new QNetworkCookieJar;
|
||||
|
||||
if (is_premium_account()) {
|
||||
qLog(Debug) << "Setting premium account cookies";
|
||||
cookies->setCookiesFromUrl(QList<QNetworkCookie>()
|
||||
<< QNetworkCookie("_amember_ru", username_.toUtf8())
|
||||
<< QNetworkCookie("_amember_rp", password_.toUtf8()),
|
||||
QUrl("http://www.di.fm/"));
|
||||
}
|
||||
|
||||
network_->setCookieJar(cookies);
|
||||
}
|
||||
|
||||
void DigitallyImportedService::LoadStation(const QString& key) {
|
||||
QUrl playlist_url(playlists_[audio_type_].url_template_.arg(key));
|
||||
qLog(Debug) << "Getting playlist URL" << playlist_url;
|
||||
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(playlist_url));
|
||||
connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished()));
|
||||
}
|
||||
|
@ -23,10 +23,6 @@
|
||||
class DigitallyImportedService : public DigitallyImportedServiceBase {
|
||||
public:
|
||||
DigitallyImportedService(InternetModel* model, QObject* parent = NULL);
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
void LoadStation(const QString& key);
|
||||
};
|
||||
|
||||
#endif // DIGITALLYIMPORTEDSERVICE_H
|
||||
|
@ -15,6 +15,7 @@
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "digitallyimportedclient.h"
|
||||
#include "digitallyimportedservicebase.h"
|
||||
#include "digitallyimportedurlhandler.h"
|
||||
#include "internetmodel.h"
|
||||
@ -37,29 +38,48 @@ const int DigitallyImportedServiceBase::kStreamsCacheDurationSecs =
|
||||
|
||||
|
||||
DigitallyImportedServiceBase::DigitallyImportedServiceBase(
|
||||
const QString& name, const QString& description, const QUrl& homepage_url,
|
||||
const QString& homepage_name, const QUrl& stream_list_url,
|
||||
const QString& url_scheme, const QString& icon_path,
|
||||
InternetModel* model, QObject* parent)
|
||||
const QString& name, InternetModel* model, QObject* parent)
|
||||
: InternetService(name, model, parent),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
url_handler_(new DigitallyImportedUrlHandler(this)),
|
||||
audio_type_(0),
|
||||
basic_audio_type_(1),
|
||||
premium_audio_type_(2),
|
||||
task_id_(-1),
|
||||
homepage_url_(homepage_url),
|
||||
homepage_name_(homepage_name),
|
||||
stream_list_url_(stream_list_url),
|
||||
icon_path_(icon_path),
|
||||
icon_(icon_path),
|
||||
service_description_(description),
|
||||
url_scheme_(url_scheme),
|
||||
root_(NULL),
|
||||
context_menu_(NULL),
|
||||
context_item_(NULL)
|
||||
context_item_(NULL),
|
||||
api_client_(NULL)
|
||||
{
|
||||
model->player()->RegisterUrlHandler(url_handler_);
|
||||
basic_playlists_
|
||||
<< "http://listen.%1/public3/%2.pls"
|
||||
<< "http://listen.%1/public1/%2.pls"
|
||||
<< "http://listen.%1/public5/%2.asx";
|
||||
|
||||
model->global_search()->AddProvider(new DigitallyImportedSearchProvider(this, this));
|
||||
premium_playlists_
|
||||
<< "http://listen.%1/premium_high/%3.pls?hash=%3"
|
||||
<< "http://listen.%1/premium_medium/%2.pls?hash=%3"
|
||||
<< "http://listen.%1/premium/%2.pls?hash=%3"
|
||||
<< "http://listen.%1/premium_wma_low/%2.asx?hash=%3"
|
||||
<< "http://listen.%1/premium_wma/%2.asx?hash=%3";
|
||||
}
|
||||
|
||||
void DigitallyImportedServiceBase::Init(
|
||||
const QString& description, const QUrl& homepage_url,
|
||||
const QString& homepage_name, const QUrl& stream_list_url,
|
||||
const QString& url_scheme, const QString& icon_path,
|
||||
const QString& api_service_name) {
|
||||
service_description_ = description;
|
||||
homepage_url_ = homepage_url;
|
||||
homepage_name_ = homepage_name;
|
||||
stream_list_url_ = stream_list_url;
|
||||
url_scheme_ = url_scheme;
|
||||
icon_path_ = icon_path;
|
||||
api_service_name_ = api_service_name;
|
||||
|
||||
model()->player()->RegisterUrlHandler(url_handler_);
|
||||
model()->global_search()->AddProvider(new DigitallyImportedSearchProvider(this, this));
|
||||
|
||||
api_client_ = new DigitallyImportedClient(api_service_name, this);
|
||||
}
|
||||
|
||||
DigitallyImportedServiceBase::~DigitallyImportedServiceBase() {
|
||||
@ -176,9 +196,10 @@ void DigitallyImportedServiceBase::ReloadSettings() {
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
|
||||
audio_type_ = s.value("audio_type", 0).toInt();
|
||||
basic_audio_type_ = s.value("basic_audio_type", 1).toInt();
|
||||
premium_audio_type_ = s.value("premium_audio_type", 2).toInt();
|
||||
username_ = s.value("username").toString();
|
||||
password_ = s.value("password").toString();
|
||||
listen_hash_ = s.value("listen_hash").toString();
|
||||
last_refreshed_streams_ = s.value("last_refreshed_" + url_scheme_).toDateTime();
|
||||
saved_streams_ = LoadStreams();
|
||||
}
|
||||
@ -204,24 +225,8 @@ void DigitallyImportedServiceBase::ShowContextMenu(
|
||||
context_menu_->popup(global_pos);
|
||||
}
|
||||
|
||||
QModelIndex DigitallyImportedServiceBase::GetCurrentIndex() {
|
||||
return context_item_->index();
|
||||
}
|
||||
|
||||
bool DigitallyImportedServiceBase::is_valid_stream_selected() const {
|
||||
return audio_type_ >= 0 && audio_type_ < playlists_.count();
|
||||
}
|
||||
|
||||
bool DigitallyImportedServiceBase::is_premium_account() const {
|
||||
return !username_.isEmpty() && !password_.isEmpty();
|
||||
}
|
||||
|
||||
bool DigitallyImportedServiceBase::is_premium_stream_selected() const {
|
||||
if (!is_valid_stream_selected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return playlists_[audio_type_].premium_;
|
||||
return !listen_hash_.isEmpty();
|
||||
}
|
||||
|
||||
void DigitallyImportedServiceBase::LoadPlaylistFinished() {
|
||||
@ -234,11 +239,7 @@ void DigitallyImportedServiceBase::LoadPlaylistFinished() {
|
||||
if (reply->header(QNetworkRequest::ContentTypeHeader).toString() == "text/html") {
|
||||
url_handler_->CancelTask();
|
||||
|
||||
if (is_premium_stream_selected()) {
|
||||
emit StreamError(tr("Invalid di.fm username or password"));
|
||||
} else {
|
||||
emit StreamError(tr("Error loading di.fm playlist"));
|
||||
}
|
||||
} else {
|
||||
url_handler_->LoadPlaylistFinished(reply);
|
||||
}
|
||||
@ -302,3 +303,24 @@ DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::Streams()
|
||||
|
||||
return saved_streams_;
|
||||
}
|
||||
|
||||
void DigitallyImportedServiceBase::LoadStation(const QString& key) {
|
||||
QUrl playlist_url;
|
||||
|
||||
if (is_premium_account()) {
|
||||
playlist_url = QUrl(premium_playlists_[premium_audio_type_].arg(
|
||||
homepage_name_, key, listen_hash_));
|
||||
} else {
|
||||
playlist_url = QUrl(basic_playlists_[basic_audio_type_].arg(
|
||||
homepage_name_, key));
|
||||
}
|
||||
|
||||
qLog(Debug) << "Getting playlist URL" << playlist_url;
|
||||
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(playlist_url));
|
||||
connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished()));
|
||||
}
|
||||
|
||||
QModelIndex DigitallyImportedServiceBase::GetCurrentIndex() {
|
||||
return context_item_->index();
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "internetservice.h"
|
||||
|
||||
class DigitallyImportedClient;
|
||||
class DigitallyImportedUrlHandler;
|
||||
|
||||
class QNetworkAccessManager;
|
||||
@ -30,11 +31,8 @@ class DigitallyImportedServiceBase : public InternetService {
|
||||
friend class DigitallyImportedUrlHandler;
|
||||
|
||||
public:
|
||||
DigitallyImportedServiceBase(
|
||||
const QString& name, const QString& description, const QUrl& homepage_url,
|
||||
const QString& homepage_name, const QUrl& stream_list_url,
|
||||
const QString& url_scheme, const QString& icon_path,
|
||||
InternetModel* model, QObject* parent = NULL);
|
||||
DigitallyImportedServiceBase(const QString& name, InternetModel* model,
|
||||
QObject* parent = NULL);
|
||||
~DigitallyImportedServiceBase();
|
||||
|
||||
static const char* kSettingsGroup;
|
||||
@ -46,8 +44,6 @@ public:
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
bool is_valid_stream_selected() const;
|
||||
bool is_premium_stream_selected() const;
|
||||
bool is_premium_account() const;
|
||||
|
||||
const QUrl& homepage_url() const { return homepage_url_; }
|
||||
@ -57,6 +53,7 @@ public:
|
||||
const QIcon& icon() const { return icon_; }
|
||||
const QString& service_description() const { return service_description_; }
|
||||
const QString& url_scheme() const { return url_scheme_; }
|
||||
const QString& api_service_name() const { return api_service_name_; }
|
||||
|
||||
// Public for the global search provider.
|
||||
struct Stream {
|
||||
@ -76,20 +73,14 @@ signals:
|
||||
void StreamsChanged();
|
||||
|
||||
protected:
|
||||
struct Playlist {
|
||||
Playlist(bool premium, const QString& url_template)
|
||||
: premium_(premium), url_template_(url_template) {}
|
||||
|
||||
bool premium_;
|
||||
QString url_template_;
|
||||
};
|
||||
// Subclasses must call this from their constructor
|
||||
void Init(const QString& description, const QUrl& homepage_url,
|
||||
const QString& homepage_name, const QUrl& stream_list_url,
|
||||
const QString& url_scheme, const QString& icon_path,
|
||||
const QString& api_service_name);
|
||||
|
||||
QModelIndex GetCurrentIndex();
|
||||
|
||||
// Called by DigitallyImportedUrlHandler, implemented by subclasses, must
|
||||
// call LoadPlaylistFinished eventually.
|
||||
virtual void LoadStation(const QString& key) = 0;
|
||||
|
||||
protected slots:
|
||||
void LoadPlaylistFinished();
|
||||
|
||||
@ -100,23 +91,13 @@ private slots:
|
||||
void RefreshStreamsFinished();
|
||||
void ShowSettingsDialog();
|
||||
|
||||
protected:
|
||||
QNetworkAccessManager* network_;
|
||||
DigitallyImportedUrlHandler* url_handler_;
|
||||
|
||||
int audio_type_;
|
||||
QString username_;
|
||||
QString password_;
|
||||
|
||||
int task_id_;
|
||||
|
||||
QList<Playlist> playlists_;
|
||||
|
||||
private:
|
||||
void PopulateStreams();
|
||||
StreamList LoadStreams() const;
|
||||
void SaveStreams(const StreamList& streams);
|
||||
|
||||
void LoadStation(const QString& key);
|
||||
|
||||
private:
|
||||
// Set by subclasses through the constructor
|
||||
QUrl homepage_url_;
|
||||
@ -126,6 +107,20 @@ private:
|
||||
QIcon icon_;
|
||||
QString service_description_;
|
||||
QString url_scheme_;
|
||||
QString api_service_name_;
|
||||
|
||||
QStringList basic_playlists_;
|
||||
QStringList premium_playlists_;
|
||||
|
||||
QNetworkAccessManager* network_;
|
||||
DigitallyImportedUrlHandler* url_handler_;
|
||||
|
||||
int basic_audio_type_;
|
||||
int premium_audio_type_;
|
||||
QString username_;
|
||||
QString listen_hash_;
|
||||
|
||||
int task_id_;
|
||||
|
||||
QStandardItem* root_;
|
||||
|
||||
@ -134,6 +129,8 @@ private:
|
||||
|
||||
QList<Stream> saved_streams_;
|
||||
QDateTime last_refreshed_streams_;
|
||||
|
||||
DigitallyImportedClient* api_client_;
|
||||
};
|
||||
|
||||
#endif // DIGITALLYIMPORTEDSERVICEBASE_H
|
||||
|
@ -15,47 +15,119 @@
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "digitallyimportedclient.h"
|
||||
#include "digitallyimportedservicebase.h"
|
||||
#include "digitallyimportedsettingspage.h"
|
||||
#include "ui_digitallyimportedsettingspage.h"
|
||||
#include "core/closure.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QSettings>
|
||||
|
||||
|
||||
DigitallyImportedSettingsPage::DigitallyImportedSettingsPage(SettingsDialog* dialog)
|
||||
: SettingsPage(dialog),
|
||||
ui_(new Ui_DigitallyImportedSettingsPage)
|
||||
ui_(new Ui_DigitallyImportedSettingsPage),
|
||||
client_(new DigitallyImportedClient("di", this))
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
setWindowIcon(QIcon(":/providers/digitallyimported-32.png"));
|
||||
|
||||
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
|
||||
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
|
||||
connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
|
||||
|
||||
ui_->login_state->AddCredentialField(ui_->username);
|
||||
ui_->login_state->AddCredentialField(ui_->password);
|
||||
ui_->login_state->AddCredentialGroup(ui_->credential_group);
|
||||
|
||||
ui_->login_state->SetAccountTypeText(tr(
|
||||
"You can listen for free without an account, but Premium members can "
|
||||
"listen to higher quality streams without advertisements."));
|
||||
ui_->login_state->SetAccountTypeVisible(true);
|
||||
ui_->login_state->HideLoggedInState();
|
||||
}
|
||||
|
||||
DigitallyImportedSettingsPage::~DigitallyImportedSettingsPage() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void DigitallyImportedSettingsPage::Login() {
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
|
||||
|
||||
QNetworkReply* reply = client_->Auth(ui_->username->text(), ui_->password->text());
|
||||
NewClosure(reply, SIGNAL(finished()),
|
||||
this, SLOT(LoginFinished(QNetworkReply*)), reply);
|
||||
}
|
||||
|
||||
void DigitallyImportedSettingsPage::LoginFinished(QNetworkReply* reply) {
|
||||
DigitallyImportedClient::AuthReply result = client_->ParseAuthReply(reply);
|
||||
|
||||
QString name = QString("%1 %2").arg(result.first_name_, result.last_name_);
|
||||
UpdateLoginState(result.listen_hash_, name, result.expires_);
|
||||
|
||||
if (result.success_) {
|
||||
// Clear the password field just to be sure
|
||||
ui_->password->clear();
|
||||
|
||||
// Save the listen key and account information
|
||||
QSettings s;
|
||||
s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup);
|
||||
s.setValue("listen_hash", result.listen_hash_);
|
||||
s.setValue("full_name", name);
|
||||
s.setValue("expires", result.expires_);
|
||||
} else {
|
||||
QMessageBox::warning(this, tr("Authentication failed"),
|
||||
result.error_reason_);
|
||||
}
|
||||
}
|
||||
|
||||
void DigitallyImportedSettingsPage::UpdateLoginState(
|
||||
const QString& listen_hash, const QString& name, const QDateTime& expires) {
|
||||
if (listen_hash.isEmpty()) {
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut);
|
||||
ui_->login_state->SetAccountTypeVisible(true);
|
||||
} else {
|
||||
ui_->login_state->SetLoggedIn(
|
||||
LoginStateWidget::LoggedIn,
|
||||
name + " (" + tr("Expires on %1").arg(expires.date().toString(Qt::SystemLocaleLongDate)) + ")");
|
||||
ui_->login_state->SetAccountTypeVisible(false);
|
||||
}
|
||||
|
||||
ui_->premium_audio_type->setEnabled(!listen_hash.isEmpty());
|
||||
ui_->premium_audio_label->setEnabled(!listen_hash.isEmpty());
|
||||
}
|
||||
|
||||
void DigitallyImportedSettingsPage::Logout() {
|
||||
QSettings s;
|
||||
s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup);
|
||||
s.setValue("listen_hash", QString());
|
||||
s.setValue("full_name", QString());
|
||||
s.setValue("expires", QDateTime());
|
||||
|
||||
UpdateLoginState(QString(), QString(), QDateTime());
|
||||
}
|
||||
|
||||
void DigitallyImportedSettingsPage::Load() {
|
||||
QSettings s;
|
||||
s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup);
|
||||
|
||||
ui_->audio_type->setCurrentIndex(s.value("audio_type", 0).toInt());
|
||||
ui_->basic_audio_type->setCurrentIndex(s.value("basic_audio_type", 1).toInt());
|
||||
ui_->premium_audio_type->setCurrentIndex(s.value("premium_audio_type", 2).toInt());
|
||||
ui_->username->setText(s.value("username").toString());
|
||||
ui_->password->setText(s.value("password").toString());
|
||||
|
||||
UpdateLoginState(s.value("listen_hash").toString(),
|
||||
s.value("full_name").toString(),
|
||||
s.value("expires").toDateTime());
|
||||
}
|
||||
|
||||
void DigitallyImportedSettingsPage::Save() {
|
||||
QSettings s;
|
||||
s.beginGroup(DigitallyImportedServiceBase::kSettingsGroup);
|
||||
|
||||
s.setValue("audio_type", ui_->audio_type->currentIndex());
|
||||
s.setValue("basic_audio_type", ui_->basic_audio_type->currentIndex());
|
||||
s.setValue("premium_audio_type", ui_->premium_audio_type->currentIndex());
|
||||
s.setValue("username", ui_->username->text());
|
||||
s.setValue("password", ui_->password->text());
|
||||
}
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "ui/settingspage.h"
|
||||
|
||||
class DigitallyImportedClient;
|
||||
class Ui_DigitallyImportedSettingsPage;
|
||||
|
||||
class DigitallyImportedSettingsPage : public SettingsPage {
|
||||
@ -32,8 +33,20 @@ public:
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
private slots:
|
||||
void Login();
|
||||
void Logout();
|
||||
|
||||
void LoginFinished(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
void UpdateLoginState(const QString& listen_hash, const QString& name,
|
||||
const QDateTime& expires);
|
||||
|
||||
private:
|
||||
Ui_DigitallyImportedSettingsPage* ui_;
|
||||
|
||||
DigitallyImportedClient* client_;
|
||||
};
|
||||
|
||||
#endif // DIGITALLYIMPORTEDSETTINGSPAGE_H
|
||||
|
@ -18,7 +18,7 @@
|
||||
<widget class="LoginStateWidget" name="login_state" native="true"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_10">
|
||||
<widget class="QGroupBox" name="credential_group">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
@ -28,7 +28,7 @@
|
||||
<property name="title">
|
||||
<string>Account details (Premium)</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_22">
|
||||
<property name="text">
|
||||
@ -37,8 +37,19 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="username"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="login">
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_23">
|
||||
<property name="text">
|
||||
@ -53,7 +64,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="upgrade_link">
|
||||
<property name="text">
|
||||
<string><a href="http://www.di.fm/premium/">Upgrade to Premium now</a></string>
|
||||
@ -82,52 +93,63 @@
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_21">
|
||||
<widget class="QLabel" name="basic_audio_label">
|
||||
<property name="text">
|
||||
<string>Audio type</string>
|
||||
<string>Basic audio type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="audio_type">
|
||||
<widget class="QComboBox" name="basic_audio_type">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>MP3 96k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>MP3 256k (Premium only)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>AAC 32k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>AAC 64k (Premium only)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>AAC 128k (Premium only)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Windows Media 40k</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="premium_audio_label">
|
||||
<property name="text">
|
||||
<string>Premium audio type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="premium_audio_type">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Windows Media 64k (Premium only)</string>
|
||||
<string>MP3 256k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Windows Media 128k (Premium only)</string>
|
||||
<string>AAC 64k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>AAC 128k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Windows Media 64k</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Windows Media 128k</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
@ -158,6 +180,12 @@
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>username</tabstop>
|
||||
<tabstop>password</tabstop>
|
||||
<tabstop>login</tabstop>
|
||||
<tabstop>basic_audio_type</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -39,16 +39,6 @@ UrlHandler::LoadResult DigitallyImportedUrlHandler::StartLoading(const QUrl& url
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!service_->is_valid_stream_selected()) {
|
||||
service_->StreamError(tr("You have selected an invalid audio type setting"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (service_->is_premium_stream_selected() && !service_->is_premium_account()) {
|
||||
service_->StreamError(tr("You have selected a Premium-only audio type but do not have any account details entered"));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Start loading the station
|
||||
const QString key = url.host();
|
||||
qLog(Info) << "Loading station" << key;
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "digitallyimportedurlhandler.h"
|
||||
#include "internetmodel.h"
|
||||
#include "skyfmservice.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/taskmanager.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
@ -25,67 +26,8 @@
|
||||
|
||||
SkyFmService::SkyFmService(InternetModel* model, QObject* parent)
|
||||
: DigitallyImportedServiceBase(
|
||||
"SKY.fm", "SKY.fm", QUrl("http://www.sky.fm"), "sky.fm",
|
||||
QUrl("http://listen.sky.fm"), "skyfm", ":/providers/skyfm.png",
|
||||
model, parent)
|
||||
"SKY.fm", model, parent)
|
||||
{
|
||||
playlists_ = QList<Playlist>()
|
||||
<< Playlist(false, "http://listen.sky.fm/public3/%1.pls")
|
||||
<< Playlist(true, "http://listen.sky.fm/premium_high/%1.pls?hash=%2")
|
||||
<< Playlist(false, "http://listen.sky.fm/public1/%1.pls")
|
||||
<< Playlist(true, "http://listen.sky.fm/premium_medium/%1.pls?hash=%2")
|
||||
<< Playlist(true, "http://listen.sky.fm/premium/%1.pls?hash=%2")
|
||||
<< Playlist(false, "http://listen.sky.fm/public5/%1.asx")
|
||||
<< Playlist(true, "http://listen.sky.fm/premium_wma_low/%1.asx?hash=%2")
|
||||
<< Playlist(true, "http://listen.sky.fm/premium_wma/%1.asx?hash=%2");
|
||||
}
|
||||
|
||||
void SkyFmService::LoadStation(const QString& key) {
|
||||
if (!is_premium_stream_selected()) {
|
||||
// Non-premium streams can just start loading straight away
|
||||
LoadPlaylist(key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise we have to get the user's hashKey
|
||||
QNetworkRequest req(QUrl("http://www.sky.fm/configure_player.php"));
|
||||
QByteArray postdata = "amember_login=" + QUrl::toPercentEncoding(username_) +
|
||||
"&amember_pass=" + QUrl::toPercentEncoding(password_);
|
||||
|
||||
QNetworkReply* reply = network_->post(req, postdata);
|
||||
connect(reply, SIGNAL(finished()), SLOT(LoadHashKeyFinished()));
|
||||
|
||||
last_key_ = key;
|
||||
}
|
||||
|
||||
void SkyFmService::LoadHashKeyFinished() {
|
||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
||||
if (!reply) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString page_data = QString::fromUtf8(reply->readAll().data());
|
||||
QRegExp re("hashKey\\s*=\\s*'([0-9a-f]+)'");
|
||||
|
||||
if (re.indexIn(page_data) == -1) {
|
||||
url_handler_->CancelTask();
|
||||
emit StreamError(tr("Invalid SKY.fm username or password"));
|
||||
return;
|
||||
}
|
||||
|
||||
LoadPlaylist(last_key_, re.cap(1));
|
||||
}
|
||||
|
||||
void SkyFmService::LoadPlaylist(const QString& key, const QString& hash_key) {
|
||||
QString url_template = playlists_[audio_type_].url_template_;
|
||||
QUrl url;
|
||||
|
||||
if (hash_key.isEmpty()) {
|
||||
url = QUrl(url_template.arg(key));
|
||||
} else {
|
||||
url = QUrl(url_template.arg(key, hash_key));
|
||||
}
|
||||
|
||||
QNetworkReply* reply = network_->get(QNetworkRequest(url));
|
||||
connect(reply, SIGNAL(finished()), SLOT(LoadPlaylistFinished()));
|
||||
Init("SKY.fm", QUrl("http://www.sky.fm"), "sky.fm",
|
||||
QUrl("http://listen.sky.fm"), "skyfm", ":/providers/skyfm.png", "sky");
|
||||
}
|
||||
|
@ -21,21 +21,8 @@
|
||||
#include "digitallyimportedservicebase.h"
|
||||
|
||||
class SkyFmService : public DigitallyImportedServiceBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SkyFmService(InternetModel* model, QObject* parent = NULL);
|
||||
|
||||
void LoadStation(const QString& key);
|
||||
|
||||
private:
|
||||
void LoadPlaylist(const QString& key, const QString& hash_key = QString());
|
||||
|
||||
private slots:
|
||||
void LoadHashKeyFinished();
|
||||
|
||||
private:
|
||||
QString last_key_;
|
||||
};
|
||||
|
||||
#endif // SKYFMSERVICE_H
|
||||
|
Loading…
x
Reference in New Issue
Block a user