youtube/feed: add simple HTML and gopher output format - frontends - front-ends for some sites (experiment)
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit d22d896072715e337381959c0cca366b45810cb0
 (DIR) parent 4715c8cfb1799ef4dfe14dea87efc8e4d7c3a60e
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Thu, 18 May 2023 15:28:32 +0200
       
       youtube/feed: add simple HTML and gopher output format
       
       Diffstat:
         M youtube/feed.c                      |     136 ++++++++++++++++++++++++++++++-
         M youtube/gopher.c                    |       2 +-
       
       2 files changed, 136 insertions(+), 2 deletions(-)
       ---
 (DIR) diff --git a/youtube/feed.c b/youtube/feed.c
       @@ -86,6 +86,10 @@ static int  parsetime(const char *, long long *);
        static void atom_header(void);
        static void atom_item(void);
        static void atom_footer(void);
       +static void gph_header(void);
       +static void gph_footer(void);
       +static void html_header(void);
       +static void html_footer(void);
        static void json_header(void);
        static void json_item(void);
        static void json_footer(void);
       @@ -498,6 +502,122 @@ atom_item(void)
                fputs("</entry>\n", stdout);
        }
        
       +
       +static void
       +html_header(void)
       +{
       +        fputs("<!DOCTYPE HTML>\n"
       +        "<html>\n"
       +        "<head>\n"
       +        "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
       +        "</head>\n"
       +        "<body><pre>\n", stdout);
       +}
       +
       +static void
       +html_footer(void)
       +{
       +        fputs("</pre></body>\n</html>\n", stdout);
       +}
       +
       +static void
       +html_item(void)
       +{
       +        struct item *v, *found = NULL;
       +        size_t i;
       +
       +        /* must have a video id */
       +        if (!ctx.fields[FeedFieldYoutubeId].str.len)
       +                return;
       +
       +        for (i = 0; i < search_res->nitems; i++) {
       +                v = &(search_res->items[i]);
       +                if (!strcmp(ctx.fields[FeedFieldYoutubeId].str.data, v->id))
       +                        found = v;
       +        }
       +        /* Only print the video if it was found in the feed aswell.
       +           This way it filters away shorts too. */
       +        if (!found)
       +                return;
       +
       +        /* just print the original timestamp, it should conform */
       +        xmlencode(ctx.fields[FeedFieldTime].str.data);
       +        fputs("&nbsp;", stdout);
       +
       +        if (ctx.fields[FeedFieldLink].str.len) {
       +                fputs("<a href=\"", stdout);
       +                xmlencode(ctx.fields[FeedFieldLink].str.data);
       +                fputs("\">", stdout);
       +        }
       +
       +        xmlencode(ctx.fields[FeedFieldTitle].str.data);
       +
       +        if (found->duration[0]) {
       +                fputs(" [", stdout);
       +                xmlencode(found->duration);
       +                fputs("]", stdout);
       +        }
       +        if (ctx.fields[FeedFieldLink].str.len) {
       +                fputs("</a>", stdout);
       +        }
       +        fputs("\n", stdout);
       +}
       +
       +static void
       +gphencode(const char *s)
       +{
       +        gophertext(stdout, s, strlen(s));
       +}
       +
       +static void
       +gph_header(void)
       +{
       +}
       +
       +static void
       +gph_footer(void)
       +{
       +        fputs(".\r\n", stdout);
       +}
       +
       +static void
       +gph_item(void)
       +{
       +        struct item *v, *found = NULL;
       +        size_t i;
       +
       +        /* must have a video id */
       +        if (!ctx.fields[FeedFieldYoutubeId].str.len)
       +                return;
       +
       +        for (i = 0; i < search_res->nitems; i++) {
       +                v = &(search_res->items[i]);
       +                if (!strcmp(ctx.fields[FeedFieldYoutubeId].str.data, v->id))
       +                        found = v;
       +        }
       +        /* Only print the video if it was found in the feed aswell.
       +           This way it filters away shorts too. */
       +        if (!found)
       +                return;
       +
       +        fputs("h", stdout);
       +        /* just print the original timestamp, it should conform */
       +        gphencode(ctx.fields[FeedFieldTime].str.data);
       +        fputs(" ", stdout);
       +        gphencode(ctx.fields[FeedFieldTitle].str.data);
       +        if (found->duration[0]) {
       +                fputs(" [", stdout);
       +                gphencode(found->duration);
       +                fputs("]", stdout);
       +        }
       +        fputs("\t", stdout);
       +        if (ctx.fields[FeedFieldLink].str.len) {
       +                fputs("URL:", stdout);
       +                gphencode(ctx.fields[FeedFieldLink].str.data);
       +        }
       +        printf("\t%s\t%s\r\n", server_name, server_port);
       +}
       +
        static void
        json_header(void)
        {
       @@ -931,10 +1051,12 @@ usage(void)
                                fputs("Status: 400 Bad Request\r\n", stdout);
                                fputs("Content-Type: text/plain; charset=utf-8\r\n\r\n", stdout);
                                printf("400 %s\n", msg);
       +                        fputs("Supported extensions are: [atom|gph|html|json|tsv|txt]\n", stdout);
                        }
                        exit(0);
                } else {
       -                fputs("usage: feed <channelid> [atom|json|tsv|txt]\n", stderr);
       +                fputs("usage: feed <channelid> [atom|gph|html|json|tsv|txt]\n", stderr);
       +                fputs("For example: feed UCrbvoMC0zUvPL8vjswhLOSw txt\n", stderr);
                        exit(1);
                }
        }
       @@ -987,6 +1109,10 @@ main(int argc, char *argv[])
        
                if (!strcmp(format, "atom") || !strcmp(format, "xml"))
                        printfields = atom_item;
       +        else if (!strcmp(format, "gph"))
       +                printfields = gph_item;
       +        else if (!strcmp(format, "html"))
       +                printfields = html_item;
                else if (!strcmp(format, "json"))
                        printfields = json_item;
                else if (!strcmp(format, "tsv") || !strcmp(format, "sfeed"))
       @@ -1040,6 +1166,10 @@ main(int argc, char *argv[])
        
                if (!strcmp(format, "atom") || !strcmp(format, "xml"))
                        atom_header();
       +        else if (!strcmp(format, "gph"))
       +                gph_header();
       +        else if (!strcmp(format, "html"))
       +                html_header();
                else if (!strcmp(format, "json"))
                        json_header();
        
       @@ -1048,6 +1178,10 @@ main(int argc, char *argv[])
        
                if (!strcmp(format, "atom") || !strcmp(format, "xml"))
                        atom_footer();
       +        else if (!strcmp(format, "gph"))
       +                gph_footer();
       +        else if (!strcmp(format, "html"))
       +                html_footer();
                else if (!strcmp(format, "json"))
                        json_footer();
        
 (DIR) diff --git a/youtube/gopher.c b/youtube/gopher.c
       @@ -50,7 +50,7 @@ header(void)
        void
        footer(void)
        {
       -        printf(".\r\n");
       +        fputs(".\r\n", stdout);
        }
        
        int