Introduction
Introduction Statistics Contact Development Disclaimer Help
twitch.c - frontends - front-ends for some sites (experiment)
Log
Files
Refs
README
LICENSE
---
twitch.c (12841B)
---
1 #include <sys/socket.h>
2 #include <sys/types.h>
3
4 #include <ctype.h>
5 #include <errno.h>
6 #include <netdb.h>
7 #include <stdarg.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <time.h>
13 #include <unistd.h>
14
15 #include "https.h"
16 #include "json.h"
17 #include "twitch.h"
18 #include "util.h"
19
20 #ifndef TWITCH_API_KEY
21 #error "make sure set a TWITCH_API_KEY in twitch.c"
22 #define TWITCH_API_KEY "API key here"
23 #endif
24 static const char *twitch_headers = "Client-ID: " TWITCH_API_KEY "\r\n";
25
26 static char *
27 twitch_request(const char *path)
28 {
29 return request("api.twitch.tv", path, twitch_headers);
30 }
31
32 /* unmarshal JSON response, skip HTTP headers */
33 int
34 json_unmarshal(const char *data,
35 void (*cb)(struct json_node *, size_t, const char *, size_t, voi…
36 void *pp)
37 {
38 const char *s;
39
40 /* strip/skip header part */
41 if (!(s = strstr(data, "\r\n\r\n"))) {
42 fprintf(stderr, "error parsing HTTP response header\n");
43 return -1; /* invalid response */
44 }
45 s += strlen("\r\n\r\n");
46
47 /* parse */
48 if (parsejson(s, strlen(s), cb, pp) < 0) {
49 fprintf(stderr, "error parsing JSON\n");
50 return -1;
51 }
52
53 return 0;
54 }
55
56 char *
57 twitch_games_bygameids_data(const char *param)
58 {
59 char path[4096];
60 int r;
61
62 r = snprintf(path, sizeof(path), "/helix/games?%s", param);
63 if (r < 0 || (size_t)r >= sizeof(path))
64 return NULL;
65
66 return twitch_request(path);
67 }
68
69 char *
70 twitch_users_byuserids_data(const char *param)
71 {
72 char path[4096];
73 int r;
74
75 r = snprintf(path, sizeof(path), "/helix/users?%s", param);
76 if (r < 0 || (size_t)r >= sizeof(path))
77 return NULL;
78
79 return twitch_request(path);
80 }
81
82 char *
83 twitch_users_bylogin_data(const char *login)
84 {
85 char path[256];
86 int r;
87
88 r = snprintf(path, sizeof(path), "/helix/users?login=%s", login);
89 if (r < 0 || (size_t)r >= sizeof(path))
90 return NULL;
91
92 return twitch_request(path);
93 }
94
95 char *
96 twitch_videos_byuserid_data(const char *user_id)
97 {
98 char path[128];
99 int r;
100
101 r = snprintf(path, sizeof(path), "/helix/videos?first=100&user_i…
102 user_id);
103 if (r < 0 || (size_t)r >= sizeof(path))
104 return NULL;
105
106 return twitch_request(path);
107 }
108
109 char *
110 twitch_streams_data(void)
111 {
112 return twitch_request("/helix/streams?first=100");
113 }
114
115 char *
116 twitch_streams_game_data(const char *game_id)
117 {
118 char path[64];
119 int r;
120
121 r = snprintf(path, sizeof(path), "/helix/streams?first=100&game_…
122 game_id);
123 if (r < 0 || (size_t)r >= sizeof(path))
124 return NULL;
125
126 return twitch_request(path);
127 }
128
129 char *
130 twitch_games_top_data(void)
131 {
132 return twitch_request("/helix/games/top?first=100");
133 }
134
135 void
136 twitch_games_processnode(struct json_node *nodes, size_t depth, const ch…
137 void *pp)
138 {
139 struct games_response *r = (struct games_response *)pp;
140 struct game *item;
141
142 if (r->nitems > MAX_ITEMS)
143 return;
144
145 /* new item */
146 if (depth == 3 &&
147 nodes[0].type == TYPE_OBJECT &&
148 nodes[1].type == TYPE_ARRAY &&
149 nodes[2].type == TYPE_OBJECT &&
150 !strcmp(nodes[1].name, "data")) {
151 r->nitems++;
152 return;
153 }
154
155 if (r->nitems == 0)
156 return;
157 item = &(r->data[r->nitems - 1]);
158
159 if (depth == 4 &&
160 nodes[0].type == TYPE_OBJECT &&
161 nodes[1].type == TYPE_ARRAY &&
162 nodes[2].type == TYPE_OBJECT &&
163 nodes[3].type == TYPE_STRING &&
164 !strcmp(nodes[1].name, "data")) {
165 if (!strcmp(nodes[3].name, "id"))
166 strlcpy(item->id, value, sizeof(item->id));
167 else if (!strcmp(nodes[3].name, "name"))
168 strlcpy(item->name, value, sizeof(item->name));
169 }
170 }
171
172 struct games_response *
173 twitch_games_top(void)
174 {
175 struct games_response *r;
176 char *data;
177
178 if ((data = twitch_games_top_data()) == NULL) {
179 fprintf(stderr, "%s\n", __func__);
180 return NULL;
181 }
182
183 if (!(r = calloc(1, sizeof(*r)))) {
184 fprintf(stderr, "calloc\n");
185 return NULL;
186 }
187 if (json_unmarshal(data, twitch_games_processnode, r) == -1) {
188 free(r);
189 r = NULL;
190 }
191 free(data);
192
193 return r;
194 }
195
196 struct games_response *
197 twitch_games_bygameids(const char *param)
198 {
199 struct games_response *r;
200 char *data;
201
202 if ((data = twitch_games_bygameids_data(param)) == NULL) {
203 fprintf(stderr, "%s\n", __func__);
204 return NULL;
205 }
206
207 if (!(r = calloc(1, sizeof(*r)))) {
208 fprintf(stderr, "calloc\n");
209 return NULL;
210 }
211 if (json_unmarshal(data, twitch_games_processnode, r) == -1) {
212 free(r);
213 r = NULL;
214 }
215 free(data);
216
217 return r;
218 }
219
220 void
221 twitch_streams_processnode(struct json_node *nodes, size_t depth, const …
222 void *pp)
223 {
224 struct streams_response *r = (struct streams_response *)pp;
225 struct stream *item;
226
227 if (r->nitems > MAX_ITEMS)
228 return;
229 item = &(r->data[r->nitems]);
230
231 /* new item */
232 if (depth == 3 &&
233 nodes[0].type == TYPE_OBJECT &&
234 nodes[1].type == TYPE_ARRAY &&
235 nodes[2].type == TYPE_OBJECT &&
236 !strcmp(nodes[1].name, "data")) {
237 r->nitems++;
238 return;
239 }
240
241 if (r->nitems == 0)
242 return;
243 item = &(r->data[r->nitems - 1]);
244
245 if (depth == 4 &&
246 nodes[0].type == TYPE_OBJECT &&
247 nodes[1].type == TYPE_ARRAY &&
248 nodes[2].type == TYPE_OBJECT &&
249 !strcmp(nodes[1].name, "data")) {
250 if (nodes[3].type == TYPE_STRING) {
251 if (!strcmp(nodes[3].name, "id"))
252 strlcpy(item->id, value, sizeof(item->id…
253 else if (!strcmp(nodes[3].name, "title"))
254 strlcpy(item->title, value, sizeof(item-…
255 else if (!strcmp(nodes[3].name, "user_id"))
256 strlcpy(item->user_id, value, sizeof(ite…
257 else if (!strcmp(nodes[3].name, "user_name"))
258 strlcpy(item->user_name, value, sizeof(i…
259 else if (!strcmp(nodes[3].name, "game_id"))
260 strlcpy(item->game_id, value, sizeof(ite…
261 else if (!strcmp(nodes[3].name, "language"))
262 strlcpy(item->language, value, sizeof(it…
263 } else if (nodes[3].type == TYPE_NUMBER) {
264 /* TODO: check? */
265 if (!strcmp(nodes[3].name, "viewer_count"))
266 item->viewer_count = strtoll(value, NULL…
267 }
268 }
269 }
270
271 struct streams_response *
272 twitch_streams_bygame(const char *game_id)
273 {
274 struct streams_response *r;
275 char *data;
276
277 if (game_id[0])
278 data = twitch_streams_game_data(game_id);
279 else
280 data = twitch_streams_data();
281
282 if (!(r = calloc(1, sizeof(*r)))) {
283 fprintf(stderr, "calloc\n");
284 return NULL;
285 }
286 if (json_unmarshal(data, twitch_streams_processnode, r) == -1) {
287 free(r);
288 r = NULL;
289 }
290 free(data);
291
292 return r;
293 }
294
295 struct streams_response *
296 twitch_streams(void)
297 {
298 return twitch_streams_bygame("");
299 }
300
301 int
302 ids_cmp(const void *v1, const void *v2)
303 {
304 const char *s1 = *((const char**)v1), *s2 = *((const char **)v2);
305
306 return strcmp(s1, s2);
307 }
308
309 /* fill in games in the streams response */
310 struct games_response *
311 twitch_streams_games(struct streams_response *r)
312 {
313 struct games_response *rg;
314 char *game_ids[MAX_ITEMS];
315 char game_ids_param[4096] = "";
316 size_t i, j;
317
318 /* create a list of game_ids, sort them and filter unique */
319 for (i = 0; i < r->nitems; i++)
320 game_ids[i] = r->data[i].game_id;
321
322 qsort(game_ids, r->nitems, sizeof(*game_ids), ids_cmp);
323 for (i = 0; i < r->nitems; i++) {
324 if (!game_ids[i][0])
325 continue;
326
327 /* first or different than previous */
328 if (i && !strcmp(game_ids[i], game_ids[i - 1]))
329 continue;
330
331 if (game_ids_param[0])
332 strlcat(game_ids_param, "&", sizeof(game_ids_par…
333
334 strlcat(game_ids_param, "id=", sizeof(game_ids_param));
335 strlcat(game_ids_param, game_ids[i], sizeof(game_ids_par…
336 }
337
338 if ((rg = twitch_games_bygameids(game_ids_param))) {
339 for (i = 0; i < r->nitems; i++) {
340 for (j = 0; j < rg->nitems; j++) {
341 /* match game on game_id */
342 if (!strcmp(r->data[i].game_id, rg->data…
343 r->data[i].game = &(rg->data[j]);
344 break;
345 }
346 }
347 }
348 }
349 return rg;
350 }
351
352 /* fill in users in the streams response */
353 struct users_response *
354 twitch_streams_users(struct streams_response *r)
355 {
356 struct users_response *ru = NULL;
357 char *user_ids[MAX_ITEMS];
358 char user_ids_param[4096] = "";
359 size_t i, j;
360
361 /* create a list of user_ids, sort them and filter unique */
362 for (i = 0; i < r->nitems; i++)
363 user_ids[i] = r->data[i].user_id;
364
365 qsort(user_ids, r->nitems, sizeof(*user_ids), ids_cmp);
366 for (i = 0; i < r->nitems; i++) {
367 if (!user_ids[i][0])
368 continue;
369 /* first or different than previous */
370 if (i && !strcmp(user_ids[i], user_ids[i - 1]))
371 continue;
372
373 if (user_ids_param[0])
374 strlcat(user_ids_param, "&", sizeof(user_ids_par…
375
376 strlcat(user_ids_param, "id=", sizeof(user_ids_param));
377 strlcat(user_ids_param, user_ids[i], sizeof(user_ids_par…
378 }
379
380 if ((ru = twitch_users_byuserids(user_ids_param))) {
381 for (i = 0; i < r->nitems; i++) {
382 for (j = 0; j < ru->nitems; j++) {
383 /* match user on user_id */
384 if (!strcmp(r->data[i].user_id, ru->data…
385 r->data[i].user = &(ru->data[j]);
386 break;
387 }
388 }
389 }
390 }
391 return ru;
392 }
393
394 void
395 twitch_users_processnode(struct json_node *nodes, size_t depth, const ch…
396 void *pp)
397 {
398 struct users_response *r = (struct users_response *)pp;
399 struct user *item;
400
401 if (r->nitems > MAX_ITEMS)
402 return;
403 item = &(r->data[r->nitems]);
404
405 /* new item */
406 if (depth == 3 &&
407 nodes[0].type == TYPE_OBJECT &&
408 nodes[1].type == TYPE_ARRAY &&
409 nodes[2].type == TYPE_OBJECT &&
410 !strcmp(nodes[1].name, "data")) {
411 r->nitems++;
412 return;
413 }
414
415 if (r->nitems == 0)
416 return;
417 item = &(r->data[r->nitems - 1]);
418
419 if (depth == 4 &&
420 nodes[0].type == TYPE_OBJECT &&
421 nodes[1].type == TYPE_ARRAY &&
422 nodes[2].type == TYPE_OBJECT &&
423 !strcmp(nodes[1].name, "data")) {
424 if (nodes[3].type == TYPE_STRING) {
425 if (!strcmp(nodes[3].name, "id"))
426 strlcpy(item->id, value, sizeof(item->id…
427 else if (!strcmp(nodes[3].name, "login"))
428 strlcpy(item->login, value, sizeof(item-…
429 else if (!strcmp(nodes[3].name, "display_name"))
430 strlcpy(item->display_name, value, sizeo…
431 } else if (nodes[3].type == TYPE_NUMBER) {
432 /* TODO: check? */
433 if (!strcmp(nodes[3].name, "view_count"))
434 item->view_count = strtoll(value, NULL, …
435 }
436 }
437 }
438
439 struct users_response *
440 twitch_users_byuserids(const char *param)
441 {
442 struct users_response *r;
443 char *data;
444
445 if ((data = twitch_users_byuserids_data(param)) == NULL) {
446 fprintf(stderr, "%s\n", __func__);
447 return NULL;
448 }
449
450 if (!(r = calloc(1, sizeof(*r)))) {
451 fprintf(stderr, "calloc\n");
452 return NULL;
453 }
454 if (json_unmarshal(data, twitch_users_processnode, r) == -1) {
455 free(r);
456 r = NULL;
457 }
458 free(data);
459
460 return r;
461 }
462
463 struct users_response *
464 twitch_users_bylogin(const char *login)
465 {
466 struct users_response *r;
467 char *data;
468
469 if ((data = twitch_users_bylogin_data(login)) == NULL) {
470 fprintf(stderr, "%s\n", __func__);
471 return NULL;
472 }
473
474 if (!(r = calloc(1, sizeof(*r)))) {
475 fprintf(stderr, "calloc\n");
476 return NULL;
477 }
478 if (json_unmarshal(data, twitch_users_processnode, r) == -1) {
479 free(r);
480 r = NULL;
481 }
482 free(data);
483
484 return r;
485 }
486
487 void
488 twitch_videos_processnode(struct json_node *nodes, size_t depth, const c…
489 void *pp)
490 {
491 struct videos_response *r = (struct videos_response *)pp;
492 struct video *item;
493
494 if (r->nitems > MAX_ITEMS)
495 return;
496 item = &(r->data[r->nitems]);
497
498 /* new item */
499 if (depth == 3 &&
500 nodes[0].type == TYPE_OBJECT &&
501 nodes[1].type == TYPE_ARRAY &&
502 nodes[2].type == TYPE_OBJECT &&
503 !strcmp(nodes[1].name, "data")) {
504 r->nitems++;
505 return;
506 }
507
508 if (r->nitems == 0)
509 return;
510 item = &(r->data[r->nitems - 1]);
511
512 if (depth == 4 &&
513 nodes[0].type == TYPE_OBJECT &&
514 nodes[1].type == TYPE_ARRAY &&
515 nodes[2].type == TYPE_OBJECT &&
516 !strcmp(nodes[1].name, "data")) {
517 if (nodes[3].type == TYPE_STRING) {
518 if (!strcmp(nodes[3].name, "id"))
519 strlcpy(item->id, value, sizeof(item->id…
520 else if (!strcmp(nodes[3].name, "user_id"))
521 strlcpy(item->user_id, value, sizeof(ite…
522 else if (!strcmp(nodes[3].name, "user_name"))
523 strlcpy(item->user_name, value, sizeof(i…
524 else if (!strcmp(nodes[3].name, "title"))
525 strlcpy(item->title, value, sizeof(item-…
526 else if (!strcmp(nodes[3].name, "created_at"))
527 strlcpy(item->created_at, value, sizeof(…
528 else if (!strcmp(nodes[3].name, "url"))
529 strlcpy(item->url, value, sizeof(item->u…
530 else if (!strcmp(nodes[3].name, "duration"))
531 strlcpy(item->duration, value, sizeof(it…
532 } else if (nodes[3].type == TYPE_NUMBER) {
533 /* TODO: check? */
534 if (!strcmp(nodes[3].name, "view_count"))
535 item->view_count = strtoll(value, NULL, …
536 }
537 }
538 }
539
540 struct videos_response *
541 twitch_videos_byuserid(const char *user_id)
542 {
543 struct videos_response *r;
544 char *data;
545
546 if ((data = twitch_videos_byuserid_data(user_id)) == NULL) {
547 fprintf(stderr, "%s\n", __func__);
548 return NULL;
549 }
550
551 if (!(r = calloc(1, sizeof(*r)))) {
552 fprintf(stderr, "calloc\n");
553 return NULL;
554 }
555 if (json_unmarshal(data, twitch_videos_processnode, r) == -1) {
556 free(r);
557 r = NULL;
558 }
559 free(data);
560
561 return r;
562 }
You are viewing proxied material from codemadness.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.