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:
David Sansome 2011-11-02 23:41:46 +00:00
parent 8e314ef4d8
commit 63140f83cf
13 changed files with 385 additions and 224 deletions

View File

@ -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

View 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;
}

View 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

View File

@ -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()));
}

View File

@ -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

View File

@ -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"));
}
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();
}

View File

@ -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

View File

@ -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());
}

View File

@ -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

View File

@ -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,7 +37,18 @@
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="username"/>
<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">
@ -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>&lt;a href=&quot;http://www.di.fm/premium/&quot;&gt;Upgrade to Premium now&lt;/a&gt;</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>

View File

@ -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;

View File

@ -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");
}

View File

@ -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