cli.c - frontends - front-ends for some sites (experiment)
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
cli.c (8837B)
---
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 be: "No views" */
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("\t");
105 OUT(v->membersonly ? "1" : "0");
106 OUT("\n");
107 }
108
109 return 0;
110 }
111
112 int
113 render_search(struct search_response *r)
114 {
115 struct item *v;
116 size_t i;
117
118 for (i = 0; i < r->nitems; i++) {
119 v = &(r->items[i]);
120
121 switch (v->linktype) {
122 case Channel: OUT("Channel: "); break;
123 case Movie: OUT("Movie: "); break;
124 case Playlist: OUT("Playlist: "); break;
125 default: OUT(" "); break;
126 }
127
128 switch (v->linktype) {
129 case Channel:
130 OUT("Channel: ");
131 OUTESCAPE(v->channeltitle);
132 break;
133 default:
134 if (v->membersonly)
135 OUT("[Members only] ");
136 OUTESCAPE(v->title);
137 break;
138 }
139
140 if (v->duration[0]) {
141 OUT(" [");
142 OUTESCAPE(v->duration);
143 OUT("]");
144 }
145 OUT("\n");
146
147 if (v->id[0]) {
148 OUT("URL: https://www.youtube.com/embed/");
149 OUTESCAPE(v->id);
150 OUT("\n");
151 }
152
153 if (v->channelid[0] || v->userid[0]) {
154 OUT("Channel: ");
155 OUTESCAPE(v->channeltitle);
156 OUT(": https://www.youtube.com/feeds/videos.xml?");
157 if (v->channelid[0]) {
158 OUT("channel_id=");
159 OUTESCAPE(v->channelid);
160 } else if (v->userid[0]) {
161 OUT("user=");
162 OUTESCAPE(v->userid);
163 }
164 OUT("\n");
165 }
166 if (v->publishedat[0]) {
167 OUT("Published: ");
168 OUTESCAPE(v->publishedat);
169 OUT("\n");
170 }
171 if (v->viewcount[0]) {
172 OUT("Views: ");
173 if (!printnumsep(v->viewcount))
174 OUT("0");
175 OUT("\n");
176 }
177 OUT("\n");
178 }
179
180 return 0;
181 }
182
183 int
184 render_video(struct video_response *r)
185 {
186 struct video_format *f;
187 char buf[256];
188 int i;
189
190 OUT("URL: ");
191 OUT("https://www.youtube.com/embed/");
192 OUTESCAPE(r->id);
193 OUT("\n");
194
195 OUT("Title: ");
196 OUTESCAPE(r->title);
197 OUT("\n");
198
199 if (r->lengthseconds > 0) {
200 OUT("Length: ");
201 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < sizeof(buf))
202 OUTESCAPE(buf);
203 OUT("\n");
204 }
205
206 /* show playability state and reason: for example when it is a members-only video */
207 if (r->playabilityreason[0]) {
208 OUT("Status: ");
209 if (r->playabilitystatus[0]) {
210 OUTESCAPE(r->playabilitystatus);
211 OUT(": ");
212 }
213 OUTESCAPE(r->playabilityreason);
214 OUT("\n");
215 }
216
217 OUT("Views: ");
218 snprintf(buf, sizeof(buf), "%ld", r->viewcount);
219 if (!printnumsep(buf))
220 OUT("0");
221 OUT("\n");
222
223 if (r->publishdate[0]) {
224 OUT("Published: ");
225 OUTESCAPE(r->publishdate);
226 OUT("\n");
227 }
228
229 if (r->uploaddate[0]) {
230 OUT("Uploaded: ");
231 OUTESCAPE(r->uploaddate);
232 OUT("\n");
233 }
234
235 if (r->author[0]) {
236 OUT("Channel: ");
237 OUTESCAPE(r->author);
238 if (r->channelid[0]) {
239 OUT(": https://www.youtube.com/feeds/videos.xml?channel_id=");
240 OUTESCAPE(r->channelid);
241 }
242 OUT("\n");
243 }
244
245 if (r->shortdescription[0]) {
246 OUT("Description:\n\n");
247 printescape_multiline(r->shortdescription, "");
248 OUT("\n");
249 }
250
251 if (r->nformats == 0)
252 return 0;
253
254 OUT("\n\nFormats:\n\n");
255
256 /* links expiration */
257 if (r->expiresinseconds > 0) {
258 OUT("Expires in ");
259 printf("%ld", r->expiresinseconds);
260 OUT(" seconds\n");
261 }
262
263 for (i = 0; i < r->nformats; i++) {
264 f = &(r->formats[i]);
265
266 #if 0
267 if (f->width < 1280 || f->height < 720)
268 continue;
269 #endif
270
271 #if 0
272 OUT("Last modified: ");
273 OUTESCAPE(f->lastmodified);
274 OUT("\n");
275 #endif
276
277 if (f->url[0]) {
278 OUT("URL: ");
279 OUTESCAPE(f->url);
280 OUT("\n");
281 }
282
283 /* encrypted stream */
284 if (f->signaturecipher[0]) {
285 OUT("Signature: ");
286 OUTESCAPE(f->signaturecipher);
287 OUT("\n");
288 }
289
290 if (f->mimetype[0]) {
291 OUT("Mime: ");
292 OUTESCAPE(f->mimetype);
293 OUT("\n");
294 }
295
296 if (f->itag > 0) {
297 OUT("itag: ");
298 printf("%ld\n", f->itag);
299 }
300
301 if (f->qualitylabel[0]) {
302 OUT("Quality: ");
303 OUTESCAPE(f->qualitylabel);
304 } else if (f->quality[0]) {
305 OUT("Quality: ");
306 OUTESCAPE(f->quality);
307 }
308
309 if (f->width > 0) {
310 OUT(", ");
311 printf("%ld", f->width);
312 OUT("x");
313 printf("%ld", f->height);
314 OUT("");
315 }
316 if (f->fps > 0) {
317 OUT(", ");
318 printf("%ld", f->fps);
319 OUT(" FPS");
320 }
321 OUT("\n");
322
323 if (f->bitrate > 0) {
324 OUT("Bitrate: ");
325 printf("%ld", f->bitrate);
326 if (f->averagebitrate > 0)
327 printf(", average: %ld", f->averagebitrate);
328 OUT("\n");
329 }
330
331 if (f->contentlength > 0) {
332 OUT("Size: ");
333 printf("%lld bytes (%.2f MB)\n", f->contentlength, f->contentlength / 1024.0 / 1024.0);
334 }
335
336 if (f->audiochannels > 0 || f->audiosamplerate) {
337 OUT("Audio: ");
338 if (f->audiochannels > 0)
339 printf("%ld channels", f->audiochannels);
340 if (f->audiosamplerate > 0) {
341 if (f->audiochannels > 0)
342 OUT(", ");
343 printf("%ld sample rate", f->audiosamplerate);
344 }
345 OUT("\n");
346 }
347
348 OUT("\n");
349 }
350
351 return 0;
352 }
353
354 static void
355 usage(const char *argv0)
356 {
357 fprintf(stderr, "usage: %s [-t] <keyword> | <-c channelid> | <-u user> | <-i videoid> | [-o relevance|views|rating]\n", argv0);
358 exit(1);
359 }
360
361 int
362 main(int argc, char *argv[])
363 {
364 struct search_response *r = NULL;
365 struct video_response *vr = NULL;
366 char search[1024];
367 const char *keywords = NULL, *channelid = NULL, *user = NULL, *videoid = NULL;
368 const char *order = "relevance";
369 int i, usetsv = 0;
370
371 if (pledge("stdio dns inet rpath unveil", NULL) == -1) {
372 fprintf(stderr, "pledge: %s\n", strerror(errno));
373 exit(1);
374 }
375
376 for (i = 1; i < argc; i++) {
377 if (argv[i][0] == '-') {
378 switch (argv[i][1]) {
379 case 'c':
380 if (i + 1 >= argc)
381 usage(argv[0]);
382 channelid = argv[i + 1];
383 i++;
384 break;
385 case 'i':
386 if (i + 1 >= argc)
387 usage(argv[0]);
388 videoid = argv[i + 1];
389 i++;
390 break;
391 case 'o':
392 if (i + 1 >= argc)
393 usage(argv[0]);
394 order = argv[i + 1];
395 i++;
396 break;
397 case 'u':
398 if (i + 1 >= argc)
399 usage(argv[0]);
400 user = argv[i + 1];
401 i++;
402 break;
403 case 't':
404 usetsv = 1;
405 break;
406 default:
407 usage(argv[0]);
408 }
409 continue;
410 }
411 keywords = argv[i];
412 }
413
414 if (unveil(TLS_CA_CERT_FILE, "r") == -1) {
415 fprintf(stderr, "unveil: %s\n", strerror(errno));
416 exit(1);
417 }
418 if (unveil(NULL, NULL) == -1) {
419 fprintf(stderr, "unveil: %s\n", strerror(errno));
420 exit(1);
421 }
422
423 if (argc < 2 || !argv[1][0])
424 usage(argv[0]);
425
426 /* check order options */
427 if (strcmp(order, "relevance") &&
428 strcmp(order, "views") &&
429 strcmp(order, "rating"))
430 usage(argv[0]);
431
432 if (channelid) {
433 r = youtube_channel_videos(channelid);
434 } else if (user) {
435 r = youtube_user_videos(user);
436 } else if (videoid) {
437 vr = youtube_video(videoid);
438 if (!vr || vr->isfound == 0) {
439 OUT("No video found\n");
440 exit(1);
441 }
442 render_video(vr);
443 return 0;
444 } else if (keywords) {
445 if (!uriencode(keywords, search, sizeof(search)))
446 usage(argv[0]);
447 r = youtube_search(search, "", order);
448 }
449 if (!r || r->nitems == 0) {
450 OUT("No videos found\n");
451 exit(1);
452 }
453
454 if (pledge("stdio", NULL) == -1) {
455 fprintf(stderr, "pledge: %s\n", strerror(errno));
456 exit(1);
457 }
458
459 if (usetsv)
460 render_search_tsv(r);
461 else
462 render_search(r);
463
464 return 0;
465 }