#!/usr/bin/env python3 """Fetch daily weather for Chambourcy, Kuwana, Tokyo and generate a static HTML page (FR/EN/JA).""" import json import urllib.request from datetime import datetime, timezone # City configs CITIES = [ {"name": "Chambourcy", "lat": 48.892, "lon": 2.049, "tz": "Europe/Paris"}, {"name": "Kuwana", "lat": 35.064, "lon": 136.686, "tz": "Asia/Tokyo"}, {"name": "Tokyo", "lat": 35.6762, "lon": 139.6503, "tz": "Asia/Tokyo"}, ] # WMO weather codes -> {lang: description} WMO_CODES = { 0: {"fr": "Ciel dégagé", "en": "Clear sky", "ja": "快晴", "emoji": "☀️"}, 1: {"fr": "Peu nuageux", "en": "Mainly clear", "ja": "概ね晴れ", "emoji": "🌤️"}, 2: {"fr": "Partiellement nuageux", "en": "Partly cloudy", "ja": "所により曇り", "emoji": "⛅"}, 3: {"fr": "Couvert", "en": "Overcast", "ja": "曇り", "emoji": "☁️"}, 45: {"fr": "Brouillard", "en": "Fog", "ja": "霧", "emoji": "🌫️"}, 48: {"fr": "Brouillard givrant", "en": "Rime fog", "ja": "着氷霧", "emoji": "🌫️"}, 51: {"fr": "Bruine légère", "en": "Light drizzle", "ja": "軽い霧雨", "emoji": "🌦️"}, 53: {"fr": "Bruine modérée", "en": "Moderate drizzle", "ja": "霧雨", "emoji": "🌦️"}, 55: {"fr": "Bruine dense", "en": "Dense drizzle", "ja": "濃い霧雨", "emoji": "🌧️"}, 56: {"fr": "Bruine verglaçante", "en": "Freezing drizzle", "ja": "着氷霧雨", "emoji": "🌧️"}, 57: {"fr": "Bruine verglaçante dense", "en": "Dense freezing drizzle", "ja": "濃い着氷霧雨", "emoji": "🌧️"}, 61: {"fr": "Pluie légère", "en": "Slight rain", "ja": "小雨", "emoji": "🌧️"}, 63: {"fr": "Pluie modérée", "en": "Moderate rain", "ja": "雨", "emoji": "🌧️"}, 65: {"fr": "Pluie forte", "en": "Heavy rain", "ja": "大雨", "emoji": "🌧️"}, 66: {"fr": "Pluie verglaçante", "en": "Freezing rain", "ja": "着氷雨", "emoji": "🌧️"}, 67: {"fr": "Pluie verglaçante forte", "en": "Heavy freezing rain", "ja": "着氷大雨", "emoji": "🌧️"}, 71: {"fr": "Neige légère", "en": "Slight snow", "ja": "小雪", "emoji": "🌨️"}, 73: {"fr": "Neige modérée", "en": "Moderate snow", "ja": "雪", "emoji": "🌨️"}, 75: {"fr": "Neige forte", "en": "Heavy snow", "ja": "大雪", "emoji": "❄️"}, 77: {"fr": "Grains de neige", "en": "Snow grains", "ja": "細かい雪", "emoji": "🌨️"}, 80: {"fr": "Averses légères", "en": "Light showers", "ja": "軽いにわか雨", "emoji": "🌦️"}, 81: {"fr": "Averses modérées", "en": "Moderate showers", "ja": "にわか雨", "emoji": "🌧️"}, 82: {"fr": "Averses violentes", "en": "Violent showers", "ja": "激しいにわか雨", "emoji": "⛈️"}, 85: {"fr": "Averses de neige légères", "en": "Light snow showers", "ja": "軽いにわか雪", "emoji": "🌨️"}, 86: {"fr": "Averses de neige fortes", "en": "Heavy snow showers", "ja": "激しいにわか雪", "emoji": "❄️"}, 95: {"fr": "Orage", "en": "Thunderstorm", "ja": "雷雨", "emoji": "⛈️"}, 96: {"fr": "Orage grêle léger", "en": "Thunderstorm slight hail", "ja": "雷雨・小さな雹", "emoji": "⛈️"}, 99: {"fr": "Orage grêle fort", "en": "Thunderstorm heavy hail", "ja": "雷雨・大きな雹", "emoji": "⛈️"}, } # UI translations I18N = { "fr": { "title": "Météo — Chambourcy · Kuwana · Tokyo", "heading": "🌤️ Météo du jour", "subtitle": "Chambourcy · Kuwana · Tokyo", "feels": "Ressenti", "humidity": "Humidité", "wind": "Vent", "maxmin": "Max / Min", "precip": "Précip.", "sunrise": "Lever", "sunset": "Coucher", "data_source": "Données", "updated": "Mis à jour le", "error": "Erreur", "unknown": "Inconnu", }, "en": { "title": "Weather — Chambourcy · Kuwana · Tokyo", "heading": "🌤️ Today's Weather", "subtitle": "Chambourcy · Kuwana · Tokyo", "feels": "Feels like", "humidity": "Humidity", "wind": "Wind", "maxmin": "High / Low", "precip": "Rain", "sunrise": "Sunrise", "sunset": "Sunset", "data_source": "Data", "updated": "Updated on", "error": "Error", "unknown": "Unknown", }, "ja": { "title": "天気 — Chambourcy · Kuwana · Tokyo", "heading": "🌤️ 今日の天気", "subtitle": "Chambourcy · Kuwana · Tokyo", "feels": "体感温度", "humidity": "湿度", "wind": "風速", "maxmin": "最高 / 最低", "precip": "降水量", "sunrise": "日の出", "sunset": "日の入り", "data_source": "データ元", "updated": "更新日時", "error": "エラー", "unknown": "不明", }, } # Country names per lang COUNTRY = { "Chambourcy": {"fr": "France", "en": "France", "ja": "フランス"}, "Kuwana": {"fr": "Japon", "en": "Japan", "ja": "日本"}, "Tokyo": {"fr": "Japon", "en": "Japan", "ja": "日本"}, } # Export i18n + country maps as JSON for client-side JS I18N_JSON = json.dumps(I18N, ensure_ascii=False) COUNTRY_JSON = json.dumps(COUNTRY, ensure_ascii=False) WMO_JSON = json.dumps(WMO_CODES, ensure_ascii=False) def fetch_weather(city): lat, lon = city["lat"], city["lon"] url = ( f"https://api.open-meteo.com/v1/forecast" f"?latitude={lat}&longitude={lon}" f"¤t=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m" f"&daily=weather_code,temperature_2m_max,temperature_2m_min,sunrise,sunset,precipitation_sum" f"&timezone={city['tz']}" f"&forecast_days=1" ) req = urllib.request.Request(url, headers={"User-Agent": "weather-example/1.0"}) with urllib.request.urlopen(req, timeout=15) as resp: return json.loads(resp.read().decode()) def generate_html(): now = datetime.now(timezone.utc) cards_html = [] for city in CITIES: try: data = fetch_weather(city) except Exception as e: cards_html.append( f'

