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