230 lines
8.5 KiB
Python
Executable File
230 lines
8.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
|
||
import os
|
||
import sys
|
||
import json
|
||
import gi
|
||
import requests
|
||
from datetime import datetime, timezone
|
||
|
||
gi.require_version('Geoclue', '2.0')
|
||
from gi.repository import Geoclue
|
||
|
||
# ─────────── ICONOS DEL CLIMA ─────────── #
|
||
weather_icons = {
|
||
"01d": "", "01n": "", "02d": "", "02n": "",
|
||
"03d": "", "03n": "", "04d": "", "04n": "",
|
||
"09d": "", "09n": "", "10d": "", "10n": "",
|
||
"11d": "", "11n": "", "13d": "", "13n": "",
|
||
"50d": "", "50n": "", "default": ""
|
||
}
|
||
|
||
# ─────────── API KEYS ─────────── #
|
||
try:
|
||
with open('/home/teraflops/apikeys/openweathermaps', 'r') as f:
|
||
api_key = f.read().strip()
|
||
with open('/home/teraflops/apikeys/google_geoip', 'r') as f:
|
||
google_key = f.read().strip()
|
||
except Exception:
|
||
print(json.dumps({"text": "Error al leer las API keys"}))
|
||
sys.exit(1)
|
||
|
||
# ─────────── OBTENER UBICACIÓN ─────────── #
|
||
def get_location_from_geoclue():
|
||
try:
|
||
clue = Geoclue.Simple.new_sync("weather-script", Geoclue.AccuracyLevel.EXACT, None)
|
||
location = clue.get_location()
|
||
lat = location.get_property('latitude')
|
||
lon = location.get_property('longitude')
|
||
desc = location.get_property('description')
|
||
acc = location.get_property('accuracy')
|
||
print(f"🛰️ GeoClue: {lat}, {lon} — Precisión: {acc:.1f} m — Fuente: {desc}")
|
||
return lat, lon, desc
|
||
except Exception as e:
|
||
print("GeoClue (GI): error al obtener ubicación:", e)
|
||
return None
|
||
|
||
def get_location_from_google():
|
||
try:
|
||
url = f"https://www.googleapis.com/geolocation/v1/geolocate?key={google_key}"
|
||
response = requests.post(url, json={})
|
||
response.raise_for_status()
|
||
data = response.json()
|
||
lat = data['location']['lat']
|
||
lon = data['location']['lng']
|
||
accuracy = data.get('accuracy', 0)
|
||
print(f"🛰️ GoogleGeo: {lat}, {lon} — Precisión: {accuracy:.0f} m")
|
||
return lat, lon, f"GoogleGeo ({accuracy:.0f} m)"
|
||
except Exception as e:
|
||
print("Google Geolocation API: fallo:", e)
|
||
return None
|
||
|
||
def reverse_geocode(lat, lon):
|
||
try:
|
||
url = "https://nominatim.openstreetmap.org/reverse"
|
||
params = {
|
||
"lat": lat,
|
||
"lon": lon,
|
||
"format": "json",
|
||
"zoom": 10,
|
||
"addressdetails": 1,
|
||
}
|
||
headers = {"User-Agent": "weather-waybar-script"}
|
||
response = requests.get(url, params=params, headers=headers)
|
||
response.raise_for_status()
|
||
data = response.json()
|
||
address = data.get("address", {})
|
||
ciudad = address.get("city") or address.get("town") or address.get("village") or "Desconocido"
|
||
pais = address.get("country", "Desconocido")
|
||
return ciudad, pais
|
||
except Exception as e:
|
||
print("Reverse geocode falló:", e)
|
||
return "Desconocido", "Desconocido"
|
||
|
||
# ─────────── ALERTA LLUVIA SOLO EN TOOLTIP ─────────── #
|
||
def check_rain_alert(forecast_data, threshold=60, max_horas=6):
|
||
ahora = datetime.utcnow()
|
||
mensaje_alerta = ""
|
||
|
||
for block in forecast_data['list']:
|
||
bloque_hora = datetime.strptime(block['dt_txt'], "%Y-%m-%d %H:%M:%S")
|
||
if bloque_hora <= ahora:
|
||
continue
|
||
|
||
if (bloque_hora - ahora).total_seconds() > max_horas * 3600:
|
||
continue
|
||
|
||
pop = block.get('pop', 0) * 100
|
||
if pop >= threshold:
|
||
hora_local = bloque_hora.replace(tzinfo=timezone.utc).astimezone().strftime("%H:%M")
|
||
mensaje_alerta = f"⚠️ Posible lluvia a las {hora_local} ({int(pop)}%)"
|
||
break
|
||
|
||
return mensaje_alerta
|
||
|
||
# ─────────── PRONÓSTICO EXTENDIDO ─────────── #
|
||
def get_daily_forecast(forecast_data):
|
||
from collections import defaultdict
|
||
dias = defaultdict(list)
|
||
for entry in forecast_data['list']:
|
||
fecha = entry['dt_txt'].split(' ')[0]
|
||
dias[fecha].append(entry)
|
||
|
||
resumen = []
|
||
for i, (dia, bloques) in enumerate(dias.items()):
|
||
if i >= 3: break
|
||
temps = [b['main']['temp'] for b in bloques]
|
||
pops = [b.get('pop', 0) * 100 for b in bloques]
|
||
estado = bloques[0]['weather'][0]['description'].capitalize()
|
||
resumen.append(f"📅 {dia}: {estado}, {round(min(temps))}–{round(max(temps))}°C, ☔ {round(max(pops))}%")
|
||
return "\n".join(resumen)
|
||
|
||
# ─────────── UBICACIÓN ─────────── #
|
||
usar_ubicacion_manual = False # Cambia a True si quieres forzar ubicación
|
||
|
||
if usar_ubicacion_manual:
|
||
latitud, longitud = 40.4168, -3.7038 # Ejemplo: Madrid
|
||
region = "Manual"
|
||
print(f"🧪 Usando ubicación manual: {latitud}, {longitud}")
|
||
else:
|
||
loc = get_location_from_geoclue() or get_location_from_google()
|
||
if not loc:
|
||
print(json.dumps({"text": "Error al obtener la ubicación"}))
|
||
sys.exit(1)
|
||
latitud, longitud, region = loc
|
||
|
||
ciudad, pais = reverse_geocode(latitud, longitud)
|
||
|
||
# ─────────── API WEATHER ─────────── #
|
||
params = {
|
||
"lat": latitud,
|
||
"lon": longitud,
|
||
"appid": api_key,
|
||
"units": "metric",
|
||
"lang": "es",
|
||
}
|
||
|
||
weather_url = "https://api.openweathermap.org/data/2.5/weather"
|
||
response = requests.get(weather_url, params=params)
|
||
if response.status_code != 200:
|
||
print(json.dumps({"text": "Error al obtener datos del clima"}))
|
||
sys.exit(1)
|
||
data = response.json()
|
||
|
||
# ─────────── DATOS CLIMA ─────────── #
|
||
temp = f"{round(data['main']['temp'])}°C"
|
||
status = data['weather'][0]['description'].capitalize()
|
||
status_code = data['weather'][0]['icon']
|
||
icon = weather_icons.get(status_code, weather_icons["default"])
|
||
temp_feel = f"{round(data['main']['feels_like'])}°C"
|
||
temp_feel_text = f"Sensación de {temp_feel}"
|
||
temp_min = f"{round(data['main']['temp_min'])}°C"
|
||
temp_max = f"{round(data['main']['temp_max'])}°C"
|
||
temp_min_max = f" {temp_min}\t\t {temp_max}"
|
||
wind_speed = f"{round(data['wind']['speed'] * 3.6)} km/h"
|
||
wind_text = f" {wind_speed}"
|
||
humidity = f"{data['main']['humidity']}%"
|
||
humidity_text = f" {humidity}"
|
||
visibility = data.get('visibility', 0)
|
||
visibility_km = f"{visibility / 1000} km"
|
||
visibility_text = f" {visibility_km}"
|
||
|
||
# ─────────── CALIDAD DEL AIRE ─────────── #
|
||
aqi_url = "https://api.openweathermap.org/data/2.5/air_pollution"
|
||
aqi_response = requests.get(aqi_url, params=params)
|
||
if aqi_response.status_code == 200:
|
||
aqi = aqi_response.json()['list'][0]['main']['aqi']
|
||
aqi_levels = {1: "Bueno", 2: "Moderado", 3: "Perjudicial", 4: "Malo", 5: "Muy malo"}
|
||
air_quality_index = aqi_levels.get(aqi, "N/A")
|
||
else:
|
||
air_quality_index = "N/A"
|
||
|
||
# ─────────── PRONÓSTICO ─────────── #
|
||
forecast_url = "https://api.openweathermap.org/data/2.5/forecast"
|
||
forecast_response = requests.get(forecast_url, params=params)
|
||
if forecast_response.status_code == 200:
|
||
forecast_data = forecast_response.json()
|
||
precipitation = forecast_data['list'][0].get('pop', 0) * 100
|
||
prediction = f"\n\n {precipitation:.0f}% probabilidad de lluvia"
|
||
alerta_lluvia = check_rain_alert(forecast_data)
|
||
pronostico_dias = get_daily_forecast(forecast_data)
|
||
else:
|
||
prediction = ""
|
||
alerta_lluvia = ""
|
||
pronostico_dias = ""
|
||
|
||
# ─────────── TOOLTIP ─────────── #
|
||
time_of_day = "Día" if status_code.endswith('d') else "Noche"
|
||
ubicacion = f"{ciudad}, {region}, {pais} ({latitud:.2f}, {longitud:.2f})"
|
||
|
||
tooltip_text = ""
|
||
if alerta_lluvia:
|
||
tooltip_text += f"{alerta_lluvia}\n\n"
|
||
|
||
tooltip_text += str.format(
|
||
"{}\n{}\n{}\n{}\n\n{}\n{}\n{}{}\n\n<small>{}</small>",
|
||
f'<span size="xx-large">{temp}</span>',
|
||
f"<big>{icon}</big>",
|
||
f"<big>{status} ({time_of_day})</big>",
|
||
f"<small>{temp_feel_text}</small>",
|
||
f"<big>{temp_min_max}</big>",
|
||
f"{wind_text}\t{humidity_text}",
|
||
f"{visibility_text}\tICA {air_quality_index}",
|
||
f"<i>{prediction}</i>",
|
||
f"Ubicación: {ubicacion}"
|
||
)
|
||
|
||
if pronostico_dias:
|
||
tooltip_text += f"\n\n<b>Próximos días:</b>\n{pronostico_dias}"
|
||
|
||
# ─────────── OUTPUT PARA WAYBAR ─────────── #
|
||
out_data = {
|
||
"text": f"{icon} {temp}",
|
||
"alt": f"{status} ({time_of_day})",
|
||
"tooltip": tooltip_text,
|
||
"class": status_code,
|
||
}
|
||
|
||
print(json.dumps(out_data))
|
||
|