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 }