Compare commits

..

No commits in common. "92dad5b3dcba1709c9643342febd114db0528ffc" and "036206d2be60097e24f56678616f6203afede6ef" have entirely different histories.

5 changed files with 30 additions and 112 deletions

View File

@ -1,20 +1,15 @@
PREFIX = ~/.local PREFIX = ~/.local
VERSION = 0.2 VERSION = 0.2
PKG_CONFIG = pkg-config # CC = cc
# Comment out if JSON output support isn't needed
JSONLIBS = `$(PKG_CONFIG) --libs json-c`
JSONINCS = `$(PKG_CONFIG) --cflags json-c`
JSONFLAG = -DJSON
CURL_CONFIG = curl-config
SRC = minrss.c util.c net.c handlers.c SRC = minrss.c util.c net.c handlers.c
OBJ = $(SRC:.c=.o) OBJ = $(SRC:.c=.o)
INCS = `$(PKG_CONFIG) --cflags libxml-2.0` `$(CURL_CONFIG) --cflags` $(JSONINC) PKG_CONFIG = pkg-config
LIBS = `$(PKG_CONFIG) --libs libxml-2.0` `$(CURL_CONFIG) --libs` $(JSONLIBS) CURL_CONFIG = curl-config
INCS = `$(PKG_CONFIG) --cflags libxml-2.0` `$(CURL_CONFIG) --cflags`
LIBS = `$(PKG_CONFIG) --libs libxml-2.0` `$(CURL_CONFIG) --libs`
WARN = -Wall -Wpedantic -Wextra WARN = -Wall -Wpedantic -Wextra
CFLAGS = $(INCS) $(LIBS) $(WARN) -DVERSION=\"$(VERSION)\" $(JSONFLAG) CFLAGS = $(INCS) $(LIBS) $(WARN) -DVERSION=\"$(VERSION)\"
all: config.h minrss all: config.h minrss

11
README
View File

@ -1,10 +1,8 @@
MinRSS MinRSS
====== ======
MinRSS is an RSS/Atom feed reader for Linux inspired by suckless.org's IRC MinRSS is an RSS/Atom feed reader for Linux inspired by suckless.org's
clients ii and sic. Instead of presenting articles as entries in a menu, it IRC clients ii and sic. Instead of presenting articles as entries
saves them as files in folders. in a menu, it saves them as files in folders.
These files can either be formatted as HTML, or as JSON to help with scripting.
rss rss
|--news |--news
@ -18,9 +16,6 @@ Requirements
------------ ------------
You need libcurl and libxml2 to compile MinRSS. You need libcurl and libxml2 to compile MinRSS.
json-c is required for JSON output. To disable this feature, comment out the
relevant lines in Makefile.
Installation Installation
------------ ------------
Run this command to build MinRSS: Run this command to build MinRSS:

View File

@ -21,6 +21,7 @@ typedef struct {
.url = "https://example.com/rss/feed.rss", .url = "https://example.com/rss/feed.rss",
// This will be used as the folder name for the feed. // This will be used as the folder name for the feed.
.feedName = "examplefeed", .feedName = "examplefeed",
.tags = "test example sample"
}, },
*/ */
@ -48,13 +49,3 @@ static const int maxRedirs = 10;
// Restrict allowed protocols for curl using a bitmask. // Restrict allowed protocols for curl using a bitmask.
// For more information: https://curl.se/libcurl/c/CURLOPT_PROTOCOLS.html // For more information: https://curl.se/libcurl/c/CURLOPT_PROTOCOLS.html
static const int curlProtocols = CURLPROTO_HTTPS | CURLPROTO_HTTP; static const int curlProtocols = CURLPROTO_HTTPS | CURLPROTO_HTTP;
enum outputFormats {
OUTPUT_HTML,
#ifdef JSON
OUTPUT_JSON,
#endif // JSON
};
// When saving, sets the format of the saved file.
static const enum outputFormats outputFormat = OUTPUT_HTML;

View File

