#!/usr/bin/perl # 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 2 # 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, published at # http://www.gnu.org/copyleft/gpl.html use Date::Parse; use Getopt::Long; use HTTP::Request::Common qw(GET); use Getopt::Long; use Date::Calc; use Data::Dumper; # We make our own specialization of LWP::UserAgent that asks for # user/password if document is protected. { use LWP::UserAgent; package WikiRequestAgent; @ISA = qw(LWP::UserAgent); my ($password, $userid); sub new { $password = pop; $userid = pop; my $self = LWP::UserAgent::new(@_); $self->agent("lwp-request/$main::VERSION"); $self; } sub get_basic_credentials { return ($userid, $password); } } my %opts; # parsed options my $securityFilter = "[\\\*\?\~\^\$\@\%\`\"\'\&\;\|\<\>\x00-\x1F]"; my %options = ( "password" => ["s", undef, "TheBazaar password"], "userid" => ["s", undef, "TheBazaar userid"], "zone|web" => ["s", undef, "zone (ie. TWiki web) to read calendar events from"], "topic" => ["s", undef, "topic to read calendar events from"], "fetchurl" => ["s", "http://wiki.sh.mvista.com/thebazaar/data/%ZONE/\%TOPIC.txt", "URL to open topic text with"], "getalarms" => ["s", "atq", "command used to fetch existing alarms"], "setalarm" => ["s", "at \"%HH:%MM %MO/%DD/%YY\"", "command to set an alarm"], "rmalarm" => ["s", "atrm %JOB", "command used to delete existing alarm"], "getalarmsregex" => ["s", '(\d+)\s+([^\s]+\s[^\s]+)', "regex to parse alarm string"], "tz" => ["s", "-5", "GMT offset"], "preset" => ["s", "5", "minutes to preset alarm by before event"], "alarmjob" => ["s", "\$HOME/bin/bong 'Alarm: %DESC'", "Job to run when alarm expires" ], "help" => [undef, undef, "Ask for help"], ); my %sched; print STDERR "WikiCalendar2alarm version 1.3, \n"; &readOptions; if ($opts{help}) { &displayHelp; exit; } my ($zone, $topic); if ($ARGV[0] ne "") { ($zone, $topic) = split /\./, shift @ARGV; } $zone = $zone || $opts{zone}; $topic = $topic || $opts{topic}; print STDERR "Downloading calendar from to $zone.$topic\n"; &getatq(); &parseCalendar(getCalendar($zone, $topic)); exit; sub parseCalendar { my ($text) = @_; # keep only bullet lines my @bullets = grep { /^\s+\*/ } split( /[\n\r]+/, $text ); # bail out early if no events unless( @bullets ) { return; } &fetchDateParseDays( \@bullets, %options ); &schedAlarms(); } sub schedAlarms { my @now = Date::Calc::Localtime(); my $now = Date::Calc::Mktime(@now[0,1,2,3,4,5]); foreach my $key (keys %sched) { # delete existing alarm if (defined $sched{$key}{at}) { my $cmd = $opts{rmalarm}; $cmd =~ s/\%JOB/$sched{$key}{at}/; `$cmd`; } # create new alarms if (defined $sched{$key}{cal}) { # get and format the time my ($mm, $hh, $dd, $mo, $yy) = split ",", $key; # Don't set an alarm for the past $yy+=1900; next if ($now > Date::Calc::Mktime($yy,$mo,$dd,$hh,$mm,0)); # back the alarm off by preset ($yy,$mo,$dd,$hh,$mm,undef) = Date::Calc::Add_Delta_DHMS($yy,$mo,$dd,$hh,$mm,0, 0, 0, -$opts{preset}, 0); $mm = sprintf("%02d",$mm); $hh = sprintf("%02d",$hh); # Get the alarm set command my $cmd = $opts{setalarm}; $cmd =~ s/\%HH/$hh/; $cmd =~ s/\%MM/$mm/; $cmd =~ s/\%DD/$dd/; $cmd =~ s/\%MO/$mo/; $cmd =~ s/\%YY/$yy/; # get the alarm job command my $job = $opts{alarmjob}; $job =~ s/\%DESC/$sched{$key}{cal}/; $job =~ s/\%HH/$hh/; $job =~ s/\%MM/$mm/; $job =~ s/\%DD/$dd/; $job =~ s/\%MO/$mo/; $job =~ s/\%YY/$yy/; # Set the alarm! `echo "$job" | $cmd`; } } } sub fetchDateParseDays { if (defined $Date::Parse::VERSION) { my( $refBullets, %options) = @_; my @localBullets = @{$refBullets}; @{$refBullets} = (); my @res; foreach my $bullet ( @localBullets ) { my ($timedescr) = ( $bullet =~ /^\s+\*\s([^|]*)/ ); my @timedescr = split "-", $timedescr; my $text = pop @timedescr; my $date1 = join "-", @timedescr; my ($ss1,$mm1,$hh1,$day1,$month1,$year1,$zone1) = &Date::Parse::strptime ( $date1 ); # minimal requirements are a valid hour and minute if ( (defined $hh1) and (defined $mm1) ) { # fixup the month $month1++; # Correct for timezone of requested calendar my $zone2 = $opts{tz}; $zone2 = $zone2 * 3600; my $offset = -$zone1+$zone2; # If someone sends really bogus info from a calendar we just want to silently ignore eval { ($year1, $month1, $day1, $hh1, $mm1, $ss1) = Date::Calc::Add_Delta_YMDHMS($year1, $month1, $day1, $hh1, $mm1, $ss1, 0, 0, 0, 0, 0, $offset); }; warn "$@ from line \"$bullet\" (" . join(",", $year1, $month1, $day1, $hh1, $mm1, $ss1, 0, 0, 0, 0, 0, $offset) . ")" if $@; # valid my $datestr = join ",", ($mm1, $hh1, $day1, $month1, $year1); $datestr =~ s/00/0/g; $datestr =~ s/,0(\d)/,$1/g; $text =~ s/$securityFilter//go; $sched{$datestr}{cal} = $text; } } } } sub getCalendar { my ($zone, $topic) = @_; my $ua = WikiRequestAgent->new($opts{userid}, $opts{password}); my $url = $opts{fetchurl}; $url =~ s/\%ZONE/$zone/g; $url =~ s/\%TOPIC/$topic/g; print STDERR "$url\n"; my $req = GET "$url"; my $content = $ua->request($req)->content; # Fixup formatting $content =~ s/\t/ /g; $content =~ tr/ //d; # Parse out any EVENTS variables my ($events) = ($content =~ m/\* Set EVENTS = ([^\n]+)/); print STDERR "EVENTS = $events\n"; foreach my $zt ( split ",", $events ) { $zt =~ tr/ //d; my ($z, $t) = split /\./, $zt; if ("$z.$t" ne "$zone.$topic") { $content .= getCalendar($z, $t); } } return $content; } sub getatq { open GETALARMS, "$opts{getalarms}|" || die "Could not execute $opts{getalarms}"; while () { my ($atnum, $datestr) = ($_ =~ /$opts{getalarmsregex}/); my @date = strptime($datestr); shift @date; pop @date; my $datestr = join ",", @date; $datestr =~ s/00/0/g; $datestr =~ s/,0(\d)/,$1/g; $sched{$datestr}{at}=$atnum; } } sub displayHelp { print STDERR "\nUsage: WikiCalendar2Alarm Zone.TopicName\n\n"; print STDERR "Options:\n"; foreach my $opt (sort keys %options) { print STDERR " --$opt: " . $options{$opt}[2] . " (" . $options{$opt}[1] . ")\n"; } } sub readOptions { my @optdef; foreach my $opt (sort keys %options) { if (defined $options{$opt}[0]) { push @optdef, "$opt=" . $options{$opt}[0]; } else { push @optdef, "$opt"; } } GetOptions(\%opts, @optdef); foreach my $opt (sort keys %options) { push @optdef, "$opt=" . $options{$opt}[0]; $opts{$opt} = $opts{$opt} || $options{$opt}[1]; } }