pax_global_header00006660000000000000000000000064146622622240014520gustar00rootroot0000000000000052 comment=25385076f5dc147f726b6984b849ac6e3df0599c rsstail-2.1/000077500000000000000000000000001466226222400130435ustar00rootroot00000000000000rsstail-2.1/.github/000077500000000000000000000000001466226222400144035ustar00rootroot00000000000000rsstail-2.1/.github/FUNDING.yml000066400000000000000000000000671466226222400162230ustar00rootroot00000000000000github: [folkertvanheusden] patreon: folkertvanheusden rsstail-2.1/LICENSE000066400000000000000000000000401466226222400140420ustar00rootroot00000000000000rsstail is in the public domain rsstail-2.1/Makefile000066400000000000000000000015051466226222400145040ustar00rootroot00000000000000VERSION=2.1 DEBUG=-g LDFLAGS=-liconv_hook -lmrss $(DEBUG) CFLAGS=-O3 -Wall --std=gnu11 -DVERSION=\"$(VERSION)\" $(DEBUG) OBJS=r2t.o prefix ?= /usr/local bindir ?= $(prefix)/bin mandir ?= $(prefix)/share/man/man1 all: rsstail rsstail: $(OBJS) $(CC) -Wall -W $(OBJS) $(LDFLAGS) -o rsstail install: rsstail mkdir -p $(bindir) mkdir -p $(mandir) install rsstail $(bindir) install -m 644 rsstail.1 $(mandir) uninstall: rm $(bindir)/rsstail $(mandir)/rsstail.1 clean: rm -f $(OBJS) core rsstail package: clean # source package rm -rf rsstail-$(VERSION)* mkdir rsstail-$(VERSION) cp *.c *.1 Makefile* README.md license.* rsstail-$(VERSION) tar cf - rsstail-$(VERSION) | gzip -9 > rsstail-$(VERSION).tgz rm -rf rsstail-$(VERSION) check: cppcheck -v --enable=all --std=c++11 --inconclusive --check-config -I. . 2> err.txt rsstail-2.1/README.md000066400000000000000000000020421466226222400143200ustar00rootroot00000000000000rsstail ======= rsstail is tail for RSS feeds ## Usage Basic usage: ``` rsstail -u URL -i CHECK_INTERVAL ``` For example a command below will check every 5 minutes if anything new was published: ``` $ rsstail -u http://www.filmhuisgouda.nl/rss/rss.php -i 300 Title: Que horas ela volta? Title: 45 years Title: Madame Bovary ... ``` `rsstail -h` will show a list of what rsstail can do for you. ## Installation On Debian/Ubuntu: ``` sudo apt-get install rsstail ``` ## Building ### Dependencies rsstail depends on [`libmrss`](http://www.autistici.org/bakunin/codes.php#libmrss) (version >= 0.7). On Debian/Ubuntu libmrss will be installed automatically if you install rsstail or libmrss0-dev (as shown below) as package. To compile source code you may need to run ``` sudo apt-get install libmrss0-dev libiconv-hook-dev ``` ### Compile source code ``` $ git clone https://github.com/folkertvanheusden/rsstail.git $ cd rsstail $ sudo make install ``` ## Contact For things related to rsstail, feel free to contact me on mail@vanheusden.com. rsstail-2.1/license.txt000066400000000000000000000001771466226222400152330ustar00rootroot00000000000000The license of this program can be obtained from: http://www.vanheusden.com/license.txt It is actually the GNU Public License. rsstail-2.1/r2t.c000066400000000000000000000335011466226222400137200ustar00rootroot00000000000000#include "r2t.h" const char name[] = "rsstail " VERSION ", (C) 2006-2023 by folkert@vanheusden.com"; void replace(char *const in, const char *const what, char by_what) { size_t what_len = strlen(what); /* replace < etc. */ for(;;) { char *str = strstr(in, what); if (!str) break; memcpy(str + 1, str + what_len, strlen(str + what_len) + 1); *str = by_what; } } char *remove_html_tags(const char *const in) { char *copy = strdup(in); /* strip <...> */ for(;;) { char *lt = strchr(copy, '<'), *gt; if (!lt) break; gt = strchr(lt, '>'); if (!gt) break; memcpy(lt, gt + 1, strlen(gt + 1) + 1); } replace(copy, "<", '<'); replace(copy, ">", '>'); replace(copy, "&", '&'); return copy; } /* 0: is a new record, -1: not a new record */ int is_new_record(mrss_item_t *check_list, mrss_item_t *cur_item) { while(check_list) { if (check_list -> pubDate != NULL && cur_item -> pubDate != NULL) { if (strcmp(check_list -> pubDate, cur_item -> pubDate) == 0) return -1; } else { int navail = 0, nequal = 0; if (check_list -> title != NULL && cur_item -> title != NULL) { navail++; if (strcmp(check_list -> title, cur_item -> title) == 0) nequal++; } if (check_list -> link != NULL && cur_item -> link != NULL) { navail++; if (strcmp(check_list -> link, cur_item -> link ) == 0) nequal++; } if (check_list -> description != NULL && cur_item -> description != NULL) { navail++; if (strcmp(check_list -> description, cur_item -> description) == 0) nequal++; } if (navail == nequal && navail > 0) { return -1; } } check_list = check_list -> next; } return 0; } void version(void) { printf("%s\n", name); } char* my_convert(iconv_t converter, const char *input) { size_t in_size = strlen(input); size_t out_size = (in_size + 1) * 6; // seems to be enough char *output_start = (char *)calloc(1, out_size), *output = output_start; if (!output_start) return NULL; do { size_t converted = iconv(converter, (char **) &input, &in_size, &output, &out_size); if (converted == (size_t) -1) { free (output_start); return NULL; } } while(in_size); return output_start; } void usage(void) { version(); printf("-t show a timestamp of when the item was processed\n"); printf("-l show item's link\n"); printf("-e show item's enclosure URL\n"); printf("-d show item's description\n"); printf("-p show item's publication date\n"); printf("-a show item's author\n"); printf("-c show item's comments\n"); printf("-g show item's GUID\n"); printf("-N do not show headings\n"); printf("-b x limit description/comments to x bytes\n"); printf("-z continue even if there are XML parser errors in the RSS feed\n"); printf("-Z x print string 'x' before headings\n"); printf("-n x initially show only first 'x' items\n"); printf("-r reverse output, so it looks more like an RSS feed\n"); printf("-H strip HTML tags\n"); /* printf("-o x only show items newer then x[s/M/h/d/m/y]\n"); */ printf("-A x authenticate against webserver (username:password)\n"); printf("-u url URL of RSS feed to tail\n"); printf("-i x check interval in seconds (default is 15min)\n"); printf("-x x proxy server to use (host[:port])\n"); printf("-y x proxy authentication (username:password)\n"); printf("-P do not exit when an error occurs\n"); printf("-1 one shot mode: print new items and exit\n"); printf("-v be verbose (repeat to increase verbosity)\n"); printf("-V show version and exit\n"); printf("-h this help\n"); } int main(int argc, char *argv[]) { char **url = NULL; size_t n_url = 0; size_t cur_url = 0; int check_interval = 15 * 60; mrss_t **data_prev = NULL; mrss_t **data_cur = NULL; char *proxy = NULL, *proxy_auth = NULL; int sw = 0; int verbose = 0; char show_timestamp = 0, show_link = 0, show_enclosure_url = 0, show_description = 0, show_pubdate = 0, show_author = 0, show_comments = 0, show_guid = 0; char show_title = 1; char strip_html = 0, no_error_exit = 0; char one_shot = 0; char no_heading = 0; size_t bytes_limit = 0; time_t last_changed = (time_t)0; char continue_on_error = 0; int show_n = -1; char *heading = NULL; mrss_options_t mot; char *auth = NULL; char *current_encoding = NULL; char reverse = 0; iconv_t converter = 0; memset(&mot, 0x00, sizeof(mot)); while((sw = getopt(argc, argv, "A:Z:1b:PHztTledrpacgu:Ni:n:x:y:vVh")) != -1) { switch(sw) { case 'A': auth = optarg; break; case 'Z': heading = optarg; break; case 'N': no_heading = 1; break; case '1': one_shot = 1; break; case 'b': bytes_limit = (size_t) atoi(optarg); if (bytes_limit <= 0) { printf("-b requires a number > 0\n"); return 1; } break; case 'P': no_error_exit = 1; break; case 'H': strip_html = 1; break; case 'n': show_n = atoi(optarg); if (show_n < 0) { printf("-n requires an positive value\n"); return 1; } else if (show_n > 50) printf("Initially showing more then 50 items, must be one hell of an rss feed!\n"); break; #if 0 case 'o': dummy = optarg[strlen(optarg) - 1]; max_age = atoi(optarg); if (max_age < 0) { printf("-o requires an positive value\n"); return 1; } if (dummy == 's') max_age *= 1; else if (dummy == 'M') max_age *= 60; else if (dummy == 'h') max_age *= 3600; else if (dummy == 'd') max_age *= 86400; else if (dummy == 'm') max_age *= 86400 * 31; else if (dummy == 'y') max_age *= 86400 * 365.25; else if (isalpha(dummy)) { printf("'%c' is a not recognized multiplier\n", dummy); return 1; } break; #endif case 'z': continue_on_error = 1; break; case 'T': show_title = 0; break; case 't': show_timestamp = 1; break; case 'l': show_link = 1; break; case 'e': show_enclosure_url = 1; break; case 'd': show_description = 1; break; case 'r': reverse = 1; break; case 'p': show_pubdate = 1; break; case 'a': show_author = 1; break; case 'c': show_comments = 1; break; case 'g': show_guid = 1; break; case 'u': url = (char **)realloc(url, sizeof(char *) * (n_url + 1)); if (!url) { fprintf(stderr, "Cannot allocate memory\n"); return 2; } url[n_url++] = optarg; break; case 'i': check_interval = atoi(optarg); break; case 'x': proxy = optarg; break; case 'y': proxy_auth = optarg; break; case 'v': verbose++; break; case 'V': version(); return 1; case 'h': default: usage(); return 1; } } mot.timeout = check_interval; mot.proxy = proxy; mot.proxy_authentication = proxy_auth; mot.user_agent = (char *)name; mot.authentication = auth; if (n_url == 0) { fprintf(stderr, "Please give the URL of the RSS feed to check with the '-u' parameter.\n"); return 1; } data_prev = (mrss_t **)calloc(n_url, sizeof(mrss_t *)); data_cur = (mrss_t **)calloc(n_url, sizeof(mrss_t *)); if (!data_prev || !data_cur) { fprintf(stderr, "Cannot allocate memory\n"); return 2; } setlocale(LC_ALL, ""); current_encoding = nl_langinfo(CODESET); if (verbose) { printf("Monitoring RSS feeds:\n"); for(size_t loop=0; loop 2) printf("Feed change detected, %s", ctime(&cur_last_changed)); last_changed = cur_last_changed; if ((err_read = mrss_parse_url_with_options(url[cur_url], &data_cur[cur_url], &mot)) != MRSS_OK) { if (err_read == MRSS_ERR_POSIX) { if (errno == EINPROGRESS) { fprintf(stderr, "Time-out while connecting to RSS feed, continuing\n"); goto goto_next_url; } } else if (err_read == MRSS_ERR_PARSER && continue_on_error) { fprintf(stderr, "Error reading RSS feed: %s\n", mrss_strerror(err_read)); goto goto_next_url; } fprintf(stderr, "Error reading RSS feed: %s\n", mrss_strerror(err_read)); if (no_error_exit) goto goto_next_url; return 2; } if (data_cur[cur_url]->encoding == NULL) data_cur[cur_url]->encoding = strdup("utf-8"); if (verbose) printf("Creating converter %s -> %s\n", data_cur[cur_url] -> encoding, current_encoding); converter = iconv_open(current_encoding, data_cur[cur_url] -> encoding); if (converter == (iconv_t) -1) { fprintf(stderr, "Error creating converter: %s \n", strerror(errno)); return 2; } item_cur = data_cur[cur_url] -> item; if (reverse) { if (verbose) printf("Reversing...\n"); mrss_item_t *rev_item_cur = NULL; mrss_item_t *rev_item_last = NULL; for (;;) { rev_item_last = rev_item_cur; rev_item_cur = item_cur; if (!item_cur) break; if (!item_cur -> next) { rev_item_cur -> next = rev_item_last; break; } item_cur = item_cur -> next; rev_item_cur -> next = rev_item_last; } } tmp_first_item = item_cur; while(item_cur) { if ((data_prev[cur_url] && is_new_record(first_item[cur_url], item_cur) != -1) || !data_prev[cur_url]) { if (!data_prev[cur_url] && n_shown >= show_n && show_n != -1) { item_cur = item_cur -> next; continue; } n_shown++; if (show_link + show_enclosure_url + show_description + show_pubdate + show_author + show_comments > 1) printf("\n"); if (show_timestamp) { time_t now = time(NULL); struct tm *now_tm = localtime(&now); printf("%04d/%02d/%02d %02d:%02d:%02d ", now_tm -> tm_year + 1900, now_tm -> tm_mon + 1, now_tm -> tm_mday, now_tm -> tm_hour, now_tm -> tm_min, now_tm -> tm_sec); } if (heading) printf(" %s", heading); if (show_title && item_cur -> title != NULL) { char *title = my_convert(converter, item_cur -> title); if (title) { printf("%s%s\n", no_heading?" ":"Title: ", title); free(title); } } if (show_link && item_cur -> link != NULL) { char *link = my_convert(converter, item_cur -> link); if (link) { printf("%s%s\n", no_heading?" ":"Link: ", item_cur -> link); free(link); } } if (show_enclosure_url && item_cur -> enclosure_url != NULL) { char *enclosure_url = my_convert (converter, item_cur -> enclosure_url); if (enclosure_url) { printf("%s%s\n", no_heading?" ":"Enclosure URL: ", enclosure_url); free(enclosure_url); } } if (show_description && item_cur -> description != NULL) { if (strip_html) { char *stripped = remove_html_tags(item_cur -> description); if (bytes_limit != 0 && bytes_limit < strlen(stripped)) stripped[bytes_limit] = 0x00; char *description = my_convert(converter, stripped); if (description) { printf("%s%s\n", no_heading?" ":"Description: ", description); free(description); } free(stripped); } else { if (bytes_limit != 0 && bytes_limit < strlen(item_cur -> description)) (item_cur -> description)[bytes_limit] = 0x00; char *description = my_convert(converter, item_cur -> description); if (description) { printf("%s%s\n", no_heading?" ":"Description: ", description); free(description); } } } if (show_pubdate && item_cur -> pubDate != NULL) printf("%s%s\n", no_heading?" ":"Pub.date: ", item_cur -> pubDate); if (show_author && item_cur -> author != NULL){ char *author = my_convert(converter, item_cur -> author); if (author) { printf("%s%s\n", no_heading?" ":"Author: ", author); free(author); } } if (show_comments && item_cur -> comments != NULL) { if (bytes_limit != 0 && bytes_limit < strlen(item_cur -> comments)) (item_cur -> comments)[bytes_limit] = 0x00; char *comments = my_convert(converter, item_cur -> comments); if (comments) { printf("%s%s\n", no_heading?" ":"Comments: ", item_cur -> comments); free(comments); } } if (show_guid && item_cur -> guid != NULL) printf("%s%s\n", no_heading?" ":"guid: ", item_cur -> guid); } item_cur = item_cur -> next; } if (data_prev[cur_url]) { mrss_error_t err_free = mrss_free(data_prev[cur_url]); if (err_free != MRSS_OK) { fprintf(stderr, "Error freeing up memory: %s\n", mrss_strerror(err_free)); if (no_error_exit) goto goto_next_url; return 2; } } data_prev[cur_url] = data_cur[cur_url]; data_cur[cur_url] = NULL; first_item[cur_url] = tmp_first_item; goto_next_url: if (converter) { iconv_close(converter); converter = 0; } cur_url++; if (cur_url >= n_url) cur_url = 0; fflush(stdout); if (one_shot) break; if (verbose > 2) printf("Sleeping...\n"); sleep((unsigned int)check_interval / (unsigned int) n_url); } return 0; } rsstail-2.1/r2t.h000066400000000000000000000012151466226222400137220ustar00rootroot00000000000000#ifdef _POSIX_C_SOURCE #define _POSIX_C_SOURCE_BACKUP _POSIX_C_SOURCE #undef _POSIX_C_SOURCE #endif #define POSIX_C_SOURCE 200809L #ifndef _r2t_h_ #define _r2t_h_ #include #include #include #include #include #include #include #include #include #include void replace(char *const in, const char *const what, char by_what); char *remove_html_tags(const char *const in); int is_new_record(mrss_item_t *check_list, mrss_item_t *cur_item); void version(void); char* my_convert(iconv_t converter, const char *input); void usage(void); #endif rsstail-2.1/rsstail.1000066400000000000000000000046361466226222400146170ustar00rootroot00000000000000.TH "RSSTAIL" "1" "0.1" "" "User Commands" .SH "NAME" rsstail \- a console RSS reader that monitors feeds and outputs new entries .SH "SYNOPSIS" .PP \fBrsstail\fR [\fBOPTIONS\fB]... \fB\-u\fB URL .SH "DESCRIPTION" .PP This manual page was written for the \fBDebian\fP distribution because the original program does not have a manual page. .PP rsstail fetches RSS feeds from specified URLs and outputs them continuously, much like \fBtail -f\fB does with files. .SH "OPTIONS" .PP Options can be given in any order. Options that don't have an argument can be combined after a single dash. .IP "\fB\-t\fP" 10 Show a timestamp of when the item was processed .IP "\fB\-l\fP" 10 Show item's link .IP "\fB\-e\fP" 10 Show item's enclosure URL .IP "\fB\-d\fB" 10 Show item's description .IP "\fB\-p\fB" 10 Show item's publication date .IP "\fB\-a\fB" 10 Show item's author .IP "\fB\-c\fB" 10 Show item's comments .IP "\fB\-g\fB" 10 Show item's GUID .IP "\fB\-N\fB" 10 Do not show headings .IP "\fB\-b X\fB" 10 Limit description and comments to \fBX\fB bytes .IP "\fB\-z\fB" 10 Continue even if there are XML parser errors in the RSS feed .IP "\fB\-Z string\fB" 10 Output user-specified string before all the other headings .IP "\fB\-n X\fB" 10 Initially show only first \fBX\fB items .IP "\fB\-H\fB" 10 Strip HTML tags .IP "\fB\-A username:password\fB" 10 Authenticate against webserver .IP "\fB\-u URL\fB" 10 URL of RSS feed to tail .IP "\fB\-i seconds\fB" 10 Check interval in seconds (default is 15 minutes) .IP "\fB\-r\fB" Print items in reverse order .IP "\fB\-x host[:port]\fB" 10 Proxy server to use .IP "\fB\-y username:password\fB" 10 Credentials for the proxy server .IP "\fB\-P\fB" 10 Do not exit when an error occurs .IP "\fB\-1\fB" 10 One shot mode: check the feeds for new items, print them out and exit .IP "\fB\-v\fB" 10 Be verbose (repeat to increase verbosity) .IP "\fB\-h\fP" 10 Show options and their descriptions .IP "\fB\-V\fP" 10 Show version of the program .SH "AUTHOR" .PP This manual page was written by Rene Mayorga for the \fBDebian\fP system (but may be used by others). Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or any later version published by the Free Software Foundation. .PP On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common\-licenses/GPL.