| 
11
 | 
     1 #include "json.h"
 | 
| 
 | 
     2 #include "utils.h"
 | 
| 
 | 
     3 
 | 
| 
 | 
     4 #include <windef.h>
 | 
| 
 | 
     5 #include <minwinbase.h>
 | 
| 
 | 
     6 #include <winhttp.h>
 | 
| 
 | 
     7 #include <winuser.h>
 | 
| 
 | 
     8 
 | 
| 
 | 
     9 HINTERNET session = NULL;
 | 
| 
 | 
    10 HINTERNET connection = NULL;
 | 
| 
 | 
    11 
 | 
| 
 | 
    12 /* preferably we would use some other API for this, but
 | 
| 
 | 
    13  * meh
 | 
| 
 | 
    14 */
 | 
| 
 | 
    15 static int init_winhttp(void) {
 | 
| 
 | 
    16 	session = WinHttpOpen(
 | 
| 
 | 
    17 		NULL,
 | 
| 
 | 
    18 		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
 | 
| 
 | 
    19 		WINHTTP_NO_PROXY_NAME,
 | 
| 
 | 
    20 		WINHTTP_NO_PROXY_BYPASS,
 | 
| 
 | 
    21 		0
 | 
| 
 | 
    22 	);
 | 
| 
 | 
    23 
 | 
| 
 | 
    24 	return !!session;
 | 
| 
 | 
    25 }
 | 
| 
 | 
    26 
 | 
| 
 | 
    27 static int init_connect(void) {
 | 
| 
 | 
    28 	if (!session)
 | 
| 
 | 
    29 		return 0;
 | 
| 
 | 
    30 
 | 
| 
 | 
    31 	connection = WinHttpConnect(
 | 
| 
 | 
    32 		session,
 | 
| 
 | 
    33 		L"api.deezer.com",
 | 
| 
 | 
    34 		/* require HTTPS, we aren't in 2001 anymore */
 | 
| 
 | 
    35 		INTERNET_DEFAULT_HTTPS_PORT,
 | 
| 
 | 
    36 		0
 | 
| 
 | 
    37 	);
 | 
| 
 | 
    38 
 | 
| 
 | 
    39 	return !!connection;
 | 
| 
 | 
    40 }
 | 
| 
 | 
    41 
 | 
| 
 | 
    42 /* do this on exit */
 | 
| 
 | 
    43 void close_open_http_handles(void) {
 | 
| 
 | 
    44 	if (session) WinHttpCloseHandle(session);
 | 
| 
 | 
    45 	if (connection) WinHttpCloseHandle(connection);
 | 
| 
 | 
    46 }
 | 
| 
 | 
    47 
 | 
| 
 | 
    48 /* return MUST be free'd */
 | 
| 
 | 
    49 static LPCWSTR deezer_get_thumbnail_build_query(LPCWSTR restrict artist, LPCWSTR restrict album) {
 | 
| 
 | 
    50 	static LPCWSTR begin = L"/search/track?strict=on&q=artist:\"";
 | 
| 
 | 
    51 	static LPCWSTR album_query = L" album:\"";
 | 
| 
 | 
    52 
 | 
| 
 | 
    53 	size_t len = wcslen(begin) + wcslen(artist) + 1 /* quote */;
 | 
| 
 | 
    54 	if (album && album[0]) {
 | 
| 
 | 
    55 		len += 1 /* space */ + wcslen(album_query) + wcslen(album) + 1 /* quote */;
 | 
| 
 | 
    56 	}
 | 
| 
 | 
    57 
 | 
| 
 | 
    58 	LPWSTR final = calloc(len + 1, sizeof(WCHAR));
 | 
| 
 | 
    59 
 | 
| 
 | 
    60 	wcscpy(final, begin);
 | 
| 
 | 
    61 	wcscat(final, artist);
 | 
| 
 | 
    62 	wcscat(final, L"\"");
 | 
| 
 | 
    63 	if (album && album[0]) {
 | 
| 
 | 
    64 		wcscat(final, album_query);
 | 
| 
 | 
    65 		wcscat(final, album);
 | 
| 
 | 
    66 		wcscat(final, L"\"");
 | 
| 
 | 
    67 	}
 | 
| 
 | 
    68 
 | 
| 
 | 
    69 	return final;
 | 
| 
 | 
    70 }
 | 
