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 }