reddit: UI improvements - frontends - front-ends for some sites (experiment)
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 6aed9fd9cc023a1400b373a6d89a347b7506abe3
 (DIR) parent e7026d3b1b221247c300a78b1ee199729110c9ba
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Sat, 11 Jan 2020 18:50:36 +0100
       
       reddit: UI improvements
       
       - add pagination.
       - fix pagination JSON parsing.
       - fix parsing all items (it skipped the last one).
       - get name/id of post.
       - parse and validate links encoded as base36.
       - increase posts per page from 25 to 100.
       - use raw_json parameter for proper JSON.
       - show subreddits when on main reddit.
       - allow to view the main reddit.
       
       Diffstat:
         M reddit/cli.c                        |       7 ++++---
         M reddit/gopher.c                     |     111 ++++++++++++++++++++++---------
         M reddit/reddit.c                     |      66 +++++++++++++++++++++++--------
         M reddit/reddit.h                     |       6 +++++-
       
       4 files changed, 139 insertions(+), 51 deletions(-)
       ---
 (DIR) diff --git a/reddit/cli.c b/reddit/cli.c
       @@ -11,8 +11,9 @@
        #include <time.h>
        #include <unistd.h>
        
       -#include "util.h"
       +#include "https.h"
        #include "reddit.h"
       +#include "util.h"
        
        #define OUT(s) (fputs((s), stdout))
        #define OUTESCAPE(s) (printescape(s))
       @@ -90,7 +91,7 @@ main(int argc, char *argv[])
                struct list_response *r;
                char subreddit[1024] = "";
        
       -#if 0
       +#if 1
                if (pledge("stdio dns inet rpath unveil", NULL) == -1) {
                        fprintf(stderr, "pledge: %s\n", strerror(errno));
                        exit(1);
       @@ -110,7 +111,7 @@ main(int argc, char *argv[])
                                usage(argv[0]);
                }
        
       -        r = reddit_list(subreddit, ""); /* TODO: pagination */
       +        r = reddit_list(subreddit, 100, "", ""); /* TODO: pagination */
                if (!r || r->nitems == 0) {
                        OUT("No items found\n");
                        exit(1);
 (DIR) diff --git a/reddit/gopher.c b/reddit/gopher.c
       @@ -11,8 +11,9 @@
        #include <time.h>
        #include <unistd.h>
        
       -#include "util.h"
       +#include "https.h"
        #include "reddit.h"
       +#include "util.h"
        
        #define OUT(s) (fputs((s), stdout))
        #define OUTLINK(s) gophertext(stdout, s, strlen(s))
       @@ -21,6 +22,8 @@
        static const char *baserel = "/reddit.cgi";
        static const char *host = "127.0.0.1", *port = "70";
        
       +static char before[16], after[16], subreddit[1024];
       +
        void
        line(int _type, const char *username, const char *selector)
        {
       @@ -79,24 +82,34 @@ printitem(struct item *item)
                OUTLINK(item->url);
                printf("\t%s\t%s\r\n", host, port);
        
       -#if 0
       -        printf("1Posted in r/");
       -        OUTTEXT(item->subreddit);
       -        OUT("\t");
       -        OUTTEXT(baserel);
       -        OUT("?subreddit=");
       -        OUTTEXT(item->subreddit);
       -        printf("\t%s\t%s\r\n", host, port);
       -#endif
       -
       -//        printf("thumbnail:       %s\n", item->thumbnail);
       -        printf("iUpvotes: %ld, downvotes %ld, comments: %ld\t\t%s\t%s\r\n",
       -                item->ups, item->downs, item->num_comments, host, port);
       +        /* if global (no subreddit), show where it was posted */
       +        if (!subreddit[0]) {
       +                printf("1Posted in r/");
       +                OUTTEXT(item->subreddit);
       +                OUT("\t");
       +                OUTTEXT(baserel);
       +                OUT("?subreddit=");
       +                OUTTEXT(item->subreddit);
       +                printf("\t%s\t%s\r\n", host, port);
       +        }
        
       -        if (item->thumbnail[0]) {
       +        printf("hUpvotes: %ld, downvotes %ld, comments: %ld",
       +               item->ups, item->downs, item->num_comments);
       +        OUT("\tURL:https://old.reddit.com/r/");
       +        OUTTEXT(item->subreddit);
       +        OUTTEXT("/comments/");
       +        if (reddit_isvalidlink(item->name) && !strncmp(item->name, "t3_", 3))
       +                OUTTEXT(item->name + 3);
       +        printf("/\t%s\t%s\r\n", host, port);
       +
       +        /* TODO: also noticed "spoiler" as value, ignore? */
       +        if (item->thumbnail[0] &&
       +            strcmp(item->thumbnail, "self") &&
       +            strcmp(item->thumbnail, "default")) {
                        putchar('h');
       -                OUT("Thumbnail: ");
       -                OUTTEXT(item->thumbnail);
       +                OUT("Thumbnail");
       +//                OUT("Thumbnail: ");
       +//                OUTTEXT(item->thumbnail);
                        OUT("\tURL:");
                        OUTTEXT(item->thumbnail);
                        printf("\t%s\t%s\r\n", host, port);
       @@ -116,6 +129,36 @@ printitem(struct item *item)
        }
        
        int
       +render_pagination(struct list_response *r)
       +{
       +        int i = 0;
       +
       +        if (r->before[0]) {
       +                printf("1Previous page");
       +                OUT("\t");
       +                OUTTEXT(baserel);
       +                OUT("?subreddit=");
       +                OUTTEXT(subreddit);
       +                OUT("&before=");
       +                OUTTEXT(r->before);
       +                printf("\t%s\t%s\r\n", host, port);
       +                i++;
       +        }
       +        if (r->after[0]) {
       +                printf("1Next page");
       +                OUT("\t");
       +                OUTTEXT(baserel);
       +                OUT("?subreddit=");
       +                OUTTEXT(subreddit);
       +                OUT("&after=");
       +                OUTTEXT(r->after);
       +                printf("\t%s\t%s\r\n", host, port);
       +                i++;
       +        }
       +        return i;
       +}
       +
       +int
        render(struct list_response *r)
        {
                size_t i;
       @@ -127,15 +170,13 @@ render(struct list_response *r)
                }
        #endif
        
       +        if (render_pagination(r))
       +                info("");
       +
                for (i = 0; i < r->nitems; i++)
                        printitem(&(r->items[i]));
        
       -#if 0
       -        if (r->before[0])
       -                printf("before pagination token: %s\n", r->before);
       -        if (r->after[0])
       -                printf("after pagination token: %s\n", r->after);
       -#endif
       +        render_pagination(r);
        
                return 0;
        }
       @@ -152,10 +193,9 @@ int
        main(int argc, char *argv[])
        {
                struct list_response *r;
       -        char subreddit[1024] = "";
                char *querystring, *p, *search;
        
       -#if 0
       +#if 1
                if (pledge("stdio dns inet rpath unveil", NULL) == -1) {
                        fprintf(stderr, "pledge: %s\n", strerror(errno));
                        exit(1);
       @@ -176,18 +216,27 @@ main(int argc, char *argv[])
                if ((p = getparam(querystring, "subreddit"))) {
                        if (decodeparam(subreddit, sizeof(subreddit), p) == -1)
                                subreddit[0] = '\0';
       -        }
       -
       -        if (!subreddit[0]) {
       +        } else {
                        search = getenv("X_GOPHER_SEARCH");
                        if (search && !uriencode(search, subreddit, sizeof(subreddit)))
                                usage();
                }
        
       -        if (!subreddit[0])
       -                usage();
       +        if ((p = getparam(querystring, "before"))) {
       +                if (decodeparam(before, sizeof(before), p) == -1)
       +                        before[0] = '\0';
       +                if (!reddit_isvalidlink(before))
       +                        before[0] = '\0';
       +        }
       +
       +        if ((p = getparam(querystring, "after"))) {
       +                if (decodeparam(after, sizeof(after), p) == -1)
       +                        after[0] = '\0';
       +                if (!reddit_isvalidlink(after))
       +                        after[0] = '\0';
       +        }
        
       -        r = reddit_list(subreddit, ""); /* TODO: pagination */
       +        r = reddit_list(subreddit, 100, before, after);
                if (!r || r->nitems == 0) {
                        printf("iNo items found\t\t%s\t%s\r\n", host, port);
                        exit(1);
 (DIR) diff --git a/reddit/reddit.c b/reddit/reddit.c
       @@ -55,28 +55,37 @@ json_unmarshal(const char *data,
        }
        
        char *
       -reddit_list_data(const char *subreddit, const char *page)
       +reddit_list_data(const char *subreddit, int limit,
       +        const char *before, const char *after)
        {
                char path[4096];
                int r;
        
       +        if (limit <= 0)
       +                limit = 25;
       +
                if (subreddit[0])
       -                r = snprintf(path, sizeof(path), "/r/%s/.json", subreddit);
       +                r = snprintf(path, sizeof(path), "/r/%s/.json?raw_json=1&limit=%d",
       +                             subreddit, limit);
                else
       -                r = snprintf(path, sizeof(path), "/.json");
       +                r = snprintf(path, sizeof(path), "/.json?raw_json=1&limit=%d",
       +                             limit);
        
       +        if (before[0]) {
       +                strlcat(path, "&before=", sizeof(path));
       +                strlcat(path, before, sizeof(path));
       +        } else if (after[0]) {
       +                strlcat(path, "&after=", sizeof(path));
       +                strlcat(path, after, sizeof(path));
       +        }
        
        #ifdef DEBUG_MODE
       +        printf("DEBUG: %s: path: %s\n", __func__, path);
       +
        //        data = readfile("test.json");
        //        data = readfile("poe.json");
                return readfile("nl.json");
        #endif
       -/*
       -        TODO
       -        if (page[0]) {
       -                strlcat(path, "?page=", sizeof(path));
       -                strlcat(path, page, sizeof(path));
       -        }*/
        
                if (r < 0 || (size_t)r >= sizeof(path))
                        return NULL;
       @@ -106,10 +115,10 @@ reddit_list_processnode(struct json_node *nodes, size_t depth, const char *value
                        }
                }
        
       -        if (r->nitems >= MAX_ITEMS)
       +        if (r->nitems > MAX_ITEMS)
                        return;
       -        item = &(r->items[r->nitems]);
        
       +        /* new item */
                if (depth == 5 &&
                    nodes[0].type == TYPE_OBJECT &&
                    nodes[1].type == TYPE_OBJECT &&
       @@ -121,10 +130,14 @@ reddit_list_processnode(struct json_node *nodes, size_t depth, const char *value
                    !strcmp(nodes[2].name, "children") &&
                    !strcmp(nodes[3].name, "") &&
                    !strcmp(nodes[4].name, "data")) {
       -                if (r->nitems < MAX_ITEMS && r->items[r->nitems].title[0])
       -                        r->nitems++;
       +                r->nitems++;
       +                return;
                }
        
       +        if (r->nitems == 0)
       +                return;
       +        item = &(r->items[r->nitems - 1]);
       +
                if (depth >= 5 &&
                    nodes[0].type == TYPE_OBJECT &&
                    nodes[1].type == TYPE_OBJECT &&
       @@ -158,7 +171,9 @@ reddit_list_processnode(struct json_node *nodes, size_t depth, const char *value
                                        }
                                        break;
                                case TYPE_STRING:
       -                                if (!strcmp(node->name, "title"))
       +                                if (!strcmp(node->name, "name"))
       +                                        strlcpy(item->name, value, sizeof(item->name));
       +                                else if (!strcmp(node->name, "title"))
                                                strlcpy(item->title, value, sizeof(item->title));
                                        else if (!strcmp(node->name, "url"))
                                                strlcpy(item->url, value, sizeof(item->url));
       @@ -208,13 +223,14 @@ reddit_list_processnode(struct json_node *nodes, size_t depth, const char *value
        }
        
        struct list_response *
       -reddit_list(const char *subreddit, const char *page)
       +reddit_list(const char *subreddit, int limit,
       +        const char *before, const char *after)
        {
                struct list_response *r;
                const char *errstr;
                char *data, *s;
        
       -        if (!(data = reddit_list_data(subreddit, page))) {
       +        if (!(data = reddit_list_data(subreddit, limit, before, after))) {
                        fprintf(stderr, "%s\n", __func__);
                        return NULL;
                }
       @@ -232,3 +248,21 @@ reddit_list(const char *subreddit, const char *page)
        
                return r;
        }
       +
       +int
       +reddit_isvalidlink(const char *s)
       +{
       +        char *end = NULL;
       +        unsigned long long l;
       +        size_t i, len;
       +
       +        /* type prefix: reddit link is "t3_" */
       +        if (strncmp(s, "t3_", 3))
       +                return 0;
       +        s += 3;
       +
       +        /* link is base36 */
       +        errno = 0;
       +        l = strtoull(s, &end, 36);
       +        return (!errno && s != end && !*end && l > 0);
       +}
 (DIR) diff --git a/reddit/reddit.h b/reddit/reddit.h
       @@ -1,4 +1,5 @@
        struct item {
       +        char name[16];
                char title[1024];
                char url[4096];
                char permalink[4096];
       @@ -29,4 +30,7 @@ struct list_response {
                char after[256];
        };
        
       -struct list_response *reddit_list(const char *subreddit, const char *page);
       +struct list_response *reddit_list(const char *subreddit, int limit,
       +        const char *before, const char *after);
       +
       +int reddit_isvalidlink(const char *s);