Add the ability to select attribute values.
This commit is contained in:
parent
cbb7f861e1
commit
f117f7d547
|
@ -6,14 +6,15 @@ Perl script to monitor RSS feeds and execute scripts.
|
||||||
|
|
||||||
The config file is stored in `~/.config/rss-watch/config`. Example configuration file:
|
The config file is stored in `~/.config/rss-watch/config`. Example configuration file:
|
||||||
```cfg
|
```cfg
|
||||||
[feed "FeedName"]
|
[feed FeedName]
|
||||||
url = https://rss.example.com/
|
url = https://rss.example.com/
|
||||||
script = /home/user/script_to_exec.sh '$title' '$link'
|
script = /home/user/script_to_exec.sh '$title' '$link' '$guid<isPermaLink>'
|
||||||
```
|
```
|
||||||
|
|
||||||
This will query the url specified by the `url` key for an RSS feed and execute the given script with the arguments `title` and `link` from the feed.
|
This will query the url specified by the `url` key for an RSS feed and execute the given script with the arguments `title` and `link` from the feed.
|
||||||
|
Attributes can be selected by enclosing the attribue key in `<>` characters. For example, to select the `isPermaLink` attribute of the `guid` key, the selector would be `$guid<isPermaLink>`.
|
||||||
Multiple script keys can be specified on new lines, which will all be executed.
|
Multiple script keys can be specified on new lines, which will all be executed.
|
||||||
|
|
||||||
Only new entries will be acted on. The file `~/.local/share/rss-watch/latest/{FeedName}` will store the last `guid` value from the feed.
|
Only new entries will be acted on. The file `~/.local/share/rss-watch/latest/FeedName` will store the last `guid` value from the feed.
|
||||||
|
|
||||||
Use cron, or other tools to run this script periodically.
|
Use cron, or other tools to run this script periodically.
|
||||||
|
|
71
rss-watch.pl
71
rss-watch.pl
|
@ -12,6 +12,7 @@ my $CONFIG_FILE = "$ENV{HOME}/.config/rss-watch/config";
|
||||||
my $LATEST_DIR = "$ENV{HOME}/.local/share/rss-watch/latest";
|
my $LATEST_DIR = "$ENV{HOME}/.local/share/rss-watch/latest";
|
||||||
my %LATEST;
|
my %LATEST;
|
||||||
my %CONFIG;
|
my %CONFIG;
|
||||||
|
my $OPT_IGNORE_LAST = 0;
|
||||||
my $XPARSER = XML::Parser->new(Style => 'Tree');
|
my $XPARSER = XML::Parser->new(Style => 'Tree');
|
||||||
|
|
||||||
sub item2hash {
|
sub item2hash {
|
||||||
|
@ -19,14 +20,23 @@ sub item2hash {
|
||||||
my %ret = ();
|
my %ret = ();
|
||||||
|
|
||||||
for (my $i = 1; $i < scalar @$xml; $i+=2) {
|
for (my $i = 1; $i < scalar @$xml; $i+=2) {
|
||||||
#if (ref($xml->[$i]) eq "ST
|
|
||||||
my $key = $xml->[$i];
|
my $key = $xml->[$i];
|
||||||
next if $key eq 0;
|
next if $key eq 0;
|
||||||
|
# index 0 is the attributes, or empty
|
||||||
|
# index 1 is text value or empty
|
||||||
|
my @val_arr;
|
||||||
|
#print("Key: $key\n");
|
||||||
my $val = $xml->[$i+1];
|
my $val = $xml->[$i+1];
|
||||||
next if $val->[1] != 0; # only store string values here
|
# Copy the attributes
|
||||||
$ret{$key} = $val->[2];
|
push(@val_arr, $val->[0]);
|
||||||
|
if (scalar @$val >= 3 && $val->[1] == 0) {
|
||||||
|
# Type is a string, copy that as well
|
||||||
|
push(@val_arr, $val->[2]);
|
||||||
|
}
|
||||||
|
$ret{$key} = \@val_arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#print(Dumper(\%ret));
|
||||||
return \%ret;
|
return \%ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,12 +183,36 @@ sub exec_script {
|
||||||
foreach my $cmdRef (@$cmdArr) {
|
foreach my $cmdRef (@$cmdArr) {
|
||||||
my $cmd = $cmdRef; # Copy it
|
my $cmd = $cmdRef; # Copy it
|
||||||
|
|
||||||
while ($cmd =~ /[^\\]\$([\w:]+)/g) {
|
while ($cmd =~ /[^\\]\$([\w:<>]+)/g) {
|
||||||
my $key = $1;
|
my $fullmatch = $1;
|
||||||
#print("Matched: $key\n");
|
my $key;
|
||||||
next if not exists $xmlitem->{$key};
|
my $attr;
|
||||||
my $escaped_val = sh_escape($xmlitem->{$key});
|
if ($fullmatch =~ /^(.*?)<(.*?)>/) {
|
||||||
$cmd =~ s/\$$key/$escaped_val/;
|
# If key specifies an attribute
|
||||||
|
$key = $1;
|
||||||
|
$attr = $2;
|
||||||
|
} else {
|
||||||
|
$key = $1;
|
||||||
|
}
|
||||||
|
#print("Matched: $key with attr: $attr\n");
|
||||||
|
if (not exists $xmlitem->{$key}) {
|
||||||
|
print("No xml entry with key: '$key' found. Skipping\n");
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $val;
|
||||||
|
if ($attr) {
|
||||||
|
# We want the attribue's value, not the contents of the tag
|
||||||
|
if (not exists $xmlitem->{$key}[0]->{$attr}) {
|
||||||
|
print("No xml attribute entry with key: '$key' and attr: '$attr' found. Skipping\n");
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
$val = $xmlitem->{$key}[0]->{$attr};
|
||||||
|
} else {
|
||||||
|
$val = $xmlitem->{$key}[1];
|
||||||
|
}
|
||||||
|
#print("Using: $val\n");
|
||||||
|
my $escaped_val = sh_escape($val);
|
||||||
|
$cmd =~ s/\$$fullmatch/$escaped_val/;
|
||||||
}
|
}
|
||||||
|
|
||||||
qx($cmd);
|
qx($cmd);
|
||||||
|
@ -207,7 +241,7 @@ sub handle_feed {
|
||||||
foreach (@$items) {
|
foreach (@$items) {
|
||||||
my $item_hash = item2hash($_);
|
my $item_hash = item2hash($_);
|
||||||
|
|
||||||
if (defined($LATEST{$fname}) and $LATEST{$fname} eq $item_hash->{guid}) {
|
if ($OPT_IGNORE_LAST == 0 && defined($LATEST{$fname}) and $LATEST{$fname} eq $item_hash->{guid}[1]) {
|
||||||
# Stop if we reached the last handled item
|
# Stop if we reached the last handled item
|
||||||
# or if there is no 'last' defined
|
# or if there is no 'last' defined
|
||||||
$last_hit = 1;
|
$last_hit = 1;
|
||||||
|
@ -215,7 +249,7 @@ sub handle_feed {
|
||||||
}
|
}
|
||||||
push(@item_hashes, $item_hash);
|
push(@item_hashes, $item_hash);
|
||||||
}
|
}
|
||||||
my $lastid = $item_hashes[0]->{guid};
|
my $lastid = $item_hashes[0]->{guid}[1];
|
||||||
#print(Dumper(\@item_hashes));
|
#print(Dumper(\@item_hashes));
|
||||||
if (not defined($lastid)) {
|
if (not defined($lastid)) {
|
||||||
if ($last_hit) {
|
if ($last_hit) {
|
||||||
|
@ -228,7 +262,7 @@ sub handle_feed {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (not defined($LATEST{$fname})) {
|
if ($OPT_IGNORE_LAST == 0 && not defined($LATEST{$fname})) {
|
||||||
# This is most likely the first run, do not run all of the backlog
|
# This is most likely the first run, do not run all of the backlog
|
||||||
# only the items after this one
|
# only the items after this one
|
||||||
$LATEST{$fname} = $lastid;
|
$LATEST{$fname} = $lastid;
|
||||||
|
@ -247,7 +281,9 @@ sub handle_feed {
|
||||||
exec_script($feed, $_);
|
exec_script($feed, $_);
|
||||||
}
|
}
|
||||||
|
|
||||||
save_latest($fname);
|
if ($OPT_IGNORE_LAST == 0) {
|
||||||
|
save_latest($fname);
|
||||||
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -260,17 +296,24 @@ if (! -f $CONFIG_FILE) {
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Undocumented debug option
|
||||||
|
$OPT_IGNORE_LAST = scalar @ARGV >= 1 && $ARGV[0] eq "-d";
|
||||||
|
|
||||||
parse_config();
|
parse_config();
|
||||||
#print(Dumper(\%CONFIG));
|
#print(Dumper(\%CONFIG));
|
||||||
#exit;
|
#exit;
|
||||||
parse_latest();
|
parse_latest();
|
||||||
|
|
||||||
my $ua = LWP::UserAgent->new();
|
my $ua = LWP::UserAgent->new();
|
||||||
|
$ua->agent('RssWatch');
|
||||||
#print(Dumper(\%CONFIG));
|
#print(Dumper(\%CONFIG));
|
||||||
foreach (@{$CONFIG{feeds}}) {
|
foreach (@{$CONFIG{feeds}}) {
|
||||||
my $item = $_;
|
my $item = $_;
|
||||||
my $resp = $ua->get($item->{url});
|
my $resp = $ua->get($item->{url});
|
||||||
next if not $resp->is_success;
|
if (not $resp->is_success) {
|
||||||
|
print("Failed to GET url: '$item->{url}' status: ", $resp->status_line, "\n");
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
my $fret = handle_feed($resp->decoded_content, $item);
|
my $fret = handle_feed($resp->decoded_content, $item);
|
||||||
if (not $fret) {
|
if (not $fret) {
|
||||||
|
|
Loading…
Reference in New Issue