mirror of
https://github.com/clangen/musikcube.git
synced 2025-03-29 19:20:28 +00:00
Implemented the track.scrobble() call. Posts to last.fm work now!
This commit is contained in:
parent
8a75e7e8b3
commit
c31514c315
@ -46,6 +46,7 @@ namespace musik { namespace core { namespace io {
|
||||
class HttpClient: public std::enable_shared_from_this<HttpClient<T>> {
|
||||
public:
|
||||
enum class Thread { Current, Background };
|
||||
enum class HttpMethod { Get, Post };
|
||||
|
||||
using HttpHeaders = std::unordered_map<std::string, std::string>;
|
||||
using Callback = std::function<void(HttpClient<T>* caller, int, CURLcode)>;
|
||||
@ -65,6 +66,8 @@ namespace musik { namespace core { namespace io {
|
||||
HttpClient<T>& Decorator(DecoratorCallback decoratorCb);
|
||||
HttpClient<T>& Canceled(CanceledCallback canceledCb);
|
||||
HttpClient<T>& Mode(Thread mode);
|
||||
HttpClient<T>& PostBody(const std::string& postBody);
|
||||
HttpClient<T>& Method(HttpMethod mode);
|
||||
|
||||
const T& Stream() const { return this->ostream; }
|
||||
const HttpHeaders& ResponseHeaders() const { return this->responseHeaders; }
|
||||
@ -90,12 +93,14 @@ namespace musik { namespace core { namespace io {
|
||||
|
||||
T ostream;
|
||||
std::string url;
|
||||
std::string postBody;
|
||||
HttpHeaders requestHeaders, responseHeaders;
|
||||
HeaderCallback headersCb;
|
||||
DecoratorCallback decoratorCb;
|
||||
CanceledCallback canceledCallback;
|
||||
bool cancel;
|
||||
Thread mode{ Thread::Background };
|
||||
HttpMethod method{ HttpMethod::Get };
|
||||
CURL* curl;
|
||||
|
||||
static std::mutex instanceMutex;
|
||||
@ -226,6 +231,11 @@ namespace musik { namespace core { namespace io {
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERDATA, this);
|
||||
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &CurlHeaderCallback);
|
||||
|
||||
#if 0
|
||||
curl_easy_setopt(curl, CURLOPT_PROXY, "http://localhost");
|
||||
curl_easy_setopt(curl, CURLOPT_PROXYPORT, 8080);
|
||||
#endif
|
||||
|
||||
if (this->requestHeaders.size()) {
|
||||
struct curl_slist* slist = nullptr;
|
||||
for (auto it : this->requestHeaders) {
|
||||
@ -235,6 +245,14 @@ namespace musik { namespace core { namespace io {
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
|
||||
}
|
||||
|
||||
if (this->method == HttpMethod::Post) {
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||
|
||||
if (this->postBody.size()) {
|
||||
curl_easy_setopt(curl, CURLOPT_COPYPOSTFIELDS, this->postBody.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == Thread::Background) {
|
||||
std::unique_lock<std::mutex> lock(instanceMutex);
|
||||
instances.insert(this->shared_from_this());
|
||||
@ -293,12 +311,24 @@ namespace musik { namespace core { namespace io {
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
HttpClient<T>& HttpClient<T>::PostBody(const std::string& postBody) {
|
||||
this->postBody = postBody;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
HttpClient<T>& HttpClient<T>::Mode(Thread mode) {
|
||||
this->mode = mode;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
HttpClient<T>& HttpClient<T>::Method(HttpMethod method) {
|
||||
this->method = method;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
HttpClient<T>& HttpClient<T>::Header(const std::string& key, const std::string& value) {
|
||||
this->requestHeaders[key] = value;
|
||||
|
@ -46,36 +46,13 @@ using namespace musik;
|
||||
using namespace cursespp;
|
||||
|
||||
static std::map<LastFmOverlay::State, std::string> stateToText = {
|
||||
{
|
||||
LastFmOverlay::State::Unregistered,
|
||||
"no last.fm account linked.\n\n"
|
||||
"press ENTER to begin the linking process."
|
||||
},
|
||||
{
|
||||
LastFmOverlay::State::ObtainingToken,
|
||||
"contacting last.fm for an account link token..."
|
||||
},
|
||||
{
|
||||
LastFmOverlay::State::WaitingForUser,
|
||||
"press 'o' to open the account link page in your default browser, "
|
||||
"or manually navigate to the following url:"
|
||||
"\n\n{{link}}\n\n"
|
||||
"after granting permission, press 'ENTER' to continue.\n\n"
|
||||
"(note: you may be asked to sign into your last.fm account.)"
|
||||
},
|
||||
{
|
||||
LastFmOverlay::State::RegisteringSession,
|
||||
"getting a session token, please wait..."
|
||||
},
|
||||
{
|
||||
LastFmOverlay::State::Registered,
|
||||
"registered with account: '{{username}}'."
|
||||
},
|
||||
{
|
||||
LastFmOverlay::State::Error,
|
||||
"failed to link your last.fm account.\n\n"
|
||||
"please try again later."
|
||||
}
|
||||
{ LastFmOverlay::State::Unregistered, "settings_last_fm_dialog_message_unregistered" },
|
||||
{ LastFmOverlay::State::ObtainingToken, "settings_last_fm_dialog_message_obtaining_token" },
|
||||
{ LastFmOverlay::State::WaitingForUser, "settings_last_fm_dialog_message_waiting_for_user" },
|
||||
{ LastFmOverlay::State::RegisteringSession, "settings_last_fm_dialog_message_registering_session" },
|
||||
{ LastFmOverlay::State::Registered, "settings_last_fm_dialog_message_registered" },
|
||||
{ LastFmOverlay::State::LinkError, "settings_last_fm_dialog_message_link_error" },
|
||||
{ LastFmOverlay::State::RegisterError, "settings_last_fm_dialog_message_register_error" }
|
||||
};
|
||||
|
||||
void LastFmOverlay::Show() {
|
||||
@ -110,7 +87,7 @@ void LastFmOverlay::GetLinkToken() {
|
||||
this->PostState(State::WaitingForUser);
|
||||
}
|
||||
else {
|
||||
this->PostState(State::Error);
|
||||
this->PostState(State::LinkError);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -123,7 +100,7 @@ void LastFmOverlay::CreateSession() {
|
||||
this->PostState(State::Registered);
|
||||
}
|
||||
else {
|
||||
this->PostState(State::Error);
|
||||
this->PostState(State::RegisterError);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -139,7 +116,7 @@ void LastFmOverlay::SetState(State state) {
|
||||
}
|
||||
|
||||
void LastFmOverlay::UpdateMessage() {
|
||||
std::string message = stateToText[state];
|
||||
std::string message = _TSTR(stateToText[state]);
|
||||
|
||||
switch (this->state) {
|
||||
case State::Registered: {
|
||||
@ -148,7 +125,8 @@ void LastFmOverlay::UpdateMessage() {
|
||||
break;
|
||||
}
|
||||
|
||||
case State::WaitingForUser: {
|
||||
case State::WaitingForUser:
|
||||
case State::RegisterError: {
|
||||
std::string url = lastfm::CreateLinkUrl(this->linkToken);
|
||||
core::ReplaceAll(message, "{{link}}", url);
|
||||
break;
|
||||
@ -164,66 +142,71 @@ void LastFmOverlay::UpdateButtons() {
|
||||
switch (this->state) {
|
||||
case State::Unregistered: {
|
||||
this->AddButton(
|
||||
"KEY_ENTER",
|
||||
"ENTER",
|
||||
"start",
|
||||
"KEY_ENTER", "ENTER", _TSTR("button_start"),
|
||||
[this](std::string key) {
|
||||
this->GetLinkToken();
|
||||
});
|
||||
|
||||
this->AddButton(
|
||||
"^[", "ESC", _TSTR("button_cancel"),
|
||||
[this](std::string key) {
|
||||
this->Dismiss();
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case State::Registered: {
|
||||
this->AddButton(
|
||||
"u",
|
||||
"u",
|
||||
"unregister",
|
||||
"u", "u", "unregister",
|
||||
[this](std::string key) {
|
||||
lastfm::ClearSession();
|
||||
this->LoadDefaultState();
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case State::WaitingForUser: {
|
||||
this->AddButton(
|
||||
"o",
|
||||
"o",
|
||||
"open url",
|
||||
[this](std::string key) {
|
||||
core::OpenFile(lastfm::CreateLinkUrl(this->linkToken));
|
||||
});
|
||||
|
||||
this->AddButton(
|
||||
"KEY_ENTER",
|
||||
"ENTER",
|
||||
"start",
|
||||
[this](std::string key) {
|
||||
this->CreateSession();
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case State::Error: {
|
||||
this->AddButton(
|
||||
"KEY_ENTER",
|
||||
"ENTER",
|
||||
_TSTR("button_ok"),
|
||||
"KEY_ENTER", "ENTER", _TSTR("button_close"),
|
||||
[this](std::string key) {
|
||||
this->Dismiss();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->state != State::Error) {
|
||||
this->AddButton(
|
||||
"^[",
|
||||
"ESC",
|
||||
_TSTR("button_close"),
|
||||
[this](std::string key) {
|
||||
this->Dismiss();
|
||||
});
|
||||
case State::WaitingForUser:
|
||||
case State::RegisterError: {
|
||||
this->AddButton(
|
||||
"o", "o", "open url",
|
||||
[this](std::string key) {
|
||||
core::OpenFile(lastfm::CreateLinkUrl(this->linkToken));
|
||||
});
|
||||
|
||||
|
||||
std::string continueText = _TSTR(
|
||||
(state == State::WaitingForUser)
|
||||
? "button_continue"
|
||||
: "button_retry");
|
||||
|
||||
this->AddButton(
|
||||
"KEY_ENTER", "ENTER", continueText,
|
||||
[this](std::string key) {
|
||||
this->CreateSession();
|
||||
});
|
||||
|
||||
this->AddButton(
|
||||
"^[", "ESC", _TSTR("button_cancel"),
|
||||
[this](std::string key) {
|
||||
this->Dismiss();
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case State::LinkError: {
|
||||
this->AddButton(
|
||||
"KEY_ENTER", "ENTER", _TSTR("button_ok"),
|
||||
[this](std::string key) {
|
||||
this->Dismiss();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,8 @@ namespace musik { namespace cube {
|
||||
WaitingForUser = 2,
|
||||
RegisteringSession = 3,
|
||||
Registered = 4,
|
||||
Error = 5
|
||||
LinkError = 5,
|
||||
RegisterError = 6
|
||||
};
|
||||
|
||||
static void Show();
|
||||
|
@ -53,6 +53,7 @@ static const std::string API_SECRET = "6dc09da925fe5c115b90320213c53b46";
|
||||
static const std::string URL_BASE = "http://ws.audioscrobbler.com/2.0/";
|
||||
static const std::string GET_TOKEN = "auth.getToken";
|
||||
static const std::string GET_SESSION = "auth.getSession";
|
||||
static const std::string UPDATE_NOW_PLAYING = "track.scrobble";
|
||||
static const std::string ACCOUNT_LINK_URL_BASE = "http://www.last.fm/api/auth/?api_key=" + API_KEY + "&token=";
|
||||
|
||||
using namespace musik;
|
||||
@ -73,20 +74,32 @@ static void validate(musik::cube::lastfm::Session& session) {
|
||||
session.token.size();
|
||||
}
|
||||
|
||||
static std::string generateSignedUrl(
|
||||
static std::string encode(std::string value) {
|
||||
static CURL* curl = curl_easy_init();
|
||||
if (curl && value.c_str()) {
|
||||
char* encoded = curl_easy_escape(curl, value.c_str(), value.size());
|
||||
if (encoded) {
|
||||
value = encoded;
|
||||
curl_free(encoded);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::string gernateSignedUrlParams(
|
||||
const std::string& method,
|
||||
std::map<std::string, std::string>&& params = { })
|
||||
std::map<std::string, std::string>&& params = {})
|
||||
{
|
||||
params["method"] = method;
|
||||
params["api_key"] = API_KEY;
|
||||
|
||||
std::string toHash;
|
||||
std::string url = URL_BASE;
|
||||
std::string urlParams;
|
||||
bool first = true;
|
||||
|
||||
for (auto it : params) {
|
||||
toHash += it.first + it.second;
|
||||
url += (first ? "?" : "&") + it.first + "=" + it.second;
|
||||
urlParams += (first ? "" : "&") + it.first + "=" + encode(it.second);
|
||||
first = false;
|
||||
}
|
||||
|
||||
@ -103,9 +116,15 @@ static std::string generateSignedUrl(
|
||||
}
|
||||
hexDigest[32] = 0;
|
||||
|
||||
url += "&format=json&api_sig=" + std::string(hexDigest);
|
||||
urlParams += "&format=json&api_sig=" + std::string(hexDigest);
|
||||
return urlParams;
|
||||
}
|
||||
|
||||
return url;
|
||||
static std::string generateSignedUrl(
|
||||
const std::string& method,
|
||||
std::map<std::string, std::string>&& params = { })
|
||||
{
|
||||
return URL_BASE + "?" + gernateSignedUrlParams(method, std::move(params));
|
||||
}
|
||||
|
||||
static inline Prefs settings() {
|
||||
@ -190,4 +209,28 @@ namespace musik { namespace cube { namespace lastfm {
|
||||
SaveSession(session);
|
||||
}
|
||||
|
||||
void Scrobble(musik::core::TrackPtr track) {
|
||||
if (track) {
|
||||
auto session = LoadSession();
|
||||
if (session.valid) {
|
||||
std::string postBody = gernateSignedUrlParams(UPDATE_NOW_PLAYING, {
|
||||
{ "track", track->GetString("title") },
|
||||
{ "album", track->GetString("album") },
|
||||
{ "artist", track->GetString("artist") },
|
||||
{ "albumArtist", track->GetString("album_artist") },
|
||||
{ "trackNumber", track->GetString("track") },
|
||||
{ "timestamp", std::to_string(std::time(0)) },
|
||||
{ "sk", session.sessionId }
|
||||
});
|
||||
|
||||
createClient()->Url(URL_BASE)
|
||||
.Mode(LastFmClient::Thread::Background)
|
||||
.Header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.Method(LastFmClient::HttpMethod::Post)
|
||||
.PostBody(postBody)
|
||||
.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} } }
|
@ -35,6 +35,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdafx.h"
|
||||
#include <core/library/track/Track.h>
|
||||
|
||||
namespace musik { namespace cube { namespace lastfm {
|
||||
struct Session {
|
||||
@ -48,6 +49,7 @@ namespace musik { namespace cube { namespace lastfm {
|
||||
extern void CreateAccountLinkToken(TokenCallback callback);
|
||||
extern const std::string CreateLinkUrl(const std::string& token);
|
||||
extern void CreateSession(const std::string& token, SessionCallback session);
|
||||
extern void Scrobble(musik::core::TrackPtr track);
|
||||
|
||||
extern Session LoadSession();
|
||||
extern void SaveSession(const Session& session);
|
||||
|
@ -50,6 +50,7 @@
|
||||
|
||||
#include <app/util/Hotkeys.h>
|
||||
#include <app/util/Messages.h>
|
||||
#include <app/util/LastFm.h>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
@ -426,6 +427,11 @@ void TransportWindow::OnPlaybackServiceTrackChanged(size_t index, TrackPtr track
|
||||
this->currentTrack = track;
|
||||
this->lastTime = DEFAULT_TIME;
|
||||
this->UpdateReplayGainState();
|
||||
|
||||
if (playback.GetPlaybackState() == PlaybackPlaying) {
|
||||
lastfm::Scrobble(track);
|
||||
}
|
||||
|
||||
DEBOUNCE_REFRESH(TimeSync, 0);
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,7 @@ DialogOverlay& DialogOverlay::SetAutoDismiss(bool dismiss) {
|
||||
|
||||
DialogOverlay& DialogOverlay::ClearButtons() {
|
||||
this->shortcuts->RemoveAll();
|
||||
this->buttons.clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,9 @@
|
||||
"button_save": "save",
|
||||
"button_cancel": "cancel",
|
||||
"button_close": "close",
|
||||
"button_start": "start",
|
||||
"button_retry": "retry",
|
||||
"button_continue": "continue",
|
||||
"button_dont_remind_me": "don't remind me again",
|
||||
"button_remind_me_later": "remind me later",
|
||||
|
||||
@ -62,7 +65,14 @@
|
||||
"settings_auto_update_check": "check for updates on startup",
|
||||
"settings_save_session_on_exit": "save session on exit",
|
||||
"settings_check_for_updates": "check for updates now",
|
||||
"settings_last_fm": "last fm",
|
||||
"settings_last_fm": "last.fm",
|
||||
"settings_last_fm_dialog_message_unregistered": "no last.fm account registered.\n\npress ENTER to begin the linking process.",
|
||||
"settings_last_fm_dialog_message_obtaining_token": "contacting last.fm for an account link token...",
|
||||
"settings_last_fm_dialog_message_waiting_for_user": "press 'o' to open the account link page in your default browser, or manually navigate to the following url:\n\n{{link}}\n\nafter granting permission, press 'ENTER' to continue.\n\n(note: you may be asked to sign into your last.fm account.)",
|
||||
"settings_last_fm_dialog_message_registering_session": "getting a session token, please wait...",
|
||||
"settings_last_fm_dialog_message_registered": "registered with account: '{{username}}'.\n\ngo listen to some music!",
|
||||
"settings_last_fm_dialog_message_link_error": "failed to get an account link token from last.fm.\n\nplease try again later.",
|
||||
"settings_last_fm_dialog_message_register_error": "failed link your last.fm account.\n\nplease make sure you open the following url and grant permission:\n\n{{link}}\n\npress ENTER to try again.",
|
||||
|
||||
"settings_server_enable_websockets": "metadata server enabled",
|
||||
"settings_server_enable_http": "audio streaming enabled",
|
||||
|
Loading…
x
Reference in New Issue
Block a user