lreminder: write to tmp directory
[lugs.git] / lreminder / reminder.pl
1 #!/usr/bin/perl
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
16 #
17 # Author: Steven Schubiger <stsc@refcnt.org>
18 # Last modified: Thu Jul 17 11:55:40 CEST 2014
19
20 use strict;
21 use warnings;
22 use lib qw(lib);
23 use constant true => 1;
24 use constant false => 0;
25
26 use DateTime ();
27 use DBI ();
28 use Encode qw(encode);
29 use File::Basename ();
30 use File::Spec ();
31 use FindBin qw($Bin);
32 use Getopt::Long qw(:config no_auto_abbrev no_ignore_case);
33 use Hook::Output::File ();
34 use LUGS::Events::Parser ();
35 use Mail::Sendmail qw(sendmail);
36 use Text::Wrap::Smart::XS qw(fuzzy_wrap);
37 use URI ();
38 use WWW::Mechanize ();
39
40 my $VERSION = '0.46';
41
42 #-----------------------
43 # Start of configuration
44 #-----------------------
45
46 my $Config = {
47 events_url => 'http://www.lugs.ch/lugs/termine/termine.txt',
48 form_url => 'http://lists.lugs.ch/reminder.cgi',
49 mail_from => 'reminder@lugs.ch',
50 dbase_name => '<hidden>',
51 dbase_user => '<hidden>',
52 dbase_pass => '<hidden>',
53 };
54
55 #---------------------
56 # End of configuration
57 #---------------------
58
59 my $dbh = DBI->connect("dbi:mysql(RaiseError=>1):$Config->{dbase_name}", $Config->{dbase_user}, $Config->{dbase_pass});
60 my $file = File::Spec->catfile('tmp', (URI->new($Config->{events_url})->path_segments)[-1]);
61
62 my ($test, $run) = (false, false);
63
64 {
65 getopts(\$test, \$run);
66 my $hook = Hook::Output::File->redirect(
67 stdout => File::Spec->catfile($Bin, 'stdout.out'),
68 stderr => File::Spec->catfile($Bin, 'stderr.out'),
69 );
70 fetch_and_write_events();
71 process_events();
72 }
73
74 sub getopts
75 {
76 my ($test, $run) = @_;
77
78 GetOptions(test => $test, run => $run) or exit;
79
80 if (not $$test || $$run) {
81 die "$0: neither --test nor --run specified, exiting\n";
82 }
83 elsif ($$test && $$run) {
84 die "$0: both --test and --run specified, exiting\n";
85 }
86 return; # --test or --run specified
87 }
88
89 sub fetch_and_write_events
90 {
91 my $mech = WWW::Mechanize->new;
92 my $http = $mech->get($Config->{events_url});
93
94 open(my $fh, '>', $file) or die "Cannot open $file for writing: $!\n";
95 print {$fh} $http->content;
96 close($fh);
97 }
98
99 sub init
100 {
101 my ($parser) = @_;
102
103 $$parser = LUGS::Events::Parser->new($file, {
104 filter_html => true,
105 tag_handlers => {
106 'a href' => [ {
107 rewrite => '$TEXT - <$HREF>',
108 fields => [ qw(responsible) ],
109 }, {
110 rewrite => '$TEXT - $HREF',
111 fields => [ qw(location more) ],
112 } ],
113 'br' => [ {
114 rewrite => '',
115 fields => [ qw(more) ],
116 } ],
117 },
118 strip_text => [ 'mailto:' ],
119 });
120 unlink $file;
121 }
122
123 sub process_events
124 {
125 my $parser;
126 init(\$parser);
127
128 while (my $event = $parser->next_event) {
129 my %event = (
130 year => $event->get_event_year,
131 month => $event->get_event_month,
132 day => $event->get_event_day,
133 color => $event->get_event_color,
134 );
135
136 my %sth;
137
138 $sth{subscribers} = $dbh->prepare('SELECT mail, mode, notify FROM subscribers');
139 $sth{subscribers}->execute;
140
141 while (my $subscriber = $sth{subscribers}->fetchrow_hashref) {
142 next unless $subscriber->{mode} == 2;
143
144 $sth{subscriptions} = $dbh->prepare('SELECT * FROM subscriptions WHERE mail = ?');
145 $sth{subscriptions}->execute($subscriber->{mail});
146
147 my $subscriptions = $sth{subscriptions}->fetchrow_hashref;
148 next unless $subscriptions->{$event{color}};
149
150 my $notify = DateTime->now(time_zone => 'Europe/Zurich');
151
152 $subscriber->{notify} ||= 0;
153
154 $notify->add(days => $subscriber->{notify});
155
156 if ($event{year} == $notify->year
157 && $event{month} == $notify->month
158 && $event{day} == $notify->day
159 ) {
160 send_mail($event, $subscriber->{mail});
161 }
162 }
163 }
164 }
165
166 sub send_mail
167 {
168 my ($event, $mail_subscriber) = @_;
169
170 my $year = $event->get_event_year;
171 my $month = $event->get_event_month;
172 my $simple_day = $event->get_event_simple_day;
173 my $wday = $event->get_event_weekday;
174 my $time = $event->get_event_time;
175 my $title = $event->get_event_title;
176 my $color = $event->get_event_color;
177 my $location = $event->get_event_location;
178 my $responsible = $event->get_event_responsible;
179 my $more = $event->get_event_more || '';
180
181 wrap_text(\$more);
182 chomp $more;
183 wrap_text(\$location);
184
185 my $i;
186 my %month_names = map { sprintf('%02d', ++$i) => $_ }
187 qw(Januar Februar Maerz April Mai Juni Juli August
188 September Oktober November Dezember);
189
190 my $month_name = $month_names{$month};
191
192 my $message = (<<MSG);
193 Wann:\t$wday, $simple_day. $month_name $year, $time Uhr
194 Was :\t$title
195 Wo :\t$location
196 Wer :\t$responsible
197 Info:\t$more
198
199 Web Interface:
200 $Config->{form_url}
201
202 ${\info_string()}
203 MSG
204
205 if ($run) {
206 sendmail(
207 From => $Config->{mail_from},
208 To => $mail_subscriber,
209 Subject => encode('MIME-Q', "LUGS Reminder - $title"),
210 Message => $message,
211 ) or die "Cannot send mail: $Mail::Sendmail::error";
212 }
213 elsif ($test) {
214 printf "[%s] <$mail_subscriber> ($color)\n", scalar localtime;
215 }
216 }
217
218 sub wrap_text
219 {
220 my ($text) = @_;
221
222 return unless length $$text;
223
224 my @chunks = fuzzy_wrap($$text, 70);
225
226 my $wrapped;
227 foreach my $chunk (@chunks) {
228 $wrapped .= ' ' x (defined $wrapped ? 8 : 0);
229 $wrapped .= "$chunk\n";
230 }
231 chomp $wrapped;
232
233 $$text = $wrapped;
234 }
235
236 sub info_string
237 {
238 my $script = File::Basename::basename($0);
239 my $modified = localtime((stat($0))[9]);
240
241 $modified =~ s/(?<=\b) (?:\d{2}\:?){3} (?=\b)//x;
242 $modified =~ s/\s+/ /g;
243
244 my $info = <<EOT;
245 --
246 running $script v$VERSION - last modified: $modified
247 EOT
248 return do { local $_ = $info; chomp while /\n$/; $_ };
249 }