487 lines
18 KiB
C++
487 lines
18 KiB
C++
#include "./groovesharkdownload.h"
|
|
|
|
#include "../application/utils.h"
|
|
|
|
#include "resources/config.h"
|
|
|
|
#include <QCryptographicHash>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
#include <QUrlQuery>
|
|
#include <QUuid>
|
|
|
|
using namespace CppUtilities;
|
|
using namespace Application;
|
|
|
|
namespace Network {
|
|
|
|
QJsonValue GroovesharkDownload::m_sessionId = QJsonValue();
|
|
|
|
QString GroovesharkDownload::m_token = QString();
|
|
|
|
const QJsonValue GroovesharkDownload::m_uuid = generateUuid();
|
|
|
|
const QJsonValue GroovesharkDownload::m_country = generateDefaultCountry();
|
|
|
|
QJsonValue GroovesharkDownload::m_htmlClient = QJsonValue(QStringLiteral("htmlshark"));
|
|
|
|
QJsonValue GroovesharkDownload::m_jsClient = QJsonValue(QStringLiteral("jsqueue"));
|
|
|
|
QJsonValue GroovesharkDownload::m_clientRevision = QJsonValue(QStringLiteral("20130520"));
|
|
|
|
QJsonValue GroovesharkDownload::m_jsClientRevision = QJsonValue(QStringLiteral("20130520"));
|
|
|
|
QString GroovesharkDownload::m_htmlRandomizer = QStringLiteral(":nuggetsOfBaller:");
|
|
|
|
QString GroovesharkDownload::m_jsRandomizer = QStringLiteral(":chickenFingers:");
|
|
|
|
const QString GroovesharkDownload::m_anyRandomizer = QStringLiteral("asdfgh");
|
|
|
|
const QJsonValue GroovesharkDownload::m_privacy = QJsonValue(0);
|
|
|
|
QByteArray GroovesharkDownload::m_referer = QByteArray("http://grooveshark.com/JSQueue.swf?20120124.01");
|
|
|
|
const QByteArray GroovesharkDownload::m_accept = QByteArray("text/html, image/jpeg, *; q=.2, */*; q=.2");
|
|
|
|
/*!
|
|
* \class GroovesharkDownload
|
|
* \brief Download implementation for Grooveshark songs.
|
|
*/
|
|
|
|
/*!
|
|
* \brief Constructs a new GroovesharkDownload for the specified \a songId.
|
|
*/
|
|
GroovesharkDownload::GroovesharkDownload(const QString &songId, QObject *parent)
|
|
: GroovesharkDownload(GroovesharkRequestType::SongStream, songId, parent)
|
|
{
|
|
}
|
|
|
|
GroovesharkDownload::GroovesharkDownload(GroovesharkRequestType requestType, const QVariant &requestData, QObject *parent)
|
|
: HttpDownloadWithInfoRequst(QUrl(), parent)
|
|
, m_currentStep(0)
|
|
, m_requestType(requestType)
|
|
, m_requestData(requestData)
|
|
{
|
|
// check if "steps" to establish session can be skiped because session is already set
|
|
m_currentStep = m_sessionId.isNull() ? 0 : (m_token.isEmpty() ? 1 : (requestType == GroovesharkRequestType::SongStream ? 2 : -1));
|
|
switch (requestType) {
|
|
case GroovesharkRequestType::SongStream:
|
|
if (requestData.type() == QVariant::String)
|
|
setId(requestData.toString());
|
|
break;
|
|
case GroovesharkRequestType::AlbumGetSongs:
|
|
case GroovesharkRequestType::PlaylistGetSongs:
|
|
case GroovesharkRequestType::ArtistGetSongs:
|
|
if (requestData.canConvert<GroovesharkGetSongsRequestData>())
|
|
setId(requestData.value<GroovesharkGetSongsRequestData>().id);
|
|
else if (requestData.type() == QVariant::String)
|
|
setId(requestData.toString());
|
|
break;
|
|
case GroovesharkRequestType::SearchForAlbum:
|
|
case GroovesharkRequestType::SearchForPlaylist:
|
|
case GroovesharkRequestType::SearchForArtists:
|
|
break;
|
|
}
|
|
}
|
|
|
|
Download *GroovesharkDownload::infoRequestDownload(bool &success, QString &reasonForFail)
|
|
{
|
|
Download *download = nullptr;
|
|
QJsonObject headerObj;
|
|
QJsonObject paramObj;
|
|
switch (m_currentStep) {
|
|
case -1:
|
|
success = true;
|
|
break;
|
|
case 0:
|
|
if (m_sessionId.isNull()) {
|
|
// initiateSession
|
|
headerObj.insert(QStringLiteral("client"), m_htmlClient);
|
|
headerObj.insert(QStringLiteral("clientRevision"), m_clientRevision);
|
|
headerObj.insert(QStringLiteral("privacy"), m_privacy);
|
|
headerObj.insert(QStringLiteral("uuid"), m_uuid);
|
|
headerObj.insert(QStringLiteral("country"), m_country);
|
|
download = createJsonPostRequest(QStringLiteral("initiateSession"), headerObj, paramObj);
|
|
}
|
|
success = true;
|
|
break;
|
|
case 1:
|
|
if (m_token.isEmpty()) {
|
|
// getCommunicationToken
|
|
headerObj.insert(QStringLiteral("client"), m_htmlClient);
|
|
headerObj.insert(QStringLiteral("clientRevision"), m_clientRevision);
|
|
headerObj.insert(QStringLiteral("session"), m_sessionId);
|
|
headerObj.insert(QStringLiteral("token"), generateTokenHash(QStringLiteral("getCommunicationToken")));
|
|
headerObj.insert(QStringLiteral("privacy"), m_privacy);
|
|
headerObj.insert(QStringLiteral("uuid"), m_uuid);
|
|
paramObj.insert(QStringLiteral("secretKey"), generateSecretKey());
|
|
download = createJsonPostRequest(QStringLiteral("getCommunicationToken"), headerObj, paramObj, true);
|
|
}
|
|
success = true;
|
|
break;
|
|
case 2:
|
|
// getStreamKeyFromSongIDEx
|
|
headerObj.insert(QStringLiteral("uuid"), m_uuid);
|
|
headerObj.insert(QStringLiteral("privacy"), m_privacy);
|
|
headerObj.insert(QStringLiteral("session"), m_sessionId);
|
|
headerObj.insert(QStringLiteral("clientRevision"), m_clientRevision);
|
|
headerObj.insert(QStringLiteral("client"), m_jsClient);
|
|
headerObj.insert(QStringLiteral("token"), generateTokenHash(QStringLiteral("getStreamKeyFromSongIDEx"), 1));
|
|
paramObj.insert(QStringLiteral("country"), m_country);
|
|
paramObj.insert(QStringLiteral("mobile"), QJsonValue(false));
|
|
paramObj.insert(QStringLiteral("type"), QJsonValue(0));
|
|
paramObj.insert(QStringLiteral("songID"), QJsonValue(id()));
|
|
paramObj.insert(QStringLiteral("prefetch"), QJsonValue(false));
|
|
download = createJsonPostRequest("getStreamKeyFromSongIDEx", headerObj, paramObj);
|
|
success = true;
|
|
break;
|
|
default:
|
|
reasonForFail = QStringLiteral("Internal error.");
|
|
success = false;
|
|
}
|
|
return download;
|
|
}
|
|
|
|
bool GroovesharkDownload::isInitiatingInstantlyRecommendable() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the session ID.
|
|
*/
|
|
QJsonValue GroovesharkDownload::sessionId()
|
|
{
|
|
return m_sessionId;
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the communication token.
|
|
*/
|
|
QString GroovesharkDownload::communicationToken()
|
|
{
|
|
return m_token;
|
|
}
|
|
|
|
/*!
|
|
* \brief Resets the current session (ID and communication token).
|
|
*/
|
|
void GroovesharkDownload::resetSession()
|
|
{
|
|
m_sessionId = QJsonValue();
|
|
m_token.clear();
|
|
}
|
|
|
|
void GroovesharkDownload::evalVideoInformation(Download *, QBuffer *videoInfoBuffer)
|
|
{
|
|
QString code;
|
|
if (videoInfoBuffer) { // the buffer might be zero!
|
|
code.append(videoInfoBuffer->readAll());
|
|
}
|
|
switch (m_currentStep) {
|
|
case -1:
|
|
setupFinalRequest();
|
|
reportInitiated(true);
|
|
break;
|
|
case 0: {
|
|
QString value;
|
|
if ((substring(code, value, 0, QStringLiteral("\"session\":\""), QStringLiteral("\"")) <= 0) || value.isEmpty()) {
|
|
if ((substring(code, value, 0, QStringLiteral("\"message\":\""), QStringLiteral("\"")) <= 0) || value.isEmpty()) {
|
|
reportInitiated(false, tr("The session couldn't be initialized."));
|
|
} else {
|
|
reportInitiated(false, tr("The session couldn't be initialized (%1).").arg(value));
|
|
}
|
|
} else {
|
|
m_sessionId = QJsonValue(value);
|
|
++m_currentStep;
|
|
doInit();
|
|
}
|
|
break;
|
|
}
|
|
case 1:
|
|
if ((substring(code, m_token, 0, QStringLiteral("\"result\":\""), QStringLiteral("\"")) <= 0) || m_token.isEmpty()) {
|
|
reportInitiated(false, tr("The communication token couldn't be retireved."));
|
|
} else {
|
|
if (m_requestType == GroovesharkRequestType::SongStream) {
|
|
++m_currentStep;
|
|
doInit();
|
|
} else {
|
|
setupFinalRequest();
|
|
reportInitiated(true);
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
if ((substring(code, m_streamKey, 0, QStringLiteral("\"streamKey\":\""), QStringLiteral("\"")) <= 0) || m_streamKey.isEmpty()) {
|
|
reportInitiated(false, tr("The stream key couldn't be found."));
|
|
} else {
|
|
if ((substring(code, m_streamHost, 0, QStringLiteral("\"ip\":\""), QStringLiteral("\"")) <= 0) || m_streamHost.isEmpty()) {
|
|
reportInitiated(false, tr("The stream host couldn't be found."));
|
|
} else {
|
|
setupFinalRequest();
|
|
reportInitiated(true);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
reportInitiated(false, tr("Internal error."));
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Generates a UUID.
|
|
*/
|
|
QJsonValue GroovesharkDownload::generateUuid()
|
|
{
|
|
QString res = QUuid::createUuid().toString();
|
|
int pos = res.startsWith('{') ? 1 : 0;
|
|
int length = res.endsWith('}') ? res.length() - pos - 1 : -1;
|
|
return QJsonValue(res.mid(pos, length));
|
|
}
|
|
|
|
/*!
|
|
* \brief Generates the default country information.
|
|
*/
|
|
QJsonValue GroovesharkDownload::generateDefaultCountry()
|
|
{
|
|
QJsonObject jsonObj;
|
|
jsonObj.insert(QStringLiteral("IPR"), QJsonValue(0));
|
|
jsonObj.insert(QStringLiteral("ID"), QJsonValue(0));
|
|
jsonObj.insert(QStringLiteral("CC1"), QJsonValue(0));
|
|
jsonObj.insert(QStringLiteral("CC2"), QJsonValue(0));
|
|
jsonObj.insert(QStringLiteral("CC3"), QJsonValue(0));
|
|
jsonObj.insert(QStringLiteral("CC4"), QJsonValue(0));
|
|
return QJsonValue(jsonObj);
|
|
}
|
|
|
|
/*!
|
|
* \brief Loads the authentication information from the specified file.
|
|
*/
|
|
bool GroovesharkDownload::loadAuthenticationInformationFromFile(const QString &path, QString *errorMessage)
|
|
{
|
|
QJsonObject fileObj = loadJsonObjectFromResource(path, errorMessage);
|
|
if (fileObj.isEmpty()) {
|
|
return false;
|
|
} else {
|
|
QJsonValue clientVal;
|
|
QJsonObject clientObj;
|
|
QJsonValue name;
|
|
QJsonValue revision;
|
|
QJsonValue randomizer;
|
|
clientVal = fileObj.value(QStringLiteral("htmlClient"));
|
|
if (clientVal.isObject()) {
|
|
clientObj = clientVal.toObject();
|
|
name = clientObj.value(QStringLiteral("name"));
|
|
if (name.isString()) {
|
|
m_htmlClient = name;
|
|
}
|
|
revision = clientObj.value(QStringLiteral("revision"));
|
|
if (revision.isString()) {
|
|
m_clientRevision = revision;
|
|
}
|
|
randomizer = clientObj.value(QStringLiteral("randomizer"));
|
|
if (randomizer.isString()) {
|
|
m_htmlRandomizer = randomizer.toString();
|
|
}
|
|
}
|
|
clientVal = fileObj.value(QStringLiteral("jsClient"));
|
|
if (clientVal.isObject()) {
|
|
clientObj = clientVal.toObject();
|
|
name = clientObj.value(QStringLiteral("name"));
|
|
if (name.isString()) {
|
|
m_jsClient = name;
|
|
}
|
|
revision = clientObj.value(QStringLiteral("revision"));
|
|
if (revision.isString()) {
|
|
m_jsClientRevision = revision;
|
|
}
|
|
randomizer = clientObj.value(QStringLiteral("randomizer"));
|
|
if (randomizer.isString()) {
|
|
m_jsRandomizer = randomizer.toString();
|
|
}
|
|
}
|
|
clientVal = fileObj.value(QStringLiteral("referer"));
|
|
if (clientVal.isString()) {
|
|
m_referer.append(clientVal.toString().toUtf8());
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Generates the token hash with the specified \a method and \a mode.
|
|
*/
|
|
QJsonValue GroovesharkDownload::generateTokenHash(QString method, int mode)
|
|
{
|
|
QByteArray toHash;
|
|
toHash.append(method.toUtf8());
|
|
toHash.append(':');
|
|
toHash.append(m_token.toUtf8());
|
|
switch (mode) {
|
|
case 1:
|
|
toHash.append(m_htmlRandomizer.toUtf8());
|
|
break;
|
|
default:
|
|
toHash.append(m_jsRandomizer.toUtf8());
|
|
break;
|
|
}
|
|
toHash.append(m_anyRandomizer.toUtf8());
|
|
QString res;
|
|
res.append(m_anyRandomizer);
|
|
res.append(QString(QCryptographicHash::hash(toHash, QCryptographicHash::Sha1).toHex()).toLower());
|
|
return QJsonValue(res);
|
|
}
|
|
|
|
/*!
|
|
* \brief Generates the secret key.
|
|
*/
|
|
QJsonValue GroovesharkDownload::generateSecretKey()
|
|
{
|
|
QByteArray toHash;
|
|
toHash.append(m_sessionId.toString().toUtf8());
|
|
QString secretKey(QCryptographicHash::hash(toHash, QCryptographicHash::Md5).toHex());
|
|
return QJsonValue(secretKey);
|
|
}
|
|
|
|
/*!
|
|
* \brief Creates a JSON post request.
|
|
*/
|
|
HttpDownload *GroovesharkDownload::createJsonPostRequest(const QString &method, const QJsonObject &header, const QJsonObject ¶meters, bool https)
|
|
{
|
|
// create json post data
|
|
QJsonObject jsonObj;
|
|
jsonObj.insert(QStringLiteral("method"), QJsonValue(method));
|
|
jsonObj.insert(QStringLiteral("header"), header);
|
|
jsonObj.insert(QStringLiteral("parameters"), parameters);
|
|
QJsonDocument jsonDoc(jsonObj);
|
|
QByteArray postData = jsonDoc.toJson();
|
|
// create and setup download
|
|
QString url = https ? QStringLiteral("https") : QStringLiteral("http");
|
|
url.append(QStringLiteral("://grooveshark.com/more.php?"));
|
|
url.append(method);
|
|
HttpDownload *download = new HttpDownload(QUrl(url));
|
|
download->setMethod(HttpDownloadMethod::Post);
|
|
download->setPostData(postData);
|
|
download->setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("application/json")));
|
|
download->setHeader("Refer", m_referer);
|
|
download->setHeader("Accept", m_accept);
|
|
//download->setHeader("Connection", "keep-alive");
|
|
return download;
|
|
}
|
|
|
|
/*!
|
|
* \brief Sets the final request up.
|
|
*/
|
|
void GroovesharkDownload::setupFinalRequest()
|
|
{
|
|
setMethod(HttpDownloadMethod::Post);
|
|
switch (m_requestType) {
|
|
case GroovesharkRequestType::SongStream: {
|
|
setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QLatin1String("application/x-www-form-urlencoded")));
|
|
setHeader("Accept", m_accept);
|
|
QUrlQuery query;
|
|
query.addQueryItem("streamKey", m_streamKey);
|
|
QByteArray postData;
|
|
postData.append(query.toString(QUrl::FullyEncoded).toUtf8());
|
|
setPostData(postData);
|
|
addDownloadUrl(tr("MPEG-1 Layer 3"), QUrl(QStringLiteral("http://%1/stream.php").arg(m_streamHost)));
|
|
break;
|
|
}
|
|
default: {
|
|
QString method;
|
|
QJsonObject header;
|
|
QJsonObject params;
|
|
|
|
header.insert(QStringLiteral("client"), m_htmlClient);
|
|
header.insert(QStringLiteral("clientRevision"), m_clientRevision);
|
|
header.insert(QStringLiteral("session"), m_sessionId);
|
|
header.insert(QStringLiteral("privacy"), m_privacy);
|
|
header.insert(QStringLiteral("uuid"), m_uuid);
|
|
header.insert(QStringLiteral("country"), m_country);
|
|
|
|
switch (m_requestType) {
|
|
case GroovesharkRequestType::AlbumGetSongs:
|
|
method = QStringLiteral("albumGetAllSongs");
|
|
params.insert(QStringLiteral("offset"), QJsonValue(0));
|
|
params.insert(QStringLiteral("albumID"), QJsonValue(id()));
|
|
params.insert(QStringLiteral("isVerified"),
|
|
QJsonValue(m_requestData.canConvert<GroovesharkGetSongsRequestData>() ? m_requestData.value<GroovesharkGetSongsRequestData>().verified
|
|
: false));
|
|
break;
|
|
case GroovesharkRequestType::PlaylistGetSongs:
|
|
method = QStringLiteral("playlistGetSongs");
|
|
params.insert(QStringLiteral("playlistID"), QJsonValue(id()));
|
|
break;
|
|
case GroovesharkRequestType::ArtistGetSongs:
|
|
method = QStringLiteral("artistGetArtistSongs");
|
|
params.insert(QStringLiteral("artistID"), QJsonValue(id()));
|
|
break;
|
|
case GroovesharkRequestType::SearchForAlbum:
|
|
case GroovesharkRequestType::SearchForPlaylist:
|
|
method = QStringLiteral("getResultsFromSearch");
|
|
params.insert(QStringLiteral("guts"), QJsonValue(QStringLiteral("0")));
|
|
params.insert(QStringLiteral("query"), QJsonValue(m_requestData.toString()));
|
|
params.insert(QStringLiteral("ppOverride"), QStringLiteral("false"));
|
|
{
|
|
QJsonArray types;
|
|
switch (m_requestType) {
|
|
case GroovesharkRequestType::SearchForAlbum:
|
|
types.append(QJsonValue(QStringLiteral("Albums")));
|
|
break;
|
|
case GroovesharkRequestType::SearchForPlaylist:
|
|
types.append(QJsonValue(QStringLiteral("Playlists")));
|
|
break;
|
|
case GroovesharkRequestType::SearchForArtists:
|
|
types.append(QJsonValue(QStringLiteral("Artists")));
|
|
break;
|
|
default:;
|
|
}
|
|
params.insert(QStringLiteral("type"), QJsonValue(types));
|
|
}
|
|
break;
|
|
default:;
|
|
}
|
|
header.insert(QStringLiteral("token"), generateTokenHash(method, 1));
|
|
QJsonObject mainObj;
|
|
mainObj.insert(QStringLiteral("method"), QJsonValue(method));
|
|
mainObj.insert(QStringLiteral("header"), header);
|
|
mainObj.insert(QStringLiteral("parameters"), params);
|
|
QJsonDocument jsonDoc(mainObj);
|
|
QByteArray postData = jsonDoc.toJson();
|
|
// create and setup download
|
|
setPostData(postData);
|
|
setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
setHeader("Refer", m_referer);
|
|
setHeader("Accept", m_accept);
|
|
//setHeader("Connection", "keep-alive");
|
|
addDownloadUrl(tr("Grooveshark json request"), QUrl(QStringLiteral("http://grooveshark.com/more.php?") + method));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Returns the request type.
|
|
*/
|
|
GroovesharkRequestType GroovesharkDownload::requestType() const
|
|
{
|
|
return m_requestType;
|
|
}
|
|
|
|
QString GroovesharkDownload::suitableFilename() const
|
|
{
|
|
auto filename = Download::suitableFilename();
|
|
if (!filename.endsWith(QLatin1String(".mp3"))) {
|
|
filename.append(QStringLiteral(".mp3"));
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
QString GroovesharkDownload::typeName() const
|
|
{
|
|
return tr("Grooveshark");
|
|
}
|
|
} // namespace Network
|