]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - data/rbot/plugins/forecast.rb
forecast plugin: thread, don't block
[user/henk/code/ruby/rbot.git] / 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://api.local.yahoo.com/MapsService/V1/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://www.nws.noaa.gov/forecasts/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,"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       Thread.new {
111         begin
112           @cache_mutex.synchronize do
113             if @forecast_cache.has_key?(loc) and
114                 Time.new - @forecast_cache[loc][:date] < 3600
115                 forecast = @forecast_cache[loc][:forecast]
116                 if forecast
117                   m.reply forecast
118                   Thread.exit
119                 end
120             end
121           end
122           begin
123             l = LatLong.new
124             f = Forecast.new(*l.get_lat_long(loc))
125             forecast,forecast_date = f.forecast
126           rescue => err
127             m.reply err
128           end
129           if forecast
130             m.reply forecast
131             @cache_mutex.synchronize do
132               @forecast_cache[loc] = {
133                 :forecast => forecast,
134                 :date => forecast_date
135               }
136             end
137           else
138             m.reply "Couldn't find forecast for #{loc}"
139           end
140         rescue => e
141           m.reply "ERROR: #{e}"
142         end
143       }
144     end
145 end
146 plugin = ForecastPlugin.new
147 plugin.map 'forecast *location', :defaults => {:location => false}