gopher.c - frontends - front-ends for some sites (experiment) (DIR) Log (DIR) Files (DIR) Refs (DIR) README (DIR) LICENSE --- gopher.c (7168B) --- 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 OUTTEXT(s) gophertext(stdout, s, strlen(s)); 19 #define OUTLINK(s) gophertext(stdout, s, strlen(s)); 20 21 static const char *host = "127.0.0.1", *port = "70", *requestpath = "/"; 22 23 void 24 line(int _type, const char *username, const char *selector) 25 { 26 putchar(_type); 27 OUTTEXT(username); 28 putchar('\t'); 29 OUTLINK(selector); 30 printf("\t%s\t%s\r\n", host, port); 31 } 32 33 void 34 error(const char *s) 35 { 36 line('3', s, ""); 37 } 38 39 void 40 info(const char *s) 41 { 42 line('i', s, ""); 43 } 44 45 void 46 header(void) 47 { 48 } 49 50 void 51 footer(void) 52 { 53 fputs(".\r\n", stdout); 54 } 55 56 int 57 render_search(struct search_response *r) 58 { 59 struct item *v; 60 size_t i; 61 62 if (pledge("stdio", NULL) == -1) 63 exit(1); 64 65 header(); 66 67 for (i = 0; i < r->nitems; i++) { 68 v = &(r->items[i]); 69 70 if (i > 0) { 71 info(""); 72 info(""); 73 } 74 if (v->id[0]) 75 putchar('h'); 76 else 77 putchar('i'); 78 79 switch (v->linktype) { 80 case Channel: 81 OUT("[Channel] "); 82 OUTTEXT(v->channeltitle); 83 break; 84 case Movie: 85 OUT("[Movie] "); 86 OUTTEXT(v->title); 87 break; 88 case Playlist: 89 OUT("[Playlist] "); 90 OUTTEXT(v->title); 91 break; 92 default: 93 OUTTEXT(v->title); 94 break; 95 } 96 97 OUT("\t"); 98 if (v->id[0]) { 99 OUT("URL:https://www.youtube.com/embed/"); 100 OUTLINK(v->id); 101 } 102 printf("\t%s\t%s\r\n", host, port); 103 104 if (v->id[0]) { 105 printf("1Details\t%s?v=", requestpath); 106 OUTLINK(v->id); 107 printf("\t%s\t%s\r\n", host, port); 108 } 109 110 if (v->channelid[0]) { 111 OUT("1"); 112 OUTTEXT(v->channeltitle); 113 printf("\t%s?c=", requestpath); 114 OUTLINK(v->channelid); 115 printf("\t%s\t%s\r\n", host, port); 116 } else if (v->userid[0]) { 117 OUT("1"); 118 OUTTEXT(v->channeltitle); 119 printf("\t%s?u=", requestpath); 120 OUTLINK(v->userid); 121 printf("\t%s\t%s\r\n", host, port); 122 } else if (v->channeltitle[0]) { 123 OUT("i"); 124 OUTTEXT(v->channeltitle); 125 printf("\t%s\t%s\t%s\r\n", "", host, port); 126 } 127 128 if (v->channelid[0] || v->userid[0]) { 129 OUT("hAtom feed of "); 130 OUTTEXT(v->channeltitle); 131 OUT("\t"); 132 OUTLINK("URL:https://www.youtube.com/feeds/videos.xml?"); 133 if (v->channelid[0]) { 134 OUT("channel_id="); 135 OUTLINK(v->channelid); 136 } else if (v->userid[0]) { 137 OUT("user="); 138 OUTLINK(v->userid); 139 } 140 printf("\t%s\t%s\r\n", host, port); 141 } 142 if (v->duration[0]) { 143 OUT("iDuration: " ); 144 OUTTEXT(v->duration); 145 printf("\t%s\t%s\t%s\r\n", "", host, port); 146 } 147 if (v->publishedat[0]) { 148 OUT("iPublished: "); 149 OUTTEXT(v->publishedat); 150 printf("\t%s\t%s\t%s\r\n", "", host, port); 151 } 152 if (v->viewcount[0]) { 153 OUT("iViews: "); 154 printnumsep(v->viewcount); 155 printf("\t%s\t%s\t%s\r\n", "", host, port); 156 } 157 } 158 footer(); 159 160 return 0; 161 } 162 163 int 164 render_video(struct video_response *r) 165 { 166 char buf[256]; 167 const char *s, *e; 168 /* softwrap: try to wrap on word boundary, otherwise hardwrap */ 169 int i, sw = 72, hw = 9999; 170 171 if (pledge("stdio", NULL) == -1) 172 exit(1); 173 174 header(); 175 176 info(""); 177 178 OUT("hTitle: "); 179 OUTTEXT(r->title); 180 OUT("\tURL:https://www.youtube.com/embed/"); 181 OUTTEXT(r->id); 182 printf("\t%s\t%s\r\n", host, port); 183 184 OUT("hURL: "); 185 OUT("https://www.youtube.com/embed/"); 186 OUTTEXT(r->id); 187 OUT("\tURL:"); 188 OUT("https://www.youtube.com/embed/"); 189 OUTTEXT(r->id); 190 printf("\t%s\t%s\r\n", host, port); 191 192 if (r->lengthseconds > 0) { 193 OUT("iLength: "); 194 if (durationstr(r->lengthseconds, buf, sizeof(buf)) < sizeof(buf)) 195 OUTTEXT(buf); 196 printf("\t%s\t%s\t%s\r\n", "", host, port); 197 } 198 199 OUT("hThumbnail: https://i.ytimg.com/vi/"); 200 OUTTEXT(r->id); 201 OUT("/hqdefault.jpg\tURL:https://i.ytimg.com/vi/"); 202 OUTLINK(r->id); 203 printf("/hqdefault.jpg\t%s\t%s\r\n", host, port); 204 205 if (r->author[0]) { 206 OUT("1Channel: "); 207 OUTTEXT(r->author); 208 printf("\t%s?c=%s\t%s\t%s\r\n", requestpath, r->channelid, host, port); 209 210 if (r->channelid[0]) { 211 OUT("hAtom feed\tURL:https://www.youtube.com/feeds/videos.xml?channel_id="); 212 OUTTEXT(r->channelid); 213 printf("\t%s\t%s\r\n", host, port); 214 } 215 } 216 217 OUT("iViews: "); 218 snprintf(buf, sizeof(buf), "%ld", r->viewcount); 219 printnumsep(buf); 220 printf("\t%s\t%s\t%s\r\n", "", host, port); 221 222 if (r->publishdate[0]) { 223 OUT("iPublished: "); 224 OUTTEXT(r->publishdate); 225 printf("\t%s\t%s\t%s\r\n", "", host, port); 226 } 227 228 if (r->uploaddate[0]) { 229 OUT("iUploaded: "); 230 OUTTEXT(r->uploaddate); 231 printf("\t%s\t%s\t%s\r\n", "", host, port); 232 } 233 234 if (r->shortdescription[0]) { 235 info(""); 236 info(""); 237 238 /* multi-line text */ 239 i = 0; 240 for (s = e = r->shortdescription; ; e++) { 241 if (!i) 242 putchar('i'); 243 if (*e == '\n' || *e == '\0' || 244 i > hw || (i > sw && (*e == ' ' || *e == '\t'))) { 245 i = 0; 246 gophertext(stdout, s, e - s); 247 printf("\t%s\t%s\t%s\r\n", "", host, port); 248 if (*e == '\0') 249 break; 250 s = e + 1; 251 } else { 252 /* start of rune, wrongly assume 1 rune is 1 column for now */ 253 if (!((unsigned char)*e & 0x80)) 254 i++; 255 } 256 } 257 } 258 259 footer(); 260 261 return 0; 262 } 263 264 int 265 main(void) 266 { 267 struct search_response *r = NULL; 268 struct video_response *vr = NULL; 269 const char *channelid = "", *userid = "", *videoid = ""; 270 const char *querystring = "", *rawsearch = "", *p; 271 char search[1024] = ""; 272 273 if (pledge("stdio dns inet rpath unveil", NULL) == -1) 274 exit(1); 275 if (unveil(TLS_CA_CERT_FILE, "r") == -1 || 276 unveil(NULL, NULL) == -1) 277 exit(1); 278 279 if ((p = getenv("SERVER_NAME"))) 280 host = p; 281 if ((p = getenv("SERVER_PORT"))) 282 port = p; 283 if ((p = getenv("PATH_INFO"))) 284 requestpath = p; 285 if ((p = getenv("QUERY_STRING"))) 286 querystring = p; 287 288 p = NULL; 289 if (querystring[0] == '?') 290 querystring++; 291 if (querystring[0] == 'c' && querystring[1] == '=') { 292 channelid = querystring + 2; 293 p = querystring = ""; 294 } else if (querystring[0] == 'u' && querystring[1] == '=') { 295 userid = querystring + 2; 296 p = querystring = ""; 297 } else if (querystring[0] == 'v' && querystring[1] == '=') { 298 videoid = querystring + 2; 299 p = querystring = ""; 300 } 301 302 if (querystring[0]) 303 p = querystring; 304 if (!p) 305 p = getenv("X_GOPHER_SEARCH"); /* geomyidae */ 306 if (!p) 307 p = getenv("SEARCHREQUEST"); /* gophernicus */ 308 if (p) 309 rawsearch = p; 310 if (p && !uriencode(p, search, sizeof(search))) { 311 error("Invalid search"); 312 footer(); 313 return 0; 314 } 315 316 OUT("7Search"); 317 if (rawsearch[0]) { 318 OUT(": "); 319 OUT(rawsearch); 320 } 321 printf("\t%s\t%s\t%s\r\n", requestpath, host, port); 322 323 info(""); 324 line('1', "<- back to main directory", requestpath); 325 326 if (search[0]) { 327 r = youtube_search(search, "", "relevance"); 328 } else if (channelid[0]) { 329 r = youtube_channel_videos(channelid); 330 } else if (userid[0]) { 331 r = youtube_user_videos(userid); 332 } else if (videoid[0]) { 333 vr = youtube_video(videoid); 334 if (!vr || vr->isfound == 0) { 335 error("No video found"); 336 footer(); 337 return 0; 338 } 339 render_video(vr); 340 return 0; 341 } else { 342 footer(); 343 return 0; 344 } 345 info(""); 346 347 if (!r || r->nitems == 0) { 348 error("No videos found"); 349 footer(); 350 return 0; 351 } 352 353 render_search(r); 354 355 return 0; 356 }