lita-onewheel-forecast-io 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +14 -0
- data/Gemfile +2 -0
- data/LICENSE +19 -0
- data/README.md +37 -0
- data/Rakefile +6 -0
- data/lib/lita-onewheel-forecast-io.rb +7 -0
- data/lib/lita/handlers/constants.rb +137 -0
- data/lib/lita/handlers/forecasts.rb +376 -0
- data/lib/lita/handlers/irc_handlers.rb +177 -0
- data/lib/lita/handlers/location.rb +13 -0
- data/lib/lita/handlers/onewheel_forecast_io.rb +165 -0
- data/lib/lita/handlers/utils.rb +396 -0
- data/lita-onewheel-forecast-io.gemspec +36 -0
- data/lita_config_sample.rb +9 -0
- data/locales/en.yml +4 -0
- data/spec/heavy_rain.json +1578 -0
- data/spec/lita/handlers/forecast_io_spec.rb +371 -0
- data/spec/mock_weather.json +1519 -0
- data/spec/mock_weather_no_minute.json +1204 -0
- data/spec/mock_weather_with_snow.json +1649 -0
- data/spec/spec_helper.rb +15 -0
- metadata +231 -0
@@ -0,0 +1,177 @@
|
|
1
|
+
module ForecastIo
|
2
|
+
module IrcHandlers
|
3
|
+
#-# Handlers
|
4
|
+
def handle_irc_forecast(response)
|
5
|
+
location = geo_lookup(response.user, response.match_data[1])
|
6
|
+
forecast = get_forecast_io_results(response.user, location)
|
7
|
+
response.reply location.location_name + ' ' + forecast_text(forecast)
|
8
|
+
end
|
9
|
+
|
10
|
+
def handle_irc_ansirain(response)
|
11
|
+
location = geo_lookup(response.user, response.match_data[1])
|
12
|
+
forecast = get_forecast_io_results(response.user, location)
|
13
|
+
response.reply location.location_name + ' ' + ansi_rain_forecast(forecast)
|
14
|
+
end
|
15
|
+
|
16
|
+
def handle_irc_ascii_rain(response)
|
17
|
+
location = geo_lookup(response.user, response.match_data[1])
|
18
|
+
forecast = get_forecast_io_results(response.user, location)
|
19
|
+
response.reply location.location_name + ' ' + ascii_rain_forecast(forecast)
|
20
|
+
end
|
21
|
+
|
22
|
+
def handle_irc_all_the_things(response)
|
23
|
+
location = geo_lookup(response.user, response.match_data[1])
|
24
|
+
forecast = get_forecast_io_results(response.user, location)
|
25
|
+
response.reply location.location_name + ' ' + forecast_text(forecast)
|
26
|
+
response.reply location.location_name + ' ' + ansi_rain_forecast(forecast)
|
27
|
+
response.reply location.location_name + ' ' + ansi_rain_intensity_forecast(forecast)
|
28
|
+
response.reply location.location_name + ' ' + ansi_temp_forecast(forecast)
|
29
|
+
response.reply location.location_name + ' ' + ansi_wind_direction_forecast(forecast)
|
30
|
+
response.reply location.location_name + ' ' + do_the_sun_thing(forecast)
|
31
|
+
response.reply location.location_name + ' ' + do_the_cloud_thing(forecast)
|
32
|
+
response.reply location.location_name + ' ' + do_the_daily_rain_thing(forecast)
|
33
|
+
end
|
34
|
+
|
35
|
+
def handle_irc_ansirain_intensity(response)
|
36
|
+
location = geo_lookup(response.user, response.match_data[1])
|
37
|
+
forecast = get_forecast_io_results(response.user, location)
|
38
|
+
response.reply location.location_name + ' ' + ansi_rain_intensity_forecast(forecast)
|
39
|
+
end
|
40
|
+
|
41
|
+
def handle_irc_ansitemp(response)
|
42
|
+
location = geo_lookup(response.user, response.match_data[1])
|
43
|
+
forecast = get_forecast_io_results(response.user, location)
|
44
|
+
response.reply location.location_name + ' ' + ansi_temp_forecast(forecast)
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_irc_ascii_temp(response)
|
48
|
+
location = geo_lookup(response.user, response.match_data[1])
|
49
|
+
forecast = get_forecast_io_results(response.user, location)
|
50
|
+
response.reply location.location_name + ' ' + ascii_temp_forecast(forecast)
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_irc_daily_temp(response)
|
54
|
+
location = geo_lookup(response.user, response.match_data[1])
|
55
|
+
forecast = get_forecast_io_results(response.user, location)
|
56
|
+
response.reply location.location_name + ' ' + ansi_temp_forecast(forecast, 48)
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_irc_conditions(response)
|
60
|
+
location = geo_lookup(response.user, response.match_data[1])
|
61
|
+
forecast = get_forecast_io_results(response.user, location)
|
62
|
+
response.reply location.location_name + ' ' + conditions(forecast)
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle_irc_ansiwind(response)
|
66
|
+
location = geo_lookup(response.user, response.match_data[1])
|
67
|
+
forecast = get_forecast_io_results(response.user, location)
|
68
|
+
response.reply location.location_name + ' ' + ansi_wind_direction_forecast(forecast)
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_irc_ascii_wind(response)
|
72
|
+
location = geo_lookup(response.user, response.match_data[1])
|
73
|
+
forecast = get_forecast_io_results(response.user, location)
|
74
|
+
response.reply location.location_name + ' ' + ascii_wind_direction_forecast(forecast)
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_irc_alerts(response)
|
78
|
+
location = geo_lookup(response.user, response.match_data[1])
|
79
|
+
forecast = get_forecast_io_results(response.user, location)
|
80
|
+
alerts = get_alerts(forecast)
|
81
|
+
response.reply alerts
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle_irc_ansisun(response)
|
85
|
+
location = geo_lookup(response.user, response.match_data[1])
|
86
|
+
forecast = get_forecast_io_results(response.user, location)
|
87
|
+
response.reply location.location_name + ' ' + do_the_sun_thing(forecast)
|
88
|
+
end
|
89
|
+
|
90
|
+
def handle_irc_ansicloud(response)
|
91
|
+
location = geo_lookup(response.user, response.match_data[1])
|
92
|
+
forecast = get_forecast_io_results(response.user, location)
|
93
|
+
response.reply location.location_name + ' ' + do_the_cloud_thing(forecast)
|
94
|
+
end
|
95
|
+
|
96
|
+
def handle_irc_seven_day(response)
|
97
|
+
location = geo_lookup(response.user, response.match_data[1])
|
98
|
+
forecast = get_forecast_io_results(response.user, location)
|
99
|
+
response.reply location.location_name + ' ' + do_the_seven_day_thing(forecast)
|
100
|
+
end
|
101
|
+
|
102
|
+
def handle_irc_daily_rain(response)
|
103
|
+
location = geo_lookup(response.user, response.match_data[1])
|
104
|
+
forecast = get_forecast_io_results(response.user, location)
|
105
|
+
response.reply location.location_name + ' ' + do_the_daily_rain_thing(forecast)
|
106
|
+
end
|
107
|
+
|
108
|
+
def handle_irc_seven_day_rain(response)
|
109
|
+
location = geo_lookup(response.user, response.match_data[1])
|
110
|
+
forecast = get_forecast_io_results(response.user, location)
|
111
|
+
response.reply location.location_name + ' ' + do_the_seven_day_rain_thing(forecast)
|
112
|
+
end
|
113
|
+
|
114
|
+
def handle_irc_daily_wind(response)
|
115
|
+
location = geo_lookup(response.user, response.match_data[1])
|
116
|
+
forecast = get_forecast_io_results(response.user, location)
|
117
|
+
response.reply location.location_name + ' ' + do_the_daily_wind_thing(forecast)
|
118
|
+
end
|
119
|
+
|
120
|
+
def handle_irc_daily_humidity(response)
|
121
|
+
location = geo_lookup(response.user, response.match_data[1])
|
122
|
+
forecast = get_forecast_io_results(response.user, location)
|
123
|
+
response.reply location.location_name + ' ' + do_the_daily_humidity_thing(forecast)
|
124
|
+
end
|
125
|
+
|
126
|
+
def handle_irc_ansi_humidity(response)
|
127
|
+
location = geo_lookup(response.user, response.match_data[1])
|
128
|
+
forecast = get_forecast_io_results(response.user, location)
|
129
|
+
response.reply location.location_name + ' 48hr humidity ' + ansi_humidity_forecast(forecast)
|
130
|
+
end
|
131
|
+
|
132
|
+
def handle_irc_ansiozone(response)
|
133
|
+
location = geo_lookup(response.user, response.match_data[1])
|
134
|
+
forecast = get_forecast_io_results(response.user, location)
|
135
|
+
response.reply location.location_name + ' ' + do_the_ozone_thing(forecast)
|
136
|
+
end
|
137
|
+
|
138
|
+
def handle_irc_ansi_pressure(response)
|
139
|
+
location = geo_lookup(response.user, response.match_data[1])
|
140
|
+
forecast = get_forecast_io_results(response.user, location)
|
141
|
+
response.reply location.location_name + ' ' + do_the_pressure_thing(forecast)
|
142
|
+
end
|
143
|
+
|
144
|
+
def handle_irc_daily_pressure(response)
|
145
|
+
location = geo_lookup(response.user, response.match_data[1])
|
146
|
+
forecast = get_forecast_io_results(response.user, location)
|
147
|
+
response.reply location.location_name + ' ' + do_the_daily_pressure_thing(forecast)
|
148
|
+
end
|
149
|
+
|
150
|
+
def handle_irc_set_scale(response)
|
151
|
+
key = response.user.name + '-scale'
|
152
|
+
user_requested_scale = response.match_data[1].to_s.downcase
|
153
|
+
reply = check_and_set_scale(key, user_requested_scale)
|
154
|
+
response.reply reply
|
155
|
+
end
|
156
|
+
|
157
|
+
def handle_irc_sunrise(response)
|
158
|
+
location = geo_lookup(response.user, response.match_data[1])
|
159
|
+
forecast = get_forecast_io_results(response.user, location)
|
160
|
+
response.reply location.location_name + ' sunrise: ' + do_the_sunrise_thing(forecast)
|
161
|
+
end
|
162
|
+
|
163
|
+
def handle_irc_sunset(response)
|
164
|
+
location = geo_lookup(response.user, response.match_data[1])
|
165
|
+
forecast = get_forecast_io_results(response.user, location)
|
166
|
+
response.reply location.location_name + ' sunset: ' + do_the_sunset_thing(forecast)
|
167
|
+
end
|
168
|
+
|
169
|
+
def handle_irc_neareststorm(response)
|
170
|
+
location = geo_lookup(response.user, response.match_data[1])
|
171
|
+
forecast = get_forecast_io_results(response.user, location)
|
172
|
+
nearest_storm_distance, nearest_storm_bearing = do_the_nearest_storm_thing(forecast)
|
173
|
+
|
174
|
+
response.reply "The nearest storm is #{get_distance(nearest_storm_distance, get_scale(response.user))} to the #{get_cardinal_direction_from_bearing(nearest_storm_bearing)} of you."
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Location
|
2
|
+
attr_accessor :location_name, :latitude, :longitude
|
3
|
+
|
4
|
+
def initialize (location_name, latitude, longitude)
|
5
|
+
self.location_name = location_name
|
6
|
+
self.latitude = latitude
|
7
|
+
self.longitude = longitude
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"Location: #{self.location_name} #{self.latitude},#{self.longitude}"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'geocoder'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'magic_eightball'
|
4
|
+
require_relative 'location'
|
5
|
+
require_relative 'constants'
|
6
|
+
require_relative 'irc_handlers'
|
7
|
+
require_relative 'forecasts'
|
8
|
+
require_relative 'utils'
|
9
|
+
|
10
|
+
module Lita
|
11
|
+
module Handlers
|
12
|
+
class OnewheelForecastIo < Handler
|
13
|
+
config :api_key
|
14
|
+
config :api_uri
|
15
|
+
config :colors
|
16
|
+
|
17
|
+
include ::ForecastIo::Constants
|
18
|
+
include ::ForecastIo::IrcHandlers
|
19
|
+
include ::ForecastIo::Forecasts
|
20
|
+
include ::ForecastIo::Utils
|
21
|
+
|
22
|
+
# Temperature routes
|
23
|
+
route(/^ansitemp\s*$/i, :handle_irc_ansitemp)
|
24
|
+
route(/^ansitemp\s+(.+)/i, :handle_irc_ansitemp,
|
25
|
+
help: {'!ansitemp [location]' => 'The 24h temperature scale for [location].'})
|
26
|
+
route(/^dailytemp\s*$/i, :handle_irc_daily_temp)
|
27
|
+
route(/^dailytemp\s+(.+)/i, :handle_irc_daily_temp,
|
28
|
+
help: { '!dailytemp [location]' => '48h temperature scale for [location].'})
|
29
|
+
route(/^7day\s*$/i, :handle_irc_seven_day)
|
30
|
+
route(/^7day\s+(.+)/i, :handle_irc_seven_day,
|
31
|
+
help: { '!7day [location]' => '7 day temperature scale, featuring highs and lows.'})
|
32
|
+
route(/^weekly\s*$/i, :handle_irc_seven_day)
|
33
|
+
route(/^weekly\s+(.+)/i, :handle_irc_seven_day,
|
34
|
+
help: { '!weekly [location]' => 'Alias for !7day.'})
|
35
|
+
route(/^asciitemp\s*$/i, :handle_irc_ascii_temp)
|
36
|
+
route(/^asciitemp\s+(.+)/i, :handle_irc_ascii_temp,
|
37
|
+
help: { '!asciitemp [location]' => 'Like ansitemp, but with less ansi.'})
|
38
|
+
|
39
|
+
# General forecast routes
|
40
|
+
route(/^forecastallthethings\s*$/i, :handle_irc_all_the_things)
|
41
|
+
route(/^forecastallthethings\s+(.+)/i, :handle_irc_all_the_things,
|
42
|
+
help: { '!forecastallthethings [location]' => 'A huge dump of most available info for [location].'})
|
43
|
+
route(/^forecast\s*$/i, :handle_irc_forecast)
|
44
|
+
route(/^forecast\s+(.+)/i, :handle_irc_forecast,
|
45
|
+
help: { '!forecast [location]' => 'Text forcast of the location selected.'})
|
46
|
+
route(/^weather\s*$/i, :handle_irc_forecast)
|
47
|
+
route(/^weather\s+(.+)/i, :handle_irc_forecast,
|
48
|
+
help: { '!weather [location]' => 'Alias for !forecast.'})
|
49
|
+
route(/^condi*t*i*o*n*s*\s*$/i, :handle_irc_conditions)
|
50
|
+
route(/^condi*t*i*o*n*s*\s+(.+)/i, :handle_irc_conditions,
|
51
|
+
help: { '!cond[itions] [location]' => 'A single-line summary of the conditions at [location].'})
|
52
|
+
|
53
|
+
# One-offs
|
54
|
+
route(/^rain\s*$/i, :is_it_raining)
|
55
|
+
route(/^rain\s+(.+)/i, :is_it_raining,
|
56
|
+
help: { '!rain [location]' => 'Magic Eightball response to whether or not it is raining in [location] right now.'})
|
57
|
+
route(/^snow\s*$/i, :is_it_snowing)
|
58
|
+
route(/^snow\s+(.+)/i, :is_it_snowing,
|
59
|
+
help: { '!snow [location]' => 'Magic Eightball response to whether or not it is snowing in [location] right now.'})
|
60
|
+
route(/^geo\s*$/i, :handle_geo_lookup)
|
61
|
+
route(/^geo\s+(.+)/i, :handle_geo_lookup,
|
62
|
+
help: { '!geo [location]' => 'A simple geo-lookup returning GPS coords.'})
|
63
|
+
route(/^alerts\s*$/i, :handle_irc_alerts)
|
64
|
+
route(/^alerts\s+(.+)/i, :handle_irc_alerts,
|
65
|
+
help: { '!alerts [location]' => 'NOAA alerts for [location].'})
|
66
|
+
route(/^neareststorm\s*$/i, :handle_irc_neareststorm)
|
67
|
+
route(/^neareststorm\s+(.+)$/i, :handle_irc_neareststorm,
|
68
|
+
help: { '!neareststorm [location]' => 'Nearest storm distance for [location].'})
|
69
|
+
|
70
|
+
# State Commands
|
71
|
+
route(/^set scale (c|f|k)/i, :handle_irc_set_scale,
|
72
|
+
help: { '!set scale [c|f|k]' => 'Set the scale to your chosen degrees.'})
|
73
|
+
route(/^set scale$/i, :handle_irc_set_scale,
|
74
|
+
help: { '!set scale' => 'Toggle between C and F scales.'})
|
75
|
+
|
76
|
+
# Humidity
|
77
|
+
route(/^ansihumidity\s*$/i, :handle_irc_ansi_humidity)
|
78
|
+
route(/^ansihumidity\s+(.+)/i, :handle_irc_ansi_humidity,
|
79
|
+
help: { '!ansihumidity [location]' => '48h humidity report for [location].'})
|
80
|
+
route(/^dailyhumidity\s*$/i, :handle_irc_daily_humidity)
|
81
|
+
route(/^dailyhumidity\s+(.+)/i, :handle_irc_daily_humidity,
|
82
|
+
help: { '!dailyhumidity [location]' => '7 day humidity report.'})
|
83
|
+
|
84
|
+
# Rain related. Where we all started.
|
85
|
+
route(/^ansirain\s*$/i, :handle_irc_ansirain)
|
86
|
+
route(/^ansirain\s+(.+)/i, :handle_irc_ansirain,
|
87
|
+
help: { '!ansirain [location]' => '60m rain chance report for [location].'})
|
88
|
+
route(/^ansisnow\s*$/i, :handle_irc_ansirain)
|
89
|
+
route(/^ansisnow\s+(.+)/i, :handle_irc_ansirain,
|
90
|
+
help: { '!ansisnow [location]' => 'Alias for !ansirain.'})
|
91
|
+
route(/^dailyrain\s*$/i, :handle_irc_daily_rain)
|
92
|
+
route(/^dailyrain\s+(.+)/i, :handle_irc_daily_rain,
|
93
|
+
help: { '!dailyrain [location]' => '48h rain chance report for [location].'})
|
94
|
+
route(/^dailysnow\s*$/i, :handle_irc_daily_rain)
|
95
|
+
route(/^dailysnow\s+(.+)/i, :handle_irc_daily_rain,
|
96
|
+
help: { '!dailysnow [location]' => 'Alias for !dailyrain.'})
|
97
|
+
route(/^7dayrain\s*$/i, :handle_irc_seven_day_rain)
|
98
|
+
route(/^7dayrain\s+(.+)/i, :handle_irc_seven_day_rain,
|
99
|
+
help: { '!7dayrain [location]' => '7 day rain chance report for [location].'})
|
100
|
+
route(/^weeklyrain\s*$/i, :handle_irc_seven_day_rain)
|
101
|
+
route(/^weeklyrain\s+(.+)/i, :handle_irc_seven_day_rain,
|
102
|
+
help: { '!weeklyrain [location]' => 'Alias for !7dayrain.'})
|
103
|
+
route(/^weeklysnow\s*$/i, :handle_irc_seven_day_rain)
|
104
|
+
route(/^weeklysnow\s+(.+)/i, :handle_irc_seven_day_rain,
|
105
|
+
help: { '!weeklysnow [location]' => 'Alias for !7dayrain.'})
|
106
|
+
route(/^ansiintensity\s*$/i, :handle_irc_ansirain_intensity)
|
107
|
+
route(/^ansiintensity\s+(.+)/i, :handle_irc_ansirain_intensity,
|
108
|
+
help: { '!ansiintensity [location]' => '60m rain intensity report for [location].'})
|
109
|
+
route(/^asciirain\s*$/i, :handle_irc_ascii_rain)
|
110
|
+
route(/^asciirain\s+(.+)/i, :handle_irc_ascii_rain,
|
111
|
+
help: { '!asciirain [location]' => '60m rain chance report for [location], ascii style!'})
|
112
|
+
|
113
|
+
# don't start singing.
|
114
|
+
route(/^sunrise\s*$/i, :handle_irc_sunrise)
|
115
|
+
route(/^sunrise\s+(.+)/i, :handle_irc_sunrise,
|
116
|
+
help: { '!sunrise [location]' => 'Get today\'s sunrise time for [location].'})
|
117
|
+
route(/^sunset\s*$/i, :handle_irc_sunset)
|
118
|
+
route(/^sunset\s+(.+)/i, :handle_irc_sunset,
|
119
|
+
help: { '!sunset [location]' => 'Get today\'s sunset time for [location].'})
|
120
|
+
route(/^ansisun\s*$/i, :handle_irc_ansisun)
|
121
|
+
route(/^ansisun\s+(.+)/i, :handle_irc_ansisun,
|
122
|
+
help: { '!ansisun [location]' => '7 day chance-of-sun report for [location].'})
|
123
|
+
|
124
|
+
# Mun!
|
125
|
+
|
126
|
+
# Wind
|
127
|
+
route(/^ansiwind\s*$/i, :handle_irc_ansiwind)
|
128
|
+
route(/^ansiwind\s+(.+)/i, :handle_irc_ansiwind,
|
129
|
+
help: { '!ansiwind [location]' => '24h wind speed/direction report for [location].'})
|
130
|
+
route(/^asciiwind\s*$/i, :handle_irc_ascii_wind)
|
131
|
+
route(/^asciiwind\s+(.+)/i, :handle_irc_ascii_wind,
|
132
|
+
help: { '!asciiwind [location]' => '24h wind speed/direction report for [location], ascii style.'})
|
133
|
+
route(/^dailywind\s*$/i, :handle_irc_daily_wind)
|
134
|
+
route(/^dailywind\s+(.+)/i, :handle_irc_daily_wind,
|
135
|
+
help: { '!dailywind [location]' => '7 day wind speed/direction report for [location].'})
|
136
|
+
|
137
|
+
# Cloud cover
|
138
|
+
route(/^ansicloud\s*$/i, :handle_irc_ansicloud)
|
139
|
+
route(/^ansicloud\s+(.+)/i, :handle_irc_ansicloud,
|
140
|
+
help: { '!ansicloud [location]' => '24h cloud cover report for [location].'})
|
141
|
+
|
142
|
+
# oooOOOoooo
|
143
|
+
route(/^ansiozone\s*$/i, :handle_irc_ansiozone)
|
144
|
+
route(/^ansiozone\s+(.+)/i, :handle_irc_ansiozone,
|
145
|
+
help: { '!ansiozone [location]' => '24h ozone level report for [location].'})
|
146
|
+
|
147
|
+
# Pressure
|
148
|
+
route(/^ansipressure\s*$/i, :handle_irc_ansi_pressure)
|
149
|
+
route(/^ansipressure\s+(.+)/i, :handle_irc_ansi_pressure,
|
150
|
+
help: { '!ansipressure [location]' => '48h barometric pressure report for [location].'})
|
151
|
+
route(/^ansibarometer\s*$/i, :handle_irc_ansi_pressure)
|
152
|
+
route(/^ansibarometer\s+(.+)/i, :handle_irc_ansi_pressure,
|
153
|
+
help: { '!ansibarometer [location]' => 'Alias for !ansipressure.'})
|
154
|
+
route(/^dailypressure\s*$/i, :handle_irc_daily_pressure)
|
155
|
+
route(/^dailypressure\s+(.+)/i, :handle_irc_daily_pressure,
|
156
|
+
help: { '!dailypressure [location]' => '7 day barometric pressure report for [location].'})
|
157
|
+
route(/^dailybarometer\s*$/i, :handle_irc_daily_pressure)
|
158
|
+
route(/^dailybarometer\s+(.+)/i, :handle_irc_daily_pressure,
|
159
|
+
help: { '!dailybarometer [location]' => 'Alias for !dailypressure.'})
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
Lita.register_handler(OnewheelForecastIo)
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,396 @@
|
|
1
|
+
module ForecastIo
|
2
|
+
module Utils
|
3
|
+
REDIS_KEY = 'forecast_io'
|
4
|
+
|
5
|
+
# Return an eightball response based on the current chance of rain.
|
6
|
+
# If it's snowing, it's a hard no.
|
7
|
+
def is_it_raining(response)
|
8
|
+
geocoded = geo_lookup response.user, response.match_data[1]
|
9
|
+
forecast = get_forecast_io_results response.user, geocoded
|
10
|
+
|
11
|
+
response.reply get_eightball_response get_chance_of('rain', forecast['currently'])
|
12
|
+
end
|
13
|
+
|
14
|
+
# Return an eightball response based on the current chance of snow.
|
15
|
+
# If it's raining, it's a hard no.
|
16
|
+
def is_it_snowing(response)
|
17
|
+
geocoded = geo_lookup response.user, response.match_data[1]
|
18
|
+
forecast = get_forecast_io_results response.user, geocoded
|
19
|
+
|
20
|
+
response.reply get_eightball_response get_chance_of('snow', forecast['currently'])
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_eightball_response(chance)
|
24
|
+
case chance
|
25
|
+
when 0..0.2
|
26
|
+
MagicEightball.reply :no
|
27
|
+
when 0.201..0.7
|
28
|
+
MagicEightball.reply :maybe
|
29
|
+
when 0.701..1
|
30
|
+
MagicEightball.reply :yes
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_chance_of(rain_or_snow, currently)
|
35
|
+
# This is a fallthrough so we'll reply no to rain if it's snowing, and vice versa.
|
36
|
+
chance = 0
|
37
|
+
|
38
|
+
if currently['precipType'] == rain_or_snow # If we match the specified string ['rain', 'snow']
|
39
|
+
chance = currently['precipProbability'] # Set the probability for 8-ball reckoning.
|
40
|
+
end
|
41
|
+
|
42
|
+
chance # Probably superfluous.
|
43
|
+
end
|
44
|
+
|
45
|
+
# Geographical stuffs
|
46
|
+
# Now with moar caching!
|
47
|
+
def optimistic_geo_wrapper(query)
|
48
|
+
Lita.logger.debug "Optimistically geo wrapping #{query}!"
|
49
|
+
geocoded = nil
|
50
|
+
result = ::Geocoder.search(query)
|
51
|
+
Lita.logger.debug "Geocoder result: '#{result.inspect}'"
|
52
|
+
if result[0]
|
53
|
+
geocoded = result[0].data
|
54
|
+
end
|
55
|
+
geocoded
|
56
|
+
end
|
57
|
+
|
58
|
+
def geo_lookup(user, query)
|
59
|
+
Lita.logger.debug "Performing geolookup for '#{user.name}' for '#{query}'"
|
60
|
+
if query.nil? or query.empty?
|
61
|
+
Lita.logger.debug "No query specified, pulling from redis #{REDIS_KEY}, #{user.name}"
|
62
|
+
serialized_geocoded = redis.hget(REDIS_KEY, user.name)
|
63
|
+
unless serialized_geocoded == 'null' or serialized_geocoded.nil?
|
64
|
+
geocoded = JSON.parse(serialized_geocoded)
|
65
|
+
end
|
66
|
+
Lita.logger.debug "Cached location: #{geocoded.inspect}"
|
67
|
+
end
|
68
|
+
|
69
|
+
Lita.logger.debug "q & g #{query.inspect} #{geocoded.inspect}"
|
70
|
+
if (query.nil? or query.empty?) and geocoded.nil?
|
71
|
+
query = 'Portland, OR'
|
72
|
+
end
|
73
|
+
|
74
|
+
unless geocoded
|
75
|
+
Lita.logger.debug "Redis hget failed, performing lookup for #{query}"
|
76
|
+
geocoded = optimistic_geo_wrapper query
|
77
|
+
Lita.logger.debug "Geolocation found. '#{geocoded.inspect}' failed, performing lookup"
|
78
|
+
redis.hset(REDIS_KEY, user.name, geocoded.to_json)
|
79
|
+
end
|
80
|
+
|
81
|
+
Lita.logger.debug "geocoded: '#{geocoded}'"
|
82
|
+
|
83
|
+
loc = Location.new(
|
84
|
+
geocoded['formatted_address'],
|
85
|
+
geocoded['geometry']['location']['lat'],
|
86
|
+
geocoded['geometry']['location']['lng']
|
87
|
+
)
|
88
|
+
|
89
|
+
Lita.logger.debug "loc: '#{loc}'"
|
90
|
+
|
91
|
+
loc
|
92
|
+
end
|
93
|
+
|
94
|
+
# Wrapped for testing.
|
95
|
+
def gimme_some_weather(url)
|
96
|
+
# HTTParty.get url
|
97
|
+
response = RestClient.get(url)
|
98
|
+
JSON.parse(response.to_str)
|
99
|
+
end
|
100
|
+
|
101
|
+
def set_scale(user)
|
102
|
+
key = user.name + '-scale'
|
103
|
+
if scale = redis.hget(REDIS_KEY, key)
|
104
|
+
@scale = scale
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def get_scale(user)
|
109
|
+
key = user.name + '-scale'
|
110
|
+
scale = redis.hget(REDIS_KEY, key)
|
111
|
+
if scale.nil?
|
112
|
+
scale = 'f'
|
113
|
+
end
|
114
|
+
scale
|
115
|
+
end
|
116
|
+
|
117
|
+
def check_and_set_scale(key, user_requested_scale)
|
118
|
+
persisted_scale = redis.hget(REDIS_KEY, key)
|
119
|
+
|
120
|
+
if %w(c f k).include? user_requested_scale
|
121
|
+
scale_to_set = user_requested_scale
|
122
|
+
else
|
123
|
+
# Toggle mode
|
124
|
+
scale_to_set = get_other_scale(persisted_scale)
|
125
|
+
end
|
126
|
+
|
127
|
+
if persisted_scale == scale_to_set
|
128
|
+
reply = "Scale is already set to #{scale_to_set}!"
|
129
|
+
else
|
130
|
+
redis.hset(REDIS_KEY, key, scale_to_set)
|
131
|
+
reply = "Scale set to #{scale_to_set}"
|
132
|
+
end
|
133
|
+
|
134
|
+
reply
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def get_forecast_io_results(user, location)
|
139
|
+
if ! config.api_uri or ! config.api_key
|
140
|
+
Lita.logger.error "Configuration missing! '#{config.api_uri}' '#{config.api_key}'"
|
141
|
+
return
|
142
|
+
end
|
143
|
+
uri = config.api_uri + config.api_key + '/' + "#{location.latitude},#{location.longitude}"
|
144
|
+
Lita.logger.debug uri
|
145
|
+
set_scale(user)
|
146
|
+
forecast = gimme_some_weather uri
|
147
|
+
end
|
148
|
+
|
149
|
+
def handle_geo_lookup(response)
|
150
|
+
location = geo_lookup(response.user, response.match_data[1])
|
151
|
+
response.reply "#{location.latitude}, #{location.longitude}"
|
152
|
+
end
|
153
|
+
|
154
|
+
def forecast_text(forecast)
|
155
|
+
forecast_str = "weather is currently #{get_temperature forecast['currently']['temperature']} " +
|
156
|
+
"and #{forecast['currently']['summary'].downcase}. Winds out of the #{get_cardinal_direction_from_bearing forecast['currently']['windBearing']} at #{get_speed(forecast['currently']['windSpeed'])}. "
|
157
|
+
|
158
|
+
if forecast['minutely']
|
159
|
+
minute_forecast = forecast['minutely']['summary'].to_s.downcase.chop
|
160
|
+
forecast_str += "It will be #{minute_forecast}, and #{forecast['hourly']['summary'].to_s.downcase.chop}. "
|
161
|
+
end
|
162
|
+
|
163
|
+
forecast_str += "There are also #{forecast['currently']['ozone'].to_s} ozones."
|
164
|
+
end
|
165
|
+
|
166
|
+
def fix_time(unixtime, data_offset)
|
167
|
+
unixtime - determine_time_offset(data_offset)
|
168
|
+
end
|
169
|
+
|
170
|
+
def determine_time_offset(data_offset)
|
171
|
+
system_offset_seconds = Time.now.utc_offset
|
172
|
+
data_offset_seconds = data_offset * 60 * 60
|
173
|
+
system_offset_seconds - data_offset_seconds
|
174
|
+
end
|
175
|
+
|
176
|
+
# Utility functions
|
177
|
+
|
178
|
+
###
|
179
|
+
# get_colored_string
|
180
|
+
# Returns the dot_str colored based on our range_hash.
|
181
|
+
# range_hash is one of our color hashes, e.g. get_wind_range_colors
|
182
|
+
# key is used to index each element in data_limited to get our value to compare with the range_hash.
|
183
|
+
##
|
184
|
+
def get_colored_string(data_limited, key, dot_str, range_hash)
|
185
|
+
color = nil
|
186
|
+
prev_color = nil
|
187
|
+
collect_str = ''
|
188
|
+
colored_str = ''
|
189
|
+
|
190
|
+
data_limited.each_with_index do |data, index|
|
191
|
+
range_hash.keys.each do |range_hash_key|
|
192
|
+
if range_hash_key.cover? data[key] # Super secred cover sauce
|
193
|
+
color = range_hash[range_hash_key]
|
194
|
+
if index == 0
|
195
|
+
prev_color = color
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# If the color changed, let's update the collect_str
|
201
|
+
unless color == prev_color
|
202
|
+
colored_str += "\x03" + colors[prev_color] + collect_str
|
203
|
+
collect_str = ''
|
204
|
+
end
|
205
|
+
|
206
|
+
collect_str += dot_str[index]
|
207
|
+
prev_color = color
|
208
|
+
end
|
209
|
+
|
210
|
+
# And get the last one.
|
211
|
+
colored_str += "\x03" + colors[color] + collect_str + "\x03"
|
212
|
+
end
|
213
|
+
|
214
|
+
# this method lets us condense rain forcasts into smaller sets
|
215
|
+
# it averages the values contained in a chunk of data perportionate the the limit set
|
216
|
+
# then returns a new array of hashes containing those averaged values
|
217
|
+
def condense_data(data, limit)
|
218
|
+
return if limit >= data.length
|
219
|
+
chunk_length = (data.length / limit.to_f).round
|
220
|
+
results = []
|
221
|
+
data.each_slice(chunk_length) do |chunk|
|
222
|
+
chunk_results = {}
|
223
|
+
condensed_chunk = collect_values(chunk)
|
224
|
+
condensed_chunk.each do |k, v|
|
225
|
+
if v[0].class == Fixnum || v[0].class == Float
|
226
|
+
new_val = v.inject{ |sum,val| sum + val} / v.size
|
227
|
+
elsif v[0].class == String
|
228
|
+
new_val = v[0]
|
229
|
+
end
|
230
|
+
chunk_results[k] = new_val
|
231
|
+
end
|
232
|
+
results << chunk_results
|
233
|
+
end
|
234
|
+
results
|
235
|
+
end
|
236
|
+
|
237
|
+
# this method is simply to transform an array of hashes into a hash of arrays
|
238
|
+
# kudos to Phrogz for the info here: http://stackoverflow.com/questions/5490952/merge-array-of-hashes-to-get-hash-of-arrays-of-values
|
239
|
+
def collect_values(hashes)
|
240
|
+
{}.tap{ |r| hashes.each{ |h| h.each{ |k,v| (r[k]||=[]) << v } } }
|
241
|
+
end
|
242
|
+
|
243
|
+
def get_dot_str(chars, data, min, differential, key)
|
244
|
+
str = ''
|
245
|
+
data.each do |datum|
|
246
|
+
percentage = get_percentage(datum[key], differential, min)
|
247
|
+
str += get_dot(percentage, chars)
|
248
|
+
end
|
249
|
+
str
|
250
|
+
end
|
251
|
+
|
252
|
+
def get_percentage(number, differential, min)
|
253
|
+
if differential == 0
|
254
|
+
percentage = number
|
255
|
+
else
|
256
|
+
percentage = (number.to_f - min) / (differential)
|
257
|
+
end
|
258
|
+
percentage
|
259
|
+
end
|
260
|
+
|
261
|
+
# °℃℉
|
262
|
+
def get_dot(probability, char_array)
|
263
|
+
if probability < 0 or probability > 1
|
264
|
+
Lita.logger.error "get_dot Probably a probability problem: #{probability} should be between 0 and 1."
|
265
|
+
return '?'
|
266
|
+
end
|
267
|
+
|
268
|
+
if probability == 0
|
269
|
+
return char_array[0]
|
270
|
+
elsif probability <= 0.10
|
271
|
+
return char_array[1]
|
272
|
+
elsif probability <= 0.25
|
273
|
+
return char_array[2]
|
274
|
+
elsif probability <= 0.50
|
275
|
+
return char_array[3]
|
276
|
+
elsif probability <= 0.75
|
277
|
+
return char_array[4]
|
278
|
+
elsif probability <= 1.00
|
279
|
+
return char_array[5]
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def get_temperature(temp_f)
|
284
|
+
if @scale == 'c'
|
285
|
+
celcius(temp_f).to_s + '°C'
|
286
|
+
elsif @scale == 'k'
|
287
|
+
kelvin(temp_f).to_s + 'K'
|
288
|
+
else
|
289
|
+
temp_f.to_s + '°F'
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def get_speed(speed_imperial)
|
294
|
+
if @scale == 'c'
|
295
|
+
kilometers(speed_imperial).to_s + ' kph'
|
296
|
+
else
|
297
|
+
speed_imperial.to_s + ' mph'
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def get_distance(distance_imperial, scale)
|
302
|
+
if scale == 'c'
|
303
|
+
kilometers(distance_imperial).to_s + ' km'
|
304
|
+
else
|
305
|
+
distance_imperial.to_s + ' mi'
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def get_humidity(humidity_decimal)
|
310
|
+
(humidity_decimal * 100).round(0).to_s + '%'
|
311
|
+
end
|
312
|
+
|
313
|
+
def celcius(degrees_f)
|
314
|
+
(0.5555555556 * (degrees_f.to_f - 32)).round(2)
|
315
|
+
end
|
316
|
+
|
317
|
+
def kelvin(degrees_f)
|
318
|
+
((degrees_f.to_f + 459.67) * 5/9).round(2)
|
319
|
+
end
|
320
|
+
|
321
|
+
def kilometers(speed_imperial)
|
322
|
+
(speed_imperial * 1.6).round(2)
|
323
|
+
end
|
324
|
+
|
325
|
+
def get_cardinal_direction_from_bearing(bearing)
|
326
|
+
case bearing
|
327
|
+
when 0..25
|
328
|
+
'N'
|
329
|
+
when 26..65
|
330
|
+
'NE'
|
331
|
+
when 66..115
|
332
|
+
'E'
|
333
|
+
when 116..155
|
334
|
+
'SE'
|
335
|
+
when 156..205
|
336
|
+
'S'
|
337
|
+
when 206..245
|
338
|
+
'SW'
|
339
|
+
when 246..295
|
340
|
+
'W'
|
341
|
+
when 296..335
|
342
|
+
'NW'
|
343
|
+
when 336..360
|
344
|
+
'N'
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# This is a little weird, because the arrows are 180° rotated. That's because the wind bearing is "out of the N" not "towards the N".
|
349
|
+
def ansi_wind_arrows
|
350
|
+
case robot.config.robot.adapter
|
351
|
+
when :slack
|
352
|
+
{'N' => ':arrow_down:',
|
353
|
+
'NE' => ':arrow_lower_left:',
|
354
|
+
'E' => ':arrow_left:',
|
355
|
+
'SE' => ':arrow_upper_left:',
|
356
|
+
'S' => ':arrow_up:',
|
357
|
+
'SW' => ':arrow_upper_right:',
|
358
|
+
'W' => ':arrow_right:',
|
359
|
+
'NW' => ':arrow_lower_right:'
|
360
|
+
}
|
361
|
+
else
|
362
|
+
{'N' => '↓',
|
363
|
+
'NE' => '↙',
|
364
|
+
'E' => '←',
|
365
|
+
'SE' => '↖',
|
366
|
+
'S' => '↑',
|
367
|
+
'SW' => '↗',
|
368
|
+
'W' => '→',
|
369
|
+
'NW' => '↘'
|
370
|
+
}
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
def ascii_wind_arrows
|
375
|
+
{ 'N' => 'v',
|
376
|
+
'NE' => ',',
|
377
|
+
'E' => '<',
|
378
|
+
'SE' => "\\",
|
379
|
+
'S' => '^',
|
380
|
+
'SW' => '/',
|
381
|
+
'W' => '>',
|
382
|
+
'NW' => '.'
|
383
|
+
}
|
384
|
+
end
|
385
|
+
|
386
|
+
# A bit optimistic, but I really like the Cs.
|
387
|
+
def get_other_scale(scale)
|
388
|
+
if scale.downcase == 'c'
|
389
|
+
'f'
|
390
|
+
else
|
391
|
+
'c'
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
end
|
396
|
+
end
|