diff --git a/loadshedding-schedule.pl b/loadshedding-schedule.pl new file mode 100755 index 0000000..77e5dc4 --- /dev/null +++ b/loadshedding-schedule.pl @@ -0,0 +1,149 @@ +#!/usr/bin/perl +# Requires HTML::TableExtract and MIME::Lite +# On Ubuntu/Debian: sudo aptitude install libhtml-tableextract-perl libmime-lite-perl +# +# Downloads, caches, parses and prints the current load shedding schedule in Cape Town +# Written by Timothy Allen + +use strict; +use warnings; +use Getopt::Long; +use LWP::Simple; +use MIME::Lite; +use Time::Piece; +use Time::Seconds; +use List::MoreUtils; +use HTML::TableExtract; + +my $url = "http://www.capetown.gov.za/en/electricity/Pages/LoadShedding.aspx"; +my $schedule_file = "$ENV{HOME}/.loadshedding_schedule"; + +my @recipients = (); +my @zones; +my $verbose = 0; +GetOptions( + "t|to=s{1,}" => \@recipients, + "v|verbose" => \$verbose, + "z|zones=i{1,}" => \@zones, +) or die "Usage: $0 [-t ... ] -z ...\n"; + +if (scalar @zones < 1) { + die "Usage: $0 -z \n"; +} + +my ($last_schedule, $current_schedule, $last_str, $current_str, $last_time, $current_time, $diff); +$current_time = localtime; +$current_str = $current_time->strftime('%Y-%m-%dT%H:%M:%S%z'); + +if (-f $schedule_file) { + open STATUS, "<", $schedule_file or die $!; + my @lines = ; + close STATUS or die $!; + chomp($last_str = $lines[0]) if ($lines[0] =~ s#^\s*Time:\s*##i); + chomp($last_schedule = join "\n", @lines); + if ($last_str && $last_str =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{4}/) { + $last_time = Time::Piece->strptime($last_str, '%Y-%m-%dT%H:%M:%S%z') or warn "Time error"; + $diff = $current_time - $last_time; + } else { + undef $last_str; + } +} + +# Only refetch the page every hour +my $cached = 0; +if (!$last_str || int($diff->hours) >= 1) { + my $content = LWP::Simple::get $url or die "Couldn't get $url"; + if ($content =~ m#]*id="WebPartWPQ2"[^>]*>\s*(]*>.*?)#ims) { + $current_schedule = $1; + } else { + die "Unable to parse schedule; load shedding page must have changed.\n"; + } + open STATUS, ">", $schedule_file or die $!; + print STATUS "Time: $current_str\n"; + print STATUS "$current_schedule\n"; + close STATUS or die $!; +} else { + $current_schedule = $last_schedule; + $cached = 1; +} + +my ($html, $table); +$current_schedule =~ s#<.?span[^>]*>##imsg; +$current_schedule =~ s#\s*(<[^>]*>)\s*#$1#imsg; + +$html = HTML::TableExtract->new( + br_translate => 0, +); +$html->parse($current_schedule); +if (scalar $html->tables() < 1) { + unlink $schedule_file if (-f $schedule_file); + die "No table found"; +} +$table = $html->first_table_found(); + +# Create a hash (ref) of hash (ref)s with the load shedding info +my $shedding = {}; +my ($title, $header_row, @rows) = $table->rows; +my ($empty, @headers) = @$header_row; +foreach my $row (@rows) { + my $zone_times = {}; + my ($stage, @times) = @$row; + foreach my $i (0 .. $#headers) { + my $key = $headers[$i]; + $zone_times->{$key} = $times[$i]; + } + $shedding->{$stage} = $zone_times; +} + +my $day = shift @$title; +$day =~ s/.*?(\d+\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec).*?\s+\d{4}?).*?/$1/ or undef $day; +my $subject = "Load shedding times"; +$subject .= " for $day" if defined $day; + +my $output = "If there is load shedding today"; +$output .= " ($day)" if defined $day; +$output .= ", it will be on the following schedule. We will alert you if and when the City of Cape Town website announces the actual commencement of load shedding.\n\n"; + +foreach my $zone (List::MoreUtils::uniq @zones) { + $output .= "Load shedding hours for zone $zone"; + $output .= " on $day" if defined $day; + $output .= "\n"; + foreach my $stage (sort keys %$shedding) { + # Get applicable sets of hours for this zone in each stage + my @hours; + foreach my $key (sort keys %{$shedding->{$stage}}) { + push @hours, $key if grep /\b$zone\b/, $shedding->{$stage}->{$key}; + } + $output .= sprintf "%-10s ", "$stage:"; + if (scalar @hours > 0) { + my $str = join ", ", @hours; + $str =~ s/(.*),/$1 and/; + $output .= "$str\n"; + } else { + $output .= "No load shedding in zone $zone"; + $output .= " for $day" if defined $day; + $output .= "\n"; + } + } + $output .= "\n"; +} +if ($cached) { + $output .= "Time since last schedule download: " . $diff->pretty . "\n" if $verbose; +} + +if (scalar @recipients < 1) { + print $output; +} else { + foreach my $to (@recipients) { + my $message = MIME::Lite->new( + From => 'Loadshedding Alerts ', + To => $to, + Subject => $subject, + Data => $output, + ); + # send the message + $message->send(); + } +} + +# vim: set indentexpr= expandtab sw=2 softtabstop=2 tw=10000 : diff --git a/loadshedding-status.pl b/loadshedding-status.pl new file mode 100755 index 0000000..e4187f5 --- /dev/null +++ b/loadshedding-status.pl @@ -0,0 +1,94 @@ +#!/usr/bin/perl +# +# Downloads, parses and prints the current load shedding status. +# It is suggested that you run this script every 5-20 minutes. +# +# Written by Timothy Allen + +use strict; +use warnings; +use Getopt::Long; +use LWP::Simple; +use MIME::Lite; +use Time::Piece; +use Time::Seconds; + +my $url = "http://www.capetown.gov.za/en/electricity/Pages/LoadShedding.aspx"; +my $status_file = "$ENV{HOME}/.loadshedding_status"; + +my @recipients = (); +my $verbose = 0; +GetOptions( + "t|to=s{1,}" => \@recipients, + "v|verbose" => \$verbose, +) or die "Usage: $0 [-t ... ] \n"; + + +my ($last_status, $current_status, $last_str, $current_str, $last_time, $current_time, $diff); +$current_time = localtime; +$current_str = $current_time->strftime('%Y-%m-%dT%H:%M:%S%z'); + +if (-f $status_file) { + open STATUS, "<", $status_file or die $!; + my @lines = ; + close STATUS or die $!; + chomp($last_status = $lines[0]) if ($lines[0] =~ s#^\s*Status:\s*##i); + chomp($last_str = $lines[1]) if ($lines[1] =~ s#^\s*Time:\s*##i); + if ($last_str) { + $last_time = Time::Piece->strptime($last_str, '%Y-%m-%dT%H:%M:%S%z'); + $diff = $current_time - $last_time; + } +} + +my ($content, $output); +unless ($content = get $url) { + warn "Couldn't get $url"; + $content = get "http://www.capetown.gov.za/loadshedding/Loadshedding.html" or die "Couldn't get alternate url"; +} + +if ($content =~ m#]*id="WebPartWPQ3"[^>]*>.*?]*>(.*?)#ims) { + $current_status = $1; + $current_status =~ s#\s*(]*>|\n)\s*# #imsg; + $current_status =~ s#<.*?>##imsg; +} elsif ($content =~ m#]*class="MainHeadText"[^>]*>.{0,1500}?
(.*?)
#ims) { + $current_status = $1; + $current_status =~ s#\s*(]*>|\n)\s*# #imsg; + $current_status =~ s#<.*?>##imsg; +} else { + die "Unable to parse status; load shedding page must have changed.\n"; +} + +if (($current_status !~ /no items/i) && + (!defined $last_status || $current_status ne $last_status)) { + open STATUS, ">", $status_file or die $!; + print STATUS "Status: $current_status\n"; + print STATUS "Time: $current_str\n"; + close STATUS or die $!; + if (defined $last_status) { + $output .= "The City of Cape Town's website indicates that the load shedding status has changed from: $last_status to: $current_status\n"; + } else { + $output .= "Load shedding status for the City of Cape Town: $current_status\n"; + } + $output .= "Time since last change: " . $diff->pretty . "\n" if defined $diff and $verbose; +} else { + $output .= "Load shedding status: $current_status\n" if $verbose; +} + +if (defined $output) { + if (scalar @recipients < 1) { + print $output; + } else { + foreach my $to (@recipients) { + my $message = MIME::Lite->new( + From => 'Loadshedding Alerts ', + To => $to, + Subject => 'Load shedding status change', + Data => $output, + ); + # send the message + $message->send(); + } + } +} + +# vim: set indentexpr= expandtab sw=2 softtabstop=2 tw=10000 :