lita-onewheel-forecast-io 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|