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 }