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