reddit.c - frontends - front-ends for some sites (experiment)
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       reddit.c (6587B)
       ---
            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 "reddit.h"
           18 #include "util.h"
           19 
           20 static char *
           21 reddit_request(const char *path)
           22 {
           23         return request("old.reddit.com", path, "");
           24 }
           25 
           26 /* unmarshal JSON response, skip HTTP headers */
           27 int
           28 json_unmarshal(const char *data,
           29         void (*cb)(struct json_node *, size_t, const char *, size_t, void *),
           30         void *pp)
           31 {
           32         const char *s;
           33 
           34         /* strip/skip header part */
           35         if (!(s = strstr(data, "\r\n\r\n"))) {
           36                 fprintf(stderr, "error parsing HTTP response header\n");
           37                 return -1; /* invalid response */
           38         }
           39         s += strlen("\r\n\r\n");
           40 
           41         /* parse */
           42         if (parsejson(s, strlen(s), cb, pp) < 0) {
           43                 fprintf(stderr, "error parsing JSON\n");
           44                 return -1;
           45         }
           46 
           47         return 0;
           48 }
           49 
           50 char *
           51 reddit_list_data(const char *subreddit, int limit,
           52         const char *before, const char *after)
           53 {
           54         char path[4096];
           55         int r;
           56 
           57         if (limit <= 0)
           58                 limit = 25;
           59 
           60         if (subreddit[0])
           61                 r = snprintf(path, sizeof(path), "/r/%s/.json?raw_json=1&limit=%d",
           62                              subreddit, limit);
           63         else
           64                 r = snprintf(path, sizeof(path), "/.json?raw_json=1&limit=%d",
           65                              limit);
           66 
           67         if (before[0]) {
           68                 strlcat(path, "&before=", sizeof(path));
           69                 strlcat(path, before, sizeof(path));
           70         } else if (after[0]) {
           71                 strlcat(path, "&after=", sizeof(path));
           72                 strlcat(path, after, sizeof(path));
           73         }
           74 
           75         if (r < 0 || (size_t)r >= sizeof(path))
           76                 return NULL;
           77 
           78         return reddit_request(path);
           79 }
           80 
           81 void
           82 reddit_list_processnode(struct json_node *nodes, size_t depth, const char *value, size_t valuelen,
           83         void *pp)
           84 {
           85         struct list_response *r = (struct list_response *)pp;
           86         struct item *item;
           87         struct json_node *node;
           88         struct tm *tm;
           89 
           90         if (depth == 3 &&
           91             nodes[0].type == JSON_TYPE_OBJECT &&
           92             nodes[1].type == JSON_TYPE_OBJECT &&
           93             nodes[2].type == JSON_TYPE_STRING &&
           94             !strcmp(nodes[0].name, "") &&
           95             !strcmp(nodes[1].name, "data")) {
           96                 if (!strcmp(nodes[2].name, "before")) {
           97                         strlcpy(r->before, value, sizeof(r->before));
           98                 } else if (!strcmp(nodes[2].name, "after")) {
           99                         strlcpy(r->after, value, sizeof(r->after));
          100                 }
          101         }
          102 
          103         if (r->nitems > MAX_ITEMS)
          104                 return;
          105 
          106         /* new item */
          107         if (depth == 5 &&
          108             nodes[0].type == JSON_TYPE_OBJECT &&
          109             nodes[1].type == JSON_TYPE_OBJECT &&
          110             nodes[2].type == JSON_TYPE_ARRAY &&
          111             nodes[3].type == JSON_TYPE_OBJECT &&
          112             nodes[4].type == JSON_TYPE_OBJECT &&
          113             !strcmp(nodes[0].name, "") &&
          114             !strcmp(nodes[1].name, "data") &&
          115             !strcmp(nodes[2].name, "children") &&
          116             !strcmp(nodes[3].name, "") &&
          117             !strcmp(nodes[4].name, "data")) {
          118                 r->nitems++;
          119                 return;
          120         }
          121 
          122         if (r->nitems == 0)
          123                 return;
          124         item = &(r->items[r->nitems - 1]);
          125 
          126         if (depth >= 5 &&
          127             nodes[0].type == JSON_TYPE_OBJECT &&
          128             nodes[1].type == JSON_TYPE_OBJECT &&
          129             nodes[2].type == JSON_TYPE_ARRAY &&
          130             nodes[3].type == JSON_TYPE_OBJECT &&
          131             nodes[4].type == JSON_TYPE_OBJECT &&
          132             !strcmp(nodes[0].name, "") &&
          133             !strcmp(nodes[1].name, "data") &&
          134             !strcmp(nodes[2].name, "children") &&
          135             !strcmp(nodes[3].name, "") &&
          136             !strcmp(nodes[4].name, "data")) {
          137                 if (depth == 6) {
          138                         node = &nodes[5];
          139                         switch (node->type) {
          140                         case JSON_TYPE_BOOL:
          141                                 if (!strcmp(node->name, "is_video"))
          142                                         item->is_video = value[0] == 't';
          143                                 break;
          144                         case JSON_TYPE_NUMBER:
          145                                 if (!strcmp(node->name, "ups"))
          146                                         item->ups = strtol(value, NULL, 10);
          147                                 else if (!strcmp(node->name, "downs"))
          148                                         item->downs = strtol(value, NULL, 10);
          149                                 else if (!strcmp(node->name, "num_comments"))
          150                                         item->num_comments = strtol(value, NULL, 10);
          151                                 else if (!strcmp(node->name, "created_utc")) {
          152                                         item->created_utc = strtoll(value, NULL, 10);
          153                                         /* convert to struct tm */
          154                                         tm = gmtime(&(item->created_utc));
          155                                         memcpy(&(item->created_tm), tm, sizeof(*tm));
          156                                 }
          157                                 break;
          158                         case JSON_TYPE_STRING:
          159                                 if (!strcmp(node->name, "name"))
          160                                         strlcpy(item->name, value, sizeof(item->name));
          161                                 else if (!strcmp(node->name, "title"))
          162                                         strlcpy(item->title, value, sizeof(item->title));
          163                                 else if (!strcmp(node->name, "url"))
          164                                         strlcpy(item->url, value, sizeof(item->url));
          165                                 else if (!strcmp(node->name, "permalink"))
          166                                         strlcpy(item->permalink, value, sizeof(item->permalink));
          167                                 else if (!strcmp(node->name, "subreddit"))
          168                                         strlcpy(item->subreddit, value, sizeof(item->subreddit));
          169                                 else if (!strcmp(node->name, "author"))
          170                                         strlcpy(item->author, value, sizeof(item->author));
          171                                 else if (!strcmp(node->name, "thumbnail"))
          172                                         strlcpy(item->thumbnail, value, sizeof(item->thumbnail));
          173                                 else if (!strcmp(node->name, "link_flair_text"))
          174                                         strlcpy(item->link_flair_text, value, sizeof(item->link_flair_text));
          175                                 else if (!strcmp(node->name, "link_flair_background_color") && value[0] == '#')
          176                                         strlcpy(item->link_flair_background_color, value, sizeof(item->link_flair_background_color));
          177                                 break;
          178                         default:
          179                                 break;
          180                         }
          181                 } else if (depth == 8 &&
          182                     nodes[5].type == JSON_TYPE_OBJECT &&
          183                     nodes[6].type == JSON_TYPE_OBJECT &&
          184                     (!strcmp(nodes[5].name, "media") || !strcmp(nodes[5].name, "secure_media")) &&
          185                     !strcmp(nodes[6].name, "reddit_video")) {
          186                         node = &nodes[7];
          187 
          188                         switch (node->type) {
          189                         case JSON_TYPE_NUMBER:
          190                                 /* prefer "insecure" */
          191                                 if (nodes[5].name[0] == 's' && item->duration)
          192                                         break;
          193                                 if (!strcmp(node->name, "duration"))
          194                                         item->duration = strtol(value, NULL, 10);
          195                                 break;
          196                         case JSON_TYPE_STRING:
          197                                 /* prefer "insecure" */
          198                                 if (nodes[5].name[0] == 's' && item->dash_url[0])
          199                                         break;
          200                                 if (!strcmp(node->name, "dash_url"))
          201                                         strlcpy(item->dash_url, value, sizeof(item->dash_url));
          202                                 break;
          203                         default:
          204                                 break;
          205                         }
          206                 }
          207         }
          208 }
          209 
          210 struct list_response *
          211 reddit_list(const char *subreddit, int limit,
          212         const char *before, const char *after)
          213 {
          214         struct list_response *r;
          215         char *data;
          216 
          217         if (!(data = reddit_list_data(subreddit, limit, before, after))) {
          218                 fprintf(stderr, "%s\n", __func__);
          219                 return NULL;
          220         }
          221 
          222         if (!(r = calloc(1, sizeof(*r)))) {
          223                 fprintf(stderr, "calloc\n");
          224                 return NULL;
          225         }
          226 
          227         if (json_unmarshal(data, reddit_list_processnode, r) == -1) {
          228                 free(r);
          229                 r = NULL;
          230         }
          231         free(data);
          232 
          233         return r;
          234 }
          235 
          236 int
          237 reddit_isvalidlink(const char *s)
          238 {
          239         char *end = NULL;
          240         unsigned long long l;
          241 
          242         /* type prefix: reddit link is "t3_" */
          243         if (strncmp(s, "t3_", 3))
          244                 return 0;
          245         s += 3;
          246 
          247         /* link is base36 */
          248         errno = 0;
          249         l = strtoull(s, &end, 36);
          250         return (!errno && s != end && !*end && l > 0);
          251 }