view src/deezer.c @ 12:dd427b7cc459 default tip

json: replace with nxjson library more lightweight, reduces the binary size by about 40 kb
author Paper <paper@paper.us.eu.org>
date Fri, 15 Mar 2024 20:46:18 -0400
parents e6a594f16403
children
line wrap: on
line source

#include "json.h"
#include "utils.h"

#include <windef.h>
#include <minwinbase.h>
#include <winhttp.h>
#include <winuser.h>

HINTERNET session = NULL;
HINTERNET connection = NULL;

/* preferably we would use some other API for this, but
 * meh
*/
static int init_winhttp(void) {
	session = WinHttpOpen(
		NULL,
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME,
		WINHTTP_NO_PROXY_BYPASS,
		0
	);

	return !!session;
}

static int init_connect(void) {
	if (!session)
		return 0;

	connection = WinHttpConnect(
		session,
		L"api.deezer.com",
		/* require HTTPS, we aren't in 2001 anymore */
		INTERNET_DEFAULT_HTTPS_PORT,
		0
	);

	return !!connection;
}

/* do this on exit */
void close_open_http_handles(void) {
	if (session) WinHttpCloseHandle(session);
	if (connection) WinHttpCloseHandle(connection);
}

/* return MUST be free'd */
static LPCWSTR deezer_get_thumbnail_build_query(LPCWSTR restrict artist, LPCWSTR restrict album) {
	static LPCWSTR begin = L"/search/track?strict=on&q=artist:\"";
	static LPCWSTR album_query = L" album:\"";

	size_t len = wcslen(begin) + wcslen(artist) + 1 /* quote */;
	if (album && album[0]) {
		len += 1 /* space */ + wcslen(album_query) + wcslen(album) + 1 /* quote */;
	}

	LPWSTR final = calloc(len + 1, sizeof(WCHAR));

	wcscpy(final, begin);
	wcscat(final, artist);
	wcscat(final, L"\"");
	if (album && album[0]) {
		wcscat(final, album_query);
		wcscat(final, album);
		wcscat(final, L"\"");
	}

	return final;
}

static int deezer_album_object_get_cover(const nx_json* album, const char* name, char** url) {
	if (!url) return 0;

	const nx_json* cover = nx_json_get(album, name);
	if (!cover)
		return 0;

	free(*url);

	size_t len = strlen(cover->text_value);
	*url = malloc((len + 1) * sizeof(char));
	(*url)[len] = '\0';

	strncpy(*url, cover->text_value, len);
	return 1;
}


static int deezer_get_thumbnail_download_url(LPCWSTR restrict query, char** restrict data, size_t* restrict size) {
	if (!data || !size)
		return -1;

	HINTERNET request = WinHttpOpenRequest(
		connection,
		L"GET",
		query,
		NULL,
		WINHTTP_NO_REFERER,
		WINHTTP_DEFAULT_ACCEPT_TYPES,
		WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_PERCENT
	);
	if (!request)
		return -1;

	BOOL result = WinHttpSendRequest(
		request, 
		L"Content-Type: application/json; charset=utf-8",
		0,
		WINHTTP_NO_REQUEST_DATA,
		0,
		0,
		0
	);
	if (!result) {
		WinHttpCloseHandle(request);
		return -1;
	}

	result = WinHttpReceiveResponse(request, NULL);
	if (!result) {
		WinHttpCloseHandle(request);
		return -1;
	}

	DWORD data_available = 0;
	DWORD data_downloaded = 0;
	do {
		data_available = 0;
		if (!WinHttpQueryDataAvailable(request, &data_available)) {
			WinHttpCloseHandle(request);
			return -1;
		}

		if (!data_available)
			break;

		*data = realloc(*data, *size + data_available + 1);
		(*data)[*size + data_available] = '\0';
		if (!WinHttpReadData(request, *data + *size, data_available, &data_downloaded)) {
			WinHttpCloseHandle(request);
			return -1;
		}
		*size += data_downloaded;
	} while (data_available > 0);

	WinHttpCloseHandle(request);
	return 0;
}

/* note: please prefix these */
enum deezer_thumbnail_state {
	ERROR = 0,
	ALBUM,
	ARTIST,
};

static enum deezer_thumbnail_state deezer_get_thumbnail_parse_search_result(const nx_json* json, char** cover_url, int get_artist) {
	const nx_json* album = nx_json_get(json, "album");

	if (album->type == NX_JSON_OBJECT) {
		if (deezer_album_object_get_cover(album, "cover_medium", cover_url)
			|| deezer_album_object_get_cover(album, "cover_large", cover_url)
			|| deezer_album_object_get_cover(album, "cover_small", cover_url)
			|| deezer_album_object_get_cover(album, "cover_xl", cover_url))
			return ALBUM;
	}

	if (get_artist) {
		const nx_json* artist = nx_json_get(json, "artist");
		if (artist->type != NX_JSON_OBJECT)
			return ERROR;

		/* treat artist pictures as a fallback and only retrieve them once */
		if (deezer_album_object_get_cover(artist, "picture_medium", cover_url)
			|| deezer_album_object_get_cover(artist, "picture_large", cover_url)
			|| deezer_album_object_get_cover(artist, "picture_small", cover_url)
			|| deezer_album_object_get_cover(artist, "picture_xl", cover_url))
			return ARTIST;
	}

	return ERROR;
}

/* THIS MODIFIES `data` IN PLACE!! */
static int deezer_get_thumbnail_parse_json(char** cover_url, char* data) {
	const nx_json* json = nx_json_parse(data, NULL);
	if (!json)
		return -1;

	const nx_json* json_data = nx_json_get(json, "data"); 
	if (json_data->type != NX_JSON_ARRAY || !json_data->children.length)
		return -1;

	int have_artist = 0;
	for (size_t i = 0; i < json_data->children.length; i++) {
		const nx_json* result = nx_json_item(json_data, i);
		if (result->type != NX_JSON_OBJECT)
			return -1;

		enum deezer_thumbnail_state state = deezer_get_thumbnail_parse_search_result(result, cover_url, have_artist);
		switch (state) {
			case ERROR: return -1;
			case ARTIST: have_artist = 1; break;
			case ALBUM: return 0;
		}
	}

	nx_json_free(json);

	return !have_artist;
}

char* deezer_get_thumbnail(LPCWSTR restrict artist, LPCWSTR restrict album) {
	char* response_data = NULL;
	size_t response_size = 0;
	LPCWSTR query = NULL;
	char* cover_url = NULL;

	/* make sure everything is OK */
	if (!(session || init_winhttp()) || !(connection || init_connect()) || (!artist || !artist[0]))
		return NULL;

	query = deezer_get_thumbnail_build_query(artist, album);
	if (!query)
		return NULL;

	if (deezer_get_thumbnail_download_url(query, &response_data, &response_size))
		return NULL;

	if (deezer_get_thumbnail_parse_json(&cover_url, response_data))
		return NULL;

	free(response_data);

	return cover_url;
}