Compare commits
5 Commits
036206d2be
...
92dad5b3dc
Author | SHA1 | Date | |
---|---|---|---|
92dad5b3dc | |||
d41a612d9d | |||
1df7e7cbc7 | |||
0e81a0b5e2 | |||
418a835857 |
17
Makefile
17
Makefile
@ -1,15 +1,20 @@
|
|||||||
PREFIX = ~/.local
|
PREFIX = ~/.local
|
||||||
VERSION = 0.2
|
VERSION = 0.2
|
||||||
|
|
||||||
# CC = cc
|
PKG_CONFIG = pkg-config
|
||||||
|
|
||||||
|
# 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)
|
||||||
PKG_CONFIG = pkg-config
|
INCS = `$(PKG_CONFIG) --cflags libxml-2.0` `$(CURL_CONFIG) --cflags` $(JSONINC)
|
||||||
CURL_CONFIG = curl-config
|
LIBS = `$(PKG_CONFIG) --libs libxml-2.0` `$(CURL_CONFIG) --libs` $(JSONLIBS)
|
||||||
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)\"
|
CFLAGS = $(INCS) $(LIBS) $(WARN) -DVERSION=\"$(VERSION)\" $(JSONFLAG)
|
||||||
|
|
||||||
all: config.h minrss
|
all: config.h minrss
|
||||||
|
|
||||||
|
11
README
11
README
@ -1,8 +1,10 @@
|
|||||||
MinRSS
|
MinRSS
|
||||||
======
|
======
|
||||||
MinRSS is an RSS/Atom feed reader for Linux inspired by suckless.org's
|
MinRSS is an RSS/Atom feed reader for Linux inspired by suckless.org's IRC
|
||||||
IRC clients ii and sic. Instead of presenting articles as entries
|
clients ii and sic. Instead of presenting articles as entries in a menu, it
|
||||||
in a menu, it saves them as files in folders.
|
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
|
||||||
@ -16,6 +18,9 @@ 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:
|
||||||
|
11
config.def.h
11
config.def.h
@ -21,7 +21,6 @@ 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"
|
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -49,3 +48,13 @@ 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;
|
||||||
|
71
handlers.c
71
handlers.c
@ -4,6 +4,9 @@
|
|||||||
#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"
|
||||||
@ -53,7 +56,7 @@ atomLink(itemStruct *item, xmlNodePtr node)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!rel) {
|
if (!rel || propIs(rel, "alternate")) {
|
||||||
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);
|
||||||
@ -132,11 +135,46 @@ openFile(const char *folder, char *fileName, char *fileExt)
|
|||||||
static void
|
static void
|
||||||
outputHtml(itemStruct *item, FILE *f)
|
outputHtml(itemStruct *item, FILE *f)
|
||||||
{
|
{
|
||||||
fprintf(f, "<h1>%s</h1><br>\n", item->fields[FIELD_TITLE]);
|
if (item->fields[FIELD_TITLE])
|
||||||
fprintf(f, "<a href=\"%s\">Link</a><br>\n", item->fields[FIELD_LINK]);
|
fprintf(f, "<h1>%s</h1><br>\n", item->fields[FIELD_TITLE]);
|
||||||
fprintf(f, "%s", item->fields[FIELD_DESCRIPTION]);
|
if (item->fields[FIELD_LINK])
|
||||||
|
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)
|
||||||
{
|
{
|
||||||
@ -149,11 +187,32 @@ 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)) {
|
||||||
outputHtml(cur, itemFile);
|
outputFunction(cur, itemFile);
|
||||||
newItems++;
|
newItems++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
32
minrss.c
32
minrss.c
@ -25,7 +25,11 @@ 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"
|
||||||
|
|
||||||
#define TAGIS(X, Y) (!xmlStrcmp(X->name, (const xmlChar *) Y))
|
static inline int
|
||||||
|
tagIs(xmlNodePtr node, char *str)
|
||||||
|
{
|
||||||
|
return !xmlStrcmp(node->name, (const xmlChar *) str);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
parseXml(xmlDocPtr doc,
|
parseXml(xmlDocPtr doc,
|
||||||
@ -50,9 +54,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;
|
||||||
}
|
}
|
||||||
@ -69,10 +73,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;
|
||||||
}
|
}
|
||||||
@ -101,10 +105,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");
|
||||||
@ -128,21 +132,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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user