idiotbox

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

gph.c (4650B)


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