You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
7.0 KiB

#!/usr/bin/env perl
use strict;
use warnings;
use Getopt::Long ();
use Pod::Usage ();
use Config::Tiny ();
use HTTP::Tiny ();
use JSON::MaybeXS ();
use Time::Piece;
my $VERSION = '2.02';
my $opt = {
units => 'imperial',
};
Getopt::Long::GetOptions(
"current" => \$opt->{current},
"forecast" => \$opt->{forecast},
"version" => \$opt->{version},
"help" => \$opt->{help},
"man" => \$opt->{man},
"devel" => \$opt->{devel},
"debug" => \$opt->{debug},
) or Pod::Usage::pod2usage( -exitval => 1, -verbose => 0 );
Pod::Usage::pod2usage( -exitval => 0, -verbose => 1 ) if $opt->{help};
Pod::Usage::pod2usage( -exitval => 0, -verbose => 2, noperldoc => 1 ) if $opt->{man};
if ( $opt->{version} ) {
print "$VERSION\n";
exit 0;
}
Pod::Usage::pod2usage( -exitval => 1, -verbose => 0 ) unless ( $opt->{current} || $opt->{forecast} );
require Data::Dumper if $opt->{debug};
print "# [DEBUG] opt\n" .
Data::Dumper::Dumper( $opt ) . "\n" if $opt->{debug};
my $home = $ENV{HOME};
my $file = '.weatherrc';
my $file_path = "$home/$file";
die( "$file_path does not exist\n" ) unless -e $file_path;
my $config = Config::Tiny->read( $file_path );
my $tier = $opt->{devel} ? 'development' : 'production';
die( "the $tier key for openweathermap is not present in $file_path\n" )
unless exists $config->{openweathermap}->{$tier}
&& $config->{openweathermap}->{$tier} =~ /^\w+$/;
my $http_tiny = HTTP::Tiny->new();
my $geolocation_api = 'http://ip-api.com/json';
my $geo_res = $http_tiny->get( $geolocation_api );
print "# [DEBUG] geo_res\n" .
Data::Dumper::Dumper( $geo_res ) . "\n" if $opt->{debug};
my $location = {};
if ( $geo_res->{success} ) {
my $content = JSON::MaybeXS::decode_json( $geo_res->{content} );
print "# [DEBUG] geo_res content\n" .
Data::Dumper::Dumper( $content ) . "\n" if $opt->{debug};
# TODO: this should be a little cleaner
# to request only the fields we want
$location->{zip} = $content->{zip};
$location->{city} = $content->{city};
$location->{region} = $content->{regionName};
$location->{country} = $content->{country};
$location->{country_code} = lc $content->{countryCode};
$location->{timezone} = $content->{timezone};
$location->{lon} = $content->{lon};
$location->{lat} = $content->{lat};
}
else {
die( "the query to $geolocation_api wasn't successful\n" );
}
my $base_url = 'https://api.openweathermap.org/data/2.5';
my $query_params = "zip=$location->{zip},$location->{country_code}&appid=$config->{openweathermap}->{$tier}&units=$opt->{units}";
print "# [DEBUG] header output\n" if $opt->{debug};
print "\n### weather for $location->{city}, $location->{region}\n\n";
if ( $opt->{current} ) {
my $current_api = "$base_url/weather?$query_params";
my $current_res = $http_tiny->get( $current_api );
print "# [DEBUG] current_res\n" .
Data::Dumper::Dumper( $current_res ) . "\n" if $opt->{debug};
my $current = {};
if ( $current_res->{success} ) {
$current = JSON::MaybeXS::decode_json( $current_res->{content} );
}
else {
die( "the query to $current_api wasn't successful\n" );
}
print "# [DEBUG] current_conditions\n" .
Data::Dumper::Dumper( $current ) . "\n" .
"# [DEBUG] current output\n" if $opt->{debug};
print "## currently\n" .
"\n" .
"# " . localtime->ymd . " (today)\n" .
"\n" .
"overall:\t$current->{weather}->[0]->{description}\n" .
"temp:\t\t$current->{main}->{temp} F\n" .
"humidity:\t$current->{main}->{humidity} %\n" .
"pressure:\t$current->{main}->{pressure} hpa\n" .
"wind:\t\t$current->{wind}->{speed} mph\n" .
"clouds:\t\t$current->{clouds}->{all} %\n" .
"\n";
}
if ( $opt->{forecast} ) {
my $forecast_api = "$base_url/forecast?$query_params";
my $forecast_res = $http_tiny->get( $forecast_api );
print "# [DEBUG] forecast_res\n" .
Data::Dumper::Dumper( $forecast_res ) . "\n" if $opt->{debug};
my $forecast = {};
if ( $forecast_res->{success} ) {
$forecast = JSON::MaybeXS::decode_json( $forecast_res->{content} );
}
else {
die( "the query to $forecast_api wasn't successful\n" );
}
print "# [DEBUG] forecast\n" .
Data::Dumper::Dumper( $forecast ) . "\n" .
"# [DEBUG] forecast output\n" if $opt->{debug};
print "## 5 day forecast\n\n";
foreach my $forecast_period ( @{ $forecast->{list} } ) {
# instead of doing date time comparisons
# we're just checking the timestamp for 12am to filter
# for only the whole day entries
next unless $forecast_period->{dt_txt} =~ /00:00:00$/;
print "# " . ( split( / /, $forecast_period->{dt_txt} ) )[0] . "\n" .
"\n" .
"overall:\t$forecast_period->{weather}->[0]->{description}\n" .
"temp:\t\t$forecast_period->{main}->{temp} F\n" .
"humidity:\t$forecast_period->{main}->{humidity} %\n" .
"pressure:\t$forecast_period->{main}->{pressure} hpa\n" .
"wind:\t\t$forecast_period->{wind}->{speed} mph\n" .
"clouds:\t\t$forecast_period->{clouds}->{all} %\n" .
"\n";
}
}
__END__
=pod
=head1 NAME
weather - cli program to get the weather for your location
=head1 SYNOPSIS
weather [--current]
[--forecast]
[--version]
[--help] [--man]
[--devel] [--debug]
=head1 DESCRIPTION
This program will retrieve weather condition and forecast details for your location.
=head1 OPTIONS
--current print the current conditions
--forecast print the 5 day forecast
--version print the version and exit
--help print this dialogue
--man display the full documentation
--devel use a development tier key for openweathermap
--debug dump the internals and query responses
=head1 EXAMPLES
=over
=item get the current conditions
weather --current
=item get the current conditions and 5 day forecast
weather --current --forecast
=back
=head1 CONFIGURATION
The configuration file for weather is located in the homedir of the running user, named .weatherrc
The file may contain two sections, production and development, under the parent section, openweathermap.
~ $ cat .weatherrc
[openweathermap]
production = 1q2w3e4r5t6y7u8i9o0p1q2w3e4r5t6y
development = 0p9o8i7u6y5t4r3e2w1q0p9o8i7u6y5t
The development section is required in the case of using the --devel switch for weather.
The production section is required in normal operation.
=head1 DEPENDENCIES
=over
=item Getopt::Long
=item Pod::Usage
=item Config::Tiny
=item HTTP::Tiny
=item JSON::MaybeXS
=item Data::Dumper
=item Time::Piece
=back
=head1 AUTHOR
Blaine Motsinger, <blaine@renderorange.com>
=head1 LICENSE AND COPYRIGHT
This software is available under the MIT license.
Copyright (c) 2019 Blaine Motsinger
=cut