A while ago, I got an API key for Forecast.io, with the intention of writing something that takes a look at the day to say whether I could bike to work without getting weathered on. I have not written that, and I certainly haven't started biking to work on a regular basis.
But now, I have written something that uses that data and presents it via notify-send and Pushover. I use notify-send a lot, but as the sources change, I have to change my scripts.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env perl | |
# forecast.pl is Copyright (C) 2014, by Dave Jacoby. | |
# It is free software; you can redistribute it and/or modify it under the terms of either: | |
# | |
# a) the GNU General Public License as published by the Free Software Foundation; either external linkversion 1, or (at your option) any later versionexternal link, or | |
# | |
# b) the "Artistic License". | |
# interface to forecast.io which uses Linux notify-send to | |
# send a variety of weather information to the user's awareness | |
# Usage: | |
# --current , -c cureent conditions | |
# --alerts, -a alerts; current weather alerts if any | |
# --hourly, -h hourly forecasts for the next four hours | |
# --daily, -d forecast for the next two days | |
# You can use all the flags at once. No flags defaults to current conditions | |
use feature qw{ say state } ; | |
use strict ; | |
use warnings ; | |
use utf8 ; | |
use Carp ; | |
use Data::Dumper ; | |
use DateTime ; | |
use Getopt::Long ; | |
use JSON ; | |
use LWP::UserAgent ; | |
use YAML qw{ LoadFile } ; | |
my $config = config() ; | |
my $url = | |
'https://api.forecast.io/forecast/' | |
. $config->{ apikey } . '/' | |
. ( join ',', map { $config->{ $_ } } qw{ latitude longitude } ) ; | |
my $agent = LWP::UserAgent->new( ssl_opts => { verify_hostname => 0 } ) ; | |
my $response = $agent->get( $url ) ; | |
if ( $response->is_success ) { | |
my $content = $response->content ; | |
my $forecast = decode_json $content ; | |
my $current = $forecast->{ currently } ; | |
my $alerts = $forecast->{ alerts } ; | |
my $hourly = $forecast->{ hourly } ; | |
my $daily = $forecast->{ daily } ; | |
say join "\n", | |
( map { join "\t", '+', $_, ref $forecast->{ $_ } } sort keys %$forecast ), '' | |
if $config->{ verbose } ; | |
current_conditions( $current ) if $config->{ current } ; | |
daily_forecasts( $daily ) if $config->{ daily } ; | |
hourly_forecasts( $hourly ) if $config->{ hourly } ; | |
handle_alerts( $alerts ) if $config->{ alerts } ; | |
} | |
exit ; | |
sub current_conditions { | |
my $current = shift ; | |
my $temp = int $current->{ temperature } ; | |
my $summary = $current->{ summary } ; | |
my $liketemp = int $current->{ apparentTemperature } ; | |
my $icon = $current->{ icon } ; | |
my $title = qq{Currently ${temp}°F} ; | |
my $body = qq{$summary. Feels like ${liketemp}°F.\nPowered by Forecast.} ; | |
say $icon if $config->{ verbose } ; | |
my $png = get_icons( $icon ) ; | |
notify( $title, $body , $png ) ; | |
} | |
sub handle_alerts { | |
my $alerts = shift ; | |
my $body = qq{Powered by Forecast.} ; | |
for my $alert ( @$alerts ) { | |
my $title = $alert->{ title } ; | |
my $warning = 0 ; | |
$warning = 1 if $title =~ /warning/i ; | |
notify( $title, $body ) ; | |
} | |
} | |
sub hourly_forecasts { | |
my $hourly = shift ; | |
my $icon = $hourly->{ icon } ; | |
my $summary = $hourly->{ summary } ; | |
my $data = $hourly->{ data } ; | |
my $body = $summary . "\n\n" ; | |
for my $hour ( @$data[ 0 .. 3 ] ) { | |
my $summary = $hour->{ summary } ; | |
my $dt = DateTime->from_epoch( epoch => $hour->{ time } ) ; | |
$body .= join " ", $dt->hour_12(), $dt->am_or_pm(), '-', $summary, "\n" ; | |
} | |
$body .= qq{Powered by Forecast.} ; | |
my $title = 'Four-Hour Forecast' ; | |
my $png = get_icons( $icon ) ; | |
notify( $title, $body , $png ) ; | |
} | |
sub daily_forecasts { | |
my ( $daily, $f ) = @_ ; | |
my $icon = $daily->{ icon } ; | |
my $summary = $daily->{ summary } ; | |
my $data = $daily->{ data } ; | |
my $body ; | |
for my $day ( @$data[ 1 .. 2 ] ) { | |
my $summary = $day->{ summary } ; | |
my $dt = DateTime->from_epoch( epoch => $day->{ time } ) ; | |
my $max = int $day->{ temperatureMax } ; | |
my $min = int $day->{ temperatureMin } ; | |
my $icon = $day->{ icon } ; | |
$body .= join ' ', | |
$dt->day_name() . ':', | |
$summary, "\n\t" , | |
'H:', | |
$max . '°F', | |
'- L:', | |
$min . '°F', | |
"\n" ; | |
} | |
$body .= qq{Powered by Forecast.} ; | |
my $title = 'Two-Day Forecast' ; | |
my $png = get_icons( $icon ) ; | |
notify( $title, $body , $png ) ; | |
} | |
# Handles the actual notification, using Linux's notify-send | |
sub notify { | |
my $title = shift ; | |
my $body = shift ; | |
my $icon = shift ; | |
$body = $body || '' ; | |
$icon = $icon || $ENV{HOME} . '/Pictures/Icons/icon_black_muffin.jpg' ; | |
`notify-send "$title" "$body" -i $icon ` ; | |
} | |
# show the icons | |
sub get_icons { | |
my $icon = shift ; | |
my $path = $ENV{ HOME } . '/Pictures/forecast.io/' ; | |
# these are the icon names that forecast.io will use. | |
my @icons = qw{ | |
clear-day | |
clear-night | |
cloudy | |
fog | |
partly-cloudy-day | |
partly-cloudy-night | |
rain | |
sleet | |
snow | |
wind | |
} ; | |
# I found my weather icon set online, which had vastly | |
# different icons. I cannot distribute them, as they aren't mine | |
# and I don't hold the copyright, but you can find/make your own. | |
# Coming soon? | |
# hail | |
# thunderstorm | |
# tornado | |
if ( grep m{$icon} , @icons ) { | |
return $path . $icon . '.png' ; | |
} | |
return $path . 'default' . '.png' ; | |
} | |
# Reads configuration data from YAML file. Dies if no valid config file | |
# if no other value is given, it will choose current | |
sub config { | |
my $config_file = $ENV{ HOME } . '/.forecast.yaml' ; | |
my $output = {} ; | |
if ( defined $config_file && -f $config_file ) { | |
my $output = LoadFile( $config_file ) ; | |
GetOptions( | |
'verbose' => \$output->{ verbose }, | |
'alerts' => \$output->{ alerts }, | |
'current' => \$output->{ current }, | |
'daily' => \$output->{ daily }, | |
'hourly' => \$output->{ hourly }, | |
) ; | |
$output->{ current } = 1 | |
if !$output->{ alerts } | |
and !$output->{ hourly } | |
and !$output->{ daily } ; | |
say Dumper $output if $output->{ verbose } ; | |
return $output ; | |
} | |
croak( 'No Config File' ) ; | |
} | |
__DATA__ | |
What ~/.forecast.yaml should look like, set for Lafayette | |
--- | |
apikey: You can't have my API key | |
latitude: 40.422778 | |
longitude: -86.915278 |
I've tried to cut things down to just the things that are necessary, but I could do more for that. Carp, for example, is Perl's way of sending errors to STDERR and exiting and such, but I really don't use it. There is a verbose flag that uses Data::Dumper. I'm turning epoch timestamps to times and dates, so I need DateTime (Thank you Dave Rolsky). The API uses JSON to send the weather data, and I use YAML to hold configuration data, so those modules are given. I want to get something from the web, so I use LWP.
So, download, use, adapt, learn, and stay dry!
So, download, use, adapt, learn, and stay dry!