Add the ability to select attribute values.

This commit is contained in:
x3 2024-03-17 21:50:48 +01:00
parent cbb7f861e1
commit f117f7d547
Signed by: x3
GPG Key ID: 7E9961E8AD0E240E
2 changed files with 61 additions and 17 deletions

View File

@ -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.

View File

@ -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) {