lib/Zeta/plugins/weather.rb in zetabot-0.0.22 vs lib/Zeta/plugins/weather.rb in zetabot-1.0.0

- old
+ new

@@ -1,7 +1,10 @@ require 'ostruct' require 'persist' +require 'open-uri' +require 'json' +require 'unitwise' module Plugins # Forecast is a Cinch plugin for getting the weather forecast. # @original_author Jonah Ruiz <jonah@pixelhipsters.com> # @author Liothen <liothen@flagrun.net> @@ -13,76 +16,75 @@ set( plugin_name: "Weather", help: "Get the Weather?.\nUsage: `?weather`\nUsage: `?wx zip` `?w zip` `?setw zip` `?forecast zip`", ) - match /forecast (.+)/, method: :forecast match /w (.+)/, method: :weather match 'w', method: :weather match /setw (.+)/, method: :set_location match /wx (.+)/, method: :weather match /weather (.+)/, method: :weather match /almanac (.+)/, method: :almanac match /hurricane/, method: :hurricane ##### def initialize(*args) - @store = Persist.new( File.join(Dir.home, '.zeta', 'cache', 'weather.pstore') ) + @api_src = %w{wu noaa darksky owm} + @store = Persist.new(File.join(Dir.home, '.zeta', 'cache', 'weather.pstore')) super end - # ?forecast <location> - def forecast(msg, query) - location = geolookup(query) - return msg.reply "No results found for #{query}." if location.nil? - - data = get_conditions(location) - return msg.reply 'Problem getting data. Try again later.' if data.nil? - - msg.user.msg(weather_summary(data)) - end - # ?w <location> def weather(msg, query=nil) + # Pull data source and scrub query + # Lookup user from pstore if !@store[msg.user.to_s].nil? && query.nil? - location = geolookup(@store[msg.user.to_s]) + stored_location, stored_source = @store[msg.user.to_s].split('::') + stored_source = @api_src.include?(stored_source) ? stored_source : 'wu' + data = send("#{stored_source}_src", stored_location) + # location = geolookup(@store[msg.user.to_s]) + # data = wunderground_src(stored_location, false) elsif query.nil? - return msg.reply 'No location set. ?setw <location>' + return msg.reply 'No location set. ?setw <location> :(wu|darkscy|noaa|apixu|owm)' else - location = geolookup(query) + # data = wu_src(query, true) + src = query[/:\w+/].gsub(/:/, '') if query[/:\w+/] + query = query.gsub(/:\w+/, '').strip if query + true_src = @api_src.include?(src) ? src : 'wu' + data = send("#{true_src}_src", query) end - return msg.reply "No results found for #{query}." if location.nil? - - data = get_conditions(location) - return msg.reply 'Problem getting data. Try again later.' if data.nil? - - #[ Clarkston, WA, United States | Cloudy | Temp: 34 F (1 C) | Humidity: 73% | Winds: 8 mph ] - reply_data = "∴ #{data.county}, #{data.country} " \ - "≈ #{data.weather} #{data.temperature} " \ - "≈ Feels like #{data.feels_like} " \ - "≈ Humidity: #{data.relative_humidity} " \ - "≈ Pressure: #{data.pressure_in} psi (#{data.pressure_mb} mb) " \ - "≈ Wind: #{data.wind} ≈ Alerts: #{data.alerts} ∴" - msg.reply(reply_data) + # return msg.reply "No results found for #{query}." if data.nil? + # return msg.reply 'Problem getting data. Try again later.' if data.nil? + msg.reply(data.reply) end # ?setw <location> - def set_location(msg,query) - location = geolookup(query) - return msg.reply "No results found for #{query}." if location.nil? - @store[msg.user.to_s] = query unless location.nil? - data = get_conditions(location) - msg.reply "Your location is now set to #{data.county}, #{data.country}!" + def set_location(msg, query) + # Establish source + src = query[/:\w+/].gsub(/:/, '') if query[/:\w+/] + query = query.gsub(/:\w+/, '').strip if query + + # Sanity Check + true_src = @api_src.include?(src) ? src : 'wu' + data = send("#{true_src}_src", query) + + # Error + return msg.reply "No results found for #{query}." if data.nil? + + # Store and display general location + serial_location = "#{query}::#{src}" + @store[msg.user.to_s] = serial_location unless data.nil? + msg.reply "Your location is now set to #{data.ac.name}, #{data.ac.c}!" end # ?hurricane def hurricane(msg) url = URI.encode "http://api.wunderground.com/api/#{Zsec.wunderground}/currenthurricane/view.json" location = JSON.parse( - # RestClient.get(url) - open(url).read - ) + # RestClient.get(url) + open(url).read + ) return msg.reply "No results found for #{query}." if location.nil? reply_msg = "∴ #{location['currenthurricane'][0]['stormInfo']['stormName_Nice']} " \ "(#{location['currenthurricane'][0]['stormInfo']['stormNumber']}) "\ "≈ Category #{location['currenthurricane'][0]['Current']['SaffirSimpsonCategory']} " \ "≈ Wind #{location['currenthurricane'][0]['Current']['WindSpeed']['Mph']} mph " \ @@ -92,17 +94,17 @@ "≈ #{location['currenthurricane'][0]['Current']['Time']['pretty']} ∴" msg.reply(reply_msg) end # ?almanac <location> - def almanac(msg,locale) - autocomplete = JSON.parse( open(URI.encode("http://autocomplete.wunderground.com/aq?query=#{locale}")).read ) + def almanac(msg, locale) + autocomplete = JSON.parse(open(URI.encode("http://autocomplete.wunderground.com/aq?query=#{locale}")).read) url = URI.encode("http://api.wunderground.com/api/#{Config.secrets[:wunderground]}/almanac/#{autocomplete['RESULTS'][0]['l']}.json") location = JSON.parse( - # RestClient.get(url) - open(url).read - ) + # RestClient.get(url) + open(url).read + ) return msg.reply "No results found for #{query}." if location.nil? time = Time.now() data = OpenStruct.new( @@ -128,73 +130,142 @@ msg.reply(reply_msg) end # -private - def geolookup(locale) - autocomplete = JSON.parse( open(URI.encode("http://autocomplete.wunderground.com/aq?query=#{locale}")).read ) - url = URI.encode("http://api.wunderground.com/api/#{Config.secrets[:wunderground]}/geolookup/#{autocomplete['RESULTS'][0]['l']}.json") - location = JSON.parse( - open(url).read + private + #### Weather Sources + # Weather Underground - https://wunderground.com + def wu_src(location) + # Fuzzy location lookup + ac = JSON.parse( + open(URI.encode("https://autocomplete.wunderground.com/aq?query=#{location}")).read, + object_class: OpenStruct ) - location['location']['l'] - rescue - nil + ac = ac.RESULTS[0] + geolookup = JSON.parse( + open(URI.encode("https://api.wunderground.com/api/#{Config.secrets[:wunderground]}/geolookup/#{ac.l}.json")).read, + object_class: OpenStruct + ).location.l rescue nil + # Get Data + data = JSON.parse( + open("https://api.wunderground.com/api/#{Config.secrets[:wunderground]}/alerts/conditions#{geolookup}.json").read, + object_class: OpenStruct + ) + + debug "DATA: #{data}" + data.ac = ac + current = data.current_observation + alerts = data.alerts.empty? ? 'none' : data.alerts.map {|l| l['type']}.join(',') + pressure_si = current.pressure_mb.to_i / 10 + + data.reply = "WU ∴ #{ac.name}, #{ac.c} " \ + "≈ #{current.weather} #{current.temperature_string} " \ + "≈ Feels like #{current.feelslike_string} " \ + "≈ Humidity: #{current.relative_humidity} " \ + "≈ Pressure: #{current.pressure_in} psi (#{pressure_si} kPa) " \ + "≈ Wind: #{current.wind_string} ≈ Alerts: #{alerts} ∴" + return data + # rescue + # return nil end - def get_conditions(location) + # Open Weather map - https://openweathermap.org/api + def owm_src(location) + ac = JSON.parse( + open(URI.encode("https://autocomplete.wunderground.com/aq?query=#{location}")).read, + object_class: OpenStruct + ) + ac = ac.RESULTS[0] data = JSON.parse( - open("http://api.wunderground.com/api/#{Config.secrets[:wunderground]}/alerts/conditions#{location}.json").read + open( + URI.encode("https://api.openweathermap.org/data/2.5/weather?lat=#{ac.lat}&lon=#{ac.lon}&APPID=#{Config.secrets[:owm]}") + ).read, + object_class: OpenStruct ) - current = data['current_observation'] - alerts = data['alerts'].empty? ? 'none' : data['alerts'].map { |l| l['type'] }.join(',') - location_data = current['display_location'] - OpenStruct.new( - county: location_data['full'], - country: location_data['country'], + temp = Unitwise(data.main.temp, 'K') # Data is given in kelvin + pressure = Unitwise(data.main.pressure, '[psi]') + wind = Unitwise(data.wind.speed, 'kilometer') - lat: location_data['latitude'], - lng: location_data['longitude'], + data.reply = "OWM ∴ #{ac.name}, #{ac.c} " \ + "≈ #{data.weather[0].description}, #{temp.convert_to('[degF]').to_i.round(2)} F (#{temp.convert_to('Cel').to_i.round(2)} C) " \ + "≈ Humidity: #{data.main.humidity}% " \ + "≈ Pressure: #{pressure.to_i.round(2)} psi (#{pressure.convert_to('kPa').to_i.round(2)} kPa) " \ + "≈ Wind: #{wind.convert_to('mile').to_i.round(2)} mph (#{wind.to_i.round(2)} km/h) ∴" - observation_time: current['observation_time'], - weather: current['weather'], - temp_fahrenheit: current['temp_f'], - temp_celcius: current['temp_c'], - temperature: current['temperature_string'], - relative_humidity: current['relative_humidity'], - feels_like: current['feelslike_string'], - uv_level: current['UV'], + return data - wind: current['wind_string'], - wind_direction: current['wind_dir'], - wind_degrees: current['wind_degrees'], - wind_mph: current['wind_mph'], - wind_gust_mph: current['wind_gust_mph'], - wind_kph: current['wind_kph'], - pressure_in: current['pressure_in'], - pressure_mb: current['pressure_mb'], + end - alerts: alerts, - - forecast_url: current['forecast_url'] + # DarkSky - https://darksky.net/dev + def darksky_src(location) + ac = JSON.parse( + open(URI.encode("https://autocomplete.wunderground.com/aq?query=#{location}")).read, + object_class: OpenStruct ) - rescue - nil + ac = ac.RESULTS[0] + data = JSON.parse( + open( + URI.encode("https://api.darksky.net/forecast/#{Config.secrets[:darksky]}/#{ac.lat},#{ac.lon}") + ).read, + object_class: OpenStruct + ) + data.ac = ac + current = data.currently + alerts = data.alerts.count rescue 0 + c = Unitwise(current.temperature, '[degF]').convert_to('Cel').to_i + c_feels = Unitwise(current.apparentTemperature, '[degF]').convert_to('Cel').to_i + p = Unitwise(current.pressure, '[psi]').convert_to('kPa').to_i + gusts = Unitwise(current.windGust, 'mile').convert_to('kilometer').to_i + + tempstring = "#{current.temperature.to_i} F (#{c} C)" + feelslike = "#{current.apparentTemperature.to_i} F (#{c_feels} C)" + + data.reply = "DS ∴ #{ac.name}, #{ac.c} " \ + "≈ #{current.summary} #{tempstring} " \ + "≈ Feels like #{feelslike} " \ + "≈ Humidity: #{current.relative_humidity} " \ + "≈ Pressure: #{current.pressure} psi (#{p} kPa) " \ + "≈ Wind: gusts upto #{current.windGust} mph (#{gusts} km/h) ≈ Alerts: #{alerts} ∴" + + return data + # rescue + # return nil end - def weather_summary(data) - %Q{ - Forecast for: #{data.county}, #{data.country} - Latitude: #{data.lat}, Longitude: #{data.lng} - Weather is #{data.weather}, #{data.feels_like} - UV: #{data.uv_level}, Humidity: #{data.relative_humidity} - Wind: #{data.wind} - Direction: #{data.wind_direction}, Degrees: #{data.wind_degrees}, - #{data.observation_time} - More Info: #{data.forecast_url}} - rescue - 'Problem fetching the weather summary. Try again later.' + # NOAA - https://graphical.weather.gov/xml/ + def noaa_src(location) + ac = JSON.parse( + open(URI.encode("https://autocomplete.wunderground.com/aq?query=#{location}")).read, + object_class: OpenStruct + ) + ac = ac.RESULTS[0] + stations = JSON.parse( + open(URI.encode("https://api.weather.gov/points/#{ac.lat},#{ac.lon}/stations/")).read) + parsed = JSON.parse( + open(URI.encode("#{stations['observationStations'][0]}/observations/current")).read, + object_class: OpenStruct + ) + + data = parsed.properties + data.ac = ac + f = data.temperature.value * 9/5 + temp = "#{f.round(2)} F (#{data.temperature.value.to_i.round(2)} C) " + wind = "Gusts: #{data.windGust.value} avg: #{data.windSpeed.value.to_i.round(2)}" + feelslike = "#{data.windChill.value.to_i.round(2)} C" + pressure_in = ((data.barometricPressure.value.to_i * 0.00014503773773) * 100).round(2) + pressure = (data.barometricPressure.value.to_i / 100).round(2) + + data.reply = "NOAA ∴ #{ac.name} " \ + "≈ #{data.textDescription} #{temp} " \ + "≈ Feels like #{feelslike} " \ + "≈ Humidity: #{data.relativeHumidity.value.round(2)} " \ + "≈ Pressure: #{pressure_in} psi (#{pressure} kPa) " \ + "≈ Wind: #{wind} ≈ Alerts: ∴" + return data + # rescue + # data.reply = "Error fetching data" end end end