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
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
|
|
|