Forecast plugin patch
[rbot] / data / rbot / plugins / forecast.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # :title: Forecast plugin for rbot
5 #
6 # Author:: MrChucho (mrchucho@mrchucho.net)
7 # Copyright:: (C) 2006 Ralph M. Churchill
8
9 require 'soap/wsdlDriver'
10 # TODO why not use HttpUtil instead of open-uri?
11 require 'open-uri'
12 require 'rexml/document'
13 require 'erb'
14
15
16 class LatLong
17     include ERB::Util
18     # Determine the latitude and longitude of a location. City, State and/or ZIP
19     # are all valid.
20     # [+return+] latitude,longitude
21     def get_lat_long(loc)
22         loc = url_encode(loc)
23         url="http://where.yahooapis.com/geocode?appid=mrchucho_rbot_weather&location=#{loc}"
24         lat,long = 0,0
25         begin
26             open(url) do |xmldoc|
27                 results = (REXML::Document.new xmldoc).root
28                 lat = results.elements["//latitude/text()"].to_s
29                 long = results.elements["//longitude/text()"].to_s
30             end
31         rescue => err
32             raise err #?
33         end
34         return lat.to_f,long.to_f
35     end
36 end
37
38 class Forecast
39     WSDL_URI="http://graphical.weather.gov/xml/SOAP_server/ndfdXMLserver.php?wsdl"
40     def initialize(lat,long)
41         @lat,@long=lat,long
42         # this extra step is for backward/forward compatibility
43         factory = SOAP::WSDLDriverFactory.new(WSDL_URI)
44         @forecaster=factory.respond_to?(:create_rpc_driver) ?
45             factory.create_rpc_driver : factory.create_driver
46     end
47     def forecast
48         return parse(retrieve),Time.new
49     end
50 private
51     def retrieve
52         forecast = @forecaster.NDFDgenByDay(
53             @lat,@long,Time.now.strftime("%Y-%m-%d"),2,"e","24 hourly")
54         (REXML::Document.new(forecast)).root
55     end
56     def parse(xml)
57         msg = String.new
58         (1..2).each do |day|
59             d  = (day==1) ? 'Today' : 'Tomorrow'
60             hi = xml.elements["//temperature[@type='maximum']/value[#{day}]/text()"]
61             lo = xml.elements["//temperature[@type='minimum']/value[#{day}]/text()"]
62             w  = xml.elements["//weather/weather-conditions[#{day}]/@weather-summary"]
63             precip_am = xml.elements["//probability-of-precipitation/value[#{day*2-1}]/text()"]
64             precip_pm = xml.elements["//probability-of-precipitation/value[#{day*2}]/text()"]
65             msg += "#{d}: Hi #{hi} Lo #{lo}, #{w}. Precip: AM #{precip_am}% PM #{precip_pm}%\n"
66         end
67         msg
68     end
69 end
70
71 class ForecastPlugin < Plugin
72     USAGE='forecast <location> => show the 2-day forecast for a location. Location can be any combination of City, State, Country and ZIP'
73     def help(plugin,topic="")
74         USAGE
75     end
76     def usage(m,params={})
77         m.reply USAGE
78     end
79     def initialize
80         super
81         # this plugin only wants to store strings
82         class << @registry
83             def store(val)
84                 val
85             end
86             def restore(val)
87                 val
88             end
89         end
90         @forecast_cache = Hash.new
91         @cache_mutex = Mutex.new
92     end
93
94     def forecast(m,params)
95         if params[:location] and params[:location].any?
96             loc = params[:location].join
97             @registry[m.sourcenick] = loc
98             get_forecast(m,loc)
99         else
100             if @registry.has_key?(m.sourcenick) then
101                 loc = @registry[m.sourcenick]
102                 get_forecast(m,loc)
103             else
104                 m.reply "Please specifiy the City, State or ZIP"
105             end
106         end
107     end
108
109     def get_forecast(m,loc)
110       begin
111         @cache_mutex.synchronize do
112           if @forecast_cache.has_key?(loc) and
113             Time.new - @forecast_cache[loc][:date] < 3600
114             forecast = @forecast_cache[loc][:forecast]
115             if forecast
116               m.reply forecast
117               return
118             end
119           end
120         end
121         begin
122           l = LatLong.new
123           f = Forecast.new(*l.get_lat_long(loc))
124           forecast,forecast_date = f.forecast
125         rescue => err
126           m.reply err
127         end
128         if forecast
129           m.reply forecast
130           @cache_mutex.synchronize do
131             @forecast_cache[loc] = {
132               :forecast => forecast,
133               :date => forecast_date
134             }
135           end
136         else
137           m.reply "Couldn't find forecast for #{loc}"
138         end
139       rescue => e
140         m.reply "ERROR: #{e}"
141       end
142     end
143 end
144 plugin = ForecastPlugin.new
145 plugin.map 'forecast *location',
146   :defaults => {:location => false}, :thread => true