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