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 }
|