gopher.c - frontends - front-ends for some sites (experiment)
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       gopher.c (9547B)
       ---
            1 #include <sys/socket.h>
            2 #include <sys/types.h>
            3 
            4 #include <ctype.h>
            5 #include <errno.h>
            6 #include <locale.h>
            7 #include <netdb.h>
            8 #include <stdarg.h>
            9 #include <stdio.h>
           10 #include <stdlib.h>
           11 #include <string.h>
           12 #include <time.h>
           13 #include <unistd.h>
           14 #include <wchar.h>
           15 
           16 #include "https.h"
           17 #include "json.h"
           18 #include "twitch.h"
           19 #include "util.h"
           20 
           21 #define OUT(s)       (fputs((s), stdout))
           22 #define OUTTEXT(s)   gophertext(stdout, s, strlen(s))
           23 #define OUTLINK(s)   gophertext(stdout, s, strlen(s))
           24 
           25 extern char **environ;
           26 
           27 static const char *baserel = "/twitch.cgi";
           28 static const char *host = "127.0.0.1", *port = "70";
           29 
           30 /* page variables */
           31 static char *title = "", *pagetitle = "";
           32 
           33 void
           34 line(int _type, const char *username, const char *selector)
           35 {
           36         putchar(_type);
           37         OUTTEXT(username);
           38         putchar('\t');
           39         OUTLINK(selector);
           40         printf("\t%s\t%s\r\n", host, port);
           41 }
           42 
           43 void
           44 error(const char *s)
           45 {
           46         line('3', s, "");
           47 }
           48 
           49 void
           50 info(const char *s)
           51 {
           52         line('i', s, "");
           53 }
           54 
           55 void
           56 dir(const char *username, const char *selector)
           57 {
           58         line('1', username, selector);
           59 }
           60 
           61 void
           62 html(const char *username, const char *selector)
           63 {
           64         line('h', username, selector);
           65 }
           66 
           67 void
           68 page(int _type, const char *username, const char *page)
           69 {
           70         putchar(_type);
           71         OUTTEXT(username);
           72         putchar('\t');
           73         printf("%s?p=%s", baserel, page);
           74         printf("\t%s\t%s\r\n", host, port);
           75 }
           76 
           77 static int
           78 gamecmp_name(const void *v1, const void *v2)
           79 {
           80         struct game *g1 = (struct game *)v1;
           81         struct game *g2 = (struct game *)v2;
           82 
           83         return strcmp(g1->name, g2->name);
           84 }
           85 
           86 void
           87 header(void)
           88 {
           89         putchar('i');
           90         if (title[0]) {
           91                 OUTTEXT(title);
           92                 OUT(" - ");
           93         }
           94         if (pagetitle[0]) {
           95                 OUTTEXT(pagetitle);
           96                 OUT(" - ");
           97         }
           98         printf("Twitch.tv\t%s\t%s\t%s\r\n", "", host, port);
           99         info("---");
          100         page('1', "Featured", "featured");
          101         page('1', "Games", "games");
          102         page('1', "VODS", "vods");
          103         dir("Source-code", "/git/frontends");
          104         page('1', "Links", "links");
          105         info("---");
          106 }
          107 
          108 void
          109 footer(void)
          110 {
          111         printf(".\r\n");
          112 }
          113 
          114 void
          115 render_links(void)
          116 {
          117         header();
          118         info("");
          119         html("mpv player",    "URL:https://mpv.io/installation/");
          120         html("youtube-dl",    "URL:https://github.com/ytdl-org/youtube-dl");
          121         html("VLC",           "URL:https://www.videolan.org/");
          122         html("Twitch.tv API", "URL:https://dev.twitch.tv/docs");
          123         footer();
          124 }
          125 
          126 void
          127 render_games_top(struct games_response *r)
          128 {
          129         struct game *game;
          130         size_t i;
          131 
          132         header();
          133         info("Name");
          134         for (i = 0; i < r->nitems; i++) {
          135                 game = &(r->data[i]);
          136 
          137                 putchar('1');
          138                 OUTTEXT(game->name);
          139                 printf("\t%s?p=streams&game_id=", baserel);
          140                 OUTLINK(game->id);
          141                 printf("\t%s\t%s\r\n", host, port);
          142         }
          143         footer();
          144 }
          145 
          146 void
          147 render_streams(struct streams_response *r, const char *game_id)
          148 {
          149         struct stream *stream;
          150         char buf[256], title[256];
          151         size_t i;
          152 
          153         header();
          154         if (!game_id[0])
          155                 printf("i%-20s %-20s %-50s %7s\t%s\t%s\t%s\r\n",
          156                         "Game", "Name", "Title", "Viewers", "", host, port);
          157         else
          158                 printf("i%-20s %-50s %7s\t%s\t%s\t%s\r\n",
          159                        "Name", "Title", "Viewers", "", host, port);
          160 
          161         for (i = 0; i < r->nitems; i++) {
          162                 stream = &(r->data[i]);
          163 
          164                 if (stream->user)
          165                         putchar('h');
          166                 else
          167                         putchar('i');
          168 
          169                 if (!game_id[0]) {
          170                         if (stream->game) {
          171                                 if (utf8pad(buf, sizeof(buf), stream->game->name, 20, ' ') != -1)
          172                                         OUTTEXT(buf);
          173                         } else {
          174                                 printf("%20s", "");
          175                         }
          176                         OUT(" ");
          177                 }
          178 
          179                 if (utf8pad(buf, sizeof(buf), stream->user_name, 20, ' ') != -1)
          180                         OUTTEXT(buf);
          181                 OUT(" ");
          182 
          183                 if (stream->language[0])
          184                         snprintf(title, sizeof(title), "[%s] %s", stream->language, stream->title);
          185                         if (utf8pad(buf, sizeof(buf), title, 50, ' ') != -1)
          186                                 OUTTEXT(buf);
          187                 else {
          188                         if (utf8pad(buf, sizeof(buf), stream->title, 50, ' ') != -1)
          189                                 OUTTEXT(buf);
          190                 }
          191 
          192                 printf(" %7lld\t", stream->viewer_count);
          193 
          194                 if (stream->user) {
          195                         OUT("URL:https://www.twitch.tv/");
          196                         OUTLINK(stream->user->login);
          197                 }
          198                 printf("\t%s\t%s\r\n", host, port);
          199         }
          200         footer();
          201 }
          202 
          203 void
          204 render_videos_atom(struct videos_response *r, const char *login)
          205 {
          206         struct video *video;
          207         size_t i;
          208 
          209         OUT("<feed xmlns=\"http://www.w3.org/2005/Atom\" xml:lang=\"en\">\n");
          210         for (i = 0; i < r->nitems; i++) {
          211                 video = &(r->data[i]);
          212 
          213                 OUT("<entry>\n");
          214                 OUT("\t<title type=\"text\">");
          215                 xmlencode(video->title);
          216                 OUT("</title>\n");
          217                 OUT("\t<link rel=\"alternate\" type=\"text/html\" href=\"");
          218                 xmlencode(video->url);
          219                 OUT("\" />\n");
          220                 OUT("\t<id>");
          221                 xmlencode(video->url);
          222                 OUT("</id>\n");
          223                 OUT("\t<updated>");
          224                 xmlencode(video->created_at);
          225                 OUT("</updated>\n");
          226                 OUT("\t<published>");
          227                 xmlencode(video->created_at);
          228                 OUT("</published>\n");
          229                 OUT("</entry>\n");
          230         }
          231         OUT("</feed>\n");
          232 }
          233 
          234 void
          235 render_videos(struct videos_response *r, const char *login)
          236 {
          237         struct video *video;
          238         char buf[256];
          239         size_t i;
          240 
          241         header();
          242 
          243         page('7', "Submit Twitch login name to list VODs", "vods");
          244         info("");
          245 
          246         /* no results or no user_id parameter: quick exit */
          247         if (r == NULL) {
          248                 footer();
          249                 return;
          250         }
          251 
          252         /* link to Atom format (text). */
          253         if (login[0]) {
          254                 OUT("0Atom feed for ");
          255                 OUTLINK(login);
          256                 printf("\t%s?p=vods&format=atom&login=", baserel);
          257                 OUTLINK(login);
          258                 printf("\t%s\t%s\r\n", host, port);
          259                 info("");
          260         }
          261 
          262         printf("i%-20s %-50s %-10s %7s\t%s\t%s\t%s\r\n",
          263                "Created", "Title", "Duration", "Views", "", host, port);
          264 
          265         for (i = 0; i < r->nitems; i++) {
          266                 video = &(r->data[i]);
          267 
          268                 putchar('h');
          269                 if (utf8pad(buf, sizeof(buf), video->created_at, 20, ' ') != -1)
          270                         OUTLINK(buf);
          271                 OUT(" ");
          272                 if (utf8pad(buf, sizeof(buf), video->title, 50, ' ') != -1)
          273                         OUTLINK(buf);
          274                 OUT(" ");
          275                 if (utf8pad(buf, sizeof(buf), video->duration, 10, ' ') != -1)
          276                         OUTLINK(buf);
          277 
          278                 printf(" %7lld\t", video->view_count);
          279                 OUT("URL:");
          280                 OUTLINK(video->url);
          281                 printf("\t%s\t%s\r\n", host, port);
          282         }
          283         footer();
          284 }
          285 
          286 void
          287 handle_streams(void)
          288 {
          289         struct streams_response *r;
          290         struct users_response *ru = NULL;
          291         struct games_response *rg = NULL;
          292         char game_id[32] = "";
          293         char *p, *querystring;
          294 
          295         pagetitle = "Streams";
          296 
          297         /* parse "game_id" parameter */
          298         if ((querystring = getenv("QUERY_STRING"))) {
          299                 if ((p = getparam(querystring, "game_id"))) {
          300                         if (decodeparam(game_id, sizeof(game_id), p) == -1)
          301                                 game_id[0] = '\0';
          302                 }
          303         }
          304 
          305         if (game_id[0])
          306                 r = twitch_streams_bygame(game_id);
          307         else
          308                 r = twitch_streams();
          309 
          310         if (r == NULL)
          311                 return;
          312 
          313         /* find detailed games data with streams */
          314         if (!game_id[0])
          315                 rg = twitch_streams_games(r);
          316 
          317         /* find detailed user data with streams */
          318         ru = twitch_streams_users(r);
          319 
          320         if (pledge("stdio", NULL) == -1)
          321                 exit(1);
          322 
          323         render_streams(r, game_id);
          324 
          325         free(r);
          326         free(rg);
          327         free(ru);
          328 }
          329 
          330 void
          331 handle_videos(void)
          332 {
          333         struct videos_response *r = NULL;
          334         struct users_response *ru = NULL;
          335         char user_id[32] = "", login[64] = "", format[6] = "";
          336         char *p, *querystring;
          337 
          338         pagetitle = "Videos";
          339 
          340         /* parse "user_id" or "login" parameter */
          341         if ((querystring = getenv("QUERY_STRING"))) {
          342                 if ((p = getparam(querystring, "user_id"))) {
          343                         if (decodeparam(user_id, sizeof(user_id), p) == -1)
          344                                 user_id[0] = '\0';
          345                 }
          346                 if ((p = getparam(querystring, "login"))) {
          347                         if (decodeparam(login, sizeof(login), p) == -1)
          348                                 login[0] = '\0';
          349                 }
          350                 if ((p = getparam(querystring, "format"))) {
          351                         if (decodeparam(format, sizeof(format), p) == -1)
          352                                 format[0] = '\0';
          353                 }
          354         }
          355 
          356         /* login: if not set as query string parameter then use gopher search
          357            parameter */
          358         if (login[0] == '\0') {
          359                 if (!(p = getenv("X_GOPHER_SEARCH"))) /* geomyidae */
          360                         p = getenv("SEARCHREQUEST"); /* gophernicus */
          361                 if (p && decodeparam(login, sizeof(login), p) == -1)
          362                         login[0] = '\0';
          363         }
          364 
          365         /* no parameter given, show form */
          366         if (!user_id[0] && !login[0]) {
          367                 if (pledge("stdio", NULL) == -1)
          368                         exit(1);
          369 
          370                 render_videos(r, "");
          371                 return;
          372         }
          373 
          374         if (user_id[0]) {
          375                 r = twitch_videos_byuserid(user_id);
          376         } else {
          377                 ru = twitch_users_bylogin(login);
          378                 if (ru && ru->nitems > 0)
          379                         r = twitch_videos_byuserid(ru->data[0].id);
          380         }
          381 
          382         if (pledge("stdio", NULL) == -1)
          383                 exit(1);
          384 
          385         if (r && r->nitems > 0)
          386                 title = r->data[0].user_name;
          387 
          388         if (!strcmp(format, "atom"))
          389                 render_videos_atom(r, login);
          390         else
          391                 render_videos(r, login);
          392 
          393         free(ru);
          394         free(r);
          395 }
          396 
          397 void
          398 handle_games_top(void)
          399 {
          400         struct games_response *r;
          401 
          402         pagetitle = "Top 100 games";
          403 
          404         if (!(r = twitch_games_top()))
          405                 return;
          406 
          407         if (pledge("stdio", NULL) == -1)
          408                 exit(1);
          409 
          410         /* sort by name alphabetically, NOTE: the results are the top 100
          411            sorted by viewcount. View counts are not visible in the new
          412            Helix API data). */
          413         qsort(r->data, r->nitems, sizeof(r->data[0]), gamecmp_name);
          414 
          415         render_games_top(r);
          416 
          417         free(r);
          418 }
          419 
          420 void
          421 handle_links(void)
          422 {
          423         if (pledge("stdio", NULL) == -1)
          424                 exit(1);
          425 
          426         pagetitle = "Links";
          427 
          428         render_links();
          429 }
          430 
          431 int
          432 main(void)
          433 {
          434         char *p, path[256] = "", *querystring;
          435 
          436         setlocale(LC_CTYPE, "");
          437 
          438         if (pledge("stdio dns inet rpath unveil", NULL) == -1 ||
          439             unveil(TLS_CA_CERT_FILE, "r") == -1 ||
          440             unveil(NULL, NULL) == -1) {
          441                 exit(1);
          442         }
          443 
          444         if ((p = getenv("SERVER_NAME")))
          445                 host = p;
          446         if ((p = getenv("SERVER_PORT")))
          447                 port = p;
          448 
          449         if (!(querystring = getenv("QUERY_STRING")))
          450                 querystring = "";
          451 
          452         if ((p = getparam(querystring, "p"))) {
          453                 if (decodeparam(path, sizeof(path), p) == -1)
          454                         path[0] = '\0';
          455         }
          456 
          457         if (!strcmp(path, "") ||
          458             !strcmp(path, "featured") ||
          459             !strcmp(path, "streams")) {
          460                 /* featured / by game id */
          461                 handle_streams();
          462         } else if (!strcmp(path, "topgames") ||
          463                    !strcmp(path, "games")) {
          464                 handle_games_top();
          465         } else if (!strcmp(path, "videos") ||
          466                    !strcmp(path, "vods")) {
          467                 handle_videos();
          468         } else if (!strcmp(path, "links")) {
          469                 handle_links();
          470         } else {
          471                 error("Not Found");
          472                 printf(".\r\n");
          473                 exit(1);
          474         }
          475 
          476         return 0;
          477 }