idiotbox

youtube scraping and interfaces: CGI, Gopher, CLI
Log | Files | Refs | README | LICENSE

cli.c (4841B)


      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 "youtube.h"
     14 
     15 #ifndef __OpenBSD__
     16 #define pledge(p1,p2) 0
     17 #define unveil(p1,p2) 0
     18 #endif
     19 
     20 #ifndef TLS_CA_CERT_FILE
     21 #define TLS_CA_CERT_FILE "/etc/ssl/cert.pem"
     22 #endif
     23 
     24 #define OUT(s) (fputs((s), stdout))
     25 #define OUTESCAPE(s) (printescape(s))
     26 
     27 struct video *videos;
     28 static int nvideos;
     29 
     30 /* print: ignore control-characters */
     31 void
     32 printescape(const char *s)
     33 {
     34 	for (; *s; ++s)
     35 		if (!iscntrl((unsigned char)*s))
     36 			fputc(*s, stdout);
     37 }
     38 
     39 void
     40 die(const char *fmt, ...)
     41 {
     42 	va_list ap;
     43 
     44 	va_start(ap, fmt);
     45 	vfprintf(stderr, fmt, ap);
     46 	va_end(ap);
     47 
     48 	exit(1);
     49 }
     50 
     51 int
     52 uriencode(const char *s, char *buf, size_t bufsiz)
     53 {
     54 	static char hex[] = "0123456789ABCDEF";
     55 	char *d = buf, *e = buf + bufsiz;
     56 	unsigned char c;
     57 
     58 	if (!bufsiz)
     59 		return 0;
     60 
     61 	for (; *s; ++s) {
     62 		c = (unsigned char)*s;
     63 		if (d + 4 >= e)
     64 			return 0;
     65 		if (c == ' ' || c == '#' || c == '%' || c == '?' || c == '"' ||
     66 		    c == '&' || c == '<' || c <= 0x1f || c >= 0x7f) {
     67 			*d++ = '%';
     68 			*d++ = hex[c >> 4];
     69 			*d++ = hex[c & 0x0f];
     70 		} else {
     71 			*d++ = *s;
     72 		}
     73 	}
     74 	*d = '\0';
     75 
     76 	return 1;
     77 }
     78 
     79 int
     80 hexdigit(int c)
     81 {
     82 	if (c >= '0' && c <= '9')
     83 		return c - '0';
     84 	else if (c >= 'A' && c <= 'F')
     85 		return c - 'A' + 10;
     86 	else if (c >= 'a' && c <= 'f')
     87 		return c - 'a' + 10;
     88 
     89 	return 0;
     90 }
     91 
     92 /* decode until NUL separator or end of "key". */
     93 int
     94 decodeparam(char *buf, size_t bufsiz, const char *s)
     95 {
     96 	size_t i;
     97 
     98 	if (!bufsiz)
     99 		return -1;
    100 
    101 	for (i = 0; *s && *s != '&'; s++) {
    102 		if (i + 3 >= bufsiz)
    103 			return -1;
    104 		switch (*s) {
    105 		case '%':
    106 			if (!isxdigit((unsigned char)*(s+1)) ||
    107 			    !isxdigit((unsigned char)*(s+2)))
    108 				return -1;
    109 			buf[i++] = hexdigit(*(s+1)) * 16 + hexdigit(*(s+2));
    110 			s += 2;
    111 			break;
    112 		case '+':
    113 			buf[i++] = ' ';
    114 			break;
    115 		default:
    116 			buf[i++] = *s;
    117 			break;
    118 		}
    119 	}
    120 	buf[i] = '\0';
    121 
    122 	return i;
    123 }
    124 
    125 char *
    126 getparam(const char *query, const char *s)
    127 {
    128 	const char *p, *last = NULL;
    129 	size_t len;
    130 
    131 	len = strlen(s);
    132 	for (p = query; (p = strstr(p, s)); p += len) {
    133 		if (p[len] == '=' && (p == query || p[-1] == '&' || p[-1] == '?'))
    134 			last = p + len + 1;
    135 	}
    136 
    137 	return (char *)last;
    138 }
    139 
    140 int
    141 render(void)
    142 {
    143 	int i;
    144 
    145 	if (pledge("stdio", NULL) == -1) {
    146 		fprintf(stderr, "pledge: %s\n", strerror(errno));
    147 		exit(1);
    148 	}
    149 
    150 	for (i = 0; i < nvideos; i++) {
    151 		/* TODO: better printing of other types */
    152 		switch (videos[i].linktype) {
    153 		case Channel:
    154 			OUT("[Channel] ");
    155 			OUTESCAPE(videos[i].channeltitle);
    156 			break;
    157 		case Movie:
    158 			OUT("[Movie] ");
    159 			OUTESCAPE(videos[i].title);
    160 			break;
    161 		case Playlist:
    162 			OUT("[Playlist] ");
    163 			OUTESCAPE(videos[i].title);
    164 			break;
    165 		default:
    166 			OUTESCAPE(videos[i].title);
    167 			break;
    168 		}
    169 		OUT("\n");
    170 
    171 		if (videos[i].id[0]) {
    172 			OUT("URL:           https://www.youtube.com/embed/");
    173                         OUTESCAPE(videos[i].id);
    174 			OUT("\n");
    175 		}
    176 
    177 		if (videos[i].channelid[0] || videos[i].userid[0]) {
    178 			OUT("Atom feed:     https://www.youtube.com/feeds/videos.xml?");
    179 			if (videos[i].channelid[0]) {
    180 				OUT("channel_id=");
    181 				OUTESCAPE(videos[i].channelid);
    182 			} else if (videos[i].userid[0]) {
    183 				OUT("user=");
    184 				OUTESCAPE(videos[i].userid);
    185 			}
    186 			OUT("\n");
    187 		}
    188 
    189 		if (videos[i].channelid[0] || videos[i].userid[0]) {
    190 			OUT("Channel title: ");
    191 			OUTESCAPE(videos[i].channeltitle);
    192 			OUT("\n");
    193 			if (videos[i].channelid[0]) {
    194 				OUT("Channelid:     ");
    195 				OUTESCAPE(videos[i].channelid);
    196 				OUT("\n");
    197 			} else if (videos[i].userid[0]) {
    198 				OUT("Userid:        ");
    199 				OUTESCAPE(videos[i].userid);
    200 				OUT("\n");
    201 			}
    202 		}
    203 		if (videos[i].publishedat[0]) {
    204 			OUT("Published:     ");
    205 			OUTESCAPE(videos[i].publishedat);
    206 			OUT("\n");
    207 		}
    208 		if (videos[i].viewcount[0]) {
    209 			OUT("Viewcount:     ");
    210 			OUTESCAPE(videos[i].viewcount);
    211 			OUT("\n");
    212 		}
    213 		if (videos[i].duration[0]) {
    214 			OUT("Duration:      " );
    215 			OUTESCAPE(videos[i].duration);
    216 			OUT("\n");
    217 		}
    218 		OUT("===\n");
    219 	}
    220 
    221 	return 0;
    222 }
    223 
    224 static void
    225 usage(const char *argv0)
    226 {
    227 	fprintf(stderr, "usage: %s <keywords>\n", argv0);
    228 	exit(1);
    229 }
    230 
    231 int
    232 main(int argc, char *argv[])
    233 {
    234 	char search[1024];
    235 
    236 	if (pledge("stdio dns inet rpath unveil", NULL) == -1) {
    237 		fprintf(stderr, "pledge: %s\n", strerror(errno));
    238 		exit(1);
    239 	}
    240 	if (unveil(TLS_CA_CERT_FILE, "r") == -1) {
    241 		fprintf(stderr, "unveil: %s\n", strerror(errno));
    242 		exit(1);
    243 	}
    244 	if (unveil(NULL, NULL) == -1) {
    245 		fprintf(stderr, "unveil: %s\n", strerror(errno));
    246 		exit(1);
    247 	}
    248 
    249 	if (argc < 2 || !argv[1][0])
    250 		usage(argv[0]);
    251 	if (!uriencode(argv[1], search, sizeof(search)))
    252 		usage(argv[0]);
    253 
    254 	videos = youtube_search(&nvideos, search, "", "", "", "relevance");
    255 	if (!videos || nvideos <= 0) {
    256 		OUT("No videos found\n");
    257 		exit(1);
    258 	}
    259 
    260 	render();
    261 
    262 	return 0;
    263 }