Mercurial > wgsdk
comparison src/deezer.c @ 11:e6a594f16403
*: huge refactor
the config file has changed drastically, moving to an ini file from
that custom format; i *would* have used the win32 functions for those,
but they were barely functional, so I decided on using ini.h which is
lightweight enough.
additionally, I've added Deezer support so album art will be displayed!
unfortunately though winhttp is a pain in the ass so if I send a request
with any form of unicode chars in it it just returns a "bad request" error.
I've tried debugging this but I could never really come up with anything:
my hypothesis is that deezer expects their characters in percent-encoded
UTF-8, but winhttp is sending them in some other encoding.
the config dialog was moved out of config.c (overdue) and many more options
are given in the config as well.
main.c has been renamed to plugin.c to better differentiate it from...
everything else.
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Thu, 14 Mar 2024 20:25:37 -0400 |
parents | |
children | dd427b7cc459 |
comparison
equal
deleted
inserted
replaced
10:42ac054c0231 | 11:e6a594f16403 |
---|---|
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 } |