{city["name"]}

' f'

Erreur: {e}

' ) continue current = data.get("current", {}) daily = data.get("daily", {}) temp = current.get("temperature_2m", "N/A") feels = current.get("apparent_temperature", "N/A") humidity = current.get("relative_humidity_2m", "N/A") wind = current.get("wind_speed_10m", "N/A") code = current.get("weather_code", 0) t_max = daily.get("temperature_2m_max", [None]) t_min = daily.get("temperature_2m_min", [None]) precip = daily.get("precipitation_sum", [None]) sunrise = daily.get("sunrise", [None]) sunset = daily.get("sunset", [None]) t_max_val = t_max[0] if t_max else "N/A" t_min_val = t_min[0] if t_min else "N/A" precip_val = precip[0] if precip else "N/A" sunrise_val = sunrise[0] if sunrise else "N/A" sunset_val = sunset[0] if sunset else "N/A" card = f"""

{city['name']}

{temp}°C
Ressenti{feels}°C
Humidité{humidity}%
Vent{wind} km/h
Max / Min{t_max_val}° / {t_min_val}°
Précip.{precip_val} mm
Lever{sunrise_val}
Coucher{sunset_val}
""" cards_html.append(card) cards = "\n".join(cards_html) html = f""" Météo — Chambourcy · Kuwana · Tokyo

🌤️ Météo du jour

Chambourcy · Kuwana · Tokyo

{cards}
""" return html if __name__ == "__main__": html = generate_html() output_path = "/home/jerome/hermes-sites/weather-example.hidrago.click/index.html" with open(output_path, "w", encoding="utf-8") as f: f.write(html) print(f"Weather page generated at {output_path}")