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 }