reddit.c - frontends - front-ends for some sites (experiment) | |
Log | |
Files | |
Refs | |
README | |
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, voi… | |
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… | |
62 subreddit, limit); | |
63 else | |
64 r = snprintf(path, sizeof(path), "/.json?raw_json=1&limi… | |
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 cha… | |
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, … | |
147 else if (!strcmp(node->name, "downs")) | |
148 item->downs = strtol(value, NULL… | |
149 else if (!strcmp(node->name, "num_commen… | |
150 item->num_comments = strtol(valu… | |
151 else if (!strcmp(node->name, "created_ut… | |
152 item->created_utc = strtoll(valu… | |
153 /* convert to struct tm */ | |
154 tm = gmtime(&(item->created_utc)… | |
155 memcpy(&(item->created_tm), tm, … | |
156 } | |
157 break; | |
158 case JSON_TYPE_STRING: | |
159 if (!strcmp(node->name, "name")) | |
160 strlcpy(item->name, value, sizeo… | |
161 else if (!strcmp(node->name, "title")) | |
162 strlcpy(item->title, value, size… | |
163 else if (!strcmp(node->name, "url")) | |
164 strlcpy(item->url, value, sizeof… | |
165 else if (!strcmp(node->name, "permalink"… | |
166 strlcpy(item->permalink, value, … | |
167 else if (!strcmp(node->name, "subreddit"… | |
168 strlcpy(item->subreddit, value, … | |
169 else if (!strcmp(node->name, "author")) | |
170 strlcpy(item->author, value, siz… | |
171 else if (!strcmp(node->name, "thumbnail"… | |
172 strlcpy(item->thumbnail, value, … | |
173 else if (!strcmp(node->name, "link_flair… | |
174 strlcpy(item->link_flair_text, v… | |
175 else if (!strcmp(node->name, "link_flair… | |
176 strlcpy(item->link_flair_backgro… | |
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]… | |
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->dur… | |
192 break; | |
193 if (!strcmp(node->name, "duration")) | |
194 item->duration = strtol(value, N… | |
195 break; | |
196 case JSON_TYPE_STRING: | |
197 /* prefer "insecure" */ | |
198 if (nodes[5].name[0] == 's' && item->das… | |
199 break; | |
200 if (!strcmp(node->name, "dash_url")) | |
201 strlcpy(item->dash_url, value, s… | |
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 } |