twitch.c - frontends - front-ends for some sites (experiment) (DIR) Log (DIR) Files (DIR) Refs (DIR) README (DIR) 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, void *), 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_id=%s", 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_id=%s", 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 char *value, size_t valuelen, 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 char *value, size_t valuelen, 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->title)); 255 else if (!strcmp(nodes[3].name, "user_id")) 256 strlcpy(item->user_id, value, sizeof(item->user_id)); 257 else if (!strcmp(nodes[3].name, "user_name")) 258 strlcpy(item->user_name, value, sizeof(item->user_name)); 259 else if (!strcmp(nodes[3].name, "game_id")) 260 strlcpy(item->game_id, value, sizeof(item->game_id)); 261 else if (!strcmp(nodes[3].name, "language")) 262 strlcpy(item->language, value, sizeof(item->language)); 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, 10); 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_param)); 333 334 strlcat(game_ids_param, "id=", sizeof(game_ids_param)); 335 strlcat(game_ids_param, game_ids[i], sizeof(game_ids_param)); 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[j].id)) { 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_param)); 375 376 strlcat(user_ids_param, "id=", sizeof(user_ids_param)); 377 strlcat(user_ids_param, user_ids[i], sizeof(user_ids_param)); 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[j].id)) { 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 char *value, size_t valuelen, 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->login)); 429 else if (!strcmp(nodes[3].name, "display_name")) 430 strlcpy(item->display_name, value, sizeof(item->display_name)); 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, 10); 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 char *value, size_t valuelen, 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(item->user_id)); 522 else if (!strcmp(nodes[3].name, "user_name")) 523 strlcpy(item->user_name, value, sizeof(item->user_name)); 524 else if (!strcmp(nodes[3].name, "title")) 525 strlcpy(item->title, value, sizeof(item->title)); 526 else if (!strcmp(nodes[3].name, "created_at")) 527 strlcpy(item->created_at, value, sizeof(item->created_at)); 528 else if (!strcmp(nodes[3].name, "url")) 529 strlcpy(item->url, value, sizeof(item->url)); 530 else if (!strcmp(nodes[3].name, "duration")) 531 strlcpy(item->duration, value, sizeof(item->duration)); 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, 10); 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 }