2020-01-30 04:11:45 +01:00
|
|
|
# File: Wttr.pm
|
|
|
|
# Author: pragma-
|
|
|
|
#
|
|
|
|
# Purpose: Weather command using Wttr.in.
|
|
|
|
|
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
|
|
|
package Plugins::Wttr;
|
|
|
|
|
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
use feature 'unicode_strings';
|
|
|
|
use utf8;
|
|
|
|
|
|
|
|
use feature 'switch';
|
|
|
|
no if $] >= 5.018, warnings => "experimental::smartmatch";
|
|
|
|
|
2020-01-31 06:17:58 +01:00
|
|
|
use PBot::Utils::LWPUserAgentCached;
|
2020-01-30 04:11:45 +01:00
|
|
|
use JSON;
|
|
|
|
use Getopt::Long qw(GetOptionsFromString);
|
2020-01-31 04:18:58 +01:00
|
|
|
use URI::Escape qw/uri_escape_utf8/;
|
2020-01-30 04:11:45 +01:00
|
|
|
use Carp ();
|
|
|
|
|
|
|
|
sub new {
|
|
|
|
Carp::croak("Options to " . __FILE__ . " should be key/value pairs, not hash reference") if ref $_[1] eq 'HASH';
|
|
|
|
my ($class, %conf) = @_;
|
|
|
|
my $self = bless {}, $class;
|
|
|
|
$self->initialize(%conf);
|
|
|
|
return $self;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub initialize {
|
|
|
|
my ($self, %conf) = @_;
|
|
|
|
$self->{pbot} = $conf{pbot} // Carp::croak("Missing pbot reference to " . __FILE__);
|
|
|
|
$self->{pbot}->{commands}->register(sub { $self->wttrcmd(@_) }, "wttr", 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub unload {
|
|
|
|
my $self = shift;
|
|
|
|
$self->{pbot}->{commands}->unregister("wttr");
|
|
|
|
}
|
|
|
|
|
|
|
|
sub wttrcmd {
|
|
|
|
my ($self, $from, $nick, $user, $host, $arguments, $stuff) = @_;
|
|
|
|
|
|
|
|
my @wttr_options = (
|
|
|
|
"conditions",
|
|
|
|
"forecast",
|
|
|
|
"feelslike",
|
|
|
|
"uvindex",
|
|
|
|
"visibility",
|
|
|
|
"dewpoint",
|
|
|
|
"heatindex",
|
|
|
|
"cloudcover",
|
|
|
|
"wind",
|
|
|
|
"sunrise|sunset",
|
|
|
|
"moon",
|
|
|
|
"chances",
|
|
|
|
"sunhours",
|
|
|
|
"snowfall",
|
|
|
|
"location",
|
|
|
|
"default",
|
|
|
|
"all",
|
|
|
|
);
|
|
|
|
|
|
|
|
my $usage = "Usage: wttr [-u <user account>] [location] [" . join(' ', map { "-$_" } @wttr_options) . "]";
|
|
|
|
Getopt::Long::Configure("bundling_override", "ignorecase_always");
|
|
|
|
|
|
|
|
my $getopt_error;
|
|
|
|
local $SIG{__WARN__} = sub {
|
|
|
|
$getopt_error = shift;
|
|
|
|
chomp $getopt_error;
|
|
|
|
};
|
|
|
|
|
|
|
|
my %options;
|
|
|
|
my ($ret, $args) = GetOptionsFromString($arguments,
|
|
|
|
\%options,
|
|
|
|
'u=s',
|
|
|
|
'h',
|
|
|
|
@wttr_options
|
|
|
|
);
|
|
|
|
|
|
|
|
return "/say $getopt_error -- $usage" if defined $getopt_error;
|
|
|
|
return $usage if exists $options{h};
|
|
|
|
$arguments = "@$args";
|
|
|
|
|
|
|
|
my $hostmask = defined $options{u} ? $options{u} : "$nick!$user\@$host";
|
|
|
|
my $location_override = $self->{pbot}->{users}->get_loggedin_user_metadata($from, $hostmask, 'location') // '';
|
|
|
|
$arguments = $location_override if not length $arguments;
|
|
|
|
|
|
|
|
if (defined $options{u} and not length $location_override) {
|
|
|
|
return "No location set or user account does not exist.";
|
|
|
|
}
|
|
|
|
|
2020-02-01 08:17:02 +01:00
|
|
|
delete $options{u};
|
|
|
|
|
2020-01-30 04:11:45 +01:00
|
|
|
if (not length $arguments) {
|
|
|
|
return $usage;
|
|
|
|
}
|
|
|
|
|
|
|
|
$options{default} = 1 if not keys %options;
|
|
|
|
|
|
|
|
if (defined $options{all}) {
|
|
|
|
%options = ();
|
2020-01-30 04:32:45 +01:00
|
|
|
map { my $opt = $_; $opt =~ s/\|.*$//; $options{$opt} = 1 } @wttr_options;
|
2020-01-30 04:11:45 +01:00
|
|
|
delete $options{all};
|
|
|
|
delete $options{default};
|
|
|
|
}
|
|
|
|
|
2020-01-31 04:18:58 +01:00
|
|
|
return $self->get_wttr($arguments, %options);
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
2020-01-31 04:18:58 +01:00
|
|
|
sub get_wttr {
|
2020-01-30 04:11:45 +01:00
|
|
|
my ($self, $location, %options) = @_;
|
|
|
|
|
|
|
|
my %cache_opt = (
|
|
|
|
'namespace' => 'wttr',
|
|
|
|
'default_expires_in' => 3600
|
|
|
|
);
|
|
|
|
|
2020-01-31 04:18:58 +01:00
|
|
|
my $location_uri = uri_escape_utf8 $location;
|
2020-01-30 04:11:45 +01:00
|
|
|
|
2020-01-31 06:17:58 +01:00
|
|
|
my $ua = PBot::Utils::LWPUserAgentCached->new(\%cache_opt, timeout => 10);
|
2020-01-31 04:18:58 +01:00
|
|
|
my $response = $ua->get("http://wttr.in/$location_uri?format=j1&m");
|
2020-01-30 04:11:45 +01:00
|
|
|
|
|
|
|
my $json;
|
|
|
|
if ($response->is_success) {
|
|
|
|
$json = $response->decoded_content;
|
|
|
|
} else {
|
|
|
|
return "Failed to fetch weather data: " . $response->status_line;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $wttr = decode_json $json;
|
|
|
|
|
|
|
|
# title-case location
|
|
|
|
$location = ucfirst lc $location;
|
2020-01-30 04:57:30 +01:00
|
|
|
$location =~ s/( |\.)(\w)/$1 . uc $2/ge;
|
2020-01-30 04:11:45 +01:00
|
|
|
|
|
|
|
my $result = "Weather for $location: ";
|
|
|
|
|
|
|
|
my $c = $wttr->{'current_condition'}->[0];
|
|
|
|
my $w = $wttr->{'weather'}->[0];
|
|
|
|
my $h = $w->{'hourly'}->[0];
|
|
|
|
|
|
|
|
foreach my $option (sort keys %options) {
|
|
|
|
given ($option) {
|
|
|
|
when ('default') {
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Currently: $c->{'weatherDesc'}->[0]->{'value'}: $c->{'temp_C'}C/$c->{'temp_F'}F; ";
|
|
|
|
$result .= "Forecast: High: $w->{maxtempC}C/$w->{maxtempF}F, Low: $w->{mintempC}C/$w->{mintempF}F; ";
|
|
|
|
$result .= "Condition changes: ";
|
2020-01-30 04:11:45 +01:00
|
|
|
|
|
|
|
my $last_condition = $c->{'weatherDesc'}->[0]->{'value'};
|
|
|
|
my $sep = '';
|
|
|
|
|
|
|
|
foreach my $hour (@{ $w->{'hourly'} }) {
|
|
|
|
my $condition = $hour->{'weatherDesc'}->[0]->{'value'};
|
2020-01-31 03:59:43 +01:00
|
|
|
my $temp = "$hour->{FeelsLikeC}C/$hour->{FeelsLikeF}F";
|
|
|
|
my $time = sprintf "%04d", $hour->{'time'};
|
|
|
|
$time =~ s/(\d{2})$/:$1/;
|
2020-01-30 04:11:45 +01:00
|
|
|
|
|
|
|
if ($condition ne $last_condition) {
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "$sep$time: $condition ($temp)";
|
|
|
|
$sep = '-> ';
|
2020-01-30 04:11:45 +01:00
|
|
|
$last_condition = $condition;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($sep eq '') {
|
|
|
|
$result .= $last_condition;
|
|
|
|
}
|
|
|
|
$result .= "; ";
|
|
|
|
}
|
|
|
|
|
|
|
|
when ('conditions') {
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Current conditions: $c->{'weatherDesc'}->[0]->{'value'}: $c->{'temp_C'}C/$c->{'temp_F'}F (Feels like $c->{'FeelsLikeC'}C/$c->{'FeelsLikeF'}F); ";
|
2020-01-30 05:41:32 +01:00
|
|
|
$result .= "Cloud cover: $c->{'cloudcover'}%; Visibility: $c->{'visibility'}km; ";
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Wind: $c->{'windspeedKmph'}kph/$c->{'windspeedMiles'}mph $c->{'winddirDegree'}°/$c->{'winddir16Point'}; ";
|
2020-01-30 05:41:32 +01:00
|
|
|
$result .= "Humidity: $c->{'humidity'}%; Precip: $c->{'precipMM'}mm; Pressure: $c->{'pressure'}hPa; UV Index: $c->{'uvIndex'}; ";
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
when ('forecast') {
|
2020-01-30 05:41:32 +01:00
|
|
|
$result .= "Hourly forecast: ";
|
2020-01-30 04:11:45 +01:00
|
|
|
my ($last_temp, $last_condition, $sep) = ('', '', '');
|
|
|
|
foreach my $hour (@{ $wttr->{'weather'}->[0]->{'hourly'} }) {
|
2020-01-31 03:59:43 +01:00
|
|
|
my $temp = "$hour->{FeelsLikeC}C/$hour->{FeelsLikeF}F";
|
2020-01-30 04:11:45 +01:00
|
|
|
my $condition = $hour->{'weatherDesc'}->[0]->{'value'};
|
|
|
|
my $text = '';
|
|
|
|
|
|
|
|
if ($temp ne $last_temp) {
|
|
|
|
$text .= $temp;
|
|
|
|
$last_temp = $temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($condition ne $last_condition) {
|
|
|
|
$text .= ' ' if length $text;
|
|
|
|
$text .= $condition;
|
|
|
|
$last_condition = $condition;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length $text) {
|
2020-01-31 03:59:43 +01:00
|
|
|
my $time = sprintf "%04d", $hour->{'time'};
|
|
|
|
$time =~ s/(\d{2})$/:$1/;
|
|
|
|
$result .= "$sep $time: $text";
|
2020-01-30 04:11:45 +01:00
|
|
|
$sep = ', ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$result .= "; ";
|
|
|
|
}
|
|
|
|
|
|
|
|
when ('chances') {
|
|
|
|
$result .= "Chances of: ";
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Fog: $h->{'chanceoffog'}%, " if $h->{'chanceoffog'};
|
|
|
|
$result .= "Frost: $h->{'chanceoffrost'}%, " if $h->{'chanceoffrost'};
|
|
|
|
$result .= "High temp: $h->{'chanceofhightemp'}%, " if $h->{'chanceofhightemp'};
|
|
|
|
$result .= "Overcast: $h->{'chanceofovercast'}%, " if $h->{'chanceofovercast'};
|
|
|
|
$result .= "Rain: $h->{'chanceofrain'}%, " if $h->{'chanceofrain'};
|
2020-01-30 04:57:30 +01:00
|
|
|
$result .= "Remaining dry: $h->{'chanceofremdry'}%, " if $h->{'chanceofremdry'};
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Snow: $h->{'chanceofsnow'}%, " if $h->{'chanceofsnow'};
|
|
|
|
$result .= "Sunshine: $h->{'chanceofsunshine'}%, " if $h->{'chanceofsunshine'};
|
|
|
|
$result .= "Thunder: $h->{'chanceofthunder'}%, " if $h->{'chanceofthunder'};
|
|
|
|
$result .= "Windy: $h->{'chanceofwindy'}%, " if $h->{'chanceofwindy'};
|
2020-01-30 04:11:45 +01:00
|
|
|
$result =~ s/,\s+$/; /;
|
|
|
|
}
|
|
|
|
|
|
|
|
when ('wind') {
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Wind: $c->{'windspeedKmph'}kph/$c->{'windspeedMiles'}mph $c->{'winddirDegree'}°/$c->{'winddir16Point'}, ";
|
|
|
|
$result .= "gust: $h->{'WindGustKmph'}kph/$h->{'WindGustMiles'}mph, chill: $h->{'WindChillC'}C/$h->{'WindChillF'}F; ";
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
when ('location') {
|
|
|
|
my $l = $wttr->{'request'}->[0];
|
|
|
|
$result .= "Location: $l->{'query'} ($l->{'type'}); ";
|
|
|
|
}
|
|
|
|
|
|
|
|
when ('dewpoint') {
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Dew point: $h->{'DewPointC'}C/$h->{'DewPointF'}F; ";
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
when ('feelslike') {
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Feels like: $h->{'FeelsLikeC'}C/$h->{'FeelsLikeF'}F; ";
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
when ('heatindex') {
|
2020-01-31 03:59:43 +01:00
|
|
|
$result .= "Heat index: $h->{'HeatIndexC'}C/$h->{'HeatIndexF'}F; ";
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
when ('moon') {
|
|
|
|
my $a = $w->{'astronomy'}->[0];
|
2020-01-31 04:18:58 +01:00
|
|
|
$result .= "Moon: phase: $a->{'moon_phase'}, illumination: $a->{'moon_illumination'}%, rise: $a->{'moonrise'}, set: $a->{'moonset'}; ";
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
when ('sunrise') {
|
|
|
|
my $a = $w->{'astronomy'}->[0];
|
|
|
|
$result .= "Sun: rise: $a->{'sunrise'}, set: $a->{'sunset'}; ";
|
|
|
|
}
|
|
|
|
|
|
|
|
when ('sunhours') {
|
|
|
|
$result .= "Hours of sun: $w->{'sunHour'}; ";
|
|
|
|
}
|
|
|
|
|
|
|
|
when ('snowfall') {
|
|
|
|
$result .= "Total snow: $w->{'totalSnow_cm'}cm; ";
|
|
|
|
}
|
|
|
|
|
|
|
|
when ('uvindex') {
|
|
|
|
$result .= "UV Index: $c->{'uvIndex'}; ";
|
|
|
|
}
|
|
|
|
|
|
|
|
when ('visibility') {
|
2020-01-30 05:41:32 +01:00
|
|
|
$result .= "Visibility: $c->{'visibility'}km; ";
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
when ('cloudcover') {
|
2020-01-30 05:41:32 +01:00
|
|
|
$result .= "Cloud cover: $c->{'cloudcover'}%; ";
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
default {
|
2020-02-01 02:11:13 +01:00
|
|
|
$result .= "Option $_ coming soon; " unless lc $_ eq 'u';
|
2020-01-30 04:11:45 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$result =~ s/;\s+$//;
|
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|