/*************************************************************************** * Copyright (C) 2008 by Ilya Kotov * * forkotov02@hotmail.ru * * * * 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. * * * * This program 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 this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include "scrobbler.h" #define SCROBBLER_HS_URL "post.audioscrobbler.com" #define PROTOCOL_VER "1.2" #define CLIENT_ID "qmm" #define CLIENT_VER "0.1" Scrobbler::Scrobbler(QObject *parent) : General(parent) { m_http = new QHttp(this); m_http->setHost(SCROBBLER_HS_URL, 80); m_state = General::Stopped; QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat); settings.beginGroup("Scrobbler"); m_login = settings.value("login").toString(); m_passw = settings.value("password").toString(); settings.endGroup(); //use global proxy settings if (settings.value ("Proxy/use_proxy", FALSE).toBool()) { if (settings.value ("Proxy/authentication", FALSE).toBool()) m_http->setProxy(settings.value("Proxy/host").toString(), settings.value("Proxy/port").toInt(), settings.value("Proxy/user").toString(), settings.value("Proxy/passw").toString()); else m_http->setProxy(settings.value("Proxy/host").toString(), settings.value("Proxy/port").toInt()); } m_disabled = m_login.isEmpty() || m_passw.isEmpty(); m_passw = QString(QCryptographicHash::hash(m_passw.toAscii(), QCryptographicHash::Md5).toHex()); connect(m_http, SIGNAL(requestFinished (int, bool)), SLOT(processResponse(int, bool))); connect(m_http, SIGNAL(readyRead (const QHttpResponseHeader&)), SLOT(readResponse(const QHttpResponseHeader&))); m_time = new QTime(); m_submitedSongs = 0; m_handshakeid = 0; m_submitid = 0; if (!m_disabled) handshake(); } Scrobbler::~Scrobbler() { delete m_time; } void Scrobbler::setState(const uint &state) { m_state = state; if (m_disabled) return; switch ((uint) state) { case General::Playing: { m_start_ts = time(NULL); m_time->restart(); if (!isReady() && m_handshakeid == 0) handshake(); break; } case General::Paused: { break; } case General::Stopped: { if (!m_song.isEmpty() && ((m_time->elapsed ()/1000 > 240) || (m_time->elapsed ()/1000 > int(m_song.length()/2))) && (m_time->elapsed ()/1000 > 60)) { m_songCache << m_song; m_timeStamps << m_start_ts; } m_song.clear(); if (m_songCache.isEmpty()) break; if (m_http->error() != QHttp::NoError) m_http->clearPendingRequests (); if (isReady() && m_submitid == 0) { submit(); } break; } } } void Scrobbler::setSongInfo(const SongInfo &song) { if (m_state == General::Playing && !song.title().isEmpty() //skip empty tags && !song.artist().isEmpty() && !song.isStream() //skip stream && !song.artist().contains("&") //skip tags with special symbols && !song.title().contains("&") && !song.album().contains("&") && !song.artist().contains("=") && !song.title().contains("=") && !song.album().contains("=")) { m_song = song; } } void Scrobbler::processResponse(int id, bool error) { if (error) { qWarning("Scrobbler: %s", qPrintable(m_http->errorString ())); //TODO hard failure handling if (id == m_submitid) m_submitid = 0; else if (id == m_handshakeid) m_handshakeid = 0; return; } QString str(m_array); QStringList strlist = str.split("\n"); if (id == m_handshakeid) { m_handshakeid = 0; if (!strlist[0].contains("OK") || strlist.size() < 4) { qWarning("Scrobbler: handshake phase error: %s", qPrintable(strlist[0])); //TODO badtime handling return; } if (strlist.size() > 3) //process handshake response { qDebug("Scrobbler: reading handshake response"); qDebug("Scrobbler: Session ID: %s",qPrintable(strlist[1])); qDebug("Scrobbler: Now-Playing URL: %s",qPrintable(strlist[2])); qDebug("Scrobbler: Submission URL: %s",qPrintable(strlist[3])); m_submitUrl = strlist[3]; m_session = strlist[1]; return; } } else if (id == m_submitid) { m_submitid = 0; if (!strlist[0].contains("OK")) { qWarning("Scrobbler: submit error: %s", qPrintable(strlist[0])); //TODO badsession handling return; } qWarning("Scrobbler: submited %d song(s)", m_submitedSongs); while (m_submitedSongs) { m_submitedSongs--; m_timeStamps.removeFirst (); m_songCache.removeFirst (); } } m_array.clear(); } void Scrobbler::readResponse(const QHttpResponseHeader &header) { if (header.statusCode () != 200) { qWarning("Scrobbler: error: %s",qPrintable(header.reasonPhrase ())); //TODO Failure Handling return; } m_array = m_http->readAll(); } void Scrobbler::handshake() { qDebug("Scrobbler::handshake()"); time_t ts = time(NULL); qDebug("Scrobbler: current time stamp %ld",ts); QString auth_tmp = QString("%1%2").arg(m_passw).arg(ts); QByteArray auth = QCryptographicHash::hash(auth_tmp.toAscii (), QCryptographicHash::Md5); auth = auth.toHex(); QString url = QString("%1?hs=true&p=%2&c=%3&v=%4&u=%5&t=%6&a=%7") .arg("/") .arg(PROTOCOL_VER) .arg(CLIENT_ID) .arg(CLIENT_VER) .arg(m_login) .arg(ts) .arg(QString(auth)); qDebug("Scrobbler: request url: %s",qPrintable(url)); m_http->setHost(SCROBBLER_HS_URL, 80); m_handshakeid = m_http->get(url); } void Scrobbler::submit() { qDebug("Scrobbler::submit()"); if (m_songCache.isEmpty()) return; m_submitedSongs = m_songCache.size(); QString body = QString("s=%1").arg(m_session); for (int i = 0; i < qMin(m_songCache.size(), 25); ++i) { SongInfo info = m_songCache[i]; body += QString("&a[%9]=%1&t[%9]=%2&i[%9]=%3&o[%9]=%4&r[%9]=%5&l[%9]=%6&b[%9]=%7&n[%9]=%8&m[%9]=") .arg(info.artist()) .arg(info.title()) .arg( m_timeStamps[i]) .arg("P") .arg("") .arg(info.length()) .arg(info.album()) .arg(info.track()) .arg(i); } QUrl url(m_submitUrl); m_http->setHost(url.host(), url.port()); QHttpRequestHeader header("POST", url.path()); header.setContentType("application/x-www-form-urlencoded"); header.setValue("User-Agent","iScrobbler/1.5.1qmmp-plugins/0.2"); header.setValue("Host",url.host()); header.setValue("Accept", "*/*"); header.setContentLength(QUrl::toPercentEncoding(body,":/[]&=").size()); qDebug("Scrobbler: submit request header"); qDebug(qPrintable(header.toString())); qDebug("*****************************"); m_submitid = m_http->request(header, QUrl::toPercentEncoding(body,":/[]&=")); } bool Scrobbler::isReady() { return !m_submitUrl.isEmpty() && !m_session.isEmpty(); }