File Coverage

blib/lib/Bot/Cobalt/Plugin/Weather.pm
Criterion Covered Total %
statement 22 144 15.2
branch 0 34 0.0
condition 0 12 0.0
subroutine 8 20 40.0
pod 0 9 0.0
total 30 219 13.7


line stmt bran cond sub pod time code
1             package Bot::Cobalt::Plugin::Weather;
2             $Bot::Cobalt::Plugin::Weather::VERSION = '0.003002';
3 1     1   21922 use List::Objects::WithUtils;
  1         3437  
  1         7  
4              
5 1     1   91897 use Bot::Cobalt;
  1         3148  
  1         7  
6 1     1   1760 use Bot::Cobalt::Common;
  1         214108  
  1         6  
7              
8 1     1   9179 use POE;
  1         50490  
  1         8  
9 1     1   75479 use POEx::Weather::OpenWeatherMap;
  1         1365542  
  1         79  
10              
11 1     1   904 use Object::RateLimiter;
  1         10421  
  1         41  
12              
13 1     1   9 use PerlX::Maybe;
  1         2  
  1         2685  
14              
15 1     1 0 83 sub new { bless +{ location => +{} }, shift }
16              
17 0     0     sub _limiter { $_[0]->{_limiter} }
18              
19 0 0   0 0   sub pwx { $_[1] ? $_[0]->{pwx} = $_[1] : $_[0]->{pwx} }
20              
21             sub Cobalt_register {
22 0     0 0   my ($self, $core) = splice @_, 0, 2;
23              
24 0           POE::Session->create(
25             object_states => [
26             $self => [ qw/
27             _start
28              
29             pwx_error
30             pwx_weather
31             pwx_forecast
32             / ],
33             ],
34             );
35              
36 0           register( $self, SERVER =>
37             'public_cmd_wx',
38             'wx_timer_expire_item',
39             );
40              
41 0           logger->info("Loaded: wx");
42              
43 0           PLUGIN_EAT_NONE
44             }
45              
46             sub Cobalt_unregister {
47 0     0 0   my ($self, $core) = splice @_, 0, 2;
48 0           logger->info("Shutting down POEx::Weather::OpenWeatherMap ...");
49 0           $core->timer_del_alias( $core->get_plugin_alias($self) );
50 0           $self->{location} = +{};
51 0 0         $self->pwx->stop if $self->pwx;
52 0           logger->info("wx unloaded");
53 0           PLUGIN_EAT_NONE
54             }
55              
56             sub _start {
57 0     0     my $self = $_[OBJECT];
58              
59 0           my $pcfg = core->get_plugin_cfg($self);
60              
61 0   0       my $api_key = $pcfg->{API_Key} || $pcfg->{APIKey};
62 0 0         unless (defined $api_key) {
63 0           logger->error($_) for
64             "No 'API_Key' found in plugin configuration!",
65             "Requests will likely fail."
66             }
67              
68              
69 0 0         my $do_cache = $pcfg->{DisableCache} ? 0 : 1;
70 0           my $cache_expiry = $pcfg->{CacheExpiry};
71 0           my $cache_dir = $pcfg->{CacheDir};
72              
73 0   0       my $ratelimit = $pcfg->{RateLimit} || 60;
74 0           $self->{_limiter} = Object::RateLimiter->new(
75             seconds => 60, events => $ratelimit
76             );
77              
78 0           $self->pwx(
79             POEx::Weather::OpenWeatherMap->new(
80             event_prefix => 'pwx_',
81             cache => $do_cache,
82              
83             maybe api_key => $api_key,
84             maybe cache_expiry => $cache_expiry,
85             maybe cache_dir => $cache_dir,
86             )
87             );
88              
89 0           $self->pwx->start;
90             }
91              
92             sub pwx_error {
93 0     0 0   my $self = $_[OBJECT];
94 0           my $err = $_[ARG0];
95 0           my $tag = $err->request->tag;
96              
97 0           logger->warn("POEx::Weather::OpenWeatherMap failure: $err");
98 0           broadcast(
99             message => $tag->context => $tag->channel =>
100             "wx: error: $err"
101             );
102             }
103              
104             sub pwx_weather {
105 0     0 0   my $self = $_[OBJECT];
106 0           my $res = $_[ARG0];
107 0           my $tag = $res->request->tag;
108              
109 0           my $place = $res->name;
110              
111 0           my $tempf = $res->temp_f;
112 0           my $tempc = $res->temp_c;
113 0           my $humid = $res->humidity;
114              
115 0           my $wind = $res->wind_speed_mph;
116 0           my $gust = $res->wind_gust_mph;
117 0           my $winddir = $res->wind_direction;
118              
119 0           my $terse = $res->conditions_terse;
120 0           my $verbose = $res->conditions_verbose;
121              
122 0           my $hms = $res->dt->hms;
123              
124 0           my $str = "$place at ${hms}UTC: ${tempf}F/${tempc}C";
125 0           $str .= " and ${humid}% humidity;";
126 0           $str .= " wind is ${wind}mph $winddir";
127 0 0         $str .= " gusting to ${gust}mph" if $gust;
128 0           $str .= ". Current conditions: ${terse}: $verbose";
129              
130 0           broadcast( message => $tag->context => $tag->channel => $str );
131             }
132              
133             sub pwx_forecast {
134 0     0 0   my $self = $_[OBJECT];
135 0           my $res = $_[ARG0];
136 0           my $tag = $res->request->tag;
137 0           my $place = $res->name;
138              
139 0           broadcast(
140             message => $tag->context => $tag->channel =>
141             "Forecast for $place ->"
142             );
143              
144 0 0         if ($res->hourly) {
145 0           for my $hr ($res->as_array->sliced(1..3)->all) {
146 0           my $str = $self->_hourly_forecast_str($hr);
147 0           broadcast( message => $tag->context => $tag->channel => $str );
148             }
149             } else {
150 0           my $itr = $res->iter;
151 0           while (my $day = $itr->()) {
152 0           my $str = $self->_daily_forecast_str($day);
153 0           broadcast( message => $tag->context => $tag->channel => $str );
154             }
155             }
156             }
157              
158             sub _hourly_forecast_str {
159 0     0     my ($self, $hr) = @_;
160 0           my $date = $hr->dt->hms;
161 0           my $temp = $hr->temp;
162 0           my $temp_c = $hr->temp_c;
163              
164 0           my $terse = $hr->conditions_terse;
165 0           my $verbose = $hr->conditions_verbose;
166              
167 0           my $wind = $hr->wind_speed_mph;
168 0           my $winddir = $hr->wind_direction;
169              
170 0           my $rain = $hr->rain;
171 0           my $snow = $hr->snow;
172              
173 0 0         "${date} UTC: ${temp}F/${temp_c}C"
    0          
174             . ", wind $winddir at ${wind}mph"
175             . ", ${terse}: $verbose"
176             . ($rain ? ", rain ${rain}mm" : '')
177             . ($snow ? ", snow ${snow}mm" : '')
178             }
179              
180             sub _daily_forecast_str {
181 0     0     my ($self, $day) = @_;
182 0           my $date = $day->dt->day_name;
183              
184 0           my $temp_hi_f = $day->temp_max_f;
185 0           my $temp_lo_f = $day->temp_min_f;
186 0           my $temp_hi_c = $day->temp_max_c;
187 0           my $temp_lo_c = $day->temp_min_c;
188              
189 0           my $terse = $day->conditions_terse;
190 0           my $verbose = $day->conditions_verbose;
191              
192 0           my $wind = $day->wind_speed_mph;
193 0           my $winddir = $day->wind_direction;
194              
195 0           "${date}: High of ${temp_hi_f}F/${temp_hi_c}C"
196             . ", low of ${temp_lo_f}F/${temp_lo_c}C"
197             . ", wind $winddir at ${wind}mph"
198             . "; $terse: $verbose"
199             }
200              
201             sub Bot_wx_timer_expire_item {
202 0     0 0   my ($self, $core) = splice @_, 0, 2;
203 0           my $context = ${ $_[0] };
  0            
204 0           my $nick = ${ $_[1] };
  0            
205 0 0         return unless exists $self->{location}->{$context};
206 0           delete $self->{location}->{$context}->{$nick};
207             delete $self->{location}->{$context}
208 0 0         unless keys %{ $self->{location}->{$context} };
  0            
209             }
210              
211             sub Bot_public_cmd_wx {
212 0     0 0   my ($self, $core) = splice @_, 0, 2;
213 0           my $msg = ${ $_[0] };
  0            
214 0           my $context = $msg->context;
215              
216 0           my $loc = $self->{location};
217 0           my $lower = lc_irc $msg->src_nick;
218              
219 0           my ($location, $fcast, $hourly);
220 0           my @parts = @{ $msg->message_array };
  0            
221              
222 0 0 0       if ( ($parts[0] || '') eq 'forecast' ) {
    0 0        
223 0           $fcast++;
224 0           shift @parts;
225             } elsif ( ($parts[0] || '' ) eq 'hourly' ) {
226 0           $hourly++;
227 0           shift @parts;
228             }
229              
230 0 0         if (!@parts) {
231 0 0 0       if ($loc->{ $context } && $loc->{ $context }->{ $lower }) {
232 0           $location = $loc->{ $context }->{ $lower };
233             } else {
234 0           broadcast( message => $context => $msg->channel =>
235             $msg->src_nick . ': no location specified'
236             );
237 0           return PLUGIN_EAT_NONE
238             }
239             } else {
240 0           $location = join ' ', @parts;
241 0           $loc->{ $context }->{ $lower } = $location;
242 0           $core->timer_set( 180,
243             +{
244             Event => 'wx_timer_expire_item',
245             Args => [ $context, $lower ],
246             Alias => $core->get_plugin_alias($self),
247             }
248             );
249             }
250              
251 0 0         if ($self->_limiter->delay) {
252 0           broadcast( message => $msg->context => $msg->channel =>
253             "Weather is currently rate-limited; wait a minute and try again."
254             );
255 0           return PLUGIN_EAT_NONE
256             }
257              
258 0           my $tag = hash(
259             context => $msg->context,
260             channel => $msg->channel,
261             )->inflate;
262              
263 0 0         $self->pwx->get_weather(
    0          
264             location => $location,
265             tag => $tag,
266             ( $fcast ? (forecast => 1, days => 3) : () ),
267             ( $hourly ? (forecast => 1, hourly => 1, days => 1) : () ),
268             );
269              
270 0           PLUGIN_EAT_NONE
271             }
272              
273             1;
274              
275             =pod
276              
277             =head1 NAME
278              
279             Bot::Cobalt::Plugin::Weather - Weather retrieval plugin for Bot::Cobalt
280              
281             =head1 SYNOPSIS
282              
283             # In your plugins.conf:
284             WX:
285             Module: Bot::Cobalt::Plugin::Weather
286             Opts:
287             API_Key: "my OpenWeatherMap API key here"
288             # OpenWeatherMap's free tier allows 60 requests per minute (default):
289             RateLimit: 60
290             # Caching is on by default:
291             DisableCache: 0
292             # Defaults are probably fine:
293             CacheExpiry: 1200
294             CacheDir: ~
295              
296             # On IRC:
297             > !wx Boston, MA
298             > !wx forecast Toronto, Canada
299             # Previous queries remembered per-user for three minutes:
300             > !wx hourly
301              
302             =head1 DESCRIPTION
303              
304             A weather conditions/forecast retrieval plugin for L<Bot::Cobalt>.
305              
306             Uses L<http://www.openweathermap.org/> via L<POEx::Weather::OpenWeatherMap> /
307             L<Weather::OpenWeatherMap> for retrieval.
308              
309             =head1 AUTHOR
310              
311             Jon Portnoy <avenj@cobaltirc.org>
312              
313             =cut