| 
 | 
    71 
 | 
| 
 | 
    72 static int deezer_album_object_get_cover(cJSON* restrict album, const char* restrict name, char** restrict url) {
 | 
| 
 | 
    73 	if (!url) return 0;
 | 
| 
 | 
    74 
 | 
| 
 | 
    75 	cJSON* cover = cJSON_GetObjectItemCaseSensitive(album, name);
 | 
| 
 | 
    76 	if (cover) {
 | 
| 
 | 
    77 		if (*url)
 | 
| 
 | 
    78 			free(*url);
 | 
| 
 | 
    79 		size_t len = strlen(cover->valuestring);
 | 
| 
 | 
    80 		*url = malloc((len + 1) * sizeof(char));
 | 
| 
 | 
    81 		(*url)[len] = '\0';
 | 
| 
 | 
    82 		strncpy(*url, cover->valuestring, len);
 | 
| 
 | 
    83 		return 1;
 | 
| 
 | 
    84 	} else return 0;
 | 
| 
 | 
    85 }
 | 
| 
 | 
    86 
 | 
| 
 | 
    87 
 | 
| 
 | 
    88 static int deezer_get_thumbnail_download_url(LPCWSTR restrict query, char** restrict data, size_t* restrict size) {
 | 
| 
 | 
    89 	if (!data || !size)
 | 
| 
 | 
    90 		return -1;
 | 
| 
 | 
    91 
 | 
| 
 | 
    92 	HINTERNET request = WinHttpOpenRequest(
 | 
| 
 | 
    93 		connection,
 | 
| 
 | 
    94 		L"GET",
 | 
| 
 | 
    95 		query,
 | 
| 
 | 
    96 		NULL,
 | 
| 
 | 
    97 		WINHTTP_NO_REFERER,
 | 
| 
 | 
    98 		WINHTTP_DEFAULT_ACCEPT_TYPES,
 | 
| 
 | 
    99 		WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_PERCENT
 | 
| 
 | 
   100 	);
 | 
| 
 | 
   101 	if (!request)
 | 
| 
 | 
   102 		return -1;
 | 
| 
 | 
   103 
 | 
| 
 | 
   104 	BOOL result = WinHttpSendRequest(
 | 
| 
 | 
   105 		request, 
 | 
| 
 | 
   106 		L"Content-Type: application/json; charset=utf-8",
 | 
| 
 | 
   107 		0,
 | 
| 
 | 
   108 		WINHTTP_NO_REQUEST_DATA,
 | 
| 
 | 
   109 		0,
 | 
| 
 | 
   110 		0,
 | 
| 
 | 
   111 		0
 | 
| 
 | 
   112 	);
 | 
| 
 | 
   113 	if (!result) {
 | 
| 
 | 
   114 		WinHttpCloseHandle(request);
 | 
| 
 | 
   115 		return -1;
 | 
| 
 | 
   116 	}
 | 
| 
 | 
   117 
 | 
| 
 | 
   118 	result = WinHttpReceiveResponse(request, NULL);
 | 
| 
 | 
   119 	if (!result) {
 | 
| 
 | 
   120 		WinHttpCloseHandle(request);
 | 
| 
 | 
   121 		return -1;
 | 
| 
 | 
   122 	}
 | 
| 
 | 
   123 
 | 
| 
 | 
   124 	DWORD data_available = 0;
 | 
| 
 | 
   125 	DWORD data_downloaded = 0;
 | 
| 
 | 
   126 	do {
 | 
| 
 | 
   127 		data_available = 0;
 | 
| 
 | 
   128 		if (!WinHttpQueryDataAvailable(request, &data_available)) {
 | 
| 
 | 
   129 			WinHttpCloseHandle(request);
 | 
| 
 | 
   130 			return -1;
 | 
| 
 | 
   131 		}
 | 
| 
 | 
   132 
 | 
| 
 | 
   133 		if (!data_available)
 | 
| 
 | 
   134 			break;
 | 
| 
 | 
   135 
 | 
| 
 | 
   136 		*data = realloc(*data, *size + data_available + 1);
 | 
| 
 | 
   137 		(*data)[*size + data_available] = '\0';
 | 
| 
 | 
   138 		if (!WinHttpReadData(request, *data + *size, data_available, &data_downloaded)) {
 | 
| 
 | 
   139 			WinHttpCloseHandle(request);
 | 
| 
 | 
   140 			return -1;
 | 
| 
 | 
   141 		}
 | 
| 
 | 
   142 		*size += data_downloaded;
 | 
| 
 | 
   143 	} while (data_available > 0);
 | 
| 
 | 
   144 
 | 
| 
 | 
   145 	WinHttpCloseHandle(request);
 | 
| 
 | 
   146 	return 0;
 | 
| 
 | 
   147 }
 | 
