Ruby weather and mail example - a ruby script that mails me the weather

I just finished a Ruby client program that gets the current weather for multiple cities, using the Yahoo! Weather RSS Feed, and then mails the output to me. (Besides demonstrating this Ruby weather capability, it also shows how to send a simple mail message using the Ruby mail API.)

When I receive the email message this is what the body of the message looks like:

Santa Fe, NM:
Currently 39 degrees, 33% humidity, 13 mph winds, Partly Cloudy.
Forecast: 17 low, 36 high.
Sunrise: 6:17 am, sunset: 6:09 pm.

Savannah, GA:
Currently 77 degrees, 65% humidity, 9 mph winds, Partly Cloudy.
Forecast: 64 low, 84 high.
Sunrise: 6:37 am, sunset: 6:31 pm.

Seward, AK:
Currently 24 degrees, 34% humidity, 12 mph winds, Fair.
Forecast: 16 low, 29 high.
Sunrise: 7:20 am, sunset: 6:56 pm.

Virginia Beach, VA:
Currently 83 degrees, 39% humidity, 24 mph winds, Mostly Cloudy/Windy.
Forecast: 63 low, 85 high.
Sunrise: 6:18 am, sunset: 6:10 pm.

Ruby script to email weather information

And without any delay here's the Ruby source code that gets the weather information and sends this email message:

# a ruby program pulled together by Al Alexander, http://alvinalexander.com
# thanks to Jay Anderson and many others

require 'rexml/document'
require 'open-uri'
require 'net/smtp'

# yahoo weather url info
# http://developer.yahoo.net/weather/#examples

#Returns a hash containing the location and temperature information
#Accepts US zip codes or Yahoo location id's
def yahoo_weather_query(loc_id, units)
    h = {}
    open("http://xml.weather.yahoo.com/forecastrss?p=#{loc_id}&u=#{units}") do |http|
        response = http.read
        doc = REXML::Document.new(response)
        root              = doc.root
        channel           = root.elements['channel']
        location          = channel.elements['yweather:location']
        h[:city]          = location.attributes["city"]
        h[:region]        = location.attributes["region"]
        h[:country]       = location.attributes["country"]
        h[:temp]          = channel.elements["item"].elements["yweather:condition"].attributes["temp"]
        h[:text]          = channel.elements["item"].elements["yweather:condition"].attributes["text"]
        h[:wind_speed]    = channel.elements['yweather:wind'].attributes['speed']
        h[:humidity]      = channel.elements['yweather:atmosphere'].attributes['humidity']
        h[:sunrise]       = channel.elements['yweather:astronomy'].attributes['sunrise']
        h[:sunset]        = channel.elements['yweather:astronomy'].attributes['sunset']
        h[:forecast_low]  = channel.elements["item"].elements['yweather:forecast'].attributes['low']
        h[:forecast_high] = channel.elements["item"].elements['yweather:forecast'].attributes['high']
    end
    return h       # not cool, i know
end

def get_weather_for_city(city_code,units)
  weather_info = yahoo_weather_query(city_code, units)
  city          = weather_info[:city]
  region        = weather_info[:region]
  country       = weather_info[:country]
  temp          = weather_info[:temp]
  wind_speed    = weather_info[:wind_speed]
  humidity      = weather_info[:humidity]
  text          = weather_info[:text]
  sunrise       = weather_info[:sunrise]
  sunset        = weather_info[:sunset]
  forecast_low  = weather_info[:forecast_low]
  forecast_high = weather_info[:forecast_high]

  return "#{city}, #{region}:\n" +
         "   Currently #{temp} degrees, #{humidity}% humidity, #{wind_speed} mph winds, #{text}.\n" +
         "   Forecast: #{forecast_low} low, #{forecast_high} high.\n" +
         "   Sunrise: #{sunrise}, sunset: #{sunset}.\n"
end

class SendMail < Net::SMTP
  def initialize(options)
      super
      @user     = options["user"]
      @from     = options["from"]
      @to       = options["to"].to_a
      @pass     = options["pass"]
      @server   = options["server"]
      @subject  = options["subject"]
  end

  def body=(mail_body)
      time_foo = Time.new.httpdate
      # BUILD HEADERS
      @body =  "From: #{@from} <#{@from}>\n"
      @body << "To: #{@to}<#{@to}>\n"
      @body << "Subject: #{@subject}\n"
      @body << "Date: #{time_foo}\n"
      @body << "Importance:high\n"
      @body << "MIME-Version:1.0\n"
      @body << "\n\n\n"
      # MESSAGE BODY
      @body << mail_body
  end

  def send
    smtp = Net::SMTP.start(@server, 25)
    smtp.send_message  @body, @from, @to
    smtp.finish
  end
end

##########
#  MAIN  #
##########

# 'USAK0216' (seward, ak)
# 'USNM0292' (santa fe, nm)
# 'USVA0797' (va beach, va)
# 'USGA0506' (savannah, ga)
# 'USRI0050' (providence, ri)

weather_string = ''
units = 'f'
['USRI0050', 'USNM0292', 'USGA0506', 'USAK0216', 'USVA0797'].each {|city|
  weather_string = weather_string + get_weather_for_city(city,'f') + "\n"
}

# use the ruby mail api to create a message and send it to me

o=Hash.new
o["from"]    = ""                        # populate this with my email address
o["to"]      = ""                        # use the same email address here
o["server"]  = ""                        # put my smtp server name here
o["subject"] = "Today's Weather in Places I'd Like to Live"
mail=SendMail.new(o)
mail.body=weather_string
mail.send

Note that where I use a weather string like "USAK0216" in the Ruby weather code above, I can also use a zip code if I know it. For instance, the zip code for Talkeetna, Alaska is 99676, so I can use "99676" instead of having to know what the correct weather string is for that town.

Many thanks to Jay Anderson, who created and posted 'get weather' code at ruby-forum.com. And thanks to Yahoo! for providing this service free of charge.