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);