| 
 | 
   148 
 | 
| 
 | 
   149 enum deezer_thumbnail_state {
 | 
| 
 | 
   150 	ERROR = 0,
 | 
| 
 | 
   151 	ALBUM,
 | 
| 
 | 
   152 	ARTIST,
 | 
| 
 | 
   153 };
 | 
| 
 | 
   154 
 | 
| 
 | 
   155 static enum deezer_thumbnail_state deezer_get_thumbnail_parse_search_result(cJSON* json, char** restrict cover_url, int get_artist) {
 | 
| 
 | 
   156 	cJSON* album = cJSON_GetObjectItemCaseSensitive(json, "album");
 | 
| 
 | 
   157 
 | 
| 
 | 
   158 	if (cJSON_IsObject(album)) {
 | 
| 
 | 
   159 		if (deezer_album_object_get_cover(album, "cover_medium", cover_url)
 | 
| 
 | 
   160 			|| deezer_album_object_get_cover(album, "cover_large", cover_url)
 | 
| 
 | 
   161 			|| deezer_album_object_get_cover(album, "cover_small", cover_url)
 | 
| 
 | 
   162 			|| deezer_album_object_get_cover(album, "cover_xl", cover_url))
 | 
| 
 | 
   163 			return ALBUM;
 | 
| 
 | 
   164 	} else if (get_artist) {
 | 
| 
 | 
   165 		cJSON* artist = cJSON_GetObjectItemCaseSensitive(json, "artist");
 | 
| 
 | 
   166 		if (!cJSON_IsObject(artist))
 | 
| 
 | 
   167 			return ERROR;
 | 
| 
 | 
   168 
 | 
| 
 | 
   169 		/* treat artist pictures as a fallback and only retrieve them once */
 | 
| 
 | 
   170 		if (deezer_album_object_get_cover(artist, "picture_medium", cover_url)
 | 
| 
 | 
   171 			|| deezer_album_object_get_cover(artist, "picture_large", cover_url)
 | 
| 
 | 
   172 			|| deezer_album_object_get_cover(artist, "picture_small", cover_url)
 | 
| 
 | 
   173 			|| deezer_album_object_get_cover(artist, "picture_xl", cover_url))
 | 
| 
 | 
   174 			return ARTIST;
 | 
| 
 | 
   175 	}
 | 
| 
 | 
   176 
 | 
| 
 | 
   177 	return ERROR;
 | 
| 
 | 
   178 }
 | 
| 
 | 
   179 
 | 
