summaryrefslogtreecommitdiff
path: root/data/rbot/plugins/forecast.rb
blob: 293c7c057fb0a4c99faaf848b29ab0ee60f1bdbf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#-- vim:sw=2:et
#++
#
# :title: Forecast plugin for rbot
#
# Author:: MrChucho (mrchucho@mrchucho.net)
# Copyright:: (C) 2006 Ralph M. Churchill

require 'soap/wsdlDriver'
# TODO why not use HttpUtil instead of open-uri?
require 'open-uri'
require 'rexml/document'
require 'erb'


class LatLong
    include ERB::Util
    # Determine the latitude and longitude of a location. City, State and/or ZIP
    # are all valid.
    # [+return+] latitude,longitude
    def get_lat_long(loc)
        loc = url_encode(loc)
        url="http://api.local.yahoo.com/MapsService/V1/geocode?appid=mrchucho_rbot_weather&location=#{loc}"
        lat,long = 0,0
        begin
            open(url) do |xmldoc|
                results = (REXML::Document.new xmldoc).root
                lat = results.elements["//Latitude/text()"].to_s
                long = results.elements["//Longitude/text()"].to_s
            end
        rescue => err
            raise err #?
        end
        return lat.to_f,long.to_f
    end
end

class Forecast
    WSDL_URI="http://www.nws.noaa.gov/forecasts/xml/SOAP_server/ndfdXMLserver.php?wsdl"
    def initialize(lat,long)
        @lat,@long=lat,long
        # this extra step is for backward/forward compatibility
        factory = SOAP::WSDLDriverFactory.new(WSDL_URI)
        @forecaster=factory.respond_to?(:create_rpc_driver) ?
            factory.create_rpc_driver : factory.create_driver
    end
    def forecast
        return parse(retrieve),Time.new
    end
private
    def retrieve
        forecast = @forecaster.NDFDgenByDay(
            @lat,@long,Time.now.strftime("%Y-%m-%d"),2,"24 hourly")
        (REXML::Document.new(forecast)).root
    end
    def parse(xml)
        msg = String.new
        (1..2).each do |day|
            d  = (day==1) ? 'Today' : 'Tomorrow'
            hi = xml.elements["//temperature[@type='maximum']/value[#{day}]/text()"]
            lo = xml.elements["//temperature[@type='minimum']/value[#{day}]/text()"]
            w  = xml.elements["//weather/weather-conditions[#{day}]/@weather-summary"]
            precip_am = xml.elements["//probability-of-precipitation/value[#{day*2-1}]/text()"]
            precip_pm = xml.elements["//probability-of-precipitation/value[#{day*2}]/text()"]
            msg += "#{d}: Hi #{hi} Lo #{lo}, #{w}. Precip: AM #{precip_am}% PM #{precip_pm}%\n"
        end
        msg
    end
end

class ForecastPlugin < Plugin
    USAGE='forecast <location> => show the 2-day forecast for a location. Location can be any combination of City, State, Country and ZIP'
    def help(plugin,topic="")
        USAGE
    end
    def usage(m,params={})
        m.reply USAGE
    end
    def initialize
        super
        # this plugin only wants to store strings
        class << @registry
            def store(val)
                val
            end
            def restore(val)
                val
            end
        end
        @forecast_cache = Hash.new
        @cache_mutex = Mutex.new
    end

    def forecast(m,params)
        if params[:location] and params[:location].any?
            loc = params[:location].join
            @registry[m.sourcenick] = loc
            get_forecast(m,loc)
        else
            if @registry.has_key?(m.sourcenick) then
                loc = @registry[m.sourcenick]
                get_forecast(m,loc)
            else
                m.reply "Please specifiy the City, State or ZIP"
            end
        end
    end

    def get_forecast(m,loc)
      begin
        @cache_mutex.synchronize do
          if @forecast_cache.has_key?(loc) and
            Time.new - @forecast_cache[loc][:date] < 3600
            forecast = @forecast_cache[loc][:forecast]
            if forecast
              m.reply forecast
              return
            end
          end
        end
        begin
          l = LatLong.new
          f = Forecast.new(*l.get_lat_long(loc))
          forecast,forecast_date = f.forecast
        rescue => err
          m.reply err
        end
        if forecast
          m.reply forecast
          @cache_mutex.synchronize do
            @forecast_cache[loc] = {
              :forecast => forecast,
              :date => forecast_date
            }
          end
        else
          m.reply "Couldn't find forecast for #{loc}"
        end
      rescue => e
        m.reply "ERROR: #{e}"
      end
    end
end
plugin = ForecastPlugin.new
plugin.map 'forecast *location',
  :defaults => {:location => false}, :thread => true