Very basic POC LastFM scrobbling working, and some changes to Discord RPC
This commit is contained in:
+1
-1
@@ -23,11 +23,11 @@ add_executable(ossp MACOSX_BUNDLE
|
||||
socket/socketActions.c
|
||||
player/player.c
|
||||
player/playQueue.cpp
|
||||
player/scrobbler_lastFm.c
|
||||
libopensubsonic/crypto.c
|
||||
libopensubsonic/httpclient.c
|
||||
libopensubsonic/logger.c
|
||||
libopensubsonic/utils.c
|
||||
libopensubsonic/scrobble_lastFm.c
|
||||
libopensubsonic/scrobble_listenBrainz.c
|
||||
libopensubsonic/endpoint_getAlbum.c
|
||||
libopensubsonic/endpoint_getAlbumList.c
|
||||
|
||||
@@ -63,11 +63,13 @@ void discordrpc_update(discordrpc_data** discordrpc_struct) {
|
||||
memset(&presence, 0, sizeof(presence));
|
||||
|
||||
if ((*discordrpc_struct)->state == DISCORDRPC_STATE_IDLE) {
|
||||
printf("[DiscordRPC] Issuing Idle RPC.\n");
|
||||
asprintf(&detailsString, "Idle");
|
||||
presence.details = detailsString;
|
||||
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_OPENSUBSONIC ||
|
||||
((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_LOCALFILE)) {
|
||||
// Playing a song from an OpenSubsonic server
|
||||
printf("[DiscordRPC] Issuing OpenSubsonic/Local File Song RPC.\n");
|
||||
asprintf(&detailsString, "%s", (*discordrpc_struct)->songTitle);
|
||||
asprintf(&stateString, "by %s", (*discordrpc_struct)->songArtist);
|
||||
presence.details = detailsString;
|
||||
@@ -83,6 +85,7 @@ void discordrpc_update(discordrpc_data** discordrpc_struct) {
|
||||
}
|
||||
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PLAYING_INTERNETRADIO) {
|
||||
// Playing an internet radio station
|
||||
printf("[DiscordRPC] Issuing Internet Radio RPC.\n");
|
||||
asprintf(&detailsString, "%s", (*discordrpc_struct)->songTitle);
|
||||
asprintf(&stateString, "Internet radio station");
|
||||
presence.details = detailsString;
|
||||
@@ -94,6 +97,7 @@ void discordrpc_update(discordrpc_data** discordrpc_struct) {
|
||||
}
|
||||
} else if ((*discordrpc_struct)->state == DISCORDRPC_STATE_PAUSED) {
|
||||
// Player is paused
|
||||
printf("[DiscordRPC] Issuing Paused RPC.\n");
|
||||
asprintf(&detailsString, "Paused");
|
||||
presence.details = detailsString;
|
||||
}
|
||||
|
||||
@@ -130,3 +130,9 @@ void OSSPQ_FreeSongObjectC(OSSPQ_SongStruct* songObjectC) {
|
||||
if (songObjectC->coverArtUrl != NULL) { free(songObjectC->coverArtUrl); }
|
||||
if (songObjectC != NULL) { free(songObjectC); }
|
||||
}
|
||||
|
||||
// Used for scrobbling, called every 200ms
|
||||
long OSSPQ_getSongLength(int idx) {
|
||||
OSSPQ_SongObject songObject = OSSPQ_SongQueue[idx];
|
||||
return songObject.duration;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ void OSSPQ_advancePos();
|
||||
void OSSPQ_backtrackPos();
|
||||
OSSPQ_SongStruct* OSSPQ_getAtPos(int pos);
|
||||
void OSSPQ_FreeSongObjectC(OSSPQ_SongStruct* songObjectC);
|
||||
long OSSPQ_getSongLength(int idx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+64
-1
@@ -20,6 +20,9 @@
|
||||
#include "playQueue.hpp"
|
||||
#include "player.h"
|
||||
|
||||
// TESTING
|
||||
#include "scrobbler_lastFm.h"
|
||||
|
||||
extern configHandler_config_t* configObj;
|
||||
static int rc = 0;
|
||||
GstElement *pipeline, *playbin, *filter_bin, *conv_in, *conv_out, *in_volume, *equalizer, *pitch, *reverb, *out_volume;
|
||||
@@ -109,6 +112,7 @@ void* OSSPlayer_GMainLoop(void* arg) {
|
||||
void* OSSPlayer_ThrdInit(void* arg) {
|
||||
(void)arg;
|
||||
bool haveIssuedDiscordRPCIdle = true;
|
||||
bool haveScrobbledSong = false;
|
||||
|
||||
// Player init function for pthread entry
|
||||
logger_log_important(__func__, "Player thread running.");
|
||||
@@ -120,7 +124,9 @@ void* OSSPlayer_ThrdInit(void* arg) {
|
||||
|
||||
// Poll play queue for new items to play
|
||||
while (true) { // TODO use global bool instead
|
||||
if (OSSPQ_getTotalPos() != 0 && isPlaying == false) {
|
||||
if (OSSPQ_getTotalPos() != 0 &&
|
||||
OSSPQ_getCurrentPos() != OSSPQ_getTotalPos() &&
|
||||
isPlaying == false) {
|
||||
// Player is not playing and a song is in the song queue
|
||||
|
||||
// Pull new song from the song queue
|
||||
@@ -131,6 +137,9 @@ void* OSSPlayer_ThrdInit(void* arg) {
|
||||
// TODO: this
|
||||
}
|
||||
|
||||
// Reset scrobble
|
||||
haveScrobbledSong = false;
|
||||
|
||||
if (songObject->mode == OSSPQ_MODE_INTERNETRADIO) {
|
||||
// Setup Discord RPC
|
||||
discordrpc_data* discordrpc = NULL;
|
||||
@@ -147,6 +156,15 @@ void* OSSPlayer_ThrdInit(void* arg) {
|
||||
isPlaying = true;
|
||||
gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
||||
} else if (songObject->mode == OSSPQ_MODE_OPENSUBSONIC) {
|
||||
// Issue initial LastFM scrobble
|
||||
scrobbler_data* scrobblerData = malloc(sizeof(scrobbler_data));
|
||||
opensubsonic_scrobble_init(scrobblerData);
|
||||
scrobblerData->songTitle = strdup(songObject->title);
|
||||
scrobblerData->songAlbum = strdup(songObject->album);
|
||||
scrobblerData->songArtist = strdup(songObject->artist);
|
||||
opensubsonic_scrobble_lastFm(scrobblerData);
|
||||
opensubsonic_scrobble_free(scrobblerData);
|
||||
|
||||
// Prepare Discord RPC
|
||||
discordrpc_data* discordrpc = NULL;
|
||||
discordrpc_struct_init(&discordrpc);
|
||||
@@ -210,6 +228,44 @@ void* OSSPlayer_ThrdInit(void* arg) {
|
||||
}
|
||||
}
|
||||
|
||||
// Scrobbler
|
||||
// Nothing playing: 0.00
|
||||
// Oh and end of song (EOS) -> 0.00
|
||||
|
||||
// If song is >3/4 finished, perform final scrobble
|
||||
// Else, perform an in-progress scrobble every 45s (This can be handled later)
|
||||
// Have to fetch the total playback from Gstreamer, otherwise im malloc'ing every 200ms, a little fucking dramatic
|
||||
|
||||
// NOTE: Cannot query Playbin3 for the length, as the OpenSubsonic /stream endpoint seems to be technically livestreaming it
|
||||
|
||||
// Bad idea: Could technically do it the same way the DiscordRPC one does it
|
||||
// Gather the data at the same time, send a couple arguments and encapsulate it in it's own thread...
|
||||
// Kinda wasteful of a process though
|
||||
|
||||
if (isPlaying == true) {
|
||||
float songLength = (float)OSSPQ_getSongLength(OSSPQ_getCurrentPos());
|
||||
printf("Song length: %f\n", songLength);
|
||||
printf("Current: %f\n", OSSPlayer_GstECont_Playbin3_Position_Get());
|
||||
|
||||
// Check if song is >=3/4 finished
|
||||
if (OSSPlayer_GstECont_Playbin3_Position_Get() >= (songLength / 4 * 3) && haveScrobbledSong == false) {
|
||||
// Finalize song scrobble
|
||||
OSSPQ_SongStruct* songObject = OSSPQ_getAtPos(OSSPQ_getCurrentPos());
|
||||
|
||||
scrobbler_data* scrobblerData = malloc(sizeof(scrobbler_data));
|
||||
opensubsonic_scrobble_init(scrobblerData);
|
||||
scrobblerData->finalize = 1;
|
||||
scrobblerData->songTitle = strdup(songObject->title);
|
||||
scrobblerData->songAlbum = strdup(songObject->album);
|
||||
scrobblerData->songArtist = strdup(songObject->artist);
|
||||
opensubsonic_scrobble_lastFm(scrobblerData);
|
||||
opensubsonic_scrobble_free(scrobblerData);
|
||||
|
||||
haveScrobbledSong = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
usleep(200 * 1000);
|
||||
}
|
||||
}
|
||||
@@ -563,3 +619,10 @@ void OSSPlayer_DiscordRPC_SendPlaying(time_t startTime) {
|
||||
|
||||
OSSPQ_FreeSongObjectC(songObject);
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions that utilize scrobblers
|
||||
*/
|
||||
void OSSPlayer_Scrobbler_LastFM(int final) {
|
||||
//
|
||||
}
|
||||
|
||||
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* OpenSubSonicPlayer
|
||||
* Goldenkrew3000 2026
|
||||
* License: GNU General Public License 3.0
|
||||
* LastFM Scrobbler
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include "../libopensubsonic/httpclient.h"
|
||||
#include "../external/cJSON.h"
|
||||
#include "../external/md5.h"
|
||||
#include "../external/libcurl_uriescape.h"
|
||||
#include "../configHandler.h"
|
||||
#include "scrobbler_lastFm.h"
|
||||
|
||||
// Temp - move away from that fuckahh logger
|
||||
#include "../libopensubsonic/logger.h"
|
||||
|
||||
const char* lastFmScrobbleURL = "https://ws.audioscrobbler.com/2.0/";
|
||||
static int rc = 0;
|
||||
extern configHandler_config_t* configObj;
|
||||
|
||||
int opensubsonic_scrobble_lastFm(scrobbler_data* scrobblerData) {
|
||||
if (scrobblerData->finalize) {
|
||||
logger_log_general(__func__, "Performing final scrobble to LastFM.");
|
||||
} else {
|
||||
logger_log_general(__func__, "Performing in-progress scrobble to LastFM.");
|
||||
}
|
||||
|
||||
// Fetch the current UNIX timestamp
|
||||
time_t currentTime;
|
||||
char* currentTime_string;
|
||||
currentTime = time(NULL);
|
||||
rc = asprintf(¤tTime_string, "%ld", currentTime);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed (Could not make char* of UNIX timestamp).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Assemble the signature
|
||||
char* sig_plaintext = NULL;
|
||||
if (scrobblerData->finalize) {
|
||||
rc = asprintf(&sig_plaintext, "album%salbumArtist%sapi_key%sartist%smethodtrack.scrobblesk%stimestamp%strack%s%s",
|
||||
scrobblerData->songAlbum, scrobblerData->songArtist, configObj->lastfm_api_key, scrobblerData->songArtist,
|
||||
configObj->lastfm_api_session_key, currentTime_string, scrobblerData->songTitle, configObj->lastfm_api_secret);
|
||||
} else {
|
||||
rc = asprintf(&sig_plaintext, "album%salbumArtist%sapi_key%sartist%smethodtrack.updateNowPlayingsk%stimestamp%strack%s%s",
|
||||
scrobblerData->songAlbum, scrobblerData->songArtist, configObj->lastfm_api_key, scrobblerData->songArtist,
|
||||
configObj->lastfm_api_session_key, currentTime_string, scrobblerData->songTitle, configObj->lastfm_api_secret);
|
||||
}
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed (Could not assemble plaintext signature).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t sig_md5_bytes[16];
|
||||
char* sig_md5_text = NULL; // TODO do I have to free this? Also is be used in crypto.c
|
||||
md5String(sig_plaintext, sig_md5_bytes);
|
||||
free(sig_plaintext);
|
||||
rc = asprintf(&sig_md5_text, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
sig_md5_bytes[0], sig_md5_bytes[1], sig_md5_bytes[2], sig_md5_bytes[3],
|
||||
sig_md5_bytes[4], sig_md5_bytes[5], sig_md5_bytes[6], sig_md5_bytes[7],
|
||||
sig_md5_bytes[8], sig_md5_bytes[9], sig_md5_bytes[10], sig_md5_bytes[11],
|
||||
sig_md5_bytes[12], sig_md5_bytes[13], sig_md5_bytes[14], sig_md5_bytes[15]);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed (Could not assemble md5 signature).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// URI encode strings
|
||||
char* uri_songTitle = lcue_uriescape(scrobblerData->songTitle, (unsigned int)strlen(scrobblerData->songTitle));
|
||||
char* uri_songArtist = lcue_uriescape(scrobblerData->songArtist, (unsigned int)strlen(scrobblerData->songArtist));
|
||||
char* uri_songAlbum = lcue_uriescape(scrobblerData->songAlbum, (unsigned int)strlen(scrobblerData->songAlbum));
|
||||
if (uri_songTitle == NULL || uri_songArtist == NULL || uri_songAlbum == NULL) {
|
||||
logger_log_error(__func__, "lcue_uriescape() error (Could not URI escape required strings).");
|
||||
free(currentTime_string);
|
||||
free(sig_md5_text);
|
||||
if (uri_songTitle != NULL) { free(uri_songTitle); }
|
||||
if (uri_songArtist != NULL) { free(uri_songArtist); }
|
||||
if (uri_songAlbum != NULL) { free(uri_songAlbum); }
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Assemble the payload
|
||||
char* payload = NULL;
|
||||
if (scrobblerData->finalize) {
|
||||
rc = asprintf(&payload,
|
||||
"%s?method=track.scrobble&api_key=%s×tamp=%s&track=%s&artist=%s&album=%s&albumArtist=%s&sk=%s&api_sig=%s&format=json",
|
||||
lastFmScrobbleURL, configObj->lastfm_api_key, currentTime_string, uri_songTitle,
|
||||
uri_songArtist, uri_songAlbum, uri_songArtist,
|
||||
configObj->lastfm_api_session_key, sig_md5_text);
|
||||
} else {
|
||||
rc = asprintf(&payload,
|
||||
"%s?method=track.updateNowPlaying&api_key=%s×tamp=%s&track=%s&artist=%s&album=%s&albumArtist=%s&sk=%s&api_sig=%s&format=json",
|
||||
lastFmScrobbleURL, configObj->lastfm_api_key, currentTime_string, uri_songTitle,
|
||||
uri_songArtist, uri_songAlbum, uri_songArtist,
|
||||
configObj->lastfm_api_session_key, sig_md5_text);
|
||||
}
|
||||
free(currentTime_string);
|
||||
free(sig_md5_text);
|
||||
free(uri_songTitle);
|
||||
free(uri_songAlbum);
|
||||
free(uri_songArtist);
|
||||
if (rc == -1) {
|
||||
logger_log_error(__func__, "asprintf() failed (Could not assemble payload).");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Send scrobble and receive response
|
||||
opensubsonic_httpClientRequest_t* httpReq;
|
||||
opensubsonic_httpClient_prepareRequest(&httpReq);
|
||||
|
||||
httpReq->requestUrl = strdup(payload);
|
||||
free(payload);
|
||||
httpReq->scrobbler = SCROBBLER_LASTFM;
|
||||
httpReq->method = HTTP_METHOD_POST;
|
||||
opensubsonic_httpClient_request(&httpReq);
|
||||
|
||||
if (httpReq->responseCode != HTTP_CODE_SUCCESS) {
|
||||
logger_log_error(__func__, "HTTP POST did not return success (%d).", httpReq->responseCode);
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// Parse the scrobble response
|
||||
cJSON* root = cJSON_Parse(httpReq->responseMsg);
|
||||
opensubsonic_httpClient_cleanup(&httpReq);
|
||||
if (root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON.");
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* inner_root = NULL;
|
||||
cJSON* scrobbles_root = NULL; // Parent of inner_root, only used on final scrobble
|
||||
if (scrobblerData->finalize) {
|
||||
// Make an object from scrobbles
|
||||
scrobbles_root = cJSON_GetObjectItemCaseSensitive(root, "scrobbles");
|
||||
if (scrobbles_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - scrobbles does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// Make an object from scrobble
|
||||
inner_root = cJSON_GetObjectItemCaseSensitive(scrobbles_root, "scrobble");
|
||||
if (inner_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - scrobble does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
} else {
|
||||
// Make an object from nowplaying
|
||||
inner_root = cJSON_GetObjectItemCaseSensitive(root, "nowplaying");
|
||||
if (inner_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - nowplaying does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
}
|
||||
|
||||
// Make an object from artist, track, albumArtist, and album, and fetch codes
|
||||
cJSON* artist_root = cJSON_GetObjectItemCaseSensitive(inner_root, "artist");
|
||||
if (artist_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - artist does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* artist_corrected = cJSON_GetObjectItemCaseSensitive(artist_root, "corrected");
|
||||
if (cJSON_IsString(artist_corrected) && artist_corrected->valuestring != NULL) {
|
||||
if (*(artist_corrected->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - artist/corrected is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int corrected = (int)strtol(artist_corrected->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - artist/corrected strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (corrected == 1) {
|
||||
logger_log_important(__func__, "Warning - Artist has been autocorrected.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - artist/corrected does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* track_root = cJSON_GetObjectItemCaseSensitive(inner_root, "track");
|
||||
if (track_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - track does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* track_corrected = cJSON_GetObjectItemCaseSensitive(track_root, "corrected");
|
||||
if (cJSON_IsString(track_corrected) && track_corrected->valuestring != NULL) {
|
||||
if (*(track_corrected->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - track/corrected is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int corrected = (int)strtol(track_corrected->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - track/corrected strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (corrected == 1) {
|
||||
logger_log_important(__func__, "Warning - Track has been autocorrected.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - track/corrected does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* albumArtist_root = cJSON_GetObjectItemCaseSensitive(inner_root, "albumArtist");
|
||||
if (albumArtist_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - albumArtist does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* albumArtist_corrected = cJSON_GetObjectItemCaseSensitive(albumArtist_root, "corrected");
|
||||
if (cJSON_IsString(albumArtist_corrected) && albumArtist_corrected->valuestring != NULL) {
|
||||
if (*(albumArtist_corrected->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int corrected = (int)strtol(albumArtist_corrected->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (corrected == 1) {
|
||||
logger_log_important(__func__, "Warning - Album Artist has been autocorrected.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - albumArtist/corrected does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* album_root = cJSON_GetObjectItemCaseSensitive(inner_root, "album");
|
||||
if (album_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - album does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* album_corrected = cJSON_GetObjectItemCaseSensitive(album_root, "corrected");
|
||||
if (cJSON_IsString(album_corrected) && album_corrected->valuestring != NULL) {
|
||||
if (*(album_corrected->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - album/corrected is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int corrected = (int)strtol(album_corrected->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - album/corrected strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (corrected == 1) {
|
||||
logger_log_important(__func__, "Warning - Album has been autocorrected.");
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - album/corrected does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// Make an object from ignoredMessage, and check return code
|
||||
cJSON* ignoredMessage_root = cJSON_GetObjectItemCaseSensitive(inner_root, "ignoredMessage");
|
||||
if (ignoredMessage_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - ignoredMessage does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* ignoredMessage_code = cJSON_GetObjectItemCaseSensitive(ignoredMessage_root, "code");
|
||||
if (cJSON_IsString(ignoredMessage_code) && ignoredMessage_code->valuestring != NULL) {
|
||||
if (*(ignoredMessage_code->valuestring) == '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code is empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
char* endptr;
|
||||
int code = (int)strtol(ignoredMessage_code->valuestring, &endptr, 10);
|
||||
if (*endptr != '\0') {
|
||||
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code strtol/endptr is not empty.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
if (code == 0) {
|
||||
if (!scrobblerData->finalize) {
|
||||
logger_log_general(__func__, "In progress scrobble was successful.");
|
||||
} else {
|
||||
logger_log_general(__func__, "Final scrobble 1/2 was successful.");
|
||||
}
|
||||
} else if (code == 1) {
|
||||
logger_log_error(__func__, "Artist was ignored.");
|
||||
} else if (code == 2) {
|
||||
logger_log_error(__func__, "Track was ignored.");
|
||||
} else if (code == 3) {
|
||||
logger_log_error(__func__, "Timestamp was too old.");
|
||||
} else if (code == 4) {
|
||||
logger_log_error(__func__, "Timestamp was too new.");
|
||||
} else if (code == 5) {
|
||||
logger_log_error(__func__, "Daily scrobble limit exceeded.");
|
||||
} else {
|
||||
logger_log_error(__func__, "Unknown error code received (%d)", code);
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - ignoredMessage/code does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
if (scrobblerData->finalize) {
|
||||
cJSON* attr_root = cJSON_GetObjectItemCaseSensitive(scrobbles_root, "@attr");
|
||||
if (attr_root == NULL) {
|
||||
logger_log_error(__func__, "Error parsing JSON - @attr does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* attr_ignored = cJSON_GetObjectItemCaseSensitive(attr_root, "ignored");
|
||||
if (cJSON_IsNumber(attr_ignored)) {
|
||||
if (attr_ignored->valueint != 0) {
|
||||
logger_log_important(__func__, "Warning - @attr/ignored is not 0 (%d).", attr_ignored->valueint);
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - @attr/ignored does not exist.");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
cJSON* attr_accepted = cJSON_GetObjectItemCaseSensitive(attr_root, "accepted");
|
||||
if (cJSON_IsNumber(attr_accepted)) {
|
||||
if (attr_accepted->valueint != 1) {
|
||||
logger_log_important(__func__, "Warning - @attr/accepted is not 1 (%d).", attr_accepted->valueint);
|
||||
}
|
||||
} else {
|
||||
logger_log_error(__func__, "Error parsing JSON - @attr/accepted does not exist");
|
||||
cJSON_Delete(root);
|
||||
// TODO return error
|
||||
}
|
||||
|
||||
// At this point, attr_ignored and attr_accepted are both known to be valid
|
||||
if (attr_ignored->valueint == 0 && attr_accepted->valueint == 1) {
|
||||
logger_log_general(__func__, "Final scrobble 2/2 was successful.");
|
||||
} else {
|
||||
logger_log_important(__func__, "Final scobble 2/2 was not successful (ignored: %d, accepted: %d",
|
||||
attr_ignored->valueint, attr_accepted->valueint);
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
|
||||
void opensubsonic_scrobble_init(scrobbler_data* scrobblerData) {
|
||||
scrobblerData->finalize = 0;
|
||||
scrobblerData->songTitle = NULL;
|
||||
scrobblerData->songAlbum = NULL;
|
||||
scrobblerData->songArtist = NULL;
|
||||
}
|
||||
|
||||
void opensubsonic_scrobble_free(scrobbler_data* scrobblerData) {
|
||||
if (scrobblerData->songTitle != NULL) { free(scrobblerData->songTitle); }
|
||||
if (scrobblerData->songAlbum != NULL) { free(scrobblerData->songAlbum); }
|
||||
if (scrobblerData->songArtist != NULL) { free(scrobblerData->songArtist); }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* OpenSubSonicPlayer
|
||||
* Goldenkrew3000 2026
|
||||
* License: GNU General Public License 3.0
|
||||
* LastFM Scrobbler
|
||||
*/
|
||||
|
||||
#ifndef _SCROBBLER_LASTFM_H
|
||||
#define _SCROBBLER_LASTFM_H
|
||||
|
||||
typedef struct {
|
||||
int finalize; // 0 -> In progress, 1 -> Finalize
|
||||
char* songTitle;
|
||||
char* songAlbum;
|
||||
char* songArtist;
|
||||
} scrobbler_data;
|
||||
|
||||
int opensubsonic_scrobble_lastFm(scrobbler_data* scrobblerData);
|
||||
void opensubsonic_scrobble_init(scrobbler_data* scrobblerData);
|
||||
void opensubsonic_scrobble_free(scrobbler_data* scrobblerData);
|
||||
|
||||
// TODO fix this fuckass naming scheme
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user