From 4476d6adf1dd999a66acb2baf6dd3ff9ace5276f Mon Sep 17 00:00:00 2001 From: x3 Date: Thu, 7 Mar 2024 19:56:31 +0100 Subject: [PATCH] Initial commit. desu --- rss-watch.pl | 272 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100755 rss-watch.pl diff --git a/rss-watch.pl b/rss-watch.pl new file mode 100755 index 0000000..1eaadd0 --- /dev/null +++ b/rss-watch.pl @@ -0,0 +1,272 @@ +#!/usr/bin/perl +use strict; +use warnings; + +use Data::Dumper; +use File::Basename; +use File::Path qw(make_path); +use LWP::UserAgent; +use XML::Parser; + +my $CONFIG_FILE = "$ENV{HOME}/.config/rss-watch/config"; +my $LATEST_DIR = "$ENV{HOME}/.local/share/rss-watch/latest"; +my %LATEST; +my %CONFIG; +my $XPARSER = XML::Parser->new(Style => 'Tree'); + +sub item2hash { + my ($xml) = @_; + my %ret = (); + + for (my $i = 1; $i < scalar @$xml; $i+=2) { + #if (ref($xml->[$i]) eq "ST + my $key = $xml->[$i]; + next if $key eq 0; + my $val = $xml->[$i+1]; + next if $val->[1] != 0; # only store string values here + $ret{$key} = $val->[2]; + } + + return \%ret; +} + +sub getxmlchild { + my ($xo, $ctname) = @_; + my $start = 0; + $start = 1 if (ref($xo->[0]) eq 'HASH'); + + for (my $i = $start; $i < scalar @$xo; $i+=2) { + if ($xo->[$i] eq $ctname) { + return $xo->[$i+1]; + } + } + return undef; +} + +sub filterxmlnodes { + my ($xo, $ctname) = @_; + my @ret = (); + + for (my $i = 1; $i < scalar @$xo; $i+=2) { + #print("Processing: ", $xo->[$i], " \n"); + if ($xo->[$i] eq $ctname) { + #print("Added\n"); + #print(scalar @ret, "\n"); + push(@ret, $xo->[$i+1]); + } + } + return \@ret; +} + +sub readfile { + my ($fp) = @_; + local $/ = undef; + open my $fh, "<", $fp or die "could not open $fp $!"; + my $c = <$fh>; + local $/ = "\n"; + chomp($c); + close($fh); + return $c; +} + +sub writefile { + my ($fp, $cont) = @_; + open my $fh, ">", $fp or die "could not open $fp $!"; + print($fh $cont, "\n"); + close($fh) or die "Couldn't close file $fp $!"; +} + +sub parse_latest { + if (not -d $LATEST_DIR) { + my $crt = make_path($LATEST_DIR); + if ($crt == 0) { + print("Cannot create latest directory!\n"); + exit 1; + } + } + + my @files = glob($LATEST_DIR . '/*'); + foreach my $fp (@files) { + my $key = basename($fp); + + $LATEST{$key} = readfile($fp); + } + #print("LATEST\n"); + #print(Dumper(\%LATEST)); +} + +sub save_latest { + my ($fname) = @_; + return if not defined($LATEST{$fname}); + writefile("$LATEST_DIR/$fname", $LATEST{$fname}); +} + +sub parse_config { + open(my $fh, "<", $CONFIG_FILE) or die('Cannot open config file'); + my @feeds = (); + my %curr_feed = (); + + my $line = 0; + while (<$fh>) { + $line++; + chomp; + my $l = $_; + + next if ($l eq "" or $l =~ m/^#/); + + if ($l =~ m/^\[feed\s+(.*)\]$/) { + my $name = $1; + + if (scalar %curr_feed > 0) { + push(@feeds, {%curr_feed}); + } + %curr_feed = (); + + if (not defined($name)) { + print("No name for need!\n"); + exit 1; + } + $curr_feed{name} = $name; + next; + } + + #print("Line: $l\n"); + my @parts = split(/\s+=\s+/, $l, 2); + if (scalar @parts != 2) { + print("Error in config at line: $line\n"); + exit 1; + } + my $key = $parts[0]; + my $val = $parts[1]; + if ($key eq "script") { + # Allow more than one script to be specified + if (defined($curr_feed{script})) { + push(@{$curr_feed{script}}, $val); + } else { + $curr_feed{script} = [ $val ]; + } + } else { + # Normal operation for other keys + $curr_feed{$parts[0]} = $parts[1]; + } + } + if (scalar %curr_feed > 0) { + push(@feeds, {%curr_feed}); + } + + $CONFIG{feeds} = \@feeds; + + close($fh); +} + +sub exec_script { + my ($feed, $xmlitem) = @_; + + my $cmdArr = $feed->{script}; + #print(Dumper($xmlitem)); + foreach my $cmdRef (@$cmdArr) { + my $cmd = $cmdRef; # Copy it + + while ($cmd =~ /[^\\]\$([\w:]+)/g) { + my $key = $1; + #print("Matched: $key\n"); + next if not exists $xmlitem->{$key}; + $cmd =~ s/\$$key/$xmlitem->{$key}/; + } + + qx($cmd); + } +} + +sub handle_feed { + my ($xml, $feed) = @_; + my $xobj = $XPARSER->parse($xml); + my $fname = $feed->{name}; + #print(Dumper(\$xobj)); + + #$xobj{rss}; + #print($xobj[0]); + my $rss = getxmlchild($xobj, "rss"); + return undef if not defined($rss); + $rss = getxmlchild($rss, "channel"); + return undef if not defined($rss); + my $items = filterxmlnodes($rss, "item"); + return undef if (scalar @$items == 0); + #print(Dumper($items)); + + my $last_hit = 0; + # Convert xml items into perl hashes + my @item_hashes = (); + foreach (@$items) { + my $item_hash = item2hash($_); + + if (defined($LATEST{$fname}) and $LATEST{$fname} eq $item_hash->{guid}) { + # Stop if we reached the last handled item + # or if there is no 'last' defined + $last_hit = 1; + last; + } + push(@item_hashes, $item_hash); + } + my $lastid = $item_hashes[0]->{guid}; + #print(Dumper(\@item_hashes)); + if (not defined($lastid)) { + if ($last_hit) { + # No new entries + return 1; + } else { + # Last is not hit, but items are empty? + # Possibly an error + return 0; + } + } + + if (not defined($LATEST{$fname})) { + # This is most likely the first run, do not run all of the backlog + # only the items after this one + $LATEST{$fname} = $lastid; + return 1; + } + if (defined($LATEST{$fname}) and not $last_hit) { + print("Possible missed releases in feed '$fname'\n"); + } + + $LATEST{$fname} = $lastid; + + #print("Last id: $lastid\n"); + + foreach (@item_hashes) { + exec_script($feed, $_); + } + + save_latest($fname); + + return 1; +} + + + + +if (! -f $CONFIG_FILE) { + print("No config file exists, create one!\n"); + exit 1; +} + +parse_config(); +#print(Dumper(\%CONFIG)); +#exit; +parse_latest(); + +my $ua = LWP::UserAgent->new(); +#print(Dumper(\%CONFIG)); +foreach (@{$CONFIG{feeds}}) { + my $item = $_; + my $resp = $ua->get($item->{url}); + next if not $resp->is_success; + + my $fret = handle_feed($resp->decoded_content, $item); + if (not $fret) { + print("Getting feed failed\n"); + } + #print($resp->decoded_content, "\n"); +}