123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- #! /usr/bin/perl
- # Copyright (C) 2010–2019 Alex Schroeder <alex@gnu.org>
- # This program is free software: you can redistribute it and/or modify it under
- # the terms of the GNU General Public License as published by the Free Software
- # Foundation, either version 3 of the License, or (at your option) any later
- # version.
- #
- # This program is distributed in the hope that it will be useful, but WITHOUT
- # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License along with
- # this program. If not, see <http://www.gnu.org/licenses/>.
- package OddMuse;
- use Getopt::Std;
- use XML::RSS;
- use LWP::UserAgent;
- use MIME::Entity;
- use File::Temp;
- use File::Basename;
- use File::Path;
- use Net::SMTP;
- use Authen::SASL qw(Perl);
- # This script can be invoked as follows:
- # perl rc2mail.pl -r http://localhost/cgi-bin/wiki \
- # -p test \
- # -m "alex:*secret*@mail.epfarms.org" \
- # -f "kensanata@gmail.com" \
- # -t ~/.rc2mail
- # -n Don't send email; useful if debugging the script
- # -p Oddmuse administrator password
- # -r Oddmuse full URL, eg. http://localhost/cgi-bin/wiki
- # gets http://localhost/cgi-bin/wiki?action=rss;days=1;full=1;short=0
- # And http://localhost/cgi-bin/wiki?action=subscriptionlist;raw=1;pwd=foo
- # -m user:password@mailhost for sending email using SMTP Auth. Without this
- # information, the script will send mail to localhost. The host can end
- # in a port number, e.g. "kensanata:*secret*@smtp.migadu.com:587"
- # -f email address to use as the sender.
- # -t timestamp file; it's last modified date is used to determine when the
- # the last run was and an appropriate URL is used. Instead of days=1 it
- # will use from=n where n is the last modified date of the timestamp file.
- # -q quiet (default: number of messages sent)
- # -v verbose output (recipients)
- # -x debug output
- my %opts;
- getopts('np:r:m:f:t:qvx', \%opts);
- my $nomail = exists $opts{n};
- my $verbose = exists $opts{v};
- my $quiet = exists $opts{q};
- my $debug = exists $opts{x};
- my $admin_password = $opts{p};
- my $root = $opts{r};
- die "Must provide an url with the -r option\n" unless $root;
- $opts{m} =~ /(.*?):(.*)\@(.*)/;
- my ($user, $password, $host) = ($1, $2, $3);
- die "Cannot parse -m " . $opts{m} . "\n" if $opts{m} && !$host;
- my $from = $opts{f};
- die "Must provide sender using -f\n" if !$nomail && $host && !$from;
- my $ts = $opts{t};
- my $ua = new LWP::UserAgent;
- # Fetch subscribers first because we need to verify the password
- sub get_subscribers {
- my $url = "$root?action=subscriptionlist;raw=1;pwd=$admin_password";
- print "Getting $url\n" if $debug;
- my $response = $ua->get($url);
- die "Must provide an admin password with the -p option\n"
- if $response->code == 403 and not $admin_password;
- die "Must provide the correct admin password with the -p option\n"
- if $response->code == 403;
- die $url, "\n", $response->status_line unless $response->is_success;
- my %data;
- foreach my $line (split(/\n/, $response->content)) {
- my ($key, @entries) = split(/ +/, $line);
- # print "Subscription for $key: ", join(', ', @entries), "\n";
- $data{$key} = \@entries;
- }
- print "Found " . scalar(keys(%data)) . " subscribers\n" if $debug;
- return \%data;
- }
- # Fetch RSS feed
- sub get_timestamp {
- if ($ts and -f $ts) {
- return "from=" . (stat($ts))[9];
- } else {
- return "days=1";
- }
- }
- sub update_timestamp {
- # Only update timestamps if $ts is provided.
- return unless $ts;
- if (-f $ts) {
- # File exists: update timestamp.
- utime undef, undef, $ts;
- } else {
- # File does not exist: create it. File content is ignored on the
- # next run!
- my $dir = dirname($ts);
- mkpath($dir) unless -d $dir;
- open(F, ">$ts") or warn "Unable to create $ts: $!";
- close(F);
- }
- }
- sub get_rss {
- my $url = "$root?action=rss;full=1;short=0;" . get_timestamp();
- print "Getting $url\n" if $debug;
- my $response = $ua->get($url);
- die $url, $response->status_line unless $response->is_success;
- my $rss = new XML::RSS;
- $rss->parse($response->content);
- print "Found " . @{$rss->{items}} . " items.\n" if $debug;
- return $rss;
- }
- sub send_files {
- my ($rss, $subscribers) = @_;
- my @items = @{$rss->{items}};
- die "No items to send\n" unless @items;
- my $sent = 0;
- foreach my $item (@items) {
- my $title = $item->{title};
- print "Looking at $title\n" if $debug;
- my $id = $title;
- $id =~ s/ /_/g;
- my @subscribers = @{$subscribers->{$id}};
- print "Subscribers: ", join(', ', @subscribers), "\n" if $debug;
- $sent += @subscribers;
- send_file($id, $title, $item, @subscribers);
- }
- print "$sent messages sent\n" if $sent and not $quiet;
- }
- sub send_file {
- my ($id, $title, $item, @subscribers) = @_;
- return unless @subscribers;
- my $fh = File::Temp->new(SUFFIX => '.html');
- binmode($fh, ":utf8");
- warn "No content for $title\n" unless $item->{description};
- my $link = $item->{link};
- my $sub = "$root?action=subscriptions";
- my $text = qq(<p>Visit <a href="$link">$title</a>)
- . qq( or <a href="$sub">manage your subscriptions</a>.</p><hr />)
- . $item->{description};
- # prevent 501 Syntax error - line too long
- $text =~ s/<(p|h[1-6]|[duo]l|pre|li|form|div|blockquote|hr|table|tr)>/\r\n<$1>/gi;
- print $fh $text;
- $fh->close;
- foreach my $subscriber (@subscribers) {
- send_mail($subscriber, $title, $fh);
- }
- }
- sub send_mail {
- my ($subscriber, $title, $fh) = @_;
- print "Skipping mail to $subscriber...\n" if $debug && $nomail;
- return if $nomail;
- my $mail = new MIME::Entity->build(To => $subscriber,
- From => $from,
- Subject => $title,
- Path => $fh,
- Type=> "text/html");
- if ($host) {
- print "$root\nSending $title to $subscriber using ${user}\@${host}\n" if $verbose;
- my $smtp = Net::SMTP->new($host, Debug => $debug);
- $smtp->starttls();
- # the following requires Authen::SASL!
- $smtp->auth($user, $password);
- $smtp->mail($from);
- if ($smtp->to($subscriber)) {
- $smtp->data;
- $smtp->datasend($mail->stringify);
- $smtp->dataend;
- } else {
- warn "Error: ", $smtp->message();
- }
- $smtp->quit;
- }
- }
- sub main {
- my $rss = get_rss();
- if (@{$rss->{items}}) {
- my $subscribers = get_subscribers();
- if (%{$subscribers}) {
- send_files($rss, $subscribers);
- }
- }
- update_timestamp();
- }
- main ();
|