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.
# = 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.length – 1)
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.length – 1)
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 ;&-]+)<.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;">([&;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">([&;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.

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.





May 19th, 2009 at 20:33
Bonjour.
Peut-on espérer voir votre script codé en C# ?
May 20th, 2009 at 11:41
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
May 25th, 2009 at 20:10
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#.
May 28th, 2009 at 19:35
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.
May 30th, 2009 at 16:44
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.