├── Makefile ├── README.md ├── build └── terminal_devrant ├── code ├── entities.c ├── entities.h ├── linux_terminal_rant.cpp └── linux_terminal_rant.h ├── img1.png └── img2.png /Makefile: -------------------------------------------------------------------------------- 1 | 2 | FILES = code/linux_terminal_rant.cpp 3 | 4 | OUTPUT = build/terminal_devrant 5 | 6 | CC = clang 7 | 8 | COMPILE_FLAGS = -ggdb -W -Wno-unused-parameter `curl-config --cflags` `pkg-config gumbo --cflags` `ncurses5-config --cflags` 9 | LINKING_FLAGS = `curl-config --libs` `pkg-config gumbo --libs` `ncurses5-config --libs` 10 | 11 | all: $(FILES) 12 | $(CC) $(FILES) $(COMPILE_FLAGS) $(LINKING_FLAGS) -o $(OUTPUT) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # terminal_devrant 2 | A devRant viewer in the terminal. 3 | 4 | ## Dependencies 5 | - [libcURL](https://curl.haxx.se/libcurl/) 6 | - [gumbo parser](https://github.com/google/gumbo-parser) 7 | - [ncurses](https://www.gnu.org/software/ncurses/ncurses.html) 8 | - [clang (for building)](http://clang.llvm.org/) 9 | 10 | ## How to build 11 | run the Makefile 12 | 13 | ## Images 14 | ![img1](https://github.com/Supernerd11/terminal_devrant/blob/Alpha/img1.png) 15 | ![img2](https://github.com/Supernerd11/terminal_devrant/blob/Alpha/img2.png) 16 | 17 | -------------------------------------------------------------------------------- /build/terminal_devrant: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Invisible-Rabbit-Hunter/terminal_devrant/e2c2f2d18b537f69b28cb9dd14522214844fc995/build/terminal_devrant -------------------------------------------------------------------------------- /code/entities.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2012 Christoph Gärtner 2 | Distributed under the Boost Software License, Version 1.0 3 | */ 4 | 5 | #include "entities.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define UNICODE_MAX 0x10FFFFul 13 | 14 | static const char *const NAMED_ENTITIES[][2] = { 15 | { "AElig;", "Æ" }, 16 | { "Aacute;", "Á" }, 17 | { "Acirc;", "Â" }, 18 | { "Agrave;", "À" }, 19 | { "Alpha;", "Α" }, 20 | { "Aring;", "Ã…" }, 21 | { "Atilde;", "Ã" }, 22 | { "Auml;", "Ä" }, 23 | { "Beta;", "Î’" }, 24 | { "Ccedil;", "Ç" }, 25 | { "Chi;", "Χ" }, 26 | { "Dagger;", "‡" }, 27 | { "Delta;", "Δ" }, 28 | { "ETH;", "Ð" }, 29 | { "Eacute;", "É" }, 30 | { "Ecirc;", "Ê" }, 31 | { "Egrave;", "È" }, 32 | { "Epsilon;", "Ε" }, 33 | { "Eta;", "Η" }, 34 | { "Euml;", "Ë" }, 35 | { "Gamma;", "Γ" }, 36 | { "Iacute;", "Í" }, 37 | { "Icirc;", "ÃŽ" }, 38 | { "Igrave;", "ÃŒ" }, 39 | { "Iota;", "Ι" }, 40 | { "Iuml;", "Ï" }, 41 | { "Kappa;", "Κ" }, 42 | { "Lambda;", "Λ" }, 43 | { "Mu;", "Μ" }, 44 | { "Ntilde;", "Ñ" }, 45 | { "Nu;", "Ν" }, 46 | { "OElig;", "Å’" }, 47 | { "Oacute;", "Ó" }, 48 | { "Ocirc;", "Ô" }, 49 | { "Ograve;", "Ã’" }, 50 | { "Omega;", "Ω" }, 51 | { "Omicron;", "Ο" }, 52 | { "Oslash;", "Ø" }, 53 | { "Otilde;", "Õ" }, 54 | { "Ouml;", "Ö" }, 55 | { "Phi;", "Φ" }, 56 | { "Pi;", "Î " }, 57 | { "Prime;", "″" }, 58 | { "Psi;", "Ψ" }, 59 | { "Rho;", "Ρ" }, 60 | { "Scaron;", "Å " }, 61 | { "Sigma;", "Σ" }, 62 | { "THORN;", "Þ" }, 63 | { "Tau;", "Τ" }, 64 | { "Theta;", "Θ" }, 65 | { "Uacute;", "Ú" }, 66 | { "Ucirc;", "Û" }, 67 | { "Ugrave;", "Ù" }, 68 | { "Upsilon;", "Î¥" }, 69 | { "Uuml;", "Ü" }, 70 | { "Xi;", "Ξ" }, 71 | { "Yacute;", "Ý" }, 72 | { "Yuml;", "Ÿ" }, 73 | { "Zeta;", "Ζ" }, 74 | { "aacute;", "á" }, 75 | { "acirc;", "â" }, 76 | { "acute;", "´" }, 77 | { "aelig;", "æ" }, 78 | { "agrave;", "à " }, 79 | { "alefsym;", "ℵ" }, 80 | { "alpha;", "α" }, 81 | { "amp;", "&" }, 82 | { "and;", "∧" }, 83 | { "ang;", "∠" }, 84 | { "apos;", "'" }, 85 | { "aring;", "Ã¥" }, 86 | { "asymp;", "≈" }, 87 | { "atilde;", "ã" }, 88 | { "auml;", "ä" }, 89 | { "bdquo;", "„" }, 90 | { "beta;", "β" }, 91 | { "brvbar;", "¦" }, 92 | { "bull;", "•" }, 93 | { "cap;", "∩" }, 94 | { "ccedil;", "ç" }, 95 | { "cedil;", "¸" }, 96 | { "cent;", "¢" }, 97 | { "chi;", "χ" }, 98 | { "circ;", "ˆ" }, 99 | { "clubs;", "♣" }, 100 | { "cong;", "≅" }, 101 | { "copy;", "©" }, 102 | { "crarr;", "↵" }, 103 | { "cup;", "∪" }, 104 | { "curren;", "¤" }, 105 | { "dArr;", "⇓" }, 106 | { "dagger;", "†" }, 107 | { "darr;", "↓" }, 108 | { "deg;", "°" }, 109 | { "delta;", "δ" }, 110 | { "diams;", "♦" }, 111 | { "divide;", "÷" }, 112 | { "eacute;", "é" }, 113 | { "ecirc;", "ê" }, 114 | { "egrave;", "è" }, 115 | { "empty;", "∅" }, 116 | { "emsp;", " " }, 117 | { "ensp;", " " }, 118 | { "epsilon;", "ε" }, 119 | { "equiv;", "≡" }, 120 | { "eta;", "η" }, 121 | { "eth;", "ð" }, 122 | { "euml;", "ë" }, 123 | { "euro;", "€" }, 124 | { "exist;", "∃" }, 125 | { "fnof;", "Æ’" }, 126 | { "forall;", "∀" }, 127 | { "frac12;", "½" }, 128 | { "frac14;", "¼" }, 129 | { "frac34;", "¾" }, 130 | { "frasl;", "⁄" }, 131 | { "gamma;", "γ" }, 132 | { "ge;", "≥" }, 133 | { "gt;", ">" }, 134 | { "hArr;", "⇔" }, 135 | { "harr;", "↔" }, 136 | { "hearts;", "♥" }, 137 | { "hellip;", "…" }, 138 | { "iacute;", "í" }, 139 | { "icirc;", "î" }, 140 | { "iexcl;", "¡" }, 141 | { "igrave;", "ì" }, 142 | { "image;", "â„‘" }, 143 | { "infin;", "∞" }, 144 | { "int;", "∫" }, 145 | { "iota;", "ι" }, 146 | { "iquest;", "¿" }, 147 | { "isin;", "∈" }, 148 | { "iuml;", "ï" }, 149 | { "kappa;", "κ" }, 150 | { "lArr;", "⇐" }, 151 | { "lambda;", "λ" }, 152 | { "lang;", "〈" }, 153 | { "laquo;", "«" }, 154 | { "larr;", "←" }, 155 | { "lceil;", "⌈" }, 156 | { "ldquo;", "“" }, 157 | { "le;", "≤" }, 158 | { "lfloor;", "⌊" }, 159 | { "lowast;", "∗" }, 160 | { "loz;", "â—Š" }, 161 | { "lrm;", "\xE2\x80\x8E" }, 162 | { "lsaquo;", "‹" }, 163 | { "lsquo;", "‘" }, 164 | { "lt;", "<" }, 165 | { "macr;", "¯" }, 166 | { "mdash;", "—" }, 167 | { "micro;", "µ" }, 168 | { "middot;", "·" }, 169 | { "minus;", "−" }, 170 | { "mu;", "μ" }, 171 | { "nabla;", "∇" }, 172 | { "nbsp;", " " }, 173 | { "ndash;", "–" }, 174 | { "ne;", "≠" }, 175 | { "ni;", "∋" }, 176 | { "not;", "¬" }, 177 | { "notin;", "∉" }, 178 | { "nsub;", "⊄" }, 179 | { "ntilde;", "ñ" }, 180 | { "nu;", "ν" }, 181 | { "oacute;", "ó" }, 182 | { "ocirc;", "ô" }, 183 | { "oelig;", "Å“" }, 184 | { "ograve;", "ò" }, 185 | { "oline;", "‾" }, 186 | { "omega;", "ω" }, 187 | { "omicron;", "ο" }, 188 | { "oplus;", "⊕" }, 189 | { "or;", "∨" }, 190 | { "ordf;", "ª" }, 191 | { "ordm;", "º" }, 192 | { "oslash;", "ø" }, 193 | { "otilde;", "õ" }, 194 | { "otimes;", "⊗" }, 195 | { "ouml;", "ö" }, 196 | { "para;", "¶" }, 197 | { "part;", "∂" }, 198 | { "permil;", "‰" }, 199 | { "perp;", "⊥" }, 200 | { "phi;", "φ" }, 201 | { "pi;", "Ï€" }, 202 | { "piv;", "Ï–" }, 203 | { "plusmn;", "±" }, 204 | { "pound;", "£" }, 205 | { "prime;", "′" }, 206 | { "prod;", "∏" }, 207 | { "prop;", "∝" }, 208 | { "psi;", "ψ" }, 209 | { "quot;", "\"" }, 210 | { "rArr;", "⇒" }, 211 | { "radic;", "√" }, 212 | { "rang;", "〉" }, 213 | { "raquo;", "»" }, 214 | { "rarr;", "→" }, 215 | { "rceil;", "⌉" }, 216 | { "rdquo;", "”" }, 217 | { "real;", "ℜ" }, 218 | { "reg;", "®" }, 219 | { "rfloor;", "⌋" }, 220 | { "rho;", "ρ" }, 221 | { "rlm;", "\xE2\x80\x8F" }, 222 | { "rsaquo;", "›" }, 223 | { "rsquo;", "’" }, 224 | { "sbquo;", "‚" }, 225 | { "scaron;", "Å¡" }, 226 | { "sdot;", "â‹…" }, 227 | { "sect;", "§" }, 228 | { "shy;", "\xC2\xAD" }, 229 | { "sigma;", "σ" }, 230 | { "sigmaf;", "Ï‚" }, 231 | { "sim;", "∼" }, 232 | { "spades;", "â™ " }, 233 | { "sub;", "⊂" }, 234 | { "sube;", "⊆" }, 235 | { "sum;", "∑" }, 236 | { "sup1;", "¹" }, 237 | { "sup2;", "²" }, 238 | { "sup3;", "³" }, 239 | { "sup;", "⊃" }, 240 | { "supe;", "⊇" }, 241 | { "szlig;", "ß" }, 242 | { "tau;", "Ï„" }, 243 | { "there4;", "∴" }, 244 | { "theta;", "θ" }, 245 | { "thetasym;", "Ï‘" }, 246 | { "thinsp;", " " }, 247 | { "thorn;", "þ" }, 248 | { "tilde;", "Ëœ" }, 249 | { "times;", "×" }, 250 | { "trade;", "â„¢" }, 251 | { "uArr;", "⇑" }, 252 | { "uacute;", "ú" }, 253 | { "uarr;", "↑" }, 254 | { "ucirc;", "û" }, 255 | { "ugrave;", "ù" }, 256 | { "uml;", "¨" }, 257 | { "upsih;", "Ï’" }, 258 | { "upsilon;", "Ï…" }, 259 | { "uuml;", "ü" }, 260 | { "weierp;", "℘" }, 261 | { "xi;", "ξ" }, 262 | { "yacute;", "ý" }, 263 | { "yen;", "Â¥" }, 264 | { "yuml;", "ÿ" }, 265 | { "zeta;", "ζ" }, 266 | { "zwj;", "\xE2\x80\x8D" }, 267 | { "zwnj;", "\xE2\x80\x8C" } 268 | }; 269 | 270 | static int cmp(const void *key, const void *value) 271 | { 272 | return strncmp((const char *)key, *(const char **)value, 273 | strlen(*(const char **)value)); 274 | } 275 | 276 | static const char *get_named_entity(const char *name) 277 | { 278 | const char *const *entity = (const char *const *)bsearch(name, 279 | NAMED_ENTITIES, sizeof NAMED_ENTITIES / sizeof *NAMED_ENTITIES, 280 | sizeof *NAMED_ENTITIES, cmp); 281 | 282 | return entity ? entity[1] : NULL; 283 | } 284 | 285 | static size_t putc_utf8(unsigned long cp, char *buffer) 286 | { 287 | unsigned char *bytes = (unsigned char *)buffer; 288 | 289 | if(cp <= 0x007Ful) 290 | { 291 | bytes[0] = (unsigned char)cp; 292 | return 1; 293 | } 294 | 295 | if(cp <= 0x07FFul) 296 | { 297 | bytes[1] = (unsigned char)((2 << 6) | (cp & 0x3F)); 298 | bytes[0] = (unsigned char)((6 << 5) | (cp >> 6)); 299 | return 2; 300 | } 301 | 302 | if(cp <= 0xFFFFul) 303 | { 304 | bytes[2] = (unsigned char)(( 2 << 6) | ( cp & 0x3F)); 305 | bytes[1] = (unsigned char)(( 2 << 6) | ((cp >> 6) & 0x3F)); 306 | bytes[0] = (unsigned char)((14 << 4) | (cp >> 12)); 307 | return 3; 308 | } 309 | 310 | if(cp <= 0x10FFFFul) 311 | { 312 | bytes[3] = (unsigned char)(( 2 << 6) | ( cp & 0x3F)); 313 | bytes[2] = (unsigned char)(( 2 << 6) | ((cp >> 6) & 0x3F)); 314 | bytes[1] = (unsigned char)(( 2 << 6) | ((cp >> 12) & 0x3F)); 315 | bytes[0] = (unsigned char)((30 << 3) | (cp >> 18)); 316 | return 4; 317 | } 318 | 319 | return 0; 320 | } 321 | 322 | static bool parse_entity( 323 | const char *current, char **to, const char **from) 324 | { 325 | const char *end = strchr(current, ';'); 326 | if(!end) return 0; 327 | 328 | if(current[1] == '#') 329 | { 330 | char *tail = NULL; 331 | int errno_save = errno; 332 | bool hex = current[2] == 'x' || current[2] == 'X'; 333 | 334 | errno = 0; 335 | unsigned long cp = strtoul( 336 | current + (hex ? 3 : 2), &tail, hex ? 16 : 10); 337 | 338 | bool fail = errno || tail != end || cp > UNICODE_MAX; 339 | errno = errno_save; 340 | if(fail) return 0; 341 | 342 | *to += putc_utf8(cp, *to); 343 | *from = end + 1; 344 | 345 | return 1; 346 | } 347 | else 348 | { 349 | const char *entity = get_named_entity(¤t[1]); 350 | if(!entity) return 0; 351 | 352 | size_t len = strlen(entity); 353 | memcpy(*to, entity, len); 354 | 355 | *to += len; 356 | *from = end + 1; 357 | 358 | return 1; 359 | } 360 | } 361 | 362 | size_t decode_html_entities_utf8(char *dest, const char *src) 363 | { 364 | if(!src) src = dest; 365 | 366 | char *to = dest; 367 | const char *from = src; 368 | 369 | for(const char *current; (current = strchr(from, '&'));) 370 | { 371 | memmove(to, from, (size_t)(current - from)); 372 | to += current - from; 373 | 374 | if(parse_entity(current, &to, &from)) 375 | continue; 376 | 377 | from = current; 378 | *to++ = *from++; 379 | } 380 | 381 | size_t remaining = strlen(from); 382 | 383 | memmove(to, from, remaining); 384 | to += remaining; 385 | *to = 0; 386 | 387 | return (size_t)(to - dest); 388 | } 389 | -------------------------------------------------------------------------------- /code/entities.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2012 Christoph Gärtner 2 | Distributed under the Boost Software License, Version 1.0 3 | */ 4 | 5 | #ifndef DECODE_HTML_ENTITIES_UTF8_ 6 | #define DECODE_HTML_ENTITIES_UTF8_ 7 | 8 | #include 9 | 10 | extern size_t decode_html_entities_utf8(char *dest, const char *src); 11 | /* Takes input from and decodes into , which should be a buffer 12 | large enough to hold characters. 13 | 14 | If is , input will be taken from , decoding 15 | the entities in-place. 16 | 17 | The function returns the length of the decoded string. 18 | */ 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /code/linux_terminal_rant.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "linux_terminal_rant.h" 9 | #include "entities.c" 10 | 11 | #define ArrayCount(Array) (sizeof(Array)/sizeof(Array[0])) 12 | 13 | void init_string(struct string *str) { 14 | str->length = 0; 15 | str->pointer = (char *)malloc(str->length + 1); 16 | if (str->pointer == NULL) { 17 | fprintf(stderr, "malloc() failed\n"); 18 | exit(EXIT_FAILURE); 19 | } 20 | str->pointer[0] = '\0'; 21 | } 22 | 23 | void append_string(struct string *str, char *Append) 24 | { 25 | size_t new_length = str->length + ArrayCount(Append); 26 | str->pointer = (char *)realloc(str->pointer, new_length+1); 27 | if (str->pointer == NULL) { 28 | fprintf(stderr, "realloc() failed\n"); 29 | exit(EXIT_FAILURE); 30 | } 31 | memcpy(str->pointer+str->length, Append, ArrayCount(Append)); 32 | str->pointer[new_length] = '\0'; 33 | str->length = new_length; 34 | 35 | } 36 | 37 | size_t writefunc(void *pointer, size_t size, size_t nmemb, struct string *str) 38 | { 39 | size_t new_length = str->length + size*nmemb; 40 | str->pointer = (char *)realloc(str->pointer, new_length+1); 41 | if (str->pointer == NULL) { 42 | fprintf(stderr, "realloc() failed\n"); 43 | exit(EXIT_FAILURE); 44 | } 45 | memcpy(str->pointer+str->length, pointer, size*nmemb); 46 | str->pointer[new_length] = '\0'; 47 | str->length = new_length; 48 | 49 | return size*nmemb; 50 | } 51 | 52 | uint 53 | SearchForClass(GumboNode *Node, const char *ClassName, uint ResultCount, GumboNode **ResultArray) 54 | { 55 | if(Node->type == GUMBO_NODE_ELEMENT) 56 | { 57 | GumboAttribute *ClassAttribute; 58 | if((ClassAttribute = gumbo_get_attribute(&Node->v.element.attributes, "class")) && 59 | (strcmp(ClassAttribute->value, ClassName) == 0)) 60 | { 61 | ResultArray[ResultCount++] = Node; 62 | } 63 | 64 | GumboVector* Children = &Node->v.element.children; 65 | for(uint ChildIndex = 0; 66 | ChildIndex < Children->length; 67 | ++ChildIndex) 68 | { 69 | ResultCount = SearchForClass((GumboNode *)(Children->data[ChildIndex]), ClassName, ResultCount, ResultArray); 70 | } 71 | } 72 | 73 | return ResultCount; 74 | } 75 | 76 | void removeSubstring(char *String, const char *ToRemove) 77 | { 78 | while((String = strstr(String, ToRemove))) 79 | { 80 | memmove(String, String + strlen(ToRemove), 1 + strlen(String + strlen(ToRemove))); 81 | } 82 | } 83 | 84 | void replaceSubstring(char *String, const char *ToRemove, const char *ToReplace) 85 | { 86 | while((String = strstr(String, ToRemove))) 87 | { 88 | memmove(String + strlen(ToReplace), String + strlen(ToRemove), 1 + strlen(String + strlen(ToRemove))); 89 | memcpy(String, ToReplace, strlen(ToReplace)); 90 | } 91 | } 92 | 93 | void insertString(char *String, char *ToInsert, int Offset) 94 | { 95 | memmove(String + Offset, String + Offset + strlen(ToInsert), strlen(String + Offset + strlen(ToInsert))); 96 | } 97 | 98 | char * 99 | GetNodeText(GumboNode *Node) 100 | { 101 | uint StartPosition = Node->v.element.start_pos.offset + Node->v.element.original_tag.length; 102 | uint EndPosition = Node->v.element.end_pos.offset; 103 | uint StringLength = EndPosition - StartPosition; 104 | char *Result = (char *)Node->v.element.original_tag.data + Node->v.element.original_tag.length; 105 | *(Result + StringLength) = '\0'; 106 | 107 | decode_html_entities_utf8(Result, 0); 108 | removeSubstring(Result, "
"); 109 | replaceSubstring(Result, "\n\t \t", "("); 110 | replaceSubstring(Result, "", ")"); 111 | 112 | return(Result); 113 | } 114 | 115 | void 116 | LoadRant(CURL *curl, Rant *Rant) 117 | { 118 | CURLcode res; 119 | 120 | char *URL = Rant->URL; 121 | 122 | struct string str; 123 | 124 | init_string(&str); 125 | 126 | curl_easy_setopt(curl, CURLOPT_URL, URL); 127 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); 128 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str); 129 | res = curl_easy_perform(curl); 130 | 131 | GumboOutput *Output = gumbo_parse(str.pointer); 132 | 133 | GumboNode *RantArr[1024] = {}; 134 | GumboNode *UserList[20] = {}; 135 | uint RantCount; 136 | 137 | RantCount = SearchForClass(Output->root, "rantlist-title", 0, RantArr); 138 | SearchForClass(Output->root, "username-details", 0, UserList); 139 | 140 | 141 | Rant->Content.Text = GetNodeText(RantArr[0]); 142 | Rant->Content.User = GetNodeText(UserList[0]); 143 | for(uint RantArrIndex = 1; 144 | RantArrIndex < RantCount; 145 | ++RantArrIndex) 146 | { 147 | GumboNode *RantNode = RantArr[RantArrIndex]; 148 | 149 | Rant->Comments[RantArrIndex - 1].Text = GetNodeText(RantNode); 150 | Rant->Comments[RantArrIndex - 1].User = GetNodeText(UserList[RantArrIndex]); 151 | 152 | ++Rant->CommentCount; 153 | }; 154 | 155 | Rant->Loaded = true; 156 | } 157 | 158 | uint 159 | GatherRants(CURL *curl, uint RantsCount, Rant Rants[], char Feed[], uint PageIndex) 160 | { 161 | CURLcode res; 162 | 163 | uint RantCount = 0; 164 | 165 | struct string str; 166 | 167 | init_string(&str); 168 | 169 | char URL[50]; 170 | 171 | sprintf(URL, "https://www.devrant.io/feed/%s/%d", Feed, PageIndex); 172 | 173 | curl_easy_setopt(curl, CURLOPT_URL, URL); 174 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc); 175 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &str); 176 | res = curl_easy_perform(curl); 177 | 178 | 179 | GumboOutput *Output = gumbo_parse(str.pointer); 180 | 181 | GumboNode *RantList[20] = {}; 182 | RantCount = SearchForClass(Output->root, "rantlist-title", 0, RantList); 183 | if(RantCount) 184 | { 185 | for(uint RantIndex = 0; 186 | RantIndex < RantCount; 187 | ++RantIndex) 188 | { 189 | GumboNode *Rant = RantList[RantIndex]; 190 | 191 | Rants[RantsCount].Loaded = false; 192 | 193 | sprintf(Rants[RantsCount].URL, "https://www.devrant.io%s", gumbo_get_attribute(&Rant->v.element.attributes, "href")->value); 194 | 195 | ++RantsCount; 196 | } 197 | } 198 | 199 | gumbo_destroy_output(&kGumboDefaultOptions, Output); 200 | free(str.pointer); 201 | 202 | return RantCount; 203 | } 204 | 205 | // wrap: take a long input line and wrap it into multiple lines 206 | void wrap(char s[], const int wrapline) 207 | { 208 | int i, k, wraploc, lastwrap; 209 | 210 | lastwrap = 0; // saves character index after most recent line wrap 211 | wraploc = 0; // used to find the location for next word wrap 212 | 213 | for (i = 0; s[i] != '\0'; ++i, ++wraploc) { 214 | 215 | if (wraploc >= wrapline) { 216 | for (k = i; k > 0; --k) { 217 | // make sure word wrap doesn't overflow past maximum length 218 | if (k - lastwrap <= wrapline && s[k] == ' ') { 219 | s[k] = '\n'; 220 | lastwrap = k+1; 221 | break; 222 | } 223 | } 224 | wraploc = i-lastwrap; 225 | } 226 | 227 | } // end main loop 228 | } 229 | 230 | int main(int argc, char *argv[]) 231 | { 232 | CURL *curl; 233 | 234 | curl = curl_easy_init(); 235 | 236 | if(curl) 237 | { 238 | 239 | void *RantMemory = malloc(sizeof(Rant)*40); 240 | 241 | Rant *Rants = (Rant *)RantMemory; 242 | uint RantCount = 0; 243 | WINDOW *RantWindow; 244 | 245 | initscr(); 246 | raw(); 247 | keypad(stdscr, true); 248 | noecho(); 249 | cbreak(); 250 | // timeout(10); 251 | curs_set(0); 252 | 253 | RantCount += GatherRants(curl, RantCount, Rants, (char *)"recent", 1); 254 | // RantCount += GatherRants(curl, RantCount, Rants, (char *)"top", 2); 255 | 256 | bool Running = true; 257 | 258 | uint CurrentRantIndex = 0; 259 | Rant *CurrentRant = Rants + CurrentRantIndex; 260 | LoadRant(curl, CurrentRant); 261 | 262 | int CurrentTopLine = 0; 263 | 264 | while(Running) 265 | { 266 | int WidthSpacing = 2; 267 | int HeightSpacing = 1; 268 | int width, height; 269 | getmaxyx(stdscr, height, width); 270 | clear(); 271 | 272 | // WordWrap(CurrentRant->Content.Text, width); 273 | 274 | 275 | char LineBuffer[1000][width]; 276 | int LineCount = 0; 277 | 278 | sprintf(LineBuffer[LineCount++], "----%s----\n", CurrentRant->Content.User); 279 | 280 | char *pch, *str; 281 | str = strdup(CurrentRant->Content.Text); 282 | wrap(str, width - (2*WidthSpacing)); 283 | pch = strtok(str,"\n"); 284 | while (pch != NULL) 285 | { 286 | sprintf(LineBuffer[LineCount++], "%s\n", pch); 287 | pch = strtok (NULL, "\n"); 288 | } 289 | 290 | // sprintf(LineBuffer[LineCount++], "%s\n", CurrentRant->Content.Text); 291 | sprintf(LineBuffer[LineCount++], "------------------------\n"); 292 | 293 | for(int CommentIndex = 0; 294 | CommentIndex < CurrentRant->CommentCount; 295 | ++CommentIndex) 296 | { 297 | sprintf(LineBuffer[LineCount++], "----%s----\n", CurrentRant->Comments[CommentIndex].User); 298 | 299 | str = strdup(CurrentRant->Comments[CommentIndex].Text); 300 | wrap(str, width - (2*WidthSpacing)); 301 | pch = strtok(str,"\n"); 302 | while (pch != NULL) 303 | { 304 | sprintf(LineBuffer[LineCount++], "%s\n",pch); 305 | pch = strtok (NULL, "\n"); 306 | } 307 | 308 | // sprintf(LineBuffer[LineCount++], "%s\n", ); 309 | 310 | sprintf(LineBuffer[LineCount++], "------------------------\n"); 311 | } 312 | 313 | 314 | for(int i = 0; 315 | i < HeightSpacing; 316 | ++i) 317 | { 318 | printw("\n"); 319 | } 320 | 321 | for(int LineIndex = CurrentTopLine; 322 | (LineIndex < LineCount) && (LineIndex < (height + CurrentTopLine)); 323 | ++LineIndex) 324 | { 325 | for(int i = 0; 326 | i < WidthSpacing; 327 | ++i) 328 | { 329 | printw(" "); 330 | } 331 | 332 | printw("%s", LineBuffer[LineIndex]); 333 | } 334 | 335 | for(int i = 0; 336 | i < HeightSpacing; 337 | ++i) 338 | { 339 | printw("\n"); 340 | } 341 | 342 | int Input = getch(); 343 | 344 | switch(Input) 345 | { 346 | case 27: 347 | case 3: 348 | case 0: 349 | { 350 | Running = false; 351 | } break; 352 | 353 | case KEY_LEFT: 354 | { 355 | CurrentRantIndex = MOD(--CurrentRantIndex, RantCount); 356 | CurrentRant = Rants + CurrentRantIndex; 357 | if(!CurrentRant->Loaded) 358 | { 359 | LoadRant(curl, CurrentRant); 360 | } 361 | } break; 362 | 363 | case KEY_RIGHT: 364 | { 365 | CurrentRantIndex = MOD(++CurrentRantIndex, RantCount); 366 | CurrentRant = Rants + CurrentRantIndex; 367 | if(!CurrentRant->Loaded) 368 | { 369 | LoadRant(curl, CurrentRant); 370 | } 371 | } break; 372 | 373 | case KEY_UP: 374 | { 375 | if(CurrentTopLine > 0) 376 | { 377 | --CurrentTopLine; 378 | }; 379 | } break; 380 | 381 | case KEY_DOWN: 382 | { 383 | if((CurrentTopLine < height) && (CurrentTopLine < LineCount)) 384 | { 385 | ++CurrentTopLine; 386 | }; 387 | } break; 388 | 389 | default: 390 | { 391 | printf("\n...%d...\n", Input); 392 | printw("\n...%d...\n", Input); 393 | } break; 394 | } 395 | 396 | refresh(); 397 | } 398 | endwin(); 399 | 400 | free(RantMemory); 401 | 402 | /* always cleanup */ 403 | curl_easy_cleanup(curl); 404 | } 405 | 406 | return 0; 407 | } 408 | -------------------------------------------------------------------------------- /code/linux_terminal_rant.h: -------------------------------------------------------------------------------- 1 | #ifndef LINUX_TERMINAL_RANT_H 2 | #define LINUX_TERMINAL_RANT_H 3 | 4 | #define MOD(a,b) ((((a)%(b))+(b))%(b)) 5 | 6 | struct string 7 | { 8 | char *pointer; 9 | size_t length; 10 | }; 11 | 12 | struct Memory 13 | { 14 | void *Memory; 15 | size_t Size; 16 | }; 17 | 18 | struct RantContent 19 | { 20 | char *User; 21 | char *Text; 22 | }; 23 | 24 | struct Rant 25 | { 26 | char URL[50]; 27 | 28 | struct RantContent Content; 29 | 30 | struct RantContent Comments[200]; 31 | int CommentCount; 32 | 33 | bool Loaded; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Invisible-Rabbit-Hunter/terminal_devrant/e2c2f2d18b537f69b28cb9dd14522214844fc995/img1.png -------------------------------------------------------------------------------- /img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Invisible-Rabbit-Hunter/terminal_devrant/e2c2f2d18b537f69b28cb9dd14522214844fc995/img2.png --------------------------------------------------------------------------------