mirror of
https://github.com/owntone/owntone-server.git
synced 2025-01-13 16:03:23 -05:00
[spotify] Add an API endpoint for logging out of Spotify
This commit is contained in:
parent
1b74966ef1
commit
5e60527f40
@ -497,8 +497,9 @@ forked-daapd will not store your password, but will still be able to log you in
|
||||
automatically afterwards, because libspotify saves a login token. You can
|
||||
configure the location of your Spotify user data in the configuration file.
|
||||
|
||||
To permanently logout and remove credentials, delete the contents of
|
||||
`/var/cache/forked-daapd/libspotify` (while forked-daapd is stopped).
|
||||
To permanently logout and remove Spotify tracks + credentials make a request to
|
||||
[http://[your_server_address_here]:3689/api/spotify-logout](http://[your_server_address_here]:3689/api/spotify-logout)
|
||||
and also delete the contents of `/var/cache/forked-daapd/libspotify`.
|
||||
|
||||
Limitations:
|
||||
You will not be able to do any playlist management through forked-daapd - use
|
||||
|
@ -1251,6 +1251,15 @@ jsonapi_reply_spotify_login(struct httpd_request *hreq)
|
||||
return HTTP_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
jsonapi_reply_spotify_logout(struct httpd_request *hreq)
|
||||
{
|
||||
#ifdef HAVE_SPOTIFY_H
|
||||
spotify_logout();
|
||||
#endif
|
||||
return HTTP_NOCONTENT;
|
||||
}
|
||||
|
||||
static int
|
||||
jsonapi_reply_lastfm(struct httpd_request *hreq)
|
||||
{
|
||||
@ -4217,6 +4226,7 @@ static struct httpd_uri_map adm_handlers[] =
|
||||
EVHTTP_REQ_PUT, "^/api/update$", jsonapi_reply_update },
|
||||
{ EVHTTP_REQ_PUT, "^/api/rescan$", jsonapi_reply_meta_rescan },
|
||||
{ EVHTTP_REQ_POST, "^/api/spotify-login$", jsonapi_reply_spotify_login },
|
||||
{ EVHTTP_REQ_GET, "^/api/spotify-logout$", jsonapi_reply_spotify_logout },
|
||||
{ EVHTTP_REQ_GET, "^/api/spotify$", jsonapi_reply_spotify },
|
||||
{ EVHTTP_REQ_GET, "^/api/pairing$", jsonapi_reply_pairing_get },
|
||||
{ EVHTTP_REQ_POST, "^/api/pairing$", jsonapi_reply_pairing_pair },
|
||||
|
@ -1340,6 +1340,40 @@ spotify_status_info_get(struct spotify_status_info *info)
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&status_lck));
|
||||
}
|
||||
|
||||
/* Thread: library, httpd */
|
||||
static int
|
||||
logout(char **errmsg)
|
||||
{
|
||||
sp_error err;
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck));
|
||||
|
||||
if (SP_CONNECTION_STATE_LOGGED_IN != fptr_sp_session_connectionstate(g_sess))
|
||||
{
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
|
||||
return 0;
|
||||
}
|
||||
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Logging out of Spotify (current state is %d)\n", g_state);
|
||||
|
||||
fptr_sp_session_player_unload(g_sess);
|
||||
err = fptr_sp_session_logout(g_sess);
|
||||
if (SP_ERROR_OK != err)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Could not logout of Spotify: %s\n", fptr_sp_error_message(err));
|
||||
if (errmsg)
|
||||
*errmsg = safe_asprintf("Could not logout of Spotify: %s", fptr_sp_error_message(err));
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
|
||||
return -1;
|
||||
}
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_cond_wait(&login_cond, &login_lck)); // Wait for logged_out()
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Thread: library, httpd */
|
||||
static int
|
||||
login_user(const char *user, const char *password, char **errmsg)
|
||||
@ -1365,28 +1399,9 @@ login_user(const char *user, const char *password, char **errmsg)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SP_CONNECTION_STATE_LOGGED_IN == fptr_sp_session_connectionstate(g_sess))
|
||||
{
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck));
|
||||
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Logging out of Spotify (current state is %d)\n", g_state);
|
||||
|
||||
fptr_sp_session_player_unload(g_sess);
|
||||
err = fptr_sp_session_logout(g_sess);
|
||||
|
||||
if (SP_ERROR_OK != err)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Could not logout of Spotify: %s\n", fptr_sp_error_message(err));
|
||||
if (errmsg)
|
||||
*errmsg = safe_asprintf("Could not logout of Spotify: %s", fptr_sp_error_message(err));
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
|
||||
return -1;
|
||||
}
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_cond_wait(&login_cond, &login_lck));
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&login_lck));
|
||||
}
|
||||
ret = logout(errmsg);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&login_lck));
|
||||
|
||||
@ -1457,6 +1472,14 @@ spotify_login(char **arglist)
|
||||
spotify_login_user(NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
spotify_logout(void)
|
||||
{
|
||||
logout(NULL);
|
||||
|
||||
spotifywebapi_purge();
|
||||
}
|
||||
|
||||
/* Thread: main */
|
||||
int
|
||||
spotify_init(void)
|
||||
|
@ -50,6 +50,9 @@ spotify_login_user(const char *user, const char *password, char **errmsg);
|
||||
void
|
||||
spotify_login(char **arglist);
|
||||
|
||||
void
|
||||
spotify_logout(void);
|
||||
|
||||
void
|
||||
spotify_status_info_get(struct spotify_status_info *info);
|
||||
|
||||
|
@ -94,14 +94,19 @@ struct spotify_playlist
|
||||
};
|
||||
|
||||
// Credentials for the web api
|
||||
static char *spotify_access_token;
|
||||
static char *spotify_refresh_token;
|
||||
static char *spotify_granted_scope;
|
||||
static char *spotify_user_country;
|
||||
static char *spotify_user;
|
||||
struct spotify_credentials
|
||||
{
|
||||
char *access_token;
|
||||
char *refresh_token;
|
||||
char *granted_scope;
|
||||
char *user_country;
|
||||
char *user;
|
||||
|
||||
static int32_t expires_in = 3600;
|
||||
static time_t token_requested = 0;
|
||||
int32_t token_expires_in;
|
||||
time_t token_time_requested;
|
||||
};
|
||||
|
||||
static struct spotify_credentials spotify_credentials;
|
||||
|
||||
// Mutex to avoid conflicting requests for access tokens and protects accessing the credentials from different threads
|
||||
static pthread_mutex_t token_lck;
|
||||
@ -135,6 +140,17 @@ static const char *spotify_playlist_tracks_uri = "https://api.spotify.com/v1/pla
|
||||
static const char *spotify_artist_albums_uri = "https://api.spotify.com/v1/artists/%s/albums?include_groups=album,single";
|
||||
|
||||
|
||||
static void
|
||||
free_credentials(void)
|
||||
{
|
||||
free(spotify_credentials.access_token);
|
||||
free(spotify_credentials.refresh_token);
|
||||
free(spotify_credentials.granted_scope);
|
||||
free(spotify_credentials.user_country);
|
||||
free(spotify_credentials.user);
|
||||
|
||||
memset(&spotify_credentials, 0, sizeof(struct spotify_credentials));
|
||||
}
|
||||
|
||||
static void
|
||||
free_http_client_ctx(struct http_client_ctx *ctx)
|
||||
@ -155,7 +171,7 @@ free_http_client_ctx(struct http_client_ctx *ctx)
|
||||
static bool
|
||||
token_valid(void)
|
||||
{
|
||||
return spotify_access_token != NULL;
|
||||
return spotify_credentials.access_token != NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
@ -209,34 +225,34 @@ request_access_tokens(struct keyval *kv, const char **err)
|
||||
goto out_free_input_body;
|
||||
}
|
||||
|
||||
free(spotify_access_token);
|
||||
spotify_access_token = NULL;
|
||||
free(spotify_credentials.access_token);
|
||||
spotify_credentials.access_token = NULL;
|
||||
|
||||
tmp = jparse_str_from_obj(haystack, "access_token");
|
||||
if (tmp)
|
||||
spotify_access_token = strdup(tmp);
|
||||
spotify_credentials.access_token = strdup(tmp);
|
||||
|
||||
tmp = jparse_str_from_obj(haystack, "refresh_token");
|
||||
if (tmp)
|
||||
{
|
||||
free(spotify_refresh_token);
|
||||
spotify_refresh_token = strdup(tmp);
|
||||
free(spotify_credentials.refresh_token);
|
||||
spotify_credentials.refresh_token = strdup(tmp);
|
||||
}
|
||||
|
||||
tmp = jparse_str_from_obj(haystack, "scope");
|
||||
if (tmp)
|
||||
{
|
||||
free(spotify_granted_scope);
|
||||
spotify_granted_scope = strdup(tmp);
|
||||
free(spotify_credentials.granted_scope);
|
||||
spotify_credentials.granted_scope = strdup(tmp);
|
||||
}
|
||||
|
||||
expires_in = jparse_int_from_obj(haystack, "expires_in");
|
||||
if (expires_in == 0)
|
||||
expires_in = 3600;
|
||||
spotify_credentials.token_expires_in = jparse_int_from_obj(haystack, "expires_in");
|
||||
if (spotify_credentials.token_expires_in == 0)
|
||||
spotify_credentials.token_expires_in = 3600;
|
||||
|
||||
jparse_free(haystack);
|
||||
|
||||
if (!spotify_access_token)
|
||||
if (!spotify_credentials.access_token)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Could not find access token in reply: %s\n", body);
|
||||
|
||||
@ -245,10 +261,10 @@ request_access_tokens(struct keyval *kv, const char **err)
|
||||
goto out_free_input_body;
|
||||
}
|
||||
|
||||
token_requested = time(NULL);
|
||||
spotify_credentials.token_time_requested = time(NULL);
|
||||
|
||||
if (spotify_refresh_token)
|
||||
db_admin_set(DB_ADMIN_SPOTIFY_REFRESH_TOKEN, spotify_refresh_token);
|
||||
if (spotify_credentials.refresh_token)
|
||||
db_admin_set(DB_ADMIN_SPOTIFY_REFRESH_TOKEN, spotify_credentials.refresh_token);
|
||||
|
||||
ret = 0;
|
||||
|
||||
@ -281,7 +297,7 @@ request_endpoint(const char *uri)
|
||||
ctx->input_body = evbuffer_new();
|
||||
ctx->url = uri;
|
||||
|
||||
snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_access_token);
|
||||
snprintf(bearer_token, sizeof(bearer_token), "Bearer %s", spotify_credentials.access_token);
|
||||
if (keyval_add(ctx->output_headers, "Authorization", bearer_token) < 0)
|
||||
{
|
||||
DPRINTF(E_LOG, L_SPOTIFY, "Add bearer_token to keyval failed for request '%s'\n", uri);
|
||||
@ -331,21 +347,21 @@ request_user_info(void)
|
||||
{
|
||||
json_object *response;
|
||||
|
||||
free(spotify_user_country);
|
||||
spotify_user_country = NULL;
|
||||
free(spotify_user);
|
||||
spotify_user = NULL;
|
||||
free(spotify_credentials.user_country);
|
||||
spotify_credentials.user_country = NULL;
|
||||
free(spotify_credentials.user);
|
||||
spotify_credentials.user = NULL;
|
||||
|
||||
response = request_endpoint(spotify_me_uri);
|
||||
|
||||
if (response)
|
||||
{
|
||||
spotify_user = safe_strdup(jparse_str_from_obj(response, "id"));
|
||||
spotify_user_country = safe_strdup(jparse_str_from_obj(response, "country"));
|
||||
spotify_credentials.user = safe_strdup(jparse_str_from_obj(response, "id"));
|
||||
spotify_credentials.user_country = safe_strdup(jparse_str_from_obj(response, "country"));
|
||||
|
||||
jparse_free(response);
|
||||
|
||||
DPRINTF(E_DBG, L_SPOTIFY, "User '%s', country '%s'\n", spotify_user, spotify_user_country);
|
||||
DPRINTF(E_DBG, L_SPOTIFY, "User '%s', country '%s'\n", spotify_credentials.user, spotify_credentials.user_country);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -411,7 +427,7 @@ token_refresh(void)
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck));
|
||||
|
||||
if (token_requested && difftime(time(NULL), token_requested) < expires_in)
|
||||
if (spotify_credentials.token_time_requested && difftime(time(NULL), spotify_credentials.token_time_requested) < spotify_credentials.token_expires_in)
|
||||
{
|
||||
DPRINTF(E_DBG, L_SPOTIFY, "Spotify token still valid\n");
|
||||
|
||||
@ -524,16 +540,16 @@ request_pagingobject_endpoint(const char *href, paging_item_cb item_cb, paging_r
|
||||
int total;
|
||||
int ret;
|
||||
|
||||
if (!with_market || !spotify_user_country)
|
||||
if (!with_market || !spotify_credentials.user_country)
|
||||
{
|
||||
next_href = safe_strdup(href);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strchr(href, '?'))
|
||||
next_href = safe_asprintf("%s&market=%s", href, spotify_user_country);
|
||||
next_href = safe_asprintf("%s&market=%s", href, spotify_credentials.user_country);
|
||||
else
|
||||
next_href = safe_asprintf("%s?market=%s", href, spotify_user_country);
|
||||
next_href = safe_asprintf("%s?market=%s", href, spotify_credentials.user_country);
|
||||
}
|
||||
|
||||
while (next_href)
|
||||
@ -1815,6 +1831,19 @@ webapi_rescan(void *arg, int *ret)
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
/* Thread: library */
|
||||
static enum command_state
|
||||
webapi_purge(void *arg, int *ret)
|
||||
{
|
||||
free_credentials();
|
||||
|
||||
db_spotify_purge();
|
||||
db_admin_delete(DB_ADMIN_SPOTIFY_REFRESH_TOKEN);
|
||||
|
||||
*ret = 0;
|
||||
return COMMAND_END;
|
||||
}
|
||||
|
||||
/* Thread: library */
|
||||
static enum command_state
|
||||
webapi_pl_save(void *arg, int *ret)
|
||||
@ -1887,6 +1916,12 @@ spotifywebapi_rescan(void)
|
||||
library_exec_async(webapi_rescan, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
spotifywebapi_purge(void)
|
||||
{
|
||||
library_exec_async(webapi_purge, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
spotifywebapi_pl_save(const char *uri)
|
||||
{
|
||||
@ -1942,17 +1977,17 @@ spotifywebapi_status_info_get(struct spotifywebapi_status_info *info)
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck));
|
||||
|
||||
info->token_valid = token_valid();
|
||||
if (spotify_user)
|
||||
if (spotify_credentials.user)
|
||||
{
|
||||
strncpy(info->user, spotify_user, (sizeof(info->user) - 1));
|
||||
strncpy(info->user, spotify_credentials.user, (sizeof(info->user) - 1));
|
||||
}
|
||||
if (spotify_user_country)
|
||||
if (spotify_credentials.user_country)
|
||||
{
|
||||
strncpy(info->country, spotify_user_country, (sizeof(info->country) - 1));
|
||||
strncpy(info->country, spotify_credentials.user_country, (sizeof(info->country) - 1));
|
||||
}
|
||||
if (spotify_granted_scope)
|
||||
if (spotify_credentials.granted_scope)
|
||||
{
|
||||
strncpy(info->granted_scope, spotify_granted_scope, (sizeof(info->granted_scope) - 1));
|
||||
strncpy(info->granted_scope, spotify_credentials.granted_scope, (sizeof(info->granted_scope) - 1));
|
||||
}
|
||||
if (spotify_scope)
|
||||
{
|
||||
@ -1971,12 +2006,12 @@ spotifywebapi_access_token_get(struct spotifywebapi_access_token *info)
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&token_lck));
|
||||
|
||||
if (token_requested > 0)
|
||||
info->expires_in = expires_in - difftime(time(NULL), token_requested);
|
||||
if (spotify_credentials.token_time_requested > 0)
|
||||
info->expires_in = spotify_credentials.token_expires_in - difftime(time(NULL), spotify_credentials.token_time_requested);
|
||||
else
|
||||
info->expires_in = 0;
|
||||
|
||||
info->token = safe_strdup(spotify_access_token);
|
||||
info->token = safe_strdup(spotify_credentials.access_token);
|
||||
|
||||
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&token_lck));
|
||||
}
|
||||
@ -1999,11 +2034,7 @@ spotifywebapi_deinit()
|
||||
|
||||
spotify_deinit();
|
||||
|
||||
free(spotify_access_token);
|
||||
free(spotify_refresh_token);
|
||||
free(spotify_granted_scope);
|
||||
free(spotify_user_country);
|
||||
free(spotify_user);
|
||||
free_credentials();
|
||||
}
|
||||
|
||||
struct library_source spotifyscanner =
|
||||
|
@ -52,6 +52,8 @@ spotifywebapi_fullrescan(void);
|
||||
void
|
||||
spotifywebapi_rescan(void);
|
||||
void
|
||||
spotifywebapi_purge(void);
|
||||
void
|
||||
spotifywebapi_pl_save(const char *uri);
|
||||
void
|
||||
spotifywebapi_pl_remove(const char *uri);
|
||||
|
Loading…
Reference in New Issue
Block a user