BaroqueW

BaroqueW

and his sidekick nikkitaa

BaroqueW RSS Feed
 
 
 
 

Météo en Ruby

Il y a quelque temps j’avais fait un script pour fournir des données météorologiques sur IRC (pour la France seulement). Malheureusement, le script a été rendu caduque par un changement du site Météo France sur lequel il se basait. En voici la nouvelle version.

Ce script remplace feu MeteoqueW (PHP et TCL) par une version Ruby, histoire de s’amuser, mais surtout histoire de fournir un serveur qui se chargera de traduire les pages Météo France en quelque chose de plus simple pour le nouveau script TCL (qui du coup n’aura plus besoin de télécharger et analyser les pages Web lui-même – thin client, nous voilà !). Le script TCL sera détaillé dans un autre article comme il sert seulement d’interface avec un eggdrop pour IRC.

Ci-dessous le code du script Ruby :

Notez que j’ai dû enlever les apostrophes des expressions régulières pour que le texte s’affiche proprement ci-dessous.

#!/usr/bin/ruby
# = Meteo CGI application
#
# Meteo CGI application, formerly MeteoqueW (PHP, TCL) provides a text version of Meteo France’s weather forecast for one specific location
#
# This location is passed in argument with the parameter ‘ville’ (HTTP GET)
# Other parameter is ‘html’ to display in HTML with line breaks or in plain text, lines being separated by _
# Last parameter is ‘verbosity’ (v) to display more or less information: 0 hides winds and detailed weather forecast, 1 show detailed weather
# forecast, 2 and above shows everything.

require ‘net/http’
require "cgi"
require ‘extend_string’
require ‘uri’

# UNICODE
$KCODE=‘u’

# Web server informations
$host = ‘france.meteofrance.com’
iphost = ‘160.92.186.20′
pathprefix = ‘/france/accueil/resultat?RECHERCHE_RESULTAT_PORTLET.path=rechercheresultat&query=’
pathsuffix = ‘&type=PREV_FRANCE&satellite=france’

# Used if we do not find the city
$failure = false

# Class to store a resulting weather forecas
class Meteo
   attr_accessor :maindays, :times, :temperatures, :refreshsrc, :weathers, :winds, :windspeeds, :windpeaks, :location, :html

 # Found new time entry, let’s add it
 def add_time(object)
     object.gsub!("soiree", "soirée")
     object.gsub!("apres-midi", "après-midi")
     object.gsub!("aujourdhui", "Aujourd’hui")
     object.gsub!(‘  ’, ‘, ‘)
     @times.push(object)
 end

 # Found new mainday entry, let’s add it
 def add_mainday(object)
     @maindays.push(object)
 end

 # Found new temperatures entry, let’s add it
 def add_temperature(object)
     @temperatures.push(object)
 end

 # Found new weather entry, let’s add it
 def add_weather(object)
     object.gsub!("eparses", "éparses")
     object.gsub!("eclaircies", "éclaircies")
     object.gsub!("leger", "léger")
     @weathers.push(object)
 end

 # Found new wind entry, let’s add it
 def add_wind(object)
     @winds.push(object)
 end

 # Found new windpeak entry, let’s add it
 def add_windpeak(object)
     @windpeaks.push(object)
 end

 # Found new windspeed entry, let’s add it
 def add_windspeed(object)
     @windspeeds.push(object)
 end

 # Make sure to create the arrays
 # If html = 0 then no html output
 def initialize(html)
   @html = html
   @times = Array.new
   @weathers = Array.new
   @winds = Array.new
   @maindays = Array.new
   @windspeeds = Array.new
   @windpeaks = Array.new
   @temperatures = Array.new
 end

 # Format nicely the results
 def display (verbosity = 0)
   k = maindays.length

   # HTML display
   if html != ‘0′
     line = "<b><u>Météo à #{location}</u></b>"
     if verbosity.to_i > 0
       line += " (Actualisé à #{refreshsrc})"
     end
   puts "#{line}<br/><br/>"
   puts "<b>Tendances :</b><br/>"
    for x in (0..k-1)
      line = "#{maindays[x]} : #{weathers[x]} (#{temperatures[x]})"
      if verbosity.to_i > 1
        line += ", #{winds[x]}"
        if !windspeeds[x].include? "-"
          line += " à #{windspeeds[x]}"
        end
        if !windpeaks[x].include? "-"
            line += " avec des rafales à #{windpeaks[x]}"
          end
      end
    puts "#{line}<br/>"

    end
    if verbosity.to_i > 0
      puts "<br/><b>Météo détaillée :</b><br/>"
      for x in (k..k + times.length1)
        line = "#{times[x-k]} : #{weathers[x]} (#{temperatures[x]})"
        if verbosity.to_i > 1
          line += ", #{winds[x]}"
        end
      puts "#{line}<br/>"
      end
    end
   # Plain text display
   else
       line = "Météo à #{location}"
       if verbosity.to_i > 0
         line += " (Actualisé à #{refreshsrc})"
       end
     line += "_"
     line += "Tendances :_"
     for x in (0..k-1)
       line += "#{maindays[x]} : #{weathers[x]} (#{temperatures[x]})"
       if verbosity.to_i > 1
         line += ", #{winds[x]}"
         if !windspeeds[x].include? "-"
           line += " à #{windspeeds[x]}"
         end
         if !windpeaks[x].include? "-"
             line += " avec des rafales à #{windpeaks[x]}"
           end
       end
     line += "_"
     end
     if verbosity.to_i > 0
     line += "Météo détaillée :_"
       for x in (k..k + times.length1)
         line += "#{times[x-k]} : #{weathers[x]} (#{temperatures[x]})"
         if verbosity.to_i > 1
           line += ", #{winds[x]}"
         end
       line += "_"
       end
     end
     puts "#{line}"
   end
 end