@ -4,9 +4,6 @@
#include <libxml/parser.h> #include <libxml/parser.h>
#include <libxml/tree.h> #include <libxml/tree.h>
#include <libxml/xmlreader.h> #include <libxml/xmlreader.h>
#ifdef JSON
#include <json-c/json.h>
#endif // JSON
#include "config.h" #include "config.h"
#include "util.h" #include "util.h"
@ -56,7 +53,7 @@ atomLink(itemStruct *item, xmlNodePtr node)
return 1; return 1;
} }
if (!rel || propIs(rel, "alternate")) { if (!rel) {
copyField(item, FIELD_LINK, (char *)href); copyField(item, FIELD_LINK, (char *)href);
} else if (propIs(rel, "enclosure")) { } else if (propIs(rel, "enclosure")) {
copyField(item, FIELD_ENCLOSURE_URL, (char *)href); copyField(item, FIELD_ENCLOSURE_URL, (char *)href);
@ -135,46 +132,11 @@ openFile(const char *folder, char *fileName, char *fileExt)
static void static void
outputHtml(itemStruct *item, FILE *f) outputHtml(itemStruct *item, FILE *f)
{ {
if (item->fields[FIELD_TITLE]) fprintf(f, "<h1>%s</h1><br>\n", item->fields[FIELD_TITLE]);
fprintf(f, "<h1>%s</h1><br>\n", item->fields[FIELD_TITLE]); fprintf(f, "<a href=\"%s\">Link</a><br>\n", item->fields[FIELD_LINK]);
if (item->fields[FIELD_LINK]) fprintf(f, "%s", item->fields[FIELD_DESCRIPTION]);
fprintf(f, "<a href=\"%s\">Link</a><br>\n", item->fields[FIELD_LINK]);
if (item->fields[FIELD_ENCLOSURE_URL])
fprintf(f, "<a href=\"%s\">Enclosure</a><br>\n", item->fields[FIELD_ENCLOSURE_URL]);
if (item->fields[FIELD_DESCRIPTION])
fprintf(f, "%s", item->fields[FIELD_DESCRIPTION]);
} }
#ifdef JSON
static void
outputJson(itemStruct *item, FILE *f)
{
json_object *root = json_object_new_object();
if (item->fields[FIELD_TITLE])
json_object_object_add(root, "title",
json_object_new_string(item->fields[FIELD_TITLE]));
if (item->fields[FIELD_LINK])
json_object_object_add(root, "link",
json_object_new_string(item->fields[FIELD_LINK]));
if (item->fields[FIELD_ENCLOSURE_URL]) {
json_object *enclosure = json_object_new_object();
json_object_object_add(enclosure, "link",
json_object_new_string(item->fields[FIELD_ENCLOSURE_URL]));
json_object_object_add(root, "enclosure", enclosure);
}
if (item->fields[FIELD_DESCRIPTION])
json_object_object_add(root, "description",
json_object_new_string(item->fields[FIELD_DESCRIPTION]));
fprintf(f, "%s", json_object_to_json_string_ext(root, 0));
json_object_put(root);
}
#endif // JSON
void void
itemAction(itemStruct *item, const char *folder) itemAction(itemStruct *item, const char *folder)
{ {
@ -187,32 +149,11 @@ itemAction(itemStruct *item, const char *folder)
while (cur) { while (cur) {
prev = cur; prev = cur;
FILE *itemFile = openFile(folder, san(cur->fields[FIELD_TITLE]), ".html");
char fileExt[10];
void (*outputFunction)(itemStruct *, FILE *);
switch (outputFormat) {
case OUTPUT_HTML:
memcpy(fileExt, ".html", 6);
outputFunction = &outputHtml;
break;
#ifdef JSON
case OUTPUT_JSON:
memcpy(fileExt, ".json", 6);
outputFunction = &outputJson;
break;
#endif //JSON
default:
logMsg(0, "Output format is invalid.");
break;
}
FILE *itemFile = openFile(folder, san(cur->fields[FIELD_TITLE]), fileExt);
// Do not overwrite files // Do not overwrite files
if (!ftell(itemFile)) { if (!ftell(itemFile)) {
outputFunction(cur, itemFile); outputHtml(cur, itemFile);
newItems++; newItems++;
} }

View File

@ -25,11 +25,7 @@ You should have received a copy of the GNU General Public License along with thi
#include "handlers.h" #include "handlers.h"
#include "config.h" #include "config.h"
static inline int #define TAGIS(X, Y) (!xmlStrcmp(X->name, (const xmlChar *) Y))
tagIs(xmlNodePtr node, char *str)
{
return !xmlStrcmp(node->name, (const xmlChar *) str);
}
static int static int
parseXml(xmlDocPtr doc, parseXml(xmlDocPtr doc,
@ -54,9 +50,9 @@ parseXml(xmlDocPtr doc,
enum feedFormat format = NONE; enum feedFormat format = NONE;
if (tagIs(rootNode, "rss")) { if (TAGIS(rootNode, "rss")) {
format = RSS; format = RSS;
} else if (tagIs(rootNode, "feed")) { } else if (TAGIS(rootNode, "feed")) {
if (!xmlStrcmp(rootNode->ns->href, (const xmlChar *) "http://www.w3.org/2005/Atom")) if (!xmlStrcmp(rootNode->ns->href, (const xmlChar *) "http://www.w3.org/2005/Atom"))
format = ATOM; format = ATOM;
} }
@ -73,10 +69,10 @@ parseXml(xmlDocPtr doc,
switch (format) { switch (format) {
case RSS: case RSS:
// Get channel XML tag // Get channel XML tag
while(cur && !tagIs(cur, "channel")) while(cur && !TAGIS(cur, "channel"))
cur = cur->next; cur = cur->next;
if (!cur || !tagIs(cur, "channel")) { if (!cur || !TAGIS(cur, "channel")) {
logMsg(1, "Invalid RSS syntax.\n"); logMsg(1, "Invalid RSS syntax.\n");
return 1; return 1;
} }
@ -105,10 +101,10 @@ parseXml(xmlDocPtr doc,
switch (format) { switch (format) {
case RSS: case RSS:
isArticle = tagIs(cur, "item"); isArticle = TAGIS(cur, "item");
break; break;
case ATOM: case ATOM:
isArticle = tagIs(cur, "entry"); isArticle = TAGIS(cur, "entry");
break; break;
default: default:
logMsg(1, "Missing article tag name for format\n"); logMsg(1, "Missing article tag name for format\n");
@ -132,21 +128,21 @@ parseXml(xmlDocPtr doc,
switch (format) { switch (format) {
case RSS: case RSS:
if (tagIs(itemNode, "link")) if TAGIS(itemNode, "link")
copyField(item, FIELD_LINK, itemKey); copyField(item, FIELD_LINK, itemKey);
else if (tagIs(itemNode, "description")) else if TAGIS(itemNode, "description")
copyField(item, FIELD_DESCRIPTION, itemKey); copyField(item, FIELD_DESCRIPTION, itemKey);
else if (tagIs(itemNode, "title")) else if TAGIS(itemNode, "title")
copyField(item, FIELD_TITLE, itemKey); copyField(item, FIELD_TITLE, itemKey);
else if (tagIs(itemNode, "enclosure")) else if TAGIS(itemNode, "enclosure")
rssEnclosure(item, itemNode); rssEnclosure(item, itemNode);
break; break;
case ATOM: case ATOM:
if (tagIs(itemNode, "link")) if TAGIS(itemNode, "link")
atomLink(item, itemNode); atomLink(item, itemNode);
else if (tagIs(itemNode, "content")) else if TAGIS(itemNode, "content")
copyField(item, FIELD_DESCRIPTION, itemKey); copyField(item, FIELD_DESCRIPTION, itemKey);
else if (tagIs(itemNode, "title")) else if TAGIS(itemNode, "title")
copyField(item, FIELD_TITLE, itemKey); copyField(item, FIELD_TITLE, itemKey);
break; break;
default: default: