Skip to content

Commit

Permalink
Allow scrobbling service customization
Browse files Browse the repository at this point in the history
Fixes #10
  • Loading branch information
arkq committed Jun 27, 2017
1 parent 206b770 commit 0bff93b
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 55 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Features:
* Off-line played track cache for later submission
* POSIX ERE-based file name parser
* Desktop notification support (optionally)
* Customizable scrobbling service
* Small memory footprint

When discography is correctly tagged - at least artist and title field - scrobbling needs no
Expand Down Expand Up @@ -72,8 +73,17 @@ action might be also required when upgrading to the newer version with new featu

After that you can safely edit `~/.config/cmus/cmusfm.conf` configuration file.

By default cmusfm scrobbles to the Last.fm service. However, it is possible to change this
behavior by modifying `service-api-url` and `service-auth-url` options in the configuration file.
Afterwards, one should reinitialize cmusfm (`cmusfm init`) in order to authenticate with new
scrobbling service. In order to use [Libre.fm](https://libre.fm/) as a scrobbling service, one
shall use configuration as follows:

* `service-api-url = "https://libre.fm/2.0/"`
* `service-auth-url = "https://libre.fm/api/auth"`

~~Note, that for some changes to take place, restart of the cmusfm server process is required. To
achieved this, one has to quit cmus player and then kill the cmusfm background instance (e.g.
achieved this, one has to quit cmus player and then kill background instance of cmusfm (e.g.
`pkill cmusfm`).~~ Above statement is not valid if one's got
[inotify](http://en.wikipedia.org/wiki/Inotify) subsystem available.

Expand Down
21 changes: 15 additions & 6 deletions src/config.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* cmusfm - config.c
* Copyright (c) 2014-2015 Arkadiusz Bokowy
* Copyright (c) 2014-2017 Arkadiusz Bokowy
*
* This file is a part of a cmusfm.
*
Expand All @@ -18,10 +18,6 @@
* see <http://www.gnu.org/licenses/>.
*/

#if HAVE_CONFIG_H
#include "../config.h"
#endif

#include "config.h"

#include <ctype.h>
Expand All @@ -43,7 +39,10 @@ static char *get_config_value(char *str) {
char *end;

/* seek to the beginning of a value */
str = strchr(str, '=') + 1;
if ((str = strchr(str, '=')) == NULL)
return "";

str += 1;

/* trim leading spaces and optional quotation */
while (isspace(*str)) str++;
Expand Down Expand Up @@ -78,6 +77,8 @@ int cmusfm_config_read(const char *fname, struct cmusfm_config *conf) {

/* initialize configuration defaults */
memset(conf, 0, sizeof(*conf));
strcpy(conf->service_api_url, "https://ws.audioscrobbler.com/2.0/");
strcpy(conf->service_auth_url, "https://www.last.fm/api/auth");
strcpy(conf->format_localfile, "^(?A.+) - (?T.+)\\.[^.]+$");
strcpy(conf->format_shoutcast, "^(?A.+) - (?T.+)$");
#if ENABLE_LIBNOTIFY
Expand Down Expand Up @@ -116,6 +117,10 @@ int cmusfm_config_read(const char *fname, struct cmusfm_config *conf) {
else if (strncmp(line, CMCONF_NOTIFICATION, sizeof(CMCONF_NOTIFICATION) - 1) == 0)
conf->notification = decode_config_bool(get_config_value(line));
#endif
else if (strncmp(line, CMCONF_SERVICE_API_URL, sizeof(CMCONF_SERVICE_API_URL) - 1) == 0)
strncpy(conf->service_api_url, get_config_value(line), sizeof(conf->service_api_url) - 1);
else if (strncmp(line, CMCONF_SERVICE_AUTH_URL, sizeof(CMCONF_SERVICE_AUTH_URL) - 1) == 0)
strncpy(conf->service_auth_url, get_config_value(line), sizeof(conf->service_auth_url) - 1);
}

return fclose(f);
Expand Down Expand Up @@ -153,6 +158,10 @@ int cmusfm_config_write(const char *fname, struct cmusfm_config *conf) {
fprintf(f, "%s = \"%s\"\n", CMCONF_NOTIFICATION, encode_config_bool(conf->notification));
#endif

fprintf(f, "\n# scrobbling service\n");
fprintf(f, "%s = \"%s\"\n", CMCONF_SERVICE_API_URL, conf->service_api_url);
fprintf(f, "%s = \"%s\"\n", CMCONF_SERVICE_AUTH_URL, conf->service_auth_url);

return fclose(f);
}

Expand Down
24 changes: 17 additions & 7 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
#define CMUSFM_CONFIG_H_

#if HAVE_CONFIG_H
#include "../config.h"
# include "../config.h"
#endif

#include <stdbool.h>

/* Configuration file key definitions */

/* Configuration file setting keys. */
#define CMCONF_USER_NAME "user"
#define CMCONF_SESSION_KEY "key"
#define CMCONF_FORMAT_LOCALFILE "format-localfile"
Expand All @@ -37,9 +39,16 @@
#define CMCONF_SUBMIT_LOCALFILE "submit-localfile"
#define CMCONF_SUBMIT_SHOUTCAST "submit-shoutcast"
#define CMCONF_NOTIFICATION "notification"
#define CMCONF_SERVICE_API_URL "service-api-url"
#define CMCONF_SERVICE_AUTH_URL "service-auth-url"


struct cmusfm_config {

/* service API endpoints */
char service_api_url[64];
char service_auth_url[64];

char user_name[64];
char session_key[32 + 1];

Expand All @@ -50,13 +59,14 @@ struct cmusfm_config {
char format_coverfile[64];
#endif

unsigned int nowplaying_localfile : 1;
unsigned int nowplaying_shoutcast : 1;
unsigned int submit_localfile : 1;
unsigned int submit_shoutcast : 1;
bool nowplaying_localfile : 1;
bool nowplaying_shoutcast : 1;
bool submit_localfile : 1;
bool submit_shoutcast : 1;
#if ENABLE_LIBNOTIFY
unsigned int notification : 1;
bool notification : 1;
#endif

};


Expand Down
52 changes: 31 additions & 21 deletions src/libscrobbler2.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@
* see <http://www.gnu.org/licenses/>.
*/

#if HAVE_CONFIG_H
#include "../config.h"
#endif

#include "libscrobbler2.h"

#include <ctype.h>
Expand Down Expand Up @@ -84,12 +80,19 @@ static CURL *sb_curl_init(CURLoption method, struct sb_response_data *response)
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5);

#if CURLOPT_PROTOCOLS
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP);
#if LIBCURL_VERSION_NUM >= 0x071101 /* 7.17.1 */
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5);
curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
#endif

#if LIBCURL_VERSION_NUM >= 0x071304 /* 7.19.4 */
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
#endif
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

curl_easy_setopt(curl, method, 1);
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

memset(response, 0, sizeof(*response));
curl_easy_setopt(curl, CURLOPT_WRITEDATA, response);
Expand All @@ -116,6 +119,12 @@ static scrobbler_status_t sb_check_response(struct sb_response_data *response,
return sbs->status = SCROBBLER_STATUS_ERR_CURLPERF;
}

/* curl write callback was not called, something was mighty wrong... */
if (response->data == NULL) {
sbs->errornum = CURLE_GOT_NOTHING;
return sbs->status = SCROBBLER_STATUS_ERR_CURLPERF;
}

/* scrobbler service failure */
if (strstr(response->data, "<lfm status=\"ok\"") == NULL) {
char *error = strstr(response->data, "<error code=");
Expand Down Expand Up @@ -240,7 +249,7 @@ scrobbler_status_t scrobbler_scrobble(scrobbler_session_t *sbs,
/* make track.scrobble POST request */
sb_make_curl_getpost_string(curl, post_data, sb_data, 12);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
curl_easy_setopt(curl, CURLOPT_URL, SCROBBLER_URL);
curl_easy_setopt(curl, CURLOPT_URL, sbs->api_url);

sb_check_response(&response, curl_easy_perform(curl), sbs);
debug("scrobble status: %d", sbs->status);
Expand Down Expand Up @@ -294,7 +303,7 @@ static scrobbler_status_t sb_update_now_playing(scrobbler_session_t *sbs,
/* make track.updateNowPlaying POST request */
sb_make_curl_getpost_string(curl, post_data, sb_data, 11);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
curl_easy_setopt(curl, CURLOPT_URL, SCROBBLER_URL);
curl_easy_setopt(curl, CURLOPT_URL, sbs->api_url);

sb_check_response(&response, curl_easy_perform(curl), sbs);
debug("now playing status: %d", sbs->status);
Expand All @@ -320,14 +329,13 @@ scrobbler_status_t scrobbler_test_session_key(scrobbler_session_t *sbs) {
return sb_update_now_playing(sbs, &sbt);
}

/* Get the session key. The memory block pointed by the str has to be big
* enough to contain sizeof(session_key). */
char *scrobbler_get_session_key_str(scrobbler_session_t *sbs, char *str) {
return strncpy(str, sbs->session_key, sizeof(sbs->session_key));
/* Get the session key. */
const char *scrobbler_get_session_key(scrobbler_session_t *sbs) {
return sbs->session_key;
}

/* Set the session key. */
void scrobbler_set_session_key_str(scrobbler_session_t *sbs, const char *str) {
void scrobbler_set_session_key(scrobbler_session_t *sbs, const char *str) {
strncpy(sbs->session_key, str, sizeof(sbs->session_key));
}

Expand Down Expand Up @@ -366,7 +374,7 @@ scrobbler_status_t scrobbler_authentication(scrobbler_session_t *sbs,
mem2hex(sign_hex, sign, sizeof(sign));

/* make auth.getToken GET request */
strcpy(get_url, SCROBBLER_URL "?");
sprintf(get_url, "%s?", sbs->api_url);
sb_make_curl_getpost_string(curl, get_url + strlen(get_url), sb_data_token, 3);
curl_easy_setopt(curl, CURLOPT_URL, get_url);

Expand All @@ -380,8 +388,8 @@ scrobbler_status_t scrobbler_authentication(scrobbler_session_t *sbs,
token_hex[32] = 0;

/* perform user authorization (callback function) */
sprintf(get_url, SCROBBLER_USERAUTH_URL "?api_key=%s&token=%s",
api_key_hex, token_hex);
sprintf(get_url, "%s?api_key=%s&token=%s",
sbs->auth_url, api_key_hex, token_hex);
if (callback(get_url) != 0) {
sb_curl_cleanup(curl, &response);
return sbs->status = SCROBBLER_STATUS_ERR_CALLBACK;
Expand All @@ -395,7 +403,7 @@ scrobbler_status_t scrobbler_authentication(scrobbler_session_t *sbs,
response.size = 0;

/* make auth.getSession GET request */
strcpy(get_url, SCROBBLER_URL "?");
sprintf(get_url, "%s?", sbs->api_url);
sb_make_curl_getpost_string(curl, get_url + strlen(get_url), sb_data_session, 4);
curl_easy_setopt(curl, CURLOPT_URL, get_url);

Expand Down Expand Up @@ -425,20 +433,22 @@ scrobbler_status_t scrobbler_authentication(scrobbler_session_t *sbs,
/* Initialize scrobbler session. On success this function returns pointer
* to allocated scrobbler_session structure, which must be freed by call
* to scrobbler_free. On error NULL is returned. */
scrobbler_session_t *scrobbler_initialize(uint8_t api_key[16],
uint8_t secret[16]) {
scrobbler_session_t *scrobbler_initialize(const char *api_url,
const char *auth_url, uint8_t api_key[16], uint8_t secret[16]) {

scrobbler_session_t *sbs;

/* allocate space for scrobbler session structure */
if ((sbs = calloc(1, sizeof(scrobbler_session_t))) == NULL)
return NULL;

if (curl_global_init(CURL_GLOBAL_NOTHING) != 0) {
if (curl_global_init(CURL_GLOBAL_DEFAULT) != 0) {
free(sbs);
return NULL;
}

strncpy(sbs->api_url, api_url, sizeof(sbs->api_url) - 1);
strncpy(sbs->auth_url, auth_url, sizeof(sbs->auth_url) - 1);
memcpy(sbs->api_key, api_key, sizeof(sbs->api_key));
memcpy(sbs->secret, secret, sizeof(sbs->secret));

Expand Down
15 changes: 8 additions & 7 deletions src/libscrobbler2.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@
#include <stdint.h>
#include <time.h>

#define SCROBBLER_URL "http://ws.audioscrobbler.com/2.0/"
#define SCROBBLER_USERAUTH_URL "http://www.last.fm/api/auth"

/* Status definitions. For more comprehensive information about errors,
* see the errornum variable in the session structure. */
typedef enum scrobbler_status_tag {
Expand All @@ -43,6 +40,10 @@ typedef enum scrobbler_status_tag {

typedef struct scrobbler_session_tag {

/* service API URL */
char api_url[64];
char auth_url[64];

/* 128-bit API key */
uint8_t api_key[16];
/* 128-bit secret */
Expand All @@ -63,8 +64,8 @@ typedef struct scrobbler_trackinfo_tag {
time_t timestamp;
} scrobbler_trackinfo_t;

scrobbler_session_t *scrobbler_initialize(uint8_t api_key[16],
uint8_t secret[16]);
scrobbler_session_t *scrobbler_initialize(const char *api_url,
const char *auth_url, uint8_t api_key[16], uint8_t secret[16]);
void scrobbler_free(scrobbler_session_t *sbs);

const char *scrobbler_strerror(scrobbler_session_t *sbs);
Expand All @@ -74,8 +75,8 @@ scrobbler_status_t scrobbler_authentication(scrobbler_session_t *sbs,
scrobbler_authuser_callback_t callback);
scrobbler_status_t scrobbler_test_session_key(scrobbler_session_t *sbs);

char *scrobbler_get_session_key_str(scrobbler_session_t *sbs, char *str);
void scrobbler_set_session_key_str(scrobbler_session_t *sbs, const char *str);
const char *scrobbler_get_session_key(scrobbler_session_t *sbs);
void scrobbler_set_session_key(scrobbler_session_t *sbs, const char *str);

scrobbler_status_t scrobbler_update_now_playing(scrobbler_session_t *sbs,
scrobbler_trackinfo_t *sbt);
Expand Down
23 changes: 15 additions & 8 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,20 +113,27 @@ static void cmusfm_initialization(void) {

scrobbler_session_t *sbs;
struct cmusfm_config conf;
int fetch_session_key;
bool check_prev_session;
bool fetch_new_session;
char yesno[8], *ptr;

fetch_session_key = 1;
sbs = scrobbler_initialize(SC_api_key, SC_secret);
fetch_new_session = true;
check_prev_session = false;

/* try to make sure that the configuration directory exists */
mkdirp(get_cmus_home_dir(), S_IRWXU);

/* try to read previous configuration */
if (cmusfm_config_read(cmusfm_config_file, &conf) == 0) {
if (cmusfm_config_read(cmusfm_config_file, &conf) == 0)
check_prev_session = true;

sbs = scrobbler_initialize(conf.service_api_url,
conf.service_auth_url, SC_api_key, SC_secret);

if (check_prev_session) {
printf("Checking previous session (user: %s) ...", conf.user_name);
fflush(stdout);
scrobbler_set_session_key_str(sbs, conf.session_key);
scrobbler_set_session_key(sbs, conf.session_key);
if (scrobbler_test_session_key(sbs) == 0)
printf("OK.\n");
else
Expand All @@ -135,13 +142,13 @@ static void cmusfm_initialization(void) {
printf("Fetch new session key [yes/NO]: ");
ptr = fgets(yesno, sizeof(yesno), stdin);
if (ptr != NULL && strncmp(ptr, "yes", 3) != 0)
fetch_session_key = 0;
fetch_new_session = false;
}

if (fetch_session_key) { /* fetch new session key */
if (fetch_new_session) {
if (scrobbler_authentication(sbs, user_authorization) == 0) {
scrobbler_get_session_key_str(sbs, conf.session_key);
strncpy(conf.user_name, sbs->user_name, sizeof(conf.user_name) - 1);
strncpy(conf.session_key, sbs->session_key, sizeof(conf.session_key) - 1);
}
else
printf("Error: %s\n", scrobbler_strerror(sbs));
Expand Down
7 changes: 4 additions & 3 deletions src/server.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* cmusfm - server.c
* Copyright (c) 2010-2015 Arkadiusz Bokowy
* Copyright (c) 2010-2017 Arkadiusz Bokowy
*
* This file is a part of a cmusfm.
*
Expand Down Expand Up @@ -309,8 +309,9 @@ int cmusfm_server_start(void) {
return -1;

/* initialize scrobbling library */
sbs = scrobbler_initialize(SC_api_key, SC_secret);
scrobbler_set_session_key_str(sbs, config.session_key);
sbs = scrobbler_initialize(config.service_api_url,
config.service_auth_url, SC_api_key, SC_secret);
scrobbler_set_session_key(sbs, config.session_key);

/* catch signals which are used to quit server */
struct sigaction sigact = { .sa_handler = cmusfm_server_stop };
Expand Down
Loading

0 comments on commit 0bff93b

Please sign in to comment.