end

# Gets the correct page with the weather forecast
#
# One redirection 302 is OK, not 2
#
# If we get a 200 OK reply, fetch the first link, as we got a multiple-answer result
def fetch(uri_str, exec = 0)

response = Net::HTTP.get_response(URI.parse(uri_str))

# Check answer
case response
  # If we get a proper reply from server
    when Net::HTTPSuccess     then

            if exec == 0

            body = response.body

            firstfound = /.france.meteo\?PREVISIONS_PORTLET.path=previsionsville.\d{6}/.match(body)

            fetch(‘http://’ + $host + firstfound.to_s, exec + 1)

          elsif exec == 1

            response.body

          end

    when Net::HTTPRedirection then

            if exec == 0
              fetch(response[‘location’], exec + 1)
            elsif exec == 1
              response.body
            end

    else
            error()
end
end

# analyze the resulting body from the requested page
def analyze(text)

  #find name of city
  ville = /<p class="city"><strong>([a-zA-Z0-9 ;&amp;-]+)<.strong>/.match(text)

  # If there is indeed a city found
  if !$1.nil?

    ville_locale = $1
    arrondissement = /([0-9])+eme/.match(ville_locale)
    num_arrondissement = $1
    if !$1.nil?
      $meteoresult.location = ville_locale.gsub(num_arrondissement + "eme", num_arrondissement + "ème")
    else
      $meteoresult.location = ville_locale
    end

    refresh = /<p class="refreshed"><em>Actualise a ([0-9][0-9]?h[0-9][0-9]?)<.em>/.match(text)
    $meteoresult.refreshsrc = $1

    #find main days and general forecast
    name_of_maindays = text.scan(/<a class="lienAtmographe" style="cursor:pointer;">([&amp;;a-zA-Z -]+)<.a><.td><td class="temperatures"><img alt="([a-zA-Z -]+)" src="meteo.pictos.web.CARTE.[0-9]+.[a-zA-Z0-9 -_]+" title="[a-zA-Z -]+".> (-?[0-9]+°C . -?[0-9]+°C)<.td><td class="winds"><img alt="[a-zA-Z0-9 -]+" src="meteo.pictos.web.SITE.[0-9]+.[a-zA-Z0-9 -_]+" title="([a-zA-Z -]+)".> ([0-9]+ km.h|-)<.td><td class="winds2"> ([0-9]+ km.h|-)<.td><td class="borderRight"><.td><.tr>/)
    flat_maindays = name_of_maindays.flatten

    i = 0
    while i < flat_maindays.length
      $meteoresult.add_mainday(flat_maindays[i].to_s)
      i += 1
      $meteoresult.add_weather(flat_maindays[i].to_s)
      i += 1
      $meteoresult.add_temperature(flat_maindays[i].to_s)
      i += 1
      $meteoresult.add_wind(flat_maindays[i].to_s)
      i += 1
      $meteoresult.add_windspeed(flat_maindays[i].to_s)
      i += 1
      $meteoresult.add_windpeak(flat_maindays[i].to_s)
      i += 1
    end

    #find days and forecast
    name_of_days = text.scan(/<tr class="([a-zA-Z 0-9]+)"><td class="borderLeft"><.td><td class="firstCol">([&amp;;a-zA-Z -]+)<.td><td class="temperatures"><img alt="[a-zA-Z -]+" src="meteo.pictos.web.SITE.[0-9]+.[a-zA-Z0-9 -_]+" title="([a-zA-Z -]+)".> (-?[0-9]+°C)<.td><td class="winds"><img alt="[a-zA-Z0-9 -]+" src="meteo.pictos.web.SITE.[0-9]+.[a-zA-Z0-9 -_]+" title="([a-zA-Z -]+)".>/)
    flat_days = name_of_days.flatten
    trimmed_names = flat_days.collect {|x| x.gsub(/currentLine\d/, )}

    i = 0
    while i < trimmed_names.length do
      $meteoresult.add_time(trimmed_names[i] + ‘ ‘ + trimmed_names[i+1].downcase!)
      i += 2
      $meteoresult.add_weather(trimmed_names[i].to_s)
      i += 1
      $meteoresult.add_temperature(trimmed_names[i].to_s)
      i += 1
      $meteoresult.add_wind(trimmed_names[i].to_s)
      i += 1
      #$meteoresult.add_windspeed(trimmed_names[i].to_s)
      #i += 1
      #$meteoresult.add_windpeak(trimmed_names[i].to_s)
      #i += 1
    end
  else
    $failure = true
  end
end

cgi = CGI.new

# HTML headers
# Present, even in case of error, if html!=0

if cgi["html"] != ‘0′
  puts "Content-Type: text/html"
  puts
  puts "<html>"
  puts "<head>"
  puts "<title>Météo</title>"
  puts "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
  puts "<link REL=\"icon\" HREF=\"/temp/meteo/MeteoqueW/favicon.ico\">"
  puts "</head>"
  puts "<body><p>"
else
  puts "Content-Type: text/plain; charset=UTF-8"
  puts
end

# If we receive the argument (ville) properly
if cgi.has_key?(‘ville’)

    if !cgi["ville"].empty?
      # Fetch the argument and sanitize it
      ville = cgi["ville"]

      # Create a connection and request file
      path = pathprefix + CGI::escape(ville) + pathsuffix

      reply_body = fetch(‘http://’ + $host + path)

      $meteoresult = Meteo.new(cgi["html"])

      analyze(reply_body.removeaccents())

      v=0

      if cgi.has_key?(‘v’)
        if !cgi["v"].empty?
          v = cgi["v"]
        end
      end

      if !$failure
           $meteoresult.display(v)
      else
        puts "Ville introuvable"
      end
    # If we don’t receive the argument (ville) properly
    else
        puts "Vous n’avez pas précisé la ville"
    end
    else
        puts "Vous n’avez pas précisé la ville"
end

    # Closing HTML tags
if cgi["html"] != ‘0′
    puts "</p></body>"
    puts "</html>"
end

Le code est accompagné d’un peu de Rdoc. Je ne suis pas bien sûr de son utilité dans le cas présent mais c’est toujours intéressant de maintenir un semblant de documentation sur tous ses projets.

Le code fourni est à placer dans le dossier ‘cgi-bin’ de votre serveur Web. Les paramètres d’entrée sont respectivement ‘ville’, ‘v’ et ‘html’ pour le nom de la ville (ou code postal) pour lequel trouver la météo, le degré de verbosité de la réponse (0, 1, 2) et le format de retour (plain text avec chaque ligne séparée par un _ à cause de l’utilisation conjointe avec le script TCL pour IRC).

À noter, la classe interne “Meteo” qui stocke les données et en gère l’affichage après analyse par la fonction du même nom.

Vous pouvez télécharger la librairie ‘extend_string.rb’ sur http://www.techniconseils.ca/en/scripts-remove-accents-ruby.php.

Si vous utilisez ce script, merci de me laisser un mot ! Merci également de toujours référer à mon site si vous modifiez ce script.

Creative Commons License
MeteoqueW by http://www.baroquew.info/wordpress/archives/meteo-en-ruby is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.0 France License.

Dans un futur article, je ferai une petite introduction à Ruby basée sur ce script.

  • Share/Save/Bookmark

Related posts


5 Responses to “Météo en Ruby”

  1. 1
    Miam:

    Bonjour.
    Peut-on espérer voir votre script codé en C# ?

  2. 2
    BaroqueW:

    Malheureusement, j’en doute. Le script est surtout prévu pour IRC et je ne connais pas de bots/scripts IRC qui puissent utiliser du C# (et moi-même, je ne m’y connais pas vraiment en C# mais je suppose que je pourrais apprendre pour voir).

    Sinon, peut-être que vous pouvez directement invoquer du Ruby depuis votre code C#: http://www.igvita.com/2007/04/23/invoking-ruby-in-c-net/ ?

    Quel genre de projet aviez-vous en tête ?

    /Baro

  3. 3
    Miam:

    Je connais très peu la programmation, le C# semblant le plus facile à apprendre. J’aurais souhaité récupérer les données météo sur mon PDA (windows Mobile)par l’intermédiaire d’un exécutable codé en C#.

  4. 4
    BaroqueW:

    Hum, pour faire ca, il suffit que tu trouves comment ouvrir un socket en C# et apres tu peux reutiliser toutes mes expressions regulieres pour analyser les donnees. Ensuite le reste du codes c’est pour l’interfacer avec le bot IRC.

  5. 5
    Miam:

    Ok. Je rassemble toutes les information en ce moment. C’est vrai de ce que j’ai compris les expressions régulières c’est vraiment puissant pour analyser des chaines de caractères.

Leave a Reply

Status

  • BaroqueW sorting the first pictures
    5 hours ago
  • BaroqueW holy cow
    7 hours ago
  • BaroqueW ecoute desproges dans les fjords
    2 days ago
  • BaroqueW : it is in fact NOT funny to point people in the wrong direction when they're lost
    2 days ago

Popular posts

My Social Networks

DandyID 43 Things Clipmarks coComment Dailymotion Delicious deviantART Digg Diigo Facebook Flickr Get Satisfaction Guitar Hero HelloTxt Hulu ICQ Imdb Imeem Kiva last.fm Linkedin Netvibes orkut PeoplePond Picasa Plaxo PostCrossing RockBand Scribd Stumbleupon Tagged TripAdvisor Twitpic Twitter Xbox LIVE YouTube

Expand the experience

Blogroll

Internet Map

Meta