Skip to content

Commit

Permalink
[lbrainz] Add ListenBrainz integration
Browse files Browse the repository at this point in the history
- Support submitting listens (scrobble) to ListenBrainz.
- Add JSON API endpoints to manage ListenBrainz auth token.
  • Loading branch information
chme committed Jan 26, 2025
1 parent cc0d2ff commit ffa9e40
Show file tree
Hide file tree
Showing 11 changed files with 456 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ owntone_SOURCES = main.c \
evthr.c evthr.h \
$(SPOTIFY_SRC) \
$(LASTFM_SRC) \
listenbrainz.c listenbrainz.h \
$(MPD_SRC) \
listener.c listener.h \
commands.c commands.h \
Expand Down
1 change: 1 addition & 0 deletions src/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ enum query_type {
#define DB_ADMIN_START_TIME "start_time"
#define DB_ADMIN_LASTFM_SESSION_KEY "lastfm_sk"
#define DB_ADMIN_SPOTIFY_REFRESH_TOKEN "spotify_refresh_token"
#define DB_ADMIN_LISTENBRAINZ_TOKEN "listenbrainz_token"

/* Max value for media_file_info->rating (valid range is from 0 to 100) */
#define DB_FILES_RATING_MAX 100
Expand Down
8 changes: 4 additions & 4 deletions src/httpd.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#ifdef LASTFM
# include "lastfm.h"
#endif
#include "listenbrainz.h"
#ifdef HAVE_LIBWEBSOCKETS
# include "websocket.h"
#endif
Expand Down Expand Up @@ -162,16 +163,17 @@ playcount_inc_cb(void *arg)
db_file_inc_playcount(*id);
}

#ifdef LASTFM
/* Callback from the worker thread (async operation as it may block) */
static void
scrobble_cb(void *arg)
{
int *id = arg;

#ifdef LASTFM
lastfm_scrobble(*id);
}
#endif
listenbrainz_scrobble(*id);
}

static const char *
content_type_from_ext(const char *ext)
Expand Down Expand Up @@ -672,9 +674,7 @@ stream_end_register(struct stream_ctx *st)
{
st->no_register_playback = true;
worker_execute(playcount_inc_cb, &st->id, sizeof(int), 0);
#ifdef LASTFM
worker_execute(scrobble_cb, &st->id, sizeof(int), 1);
#endif
}
}

Expand Down
76 changes: 76 additions & 0 deletions src/httpd_jsonapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
# include "lastfm.h"
#endif
#include "library.h"
#include "listenbrainz.h"
#include "logger.h"
#include "misc.h"
#include "misc_json.h"
Expand Down Expand Up @@ -1447,6 +1448,77 @@ jsonapi_reply_lastfm_logout(struct httpd_request *hreq)
return HTTP_NOCONTENT;
}

static int
jsonapi_reply_listenbrainz(struct httpd_request *hreq)
{
struct listenbrainz_status status;
json_object *jreply;

listenbrainz_status_get(&status);

CHECK_NULL(L_WEB, jreply = json_object_new_object());

json_object_object_add(jreply, "enabled", json_object_new_boolean(!status.disabled));
json_object_object_add(jreply, "token_valid", json_object_new_boolean(status.token_valid));
if (status.user_name)
json_object_object_add(jreply, "user_name", json_object_new_string(status.user_name));
if (status.message)
json_object_object_add(jreply, "message", json_object_new_string(status.message));


CHECK_ERRNO(L_WEB, evbuffer_add_printf(hreq->out_body, "%s", json_object_to_json_string(jreply)));

jparse_free(jreply);
listenbrainz_status_free(&status, true);

return HTTP_OK;
}

static int
jsonapi_reply_listenbrainz_token_add(struct httpd_request *hreq)
{
json_object *request;
const char *token;
int ret;

request = jparse_obj_from_evbuffer(hreq->in_body);
if (!request)
{
DPRINTF(E_LOG, L_WEB, "Failed to parse incoming request\n");
return HTTP_BADREQUEST;
}

token = jparse_str_from_obj(request, "token");

ret = listenbrainz_token_set(token);

jparse_free(request);

if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Failed to set ListenBrainz token\n");
return HTTP_INTERNAL;
}

return HTTP_NOCONTENT;
}

static int
jsonapi_reply_listenbrainz_token_delete(struct httpd_request *hreq)
{
int ret;

ret = listenbrainz_token_delete();

if (ret < 0)
{
DPRINTF(E_LOG, L_WEB, "Failed to delete ListenBrainz token\n");
return HTTP_INTERNAL;
}

return HTTP_NOCONTENT;
}

/*
* Kicks off pairing of a daap/dacp client
*
Expand Down Expand Up @@ -4658,6 +4730,10 @@ static struct httpd_uri_map adm_handlers[] =

{ HTTPD_METHOD_GET, "^/api/search$", jsonapi_reply_search },

{ HTTPD_METHOD_GET, "^/api/listenbrainz$", jsonapi_reply_listenbrainz },
{ HTTPD_METHOD_POST, "^/api/listenbrainz/token$", jsonapi_reply_listenbrainz_token_add },
{ HTTPD_METHOD_DELETE, "^/api/listenbrainz/token$", jsonapi_reply_listenbrainz_token_delete },

{ 0, NULL, NULL }
};

Expand Down
4 changes: 2 additions & 2 deletions src/lastfm.c
Original file line number Diff line number Diff line change
Expand Up @@ -420,12 +420,12 @@ lastfm_logout(void)
int
lastfm_scrobble(int id)
{
DPRINTF(E_DBG, L_LASTFM, "Got LastFM scrobble request\n");

// LastFM is disabled because we already tried looking for a session key, but failed
if (lastfm_disabled)
return -1;

DPRINTF(E_DBG, L_LASTFM, "Got LastFM scrobble request\n");

return scrobble(id);
}

Expand Down
Loading

0 comments on commit ffa9e40

Please sign in to comment.