gopher.c - frontends - front-ends for some sites (experiment) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
gopher.c (7294B) | |
--- | |
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 <stdio.h> | |
9 #include <stdlib.h> | |
10 #include <string.h> | |
11 #include <unistd.h> | |
12 | |
13 #include "https.h" | |
14 #include "util.h" | |
15 #include "youtube.h" | |
16 | |
17 #define OUT(s) (fputs((s), stdout)) | |
18 #define OUTTEXT(s) gophertext(stdout, s, strlen(s)); | |
19 #define OUTLINK(s) gophertext(stdout, s, strlen(s)); | |
20 | |
21 static const char *host = "127.0.0.1", *port = "70", *requestpath = "/"; | |
22 | |
23 void | |
24 line(int _type, const char *username, const char *selector) | |
25 { | |
26 putchar(_type); | |
27 OUTTEXT(username); | |
28 putchar('\t'); | |
29 OUTLINK(selector); | |
30 printf("\t%s\t%s\r\n", host, port); | |
31 } | |
32 | |
33 void | |
34 error(const char *s) | |
35 { | |
36 line('3', s, ""); | |
37 } | |
38 | |
39 void | |
40 info(const char *s) | |
41 { | |
42 line('i', s, ""); | |
43 } | |
44 | |
45 void | |
46 header(void) | |
47 { | |
48 } | |
49 | |
50 void | |
51 footer(void) | |
52 { | |
53 fputs(".\r\n", stdout); | |
54 } | |
55 | |
56 int | |
57 render_search(struct search_response *r) | |
58 { | |
59 struct item *v; | |
60 size_t i; | |
61 | |
62 if (pledge("stdio", NULL) == -1) | |
63 exit(1); | |
64 | |
65 header(); | |
66 | |
67 for (i = 0; i < r->nitems; i++) { | |
68 v = &(r->items[i]); | |
69 | |
70 if (i > 0) { | |
71 info(""); | |
72 info(""); | |
73 } | |
74 if (v->id[0]) | |
75 putchar('h'); | |
76 else | |
77 putchar('i'); | |
78 | |
79 switch (v->linktype) { | |
80 case Channel: | |
81 OUT("[Channel] "); | |
82 OUTTEXT(v->channeltitle); | |
83 break; | |
84 case Movie: | |
85 OUT("[Movie] "); | |
86 OUTTEXT(v->title); | |
87 break; | |
88 case Playlist: | |
89 OUT("[Playlist] "); | |
90 OUTTEXT(v->title); | |
91 break; | |
92 default: | |
93 OUTTEXT(v->title); | |
94 break; | |
95 } | |
96 | |
97 OUT("\t"); | |
98 if (v->id[0]) { | |
99 OUT("URL:https://www.youtube.com/embed/"); | |
100 OUTLINK(v->id); | |
101 } | |
102 printf("\t%s\t%s\r\n", host, port); | |
103 | |
104 if (v->id[0]) { | |
105 printf("1Details\t%s?v=", requestpath); | |
106 OUTLINK(v->id); | |
107 printf("\t%s\t%s\r\n", host, port); | |
108 } | |
109 | |
110 if (v->channelid[0]) { | |
111 OUT("1"); | |
112 OUTTEXT(v->channeltitle); | |
113 printf("\t%s?c=", requestpath); | |
114 OUTLINK(v->channelid); | |
115 printf("\t%s\t%s\r\n", host, port); | |
116 } else if (v->userid[0]) { | |
117 OUT("1"); | |
118 OUTTEXT(v->channeltitle); | |
119 printf("\t%s?u=", requestpath); | |
120 OUTLINK(v->userid); | |
121 printf("\t%s\t%s\r\n", host, port); | |
122 } else if (v->channeltitle[0]) { | |
123 OUT("i"); | |
124 OUTTEXT(v->channeltitle); | |
125 printf("\t%s\t%s\t%s\r\n", "", host, port); | |
126 } | |
127 | |
128 if (v->channelid[0] || v->userid[0]) { | |
129 OUT("hAtom feed of "); | |
130 OUTTEXT(v->channeltitle); | |
131 OUT("\t"); | |
132 OUTLINK("URL:https://www.youtube.com/feeds/video… | |
133 if (v->channelid[0]) { | |
134 OUT("channel_id="); | |
135 OUTLINK(v->channelid); | |
136 } else if (v->userid[0]) { | |
137 OUT("user="); | |
138 OUTLINK(v->userid); | |
139 } | |
140 printf("\t%s\t%s\r\n", host, port); | |
141 } | |
142 if (v->duration[0]) { | |
143 OUT("iDuration: " ); | |
144 OUTTEXT(v->duration); | |
145 printf("\t%s\t%s\t%s\r\n", "", host, port); | |
146 } | |
147 if (v->publishedat[0]) { | |
148 OUT("iPublished: "); | |
149 OUTTEXT(v->publishedat); | |
150 printf("\t%s\t%s\t%s\r\n", "", host, port); | |
151 } | |
152 if (v->viewcount[0]) { | |
153 OUT("iViews: "); | |
154 if (!printnumsep(v->viewcount)) | |
155 OUT("0"); | |
156 printf("\t%s\t%s\t%s\r\n", "", host, port); | |
157 } | |
158 } | |
159 footer(); | |
160 | |
161 return 0; | |
162 } | |
163 | |
164 int | |
165 render_video(struct video_response *r) | |
166 { | |
167 char buf[256]; | |
168 const char *s, *e; | |
169 /* softwrap: try to wrap on word boundary, otherwise hardwrap */ | |
170 int i, sw = 72, hw = 9999; | |
171 | |
172 if (pledge("stdio", NULL) == -1) | |
173 exit(1); | |
174 | |
175 header(); | |
176 | |
177 info(""); | |
178 | |
179 OUT("hTitle: "); | |
180 OUTTEXT(r->title); | |
181 OUT("\tURL:https://www.youtube.com/embed/"); | |
182 OUTTEXT(r->id); | |
183 printf("\t%s\t%s\r\n", host, port); | |
184 | |
185 OUT("hURL: "); | |
186 OUT("https://www.youtube.com/embed/"); | |
187 OUTTEXT(r->id); | |
188 OUT("\tURL:"); | |
189 OUT("https://www.youtube.com/embed/"); | |
190 OUTTEXT(r->id); | |
191 printf("\t%s\t%s\r\n", host, port); | |
192 | |
193 if (r->lengthseconds > 0) { | |
194 OUT("iLength: "); | |
195 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < si… | |
196 OUTTEXT(buf); | |
197 printf("\t%s\t%s\t%s\r\n", "", host, port); | |
198 } | |
199 | |
200 OUT("hThumbnail: https://i.ytimg.com/vi/"); | |
201 OUTTEXT(r->id); | |
202 OUT("/hqdefault.jpg\tURL:https://i.ytimg.com/vi/"); | |
203 OUTLINK(r->id); | |
204 printf("/hqdefault.jpg\t%s\t%s\r\n", host, port); | |
205 | |
206 if (r->author[0]) { | |
207 OUT("1Channel: "); | |
208 OUTTEXT(r->author); | |
209 printf("\t%s?c=%s\t%s\t%s\r\n", requestpath, r->channeli… | |
210 | |
211 if (r->channelid[0]) { | |
212 OUT("hAtom feed\tURL:https://www.youtube.com/fee… | |
213 OUTTEXT(r->channelid); | |
214 printf("\t%s\t%s\r\n", host, port); | |
215 } | |
216 } | |
217 | |
218 OUT("iViews: "); | |
219 snprintf(buf, sizeof(buf), "%ld", r->viewcount); | |
220 if (!printnumsep(buf)) | |
221 OUT("0"); | |
222 printf("\t%s\t%s\t%s\r\n", "", host, port); | |
223 | |
224 if (r->publishdate[0]) { | |
225 OUT("iPublished: "); | |
226 OUTTEXT(r->publishdate); | |
227 printf("\t%s\t%s\t%s\r\n", "", host, port); | |
228 } | |
229 | |
230 if (r->uploaddate[0]) { | |
231 OUT("iUploaded: "); | |
232 OUTTEXT(r->uploaddate); | |
233 printf("\t%s\t%s\t%s\r\n", "", host, port); | |
234 } | |
235 | |
236 if (r->shortdescription[0]) { | |
237 info(""); | |
238 info(""); | |
239 | |
240 /* multi-line text */ | |
241 i = 0; | |
242 for (s = e = r->shortdescription; ; e++) { | |
243 if (!i) | |
244 putchar('i'); | |
245 if (*e == '\n' || *e == '\0' || | |
246 i > hw || (i > sw && (*e == ' ' || *e == '\t'… | |
247 i = 0; | |
248 gophertext(stdout, s, e - s); | |
249 printf("\t%s\t%s\t%s\r\n", "", host, por… | |
250 if (*e == '\0') | |
251 break; | |
252 s = e + 1; | |
253 } else { | |
254 /* start of rune, wrongly assume 1 rune … | |
255 if (!((unsigned char)*e & 0x80)) | |
256 i++; | |
257 } | |
258 } | |
259 } | |
260 | |
261 footer(); | |
262 | |
263 return 0; | |
264 } | |
265 | |
266 int | |
267 main(void) | |
268 { | |
269 struct search_response *r = NULL; | |
270 struct video_response *vr = NULL; | |
271 const char *channelid = "", *userid = "", *videoid = ""; | |
272 const char *querystring = "", *rawsearch = "", *p; | |
273 char search[1024] = ""; | |
274 | |
275 if (pledge("stdio dns inet rpath unveil", NULL) == -1) | |
276 exit(1); | |
277 if (unveil(TLS_CA_CERT_FILE, "r") == -1 || | |
278 unveil(NULL, NULL) == -1) | |
279 exit(1); | |
280 | |
281 if ((p = getenv("SERVER_NAME"))) | |
282 host = p; | |
283 if ((p = getenv("SERVER_PORT"))) | |
284 port = p; | |
285 if ((p = getenv("PATH_TRANSLATED"))) | |
286 requestpath = p; | |
287 else if ((p = getenv("PATH_INFO"))) /* fallback to PATH_INFO */ | |
288 requestpath = p; | |
289 if ((p = getenv("QUERY_STRING"))) | |
290 querystring = p; | |
291 | |
292 p = NULL; | |
293 if (querystring[0] == '?') | |
294 querystring++; | |
295 if (querystring[0] == 'c' && querystring[1] == '=') { | |
296 channelid = querystring + 2; | |
297 p = querystring = ""; | |
298 } else if (querystring[0] == 'u' && querystring[1] == '=') { | |
299 userid = querystring + 2; | |
300 p = querystring = ""; | |
301 } else if (querystring[0] == 'v' && querystring[1] == '=') { | |
302 videoid = querystring + 2; | |
303 p = querystring = ""; | |
304 } | |
305 | |
306 if (querystring[0]) | |
307 p = querystring; | |
308 if (!p) | |
309 p = getenv("X_GOPHER_SEARCH"); /* geomyidae */ | |
310 if (!p) | |
311 p = getenv("SEARCHREQUEST"); /* gophernicus */ | |
312 if (p) | |
313 rawsearch = p; | |
314 if (p && !uriencode(p, search, sizeof(search))) { | |
315 error("Invalid search"); | |
316 footer(); | |
317 return 0; | |
318 } | |
319 | |
320 OUT("7Search"); | |
321 if (rawsearch[0]) { | |
322 OUT(": "); | |
323 OUT(rawsearch); | |
324 } | |
325 printf("\t%s\t%s\t%s\r\n", requestpath, host, port); | |
326 | |
327 info(""); | |
328 line('1', "<- back to main directory", requestpath); | |
329 | |
330 if (search[0]) { | |
331 r = youtube_search(search, "", "relevance"); | |
332 } else if (channelid[0]) { | |
333 r = youtube_channel_videos(channelid); | |
334 } else if (userid[0]) { | |
335 r = youtube_user_videos(userid); | |
336 } else if (videoid[0]) { | |
337 vr = youtube_video(videoid); | |
338 if (!vr || vr->isfound == 0) { | |
339 error("No video found"); | |
340 footer(); | |
341 return 0; | |
342 } | |
343 render_video(vr); | |
344 return 0; | |
345 } else { | |
346 footer(); | |
347 return 0; | |
348 } | |
349 info(""); | |
350 | |
351 if (!r || r->nitems == 0) { | |
352 error("No videos found"); | |
353 footer(); | |
354 return 0; | |
355 } | |
356 | |
357 render_search(r); | |
358 | |
359 return 0; | |
360 } |