2 # Copyright (C) 2010 Alex Schroeder <alex@gnu.org>
4 # This program is free software: you can redistribute it and/or modify it under
5 # the terms of the GNU General Public License as published by the Free Software
6 # Foundation, either version 3 of the License, or (at your option) any later
9 # This program is distributed in the hope that it will be useful, but WITHOUT
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along with
14 # this program. If not, see <http://www.gnu.org/licenses/>.
26 # This script can be invoked as follows:
27 # perl rc2mail.pl -r http://localhost/cgi-bin/wiki \
29 # -m "alex:*secret*@mail.epfarms.org" \
30 # -f "kensanata@gmail.com" \
33 # -n Don't send email; useful if debugging the script
34 # -p Oddmuse administrator password
35 # -r Oddmuse full URL, eg. http://localhost/cgi-bin/wiki
36 # gets http://localhost/cgi-bin/wiki?action=rss;days=1;full=1;short=0
37 # And http://localhost/cgi-bin/wiki?action=subscriptionlist;raw=1;pwd=foo
38 # -m user:password@mailhost for sending email using SMTP Auth. Without this
39 # information, the script will send mail to localhost.
40 # -f email address to use as the sender.
41 # -t timestamp file; it's last modified date is used to determine when the
42 # the last run was and an appropriate URL is used. Instead of days=1 it
43 # will use from=n where n is the last modified date of the timestamp file.
44 # -q quiet (default: number of messages sent)
45 # -v verbose output (recipients)
49 getopts
('np:r:m:f:t:qvx', \
%opts);
50 my $nomail = exists $opts{n
};
51 my $verbose = exists $opts{v
};
52 my $quiet = exists $opts{q
};
53 my $debug = exists $opts{x
};
54 my $admin_password = $opts{p
};
56 die "Must provide an url with the -r option\n" unless $root;
57 $opts{m
} =~ /(.*?):(.*)\@(.*)/;
58 my ($user, $password, $host) = ($1, $2, $3);
59 die "Cannot parse -m " . $opts{m
} . "\n" if $opts{m
} && !$host;
61 die "Must provide sender using -f\n" if !$nomail && $host && !$from;
64 my $ua = new LWP
::UserAgent
;
66 # Fetch subscribers first because we need to verify the password
69 my $url = "$root?action=subscriptionlist;raw=1;pwd=$admin_password";
70 print "Getting $url\n" if $debug;
71 my $response = $ua->get($url);
72 die "Must provide an admin password with the -p option\n"
73 if $response->code == 403 and not $admin_password;
74 die "Must provide the correct admin password with the -p option\n"
75 if $response->code == 403;
76 die $url, "\n", $response->status_line unless $response->is_success;
79 foreach my $line (split(/\n/, $response->content)) {
80 my ($key, @entries) = split(/ +/, $line);
81 # print "Subscription for $key: ", join(', ', @entries), "\n";
82 $data{$key} = \
@entries;
84 print "Found " . scalar(keys(%data)) . " subscribers\n" if $debug;
92 return "from=" . (stat($ts))[9];
98 sub update_timestamp
{
99 # Only update timestamps if $ts is provided.
102 # File exists: update timestamp.
103 utime undef, undef, $ts;
105 # File does not exist: create it. File content is ignored on the
107 my $dir = dirname
($ts);
108 mkpath
($dir) unless -d
$dir;
109 open(F
, ">$ts") or warn "Unable to create $ts: $!";
115 my $url = "$root?action=rss;full=1;short=0;" . get_timestamp
();
116 print "Getting $url\n" if $debug;
117 my $response = $ua->get($url);
118 die $url, $response->status_line unless $response->is_success;
119 my $rss = new XML
::RSS
;
120 $rss->parse($response->content);
121 print "Found " . @
{$rss->{items
}} . " items.\n" if $debug;
126 my ($rss, $subscribers) = @_;
127 my @items = @
{$rss->{items
}};
128 die "No items to send\n" unless @items;
130 foreach my $item (@items) {
131 my $title = $item->{title
};
132 print "Looking at $title\n" if $debug;
135 my @subscribers = @
{$subscribers->{$id}};
136 print "Subscribers: ", join(', ', @subscribers), "\n" if $debug;
137 $sent += @subscribers;
138 send_file
($id, $title, $item, @subscribers);
140 print "$sent messages sent\n" if $sent;
144 my ($id, $title, $item, @subscribers) = @_;
145 return unless @subscribers;
146 my $fh = File
::Temp
->new(SUFFIX
=> '.html');
147 binmode($fh, ":utf8");
148 warn "No content for $title\n" unless $item->{description
};
149 my $link = $item->{link};
150 my $sub = "$root?action=subscriptions";
151 my $text = qq(<p
>Visit
<a href
="$link">$title</a
>)
152 . qq( or <a href
="$sub">manage your subscriptions
</a>.</p><hr
/>)
153 . $item->{description
};
154 # prevent 501 Syntax error - line too long
155 $text =~ s/<(p|h[1-6]|[duo]l|pre|li|form|div|blockquote|hr|table|tr)>/\r\n<$1>/gi;
158 foreach my $subscriber (@subscribers) {
159 send_mail
($subscriber, $title, $fh);
164 my ($subscriber, $title, $fh) = @_;
165 print "Skipping mail to $subscriber...\n" if $debug && $nomail;
167 my $mail = new MIME
::Entity
->build(To
=> $subscriber,
173 print "Sending $title to $subscriber using ${user}\@${host}\n" if $verbose;
175 require Net
::SMTP
::TLS
;
176 my $smtp = Net
::SMTP
::TLS
->new($host,
178 Password
=> $password);
180 $smtp->to($subscriber);
182 $smtp->datasend($mail->stringify);
187 require Net
::SMTP
::SSL
;
188 my $smtp = Net
::SMTP
::SSL
->new($host, Port
=> 465);
189 $smtp->auth($user, $password);
191 $smtp->to($subscriber);
193 $smtp->datasend($mail->stringify);
198 my @recipients = $mail->smtpsend();
200 print "Sent $title to ", join(', ', @recipients), "\n" unless $quiet;
202 print "Failed to send $title to $subscriber\n" unless $quiet;
209 return unless @
{$rss->{items
}};
210 my $subscribers = get_subscribers
();
211 return unless %{$subscribers};
212 send_files
($rss, $subscribers);