| 
 | 
   180 static int deezer_get_thumbnail_parse_json(char** restrict cover_url, const char* restrict data, size_t size) {
 | 
| 
 | 
   181 	cJSON* json = cJSON_ParseWithLength(data, size);
 | 
| 
 | 
   182 	if (!json) {
 | 
| 
 | 
   183 		const char* err_ptr = cJSON_GetErrorPtr();
 | 
| 
 | 
   184 		if (err_ptr)
 | 
| 
 | 
   185 			MessageBoxA(NULL, err_ptr, "wgsdk: Error parsing Deezer JSON!", MB_ICONERROR | MB_OK);
 | 
| 
 | 
   186 		cJSON_Delete(json);
 | 
| 
 | 
   187 		return -1;
 | 
| 
 | 
   188 	}
 | 
| 
 | 
   189 
 | 
| 
 | 
   190 	cJSON* json_data = cJSON_GetObjectItemCaseSensitive(json, "data"); 
 | 
| 
 | 
   191 	size_t json_data_size;
 | 
| 
 | 
   192 	if (!cJSON_IsArray(json_data) || !(json_data_size = cJSON_GetArraySize(json_data)))
 | 
| 
 | 
   193 		return -1;
 | 
| 
 | 
   194 
 | 
| 
 | 
   195 	int have_artist = 0;
 | 
| 
 | 
   196 	for (size_t i = 0; i < json_data_size; i++) {
 | 
| 
 | 
   197 		cJSON* result = cJSON_GetArrayItem(json_data, i);
 | 
| 
 | 
   198 		if (!cJSON_IsObject(result))
 | 
| 
 | 
   199 			return -1;
 | 
| 
 | 
   200 
 | 
| 
 | 
   201 		enum deezer_thumbnail_state state = deezer_get_thumbnail_parse_search_result(result, cover_url, have_artist);
 | 
| 
 | 
   202 		switch (state) {
 | 
| 
 | 
   203 			case ERROR: return -1;
 | 
| 
 | 
   204 			case ARTIST: have_artist = 1; break;
 | 
| 
 | 
   205 			case ALBUM: return 0;
 | 
| 
 | 
   206 		}
 | 
| 
 | 
   207 	}
 | 
| 
 | 
   208 
 | 
| 
 | 
   209 	cJSON_Delete(json);
 | 
| 
 | 
   210 	return !have_artist;
 | 
| 
 | 
   211 }
 | 
| 
 | 
   212 
 | 
| 
 | 
   213 char* deezer_get_thumbnail(LPCWSTR restrict artist, LPCWSTR restrict album) {
 | 
| 
 | 
   214 	char* response_data = NULL;
 | 
| 
 | 
   215 	size_t response_size = 0;
 | 
| 
 | 
   216 	LPCWSTR query = NULL;
 | 
| 
 | 
   217 	char* cover_url = NULL;
 | 
| 
 | 
   218 
 | 
| 
 | 
   219 	/* make sure everything is OK */
 | 
| 
 | 
   220 	if (!(session || init_winhttp()) || !(connection || init_connect()) || (!artist || !artist[0]))
 | 
| 
 | 
   221 		return NULL;
 | 
| 
 | 
   222 
 | 
| 
 | 
   223 	query = deezer_get_thumbnail_build_query(artist, album);
 | 
| 
 | 
   224 	if (!query)
 | 
| 
 | 
   225 		return NULL;
 | 
| 
 | 
   226 
 | 
| 
 | 
   227 	if (deezer_get_thumbnail_download_url(query, &response_data, &response_size))
 | 
| 
 | 
   228 		return NULL;
 | 
| 
 | 
   229 
 | 
| 
 | 
   230 	if (deezer_get_thumbnail_parse_json(&cover_url, response_data, response_size))
 | 
| 
 | 
   231 		return NULL;
 | 
| 
 | 
   232 
 | 
| 
 | 
   233 	return cover_url;
 | 
| 
 | 
   234 }
 |