Add last.fm scrobbling support to JuK.

No configuration GUI yet, you have to add a [Scrobbling] section to your jukrc,
with a Username=foo and Password=bar field to make it work.

Doesn't use liblastfm, because liblastfm doesn't support the latest last.fm API.

FEATURE:
CCBUG:262194


svn path=/trunk/KDE/kdemultimedia/juk/; revision=1281560
parent 0c869afe
......@@ -75,6 +75,7 @@ set(juk_SRCS ${tunepimp_SRCS}
playlistitem.cpp
playlistsearch.cpp
playlistsplitter.cpp
scrobbler.cpp
searchplaylist.cpp
searchwidget.cpp
slideraction.cpp
......
......@@ -46,6 +46,7 @@
#include "collectionlist.h"
#include "coverinfo.h"
#include "tag.h"
#include "scrobbler.h"
using namespace ActionCollection;
......@@ -69,6 +70,10 @@ PlayerManager::PlayerManager() :
// later, just disable it here. -- mpyne
// setup();
new PlayerAdaptor( this );
m_scrobbler = new Scrobbler(this);
connect(this, SIGNAL(signalItemChanged(FileHandle)), m_scrobbler, SLOT(nowPlaying(FileHandle)));
}
PlayerManager::~PlayerManager()
......@@ -216,7 +221,8 @@ void PlayerManager::play(const FileHandle &file)
// The "currently playing" media object.
Phonon::MediaObject *mediaObject = m_media[m_curOutputPath];
connect(mediaObject, SIGNAL(aboutToFinish()), m_scrobbler, SLOT(scrobble()));
if(file.isNull()) {
if(paused())
mediaObject->play();
......
......@@ -29,6 +29,7 @@
#include <Phonon/Global>
#include <Phonon/Path>
class Scrobbler;
class KSelectAction;
class StatusLabel;
class PlaylistInterface;
......@@ -127,6 +128,7 @@ private:
FileHandle m_file;
PlaylistInterface *m_playlistInterface;
StatusLabel *m_statusLabel;
Scrobbler *m_scrobbler;
bool m_muted;
bool m_setup;
bool m_crossfadeTracks;
......
#include "scrobbler.h"
#include <kglobal.h>
#include <kconfiggroup.h>
#include <QDir>
#include <KDebug>
#include <QCryptographicHash>
#include <KSharedConfig>
#include <KSharedPtr>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QDomDocument>
#include "tag.h"
Scrobbler::Scrobbler(QObject* parent): QObject(parent)
{
KConfigGroup config(KGlobal::config(), "Scrobbling");
m_sessionKey = config.readEntry("SessionKey", "");//TODO: use kwallet
if (m_sessionKey.isEmpty())
getAuthToken();
}
Scrobbler::~Scrobbler()
{
}
QByteArray Scrobbler::md5(QByteArray data)
{
return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex().rightJustified(32, '0').toLower();
}
void Scrobbler::sign(QMap< QString, QString >& params)
{
params["api_key"] = "3e6ecbd7284883089e8f2b5b53b0aecd";
QString s;
QMapIterator<QString, QString> i( params );
while (i.hasNext()) {
i.next();
s += i.key() + i.value();
}
s += "2cab3957b1f70d485e9815ac1ac94096"; //shared secret
params["api_sig"] = md5(s.toUtf8());
}
void Scrobbler::getAuthToken()
{
kDebug() << "Getting new auth token...";
KConfigGroup config(KGlobal::config(), "Scrobbling");
QString username = config.readEntry("Username", "");//TODO: use kwallet
QString password = config.readEntry("Password", "");//TODO: use kwallet
if (username.isEmpty() || password.isEmpty())
return;
QByteArray authToken = md5((username + md5(password.toUtf8() )).toUtf8());
QMap<QString, QString> params;
params["method"] = "auth.getMobileSession";
params["authToken"] = authToken;
params["username"] = username;
QNetworkAccessManager *qnam = new QNetworkAccessManager(this);
QUrl url("http://ws.audioscrobbler.com/2.0/?");
sign(params);
foreach(QString key, params.keys()) {
url.addQueryItem(key, params[key]);
}
QNetworkReply *reply = qnam->get(QNetworkRequest(url));
connect(reply, SIGNAL(finished()), this, SLOT(handleAuthenticationReply()));
}
void Scrobbler::handleAuthenticationReply()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
qWarning() << "got authentication reply";
if (reply->error() != QNetworkReply::NoError) {
qWarning() << reply->errorString();
return;
}
QDomDocument doc;
QByteArray data = reply->readAll();
qWarning() << data;
doc.setContent(data);
QDomElement el = doc.documentElement();
m_sessionKey = el.firstChildElement("session").firstChildElement("key").text();
KConfigGroup config(KGlobal::config(), "Scrobbling");
config.writeEntry("SessionKey", m_sessionKey);
return;
}
void Scrobbler::nowPlaying(const FileHandle& file)
{
if (m_sessionKey.isEmpty()) {
getAuthToken();
return;
}
qWarning() << "Now playing" << file.tag()->title();
QMap<QString, QString> params;
params["method"] = "track.updateNowPlaying";
params["sk"] = m_sessionKey;
params["track"] = file.tag()->title();
params["artist"] = file.tag()->artist();
params["album"] = file.tag()->album();
params["trackNumber"] = QString::number(file.tag()->track());
params["duration"] = QString::number(file.tag()->seconds());
sign(params);
post(params);
m_file = file;
m_startedPlaying = QDateTime::currentMSecsSinceEpoch() / 1000;
}
void Scrobbler::scrobble()
{
if (m_sessionKey.isEmpty()) {
getAuthToken();
return;
}
qWarning() << "Scrobbling" << m_file.tag()->title();
QMap<QString, QString> params;
params["method"] = "track.scrobble";
params["sk"] = m_sessionKey;
params["timestamp"] = QString::number(m_startedPlaying);
params["track"] = m_file.tag()->title();
params["artist"] = m_file.tag()->artist();
params["album"] = m_file.tag()->album();
params["trackNumber"] = QString::number(m_file.tag()->track());
params["duration"] = QString::number(m_file.tag()->seconds());
sign(params);
post(params);
}
void Scrobbler::post(QMap<QString, QString> &params)
{
QNetworkAccessManager *qnam = new QNetworkAccessManager(this);
QUrl url("http://ws.audioscrobbler.com/2.0/");
QByteArray data;
foreach(QString key, params.keys()) {
data += QUrl::toPercentEncoding(key) + "=" + QUrl::toPercentEncoding(params[key]) + "&";
}
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply *reply = qnam->post(req, data);
connect(reply, SIGNAL(finished()), this, SLOT(handleResults()));
}
void Scrobbler::handleResults()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
QByteArray data = reply->readAll();
qWarning() << data;
if (data.contains("code=\"9\"")) // We need a new token
getAuthToken();
}
/***************************************************************************
begin : Tue Feb 21 2012
copyright : (C) 2012 by Martin Sandsmark
email : martin.sandsmark@kde.org
***************************************************************************/
/***************************************************************************
* *
* This program 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 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef SCROBBLER_H
#define SCROBBLER_H
#include <QObject>
#include <QByteArray>
#include <QMap>
#include "filehandle.h"
/**
* A class that handles scrobbling of tracks to last.fm
*/
class Scrobbler : public QObject {
Q_OBJECT
public:
explicit Scrobbler(QObject* parent = 0);
virtual ~Scrobbler();
public slots:
void nowPlaying(const FileHandle&);
void scrobble();
private slots:
void getAuthToken();
void handleAuthenticationReply();
void handleResults();
private:
void sign(QMap<QString, QString> &request);
void post(QMap<QString, QString> &request);
QByteArray md5(QByteArray data);
FileHandle m_file;
qint64 m_startedPlaying;
QString m_sessionKey;
};
#endif /* SCROBBLER_H */
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment