cli.c - frontends - front-ends for some sites (experiment) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
cli.c (8420B) | |
--- | |
1 #include <sys/socket.h> | |
2 #include <sys/types.h> | |
3 | |
4 #include <errno.h> | |
5 #include <netdb.h> | |
6 #include <stdarg.h> | |
7 #include <stdio.h> | |
8 #include <stdlib.h> | |
9 #include <string.h> | |
10 #include <unistd.h> | |
11 | |
12 #include "https.h" | |
13 #include "util.h" | |
14 #include "youtube.h" | |
15 | |
16 #define OUT(s) fputs((s), stdout) | |
17 #define OUTESCAPE(s) printescape((s)) | |
18 #define OUTENCODED(s) printencoded((s)) | |
19 | |
20 /* print: ignore control-characters */ | |
21 void | |
22 printescape(const char *s) | |
23 { | |
24 for (; *s; ++s) | |
25 if (!ISCNTRL((unsigned char)*s)) | |
26 fputc(*s, stdout); | |
27 } | |
28 | |
29 /* print: encode TAB, newline and '\', remove other whitespace. */ | |
30 void | |
31 printencoded(const char *s) | |
32 { | |
33 for (; *s; ++s) { | |
34 switch (*s) { | |
35 case '\n': putchar('\\'); putchar('n'); break; | |
36 case '\\': putchar('\\'); putchar('\\'); break; | |
37 case '\t': putchar('\\'); putchar('t'); break; | |
38 default: | |
39 /* ignore control chars */ | |
40 if (!ISCNTRL((unsigned char)*s)) | |
41 putchar(*s); | |
42 break; | |
43 } | |
44 } | |
45 } | |
46 | |
47 void | |
48 printescape_multiline(const char *s, const char *indent) | |
49 { | |
50 int i = 0; | |
51 | |
52 for (; *s; ++s) { | |
53 if (!i) | |
54 fputs(indent, stdout); | |
55 | |
56 if (*s == '\n') { | |
57 i = 0; | |
58 fputc(*s, stdout); | |
59 } else if (!ISCNTRL((unsigned char)*s)) { | |
60 fputc(*s, stdout); | |
61 i = 1; | |
62 } | |
63 } | |
64 } | |
65 | |
66 int | |
67 render_search_tsv(struct search_response *r) | |
68 { | |
69 struct item *v; | |
70 size_t i; | |
71 | |
72 for (i = 0; i < r->nitems; i++) { | |
73 v = &(r->items[i]); | |
74 | |
75 OUTESCAPE(v->id); | |
76 OUT("\t"); | |
77 if (v->id[0]) { | |
78 OUT("https://www.youtube.com/embed/"); | |
79 OUTESCAPE(v->id); | |
80 } | |
81 OUT("\t"); | |
82 OUTESCAPE(v->title); | |
83 OUT("\t"); | |
84 OUTESCAPE(v->publishedat); | |
85 OUT("\t"); | |
86 OUTESCAPE(v->viewcount); /* from Youtube, the text can b… | |
87 OUT("\t"); | |
88 OUTESCAPE(v->duration); | |
89 OUT("\t"); | |
90 switch (v->linktype) { | |
91 case Channel: OUT("channel"); break; | |
92 case Movie: OUT("movie"); break; | |
93 case Playlist: OUT("playlist"); break; | |
94 default: break; | |
95 } | |
96 OUT("\t"); | |
97 OUTESCAPE(v->channelid); | |
98 OUT("\t"); | |
99 OUTESCAPE(v->channeltitle); | |
100 OUT("\t"); | |
101 OUTESCAPE(v->userid); | |
102 OUT("\t"); | |
103 OUTENCODED(v->shortdescription); | |
104 OUT("\n"); | |
105 } | |
106 | |
107 return 0; | |
108 } | |
109 | |
110 int | |
111 render_search(struct search_response *r) | |
112 { | |
113 struct item *v; | |
114 size_t i; | |
115 | |
116 for (i = 0; i < r->nitems; i++) { | |
117 v = &(r->items[i]); | |
118 | |
119 switch (v->linktype) { | |
120 case Channel: | |
121 OUT("Channel: "); | |
122 OUTESCAPE(v->channeltitle); | |
123 break; | |
124 case Movie: | |
125 OUT("Movie: "); | |
126 OUTESCAPE(v->title); | |
127 break; | |
128 case Playlist: | |
129 OUT("Playlist: "); | |
130 OUTESCAPE(v->title); | |
131 break; | |
132 default: | |
133 OUT(" "); | |
134 OUTESCAPE(v->title); | |
135 break; | |
136 } | |
137 if (v->duration[0]) { | |
138 OUT(" ["); | |
139 OUTESCAPE(v->duration); | |
140 OUT("]"); | |
141 } | |
142 OUT("\n"); | |
143 | |
144 if (v->id[0]) { | |
145 OUT("URL: https://www.youtube.com/embed/"); | |
146 OUTESCAPE(v->id); | |
147 OUT("\n"); | |
148 } | |
149 | |
150 if (v->channelid[0] || v->userid[0]) { | |
151 OUT("Channel: "); | |
152 OUTESCAPE(v->channeltitle); | |
153 OUT(": https://www.youtube.com/feeds/videos.xml?… | |
154 if (v->channelid[0]) { | |
155 OUT("channel_id="); | |
156 OUTESCAPE(v->channelid); | |
157 } else if (v->userid[0]) { | |
158 OUT("user="); | |
159 OUTESCAPE(v->userid); | |
160 } | |
161 OUT("\n"); | |
162 } | |
163 if (v->publishedat[0]) { | |
164 OUT("Published: "); | |
165 OUTESCAPE(v->publishedat); | |
166 OUT("\n"); | |
167 } | |
168 if (v->viewcount[0]) { | |
169 OUT("Views: "); | |
170 if (!printnumsep(v->viewcount)) | |
171 OUT("0"); | |
172 OUT("\n"); | |
173 } | |
174 OUT("\n"); | |
175 } | |
176 | |
177 return 0; | |
178 } | |
179 | |
180 int | |
181 render_video(struct video_response *r) | |
182 { | |
183 struct video_format *f; | |
184 char buf[256]; | |
185 int i; | |
186 | |
187 OUT("URL: "); | |
188 OUT("https://www.youtube.com/embed/"); | |
189 OUTESCAPE(r->id); | |
190 OUT("\n"); | |
191 | |
192 OUT("Title: "); | |
193 OUTESCAPE(r->title); | |
194 OUT("\n"); | |
195 | |
196 if (r->lengthseconds > 0) { | |
197 OUT("Length: "); | |
198 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < si… | |
199 OUTESCAPE(buf); | |
200 OUT("\n"); | |
201 } | |
202 | |
203 OUT("Views: "); | |
204 snprintf(buf, sizeof(buf), "%ld", r->viewcount); | |
205 if (!printnumsep(buf)) | |
206 OUT("0"); | |
207 OUT("\n"); | |
208 | |
209 if (r->publishdate[0]) { | |
210 OUT("Published: "); | |
211 OUTESCAPE(r->publishdate); | |
212 OUT("\n"); | |
213 } | |
214 | |
215 if (r->uploaddate[0]) { | |
216 OUT("Uploaded: "); | |
217 OUTESCAPE(r->uploaddate); | |
218 OUT("\n"); | |
219 } | |
220 | |
221 if (r->author[0]) { | |
222 OUT("Channel: "); | |
223 OUTESCAPE(r->author); | |
224 if (r->channelid[0]) { | |
225 OUT(": https://www.youtube.com/feeds/videos.xml?… | |
226 OUTESCAPE(r->channelid); | |
227 } | |
228 OUT("\n"); | |
229 } | |
230 | |
231 if (r->shortdescription[0]) { | |
232 OUT("Description:\n\n"); | |
233 printescape_multiline(r->shortdescription, ""); | |
234 OUT("\n"); | |
235 } | |
236 | |
237 if (r->nformats == 0) | |
238 return 0; | |
239 | |
240 OUT("\n\nFormats:\n\n"); | |
241 | |
242 /* links expiration */ | |
243 if (r->expiresinseconds > 0) { | |
244 OUT("Expires in "); | |
245 printf("%ld", r->expiresinseconds); | |
246 OUT(" seconds\n"); | |
247 } | |
248 | |
249 for (i = 0; i < r->nformats; i++) { | |
250 f = &(r->formats[i]); | |
251 | |
252 #if 0 | |
253 if (f->width < 1280 || f->height < 720) | |
254 continue; | |
255 #endif | |
256 | |
257 #if 0 | |
258 OUT("Last modified: "); | |
259 OUTESCAPE(f->lastmodified); | |
260 OUT("\n"); | |
261 #endif | |
262 | |
263 if (f->url[0]) { | |
264 OUT("URL: "); | |
265 OUTESCAPE(f->url); | |
266 OUT("\n"); | |
267 } | |
268 | |
269 /* encrypted stream */ | |
270 if (f->signaturecipher[0]) { | |
271 OUT("Signature: "); | |
272 OUTESCAPE(f->signaturecipher); | |
273 OUT("\n"); | |
274 } | |
275 | |
276 if (f->mimetype[0]) { | |
277 OUT("Mime: "); | |
278 OUTESCAPE(f->mimetype); | |
279 OUT("\n"); | |
280 } | |
281 | |
282 if (f->itag > 0) { | |
283 OUT("itag: "); | |
284 printf("%ld\n", f->itag); | |
285 } | |
286 | |
287 if (f->qualitylabel[0]) { | |
288 OUT("Quality: "); | |
289 OUTESCAPE(f->qualitylabel); | |
290 } else if (f->quality[0]) { | |
291 OUT("Quality: "); | |
292 OUTESCAPE(f->quality); | |
293 } | |
294 | |
295 if (f->width > 0) { | |
296 OUT(", "); | |
297 printf("%ld", f->width); | |
298 OUT("x"); | |
299 printf("%ld", f->height); | |
300 OUT(""); | |
301 } | |
302 if (f->fps > 0) { | |
303 OUT(", "); | |
304 printf("%ld", f->fps); | |
305 OUT(" FPS"); | |
306 } | |
307 OUT("\n"); | |
308 | |
309 if (f->bitrate > 0) { | |
310 OUT("Bitrate: "); | |
311 printf("%ld", f->bitrate); | |
312 if (f->averagebitrate > 0) | |
313 printf(", average: %ld", f->averagebitra… | |
314 OUT("\n"); | |
315 } | |
316 | |
317 if (f->contentlength > 0) { | |
318 OUT("Size: "); | |
319 printf("%lld bytes (%.2f MB)\n", f->contentlengt… | |
320 } | |
321 | |
322 if (f->audiochannels > 0 || f->audiosamplerate) { | |
323 OUT("Audio: "); | |
324 if (f->audiochannels > 0) | |
325 printf("%ld channels", f->audiochannels); | |
326 if (f->audiosamplerate > 0) { | |
327 if (f->audiochannels > 0) | |
328 OUT(", "); | |
329 printf("%ld sample rate", f->audiosample… | |
330 } | |
331 OUT("\n"); | |
332 } | |
333 | |
334 OUT("\n"); | |
335 } | |
336 | |
337 return 0; | |
338 } | |
339 | |
340 static void | |
341 usage(const char *argv0) | |
342 { | |
343 fprintf(stderr, "usage: %s [-t] <keyword> | <-c channelid> | <-u… | |
344 exit(1); | |
345 } | |
346 | |
347 int | |
348 main(int argc, char *argv[]) | |
349 { | |
350 struct search_response *r = NULL; | |
351 struct video_response *vr = NULL; | |
352 char search[1024]; | |
353 const char *keywords = NULL, *channelid = NULL, *user = NULL, *v… | |
354 const char *order = "relevance"; | |
355 int i, usetsv = 0; | |
356 | |
357 if (pledge("stdio dns inet rpath unveil", NULL) == -1) { | |
358 fprintf(stderr, "pledge: %s\n", strerror(errno)); | |
359 exit(1); | |
360 } | |
361 | |
362 for (i = 1; i < argc; i++) { | |
363 if (argv[i][0] == '-') { | |
364 switch (argv[i][1]) { | |
365 case 'c': | |
366 if (i + 1 >= argc) | |
367 usage(argv[0]); | |
368 channelid = argv[i + 1]; | |
369 i++; | |
370 break; | |
371 case 'i': | |
372 if (i + 1 >= argc) | |
373 usage(argv[0]); | |
374 videoid = argv[i + 1]; | |
375 i++; | |
376 break; | |
377 case 'o': | |
378 if (i + 1 >= argc) | |
379 usage(argv[0]); | |
380 order = argv[i + 1]; | |
381 i++; | |
382 break; | |
383 case 'u': | |
384 if (i + 1 >= argc) | |
385 usage(argv[0]); | |
386 user = argv[i + 1]; | |
387 i++; | |
388 break; | |
389 case 't': | |
390 usetsv = 1; | |
391 break; | |
392 default: | |
393 usage(argv[0]); | |
394 } | |
395 continue; | |
396 } | |
397 keywords = argv[i]; | |
398 } | |
399 | |
400 if (unveil(TLS_CA_CERT_FILE, "r") == -1) { | |
401 fprintf(stderr, "unveil: %s\n", strerror(errno)); | |
402 exit(1); | |
403 } | |
404 if (unveil(NULL, NULL) == -1) { | |
405 fprintf(stderr, "unveil: %s\n", strerror(errno)); | |
406 exit(1); | |
407 } | |
408 | |
409 if (argc < 2 || !argv[1][0]) | |
410 usage(argv[0]); | |
411 | |
412 /* check order options */ | |
413 if (strcmp(order, "relevance") && | |
414 strcmp(order, "views") && | |
415 strcmp(order, "rating")) | |
416 usage(argv[0]); | |
417 | |
418 if (channelid) { | |
419 r = youtube_channel_videos(channelid); | |
420 } else if (user) { | |
421 r = youtube_user_videos(user); | |
422 } else if (videoid) { | |
423 vr = youtube_video(videoid); | |
424 if (!vr || vr->isfound == 0) { | |
425 OUT("No video found\n"); | |
426 exit(1); | |
427 } | |
428 render_video(vr); | |
429 return 0; | |
430 } else if (keywords) { | |
431 if (!uriencode(keywords, search, sizeof(search))) | |
432 usage(argv[0]); | |
433 r = youtube_search(search, "", order); | |
434 } | |
435 if (!r || r->nitems == 0) { | |
436 OUT("No videos found\n"); | |
437 exit(1); | |
438 } | |
439 | |
440 if (pledge("stdio", NULL) == -1) { | |
441 fprintf(stderr, "pledge: %s\n", strerror(errno)); | |
442 exit(1); | |
443 } | |
444 | |
445 if (usetsv) | |
446 render_search_tsv(r); | |
447 else | |
448 render_search(r); | |
449 | |
450 return 0; | |
451 } |