Backup automatico script del 2026-01-01 14:12
This commit is contained in:
@@ -18,9 +18,9 @@ from telegram.ext import (
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# LOOGLE BOT V7.0 (ULTIMATE)
|
||||
# - Dashboard Sistema (SSH/Ping)
|
||||
# - Meteo Arome ASCII (On-Demand + Schedulato)
|
||||
# LOOGLE BOT V7.9 (ULTIMATE + GLOBAL GFS FIX)
|
||||
# - Dashboard Sistema
|
||||
# - Meteo Smart: Arome (EU), Icon (EU-Est), JMA (JP), GFS (Mondo)
|
||||
# - Multi-User Security
|
||||
# =============================================================================
|
||||
|
||||
@@ -45,8 +45,7 @@ TZINFO = ZoneInfo(TZ)
|
||||
|
||||
OPEN_METEO_URL = "https://api.open-meteo.com/v1/forecast"
|
||||
GEOCODING_URL = "https://geocoding-api.open-meteo.com/v1/search"
|
||||
MODEL = "meteofrance_arome_france_hd"
|
||||
HTTP_HEADERS = {"User-Agent": "loogle-bot-v7"}
|
||||
HTTP_HEADERS = {"User-Agent": "loogle-bot-v7.9"}
|
||||
|
||||
# --- LISTE DISPOSITIVI ---
|
||||
CORE_DEVICES = [
|
||||
@@ -123,7 +122,7 @@ def run_speedtest():
|
||||
except: return "Errore Speedtest"
|
||||
|
||||
# =============================================================================
|
||||
# SEZIONE 2: FUNZIONI METEO (AROME ASCII)
|
||||
# SEZIONE 2: METEO INTELLIGENTE (MULTI-MODELLO MOSAICO)
|
||||
# =============================================================================
|
||||
|
||||
def now_local() -> datetime.datetime:
|
||||
@@ -158,24 +157,81 @@ def get_icon_set(prec, snow, code, is_day, cloud, vis, temp, rain, gust, cape):
|
||||
elif gust > 50: sgx = "💨"
|
||||
return sky, sgx
|
||||
|
||||
def get_coordinates(city_name: str) -> Optional[Tuple[float, float, str]]:
|
||||
params = {"name": city_name, "count": 1, "language": "it", "format": "json"}
|
||||
def get_coordinates(city_name: str):
|
||||
"""
|
||||
Cerca le coordinate della città con fallback EN.
|
||||
"""
|
||||
# 1. Tentativo ITALIANO
|
||||
params = {"name": city_name, "count": 10, "language": "it", "format": "json"}
|
||||
try:
|
||||
r = requests.get(GEOCODING_URL, params=params, headers=HTTP_HEADERS, timeout=10)
|
||||
data = r.json()
|
||||
if "results" in data and len(data["results"]) > 0:
|
||||
res = data["results"][0]
|
||||
name = f"{res.get('name')} ({res.get('country_code','')})"
|
||||
return res["latitude"], res["longitude"], name
|
||||
except Exception as e: logger.error(f"Geocoding error: {e}")
|
||||
cc = res.get("country_code", "IT").upper()
|
||||
name = f"{res.get('name')} ({cc})"
|
||||
return res["latitude"], res["longitude"], name, cc
|
||||
except Exception as e: logger.error(f"Geocoding IT error: {e}")
|
||||
|
||||
# 2. Tentativo FALLBACK INGLESE
|
||||
try:
|
||||
params["language"] = "en"
|
||||
r = requests.get(GEOCODING_URL, params=params, headers=HTTP_HEADERS, timeout=10)
|
||||
data = r.json()
|
||||
if "results" in data and len(data["results"]) > 0:
|
||||
res = data["results"][0]
|
||||
cc = res.get("country_code", "IT").upper()
|
||||
name = f"{res.get('name')} ({cc})"
|
||||
return res["latitude"], res["longitude"], name, cc
|
||||
except Exception as e: logger.error(f"Geocoding EN error: {e}")
|
||||
|
||||
return None
|
||||
|
||||
def get_forecast(lat, lon) -> Optional[Dict]:
|
||||
def choose_best_model(lat, lon, cc):
|
||||
"""
|
||||
Seleziona il modello migliore.
|
||||
Fallback Generale: NOAA GFS (perché ha sempre Visibilità/Cape).
|
||||
"""
|
||||
|
||||
# 1. GIAPPONE -> JMA MSM
|
||||
if cc == 'JP':
|
||||
return "jma_msm", "JMA MSM (5km)"
|
||||
|
||||
# 2. SCANDINAVIA -> Yr.no
|
||||
if cc in ['NO', 'SE', 'FI', 'DK', 'IS']:
|
||||
return "metno_nordic", "Yr.no (Nordic)"
|
||||
|
||||
# 3. UK & IRLANDA -> UK Met Office
|
||||
if cc in ['GB', 'IE']:
|
||||
return "ukmo_global", "UK MetOffice"
|
||||
|
||||
# 4. TUNING ITALIA (Mosaico)
|
||||
if cc == 'IT' or cc == 'SM':
|
||||
# ZONA 1: Nord-Ovest, Tirreno, Sardegna (Lon <= 13.0, Lat > 40.5)
|
||||
if lon <= 13.0 and lat > 40.5:
|
||||
return "meteofrance_arome_france_hd", "Arome HD"
|
||||
# ZONA 2: Nord-Est, Adriatico Nord/Centro
|
||||
if lat >= 43.0:
|
||||
return "icon_d2", "ICON-D2 (2km)"
|
||||
# ZONA 3: Sud Italia -> ICON-EU (Meglio di GFS per locale)
|
||||
return "icon_eu", "ICON-EU (7km)"
|
||||
|
||||
# 5. RESTO DEL MONDO (Europa Centrale ICON-D2)
|
||||
if cc in ['DE', 'AT', 'CH', 'LI']:
|
||||
return "icon_d2", "ICON-D2"
|
||||
|
||||
# 6. RESTO DEL MONDO -> NOAA GFS
|
||||
# Usiamo GFS invece di ECMWF perché ECMWF spesso manca di 'visibility'/'cape'
|
||||
# facendo crashare o svuotare il report. GFS è completo.
|
||||
return "gfs_global", "NOAA GFS Global"
|
||||
|
||||
def get_forecast(lat, lon, model):
|
||||
params = {
|
||||
"latitude": lat, "longitude": lon, "timezone": TZ,
|
||||
"forecast_days": 3, "models": MODEL,
|
||||
"wind_speed_unit": "kmh", "precipitation_unit": "mm",
|
||||
"hourly": "temperature_2m,relative_humidity_2m,cloudcover,windspeed_10m,winddirection_10m,windgusts_10m,precipitation,rain,snowfall,weathercode,is_day,cape,visibility",
|
||||
"latitude": lat, "longitude": lon, "timezone": TZ,
|
||||
"forecast_days": 3,
|
||||
"models": model,
|
||||
"wind_speed_unit": "kmh", "precipitation_unit": "mm",
|
||||
"hourly": "temperature_2m,relative_humidity_2m,cloudcover,windspeed_10m,winddirection_10m,windgusts_10m,precipitation,rain,snowfall,weathercode,is_day,cape,visibility"
|
||||
}
|
||||
try:
|
||||
r = requests.get(OPEN_METEO_URL, params=params, headers=HTTP_HEADERS, timeout=25)
|
||||
@@ -183,13 +239,41 @@ def get_forecast(lat, lon) -> Optional[Dict]:
|
||||
return r.json()
|
||||
except Exception as e: logger.error(f"Meteo API error: {e}"); return None
|
||||
|
||||
def generate_weather_report(lat, lon, location_name) -> str:
|
||||
data = get_forecast(lat, lon)
|
||||
if not data: return "❌ Errore API Meteo."
|
||||
def safe_get_list(hourly_data, key, length, default=None):
|
||||
"""Estrae una lista sicura, gestendo chiavi mancanti"""
|
||||
if key in hourly_data and hourly_data[key] is not None:
|
||||
return hourly_data[key]
|
||||
return [default] * length
|
||||
|
||||
def generate_weather_report(lat, lon, location_name, cc="IT") -> str:
|
||||
model_id, model_name = choose_best_model(lat, lon, cc)
|
||||
|
||||
data = get_forecast(lat, lon, model_id)
|
||||
if not data: return f"❌ Errore API Meteo ({model_name})."
|
||||
|
||||
hourly = data.get("hourly", {})
|
||||
times = hourly.get("time", [])
|
||||
if not times: return "❌ Dati orari mancanti."
|
||||
|
||||
L = len(times)
|
||||
|
||||
try:
|
||||
l_temp = safe_get_list(hourly, "temperature_2m", L, 0)
|
||||
l_rh = safe_get_list(hourly, "relative_humidity_2m", L, 0)
|
||||
l_cl = safe_get_list(hourly, "cloudcover", L, 0)
|
||||
l_prec = safe_get_list(hourly, "precipitation", L, 0)
|
||||
l_rain = safe_get_list(hourly, "rain", L, 0)
|
||||
l_snow = safe_get_list(hourly, "snowfall", L, 0)
|
||||
l_wspd = safe_get_list(hourly, "windspeed_10m", L, 0)
|
||||
l_gust = safe_get_list(hourly, "windgusts_10m", L, 0)
|
||||
l_wdir = safe_get_list(hourly, "winddirection_10m", L, 0)
|
||||
l_code = safe_get_list(hourly, "weathercode", L, 0)
|
||||
l_day = safe_get_list(hourly, "is_day", L, 1)
|
||||
l_cape = safe_get_list(hourly, "cape", L, 0)
|
||||
l_vis = safe_get_list(hourly, "visibility", L, 10000)
|
||||
|
||||
except Exception as e:
|
||||
return f"❌ Errore elaborazione dati meteo: {e}"
|
||||
|
||||
now = now_local().replace(minute=0, second=0, microsecond=0)
|
||||
blocks = []
|
||||
@@ -198,7 +282,6 @@ def generate_weather_report(lat, lon, location_name) -> str:
|
||||
end_time = now + datetime.timedelta(hours=hours_duration)
|
||||
lines = [f"{'LT':<2} {'T°':>3} {'h%':>3} {'mm':>2} {'Vento':<5} {'Nv%':>3} {'Sky':<2} {'Sgx':<3}", "-" * 30]
|
||||
count = 0
|
||||
|
||||
for i, t_str in enumerate(times):
|
||||
try: dt = parse_time(t_str)
|
||||
except: continue
|
||||
@@ -206,48 +289,41 @@ def generate_weather_report(lat, lon, location_name) -> str:
|
||||
if dt.hour % step != 0: continue
|
||||
|
||||
try:
|
||||
T = float(hourly["temperature_2m"][i])
|
||||
Rh = int(hourly["relative_humidity_2m"][i] or 0)
|
||||
Cl = int(hourly["cloudcover"][i] or 0)
|
||||
Pr = float(hourly["precipitation"][i] or 0)
|
||||
Rn = float(hourly["rain"][i] or 0)
|
||||
Sn = float(hourly["snowfall"][i] or 0)
|
||||
Wspd = float(hourly["windspeed_10m"][i] or 0)
|
||||
Gust = float(hourly["windgusts_10m"][i] or 0)
|
||||
Wdir = int(hourly["winddirection_10m"][i] or 0)
|
||||
Cape = float(hourly["cape"][i] or 0)
|
||||
Vis = float(hourly["visibility"][i] or 10000)
|
||||
Code = int(hourly["weathercode"][i]) if hourly["weathercode"][i] is not None else None
|
||||
IsDay = int(hourly["is_day"][i] if hourly["is_day"][i] is not None else 1)
|
||||
T = float(l_temp[i])
|
||||
Rh = int(l_rh[i] or 0)
|
||||
Cl = int(l_cl[i] or 0)
|
||||
Pr = float(l_prec[i] or 0)
|
||||
Rn = float(l_rain[i] or 0)
|
||||
Sn = float(l_snow[i] or 0)
|
||||
Wspd = float(l_wspd[i] or 0)
|
||||
Gust = float(l_gust[i] or 0)
|
||||
Wdir = int(l_wdir[i] or 0)
|
||||
Cape = float(l_cape[i] or 0)
|
||||
Vis = float(l_vis[i] or 10000)
|
||||
Code = int(l_code[i]) if l_code[i] is not None else None
|
||||
IsDay = int(l_day[i] if l_day[i] is not None else 1)
|
||||
|
||||
t_s = f"{int(round(T))}"
|
||||
p_s = "--" if Pr < 0.2 else f"{int(round(Pr))}"
|
||||
|
||||
card = degrees_to_cardinal(Wdir)
|
||||
w_val = Wspd
|
||||
is_g = (Gust - Wspd) > 15
|
||||
if is_g: w_val = Gust
|
||||
|
||||
w_val = Gust if (Gust - Wspd) > 15 else Wspd
|
||||
w_txt = f"{card} {int(round(w_val))}"
|
||||
if is_g:
|
||||
if (Gust - Wspd) > 15:
|
||||
g_txt = f"G{int(round(w_val))}"
|
||||
if len(card)+len(g_txt) <= 5: w_txt = f"{card}{g_txt}"
|
||||
elif len(card)+1+len(g_txt) <= 5: w_txt = f"{card} {g_txt}"
|
||||
else: w_txt = g_txt
|
||||
|
||||
w_fmt = f"{w_txt:<5}"
|
||||
sky, sgx = get_icon_set(Pr, Sn, Code, IsDay, Cl, Vis, T, Rn, Gust, Cape)
|
||||
|
||||
lines.append(f"{dt.strftime('%H'):<2} {t_s:>3} {Rh:>3} {p_s:>2} {w_fmt} {Cl:>3} {sky:<2} {sgx:<3}")
|
||||
count += 1
|
||||
except: continue
|
||||
|
||||
if count > 0:
|
||||
day_label = f"{['Lun','Mar','Mer','Gio','Ven','Sab','Dom'][now.weekday()]} {now.day}"
|
||||
blocks.append(f"*{day_label} ({label})*\n```text\n" + "\n".join(lines) + "\n```")
|
||||
now = end_time
|
||||
|
||||
return f"🌤️ *METEO REPORT*\n📍 {location_name}\n\n" + "\n\n".join(blocks)
|
||||
|
||||
return f"🌤️ *METEO REPORT*\n📍 {location_name}\n🧠 Fonte: {model_name}\n\n" + "\n\n".join(blocks)
|
||||
|
||||
# =============================================================================
|
||||
# SEZIONE 3: BOT HANDLERS & SCHEDULER
|
||||
@@ -271,7 +347,7 @@ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
[InlineKeyboardButton("🛡️ Pi-hole", callback_data="menu_pihole"), InlineKeyboardButton("🌐 Rete", callback_data="menu_net")],
|
||||
[InlineKeyboardButton("🌤️ Meteo Casa", callback_data="req_meteo_home"), InlineKeyboardButton("📜 Logs", callback_data="menu_logs")]
|
||||
]
|
||||
text = "🎛 **Loogle Control Center v7.0**\nComandi disponibili:\n🔹 `/meteo <città>`\n🔹 Pulsanti sotto"
|
||||
text = "🎛 **Loogle Control Center v7.9**\nComandi disponibili:\n🔹 `/meteo <città>`\n🔹 Pulsanti sotto"
|
||||
|
||||
if update.message: await update.message.reply_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown")
|
||||
else: await update.callback_query.edit_message_text(text, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown")
|
||||
@@ -287,22 +363,19 @@ async def meteo_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> N
|
||||
|
||||
coords = get_coordinates(city)
|
||||
if coords:
|
||||
lat, lon, name = coords
|
||||
report = generate_weather_report(lat, lon, name)
|
||||
lat, lon, name, cc = coords
|
||||
report = generate_weather_report(lat, lon, name, cc)
|
||||
await update.message.reply_text(report, parse_mode="Markdown")
|
||||
else:
|
||||
await update.message.reply_text(f"❌ Città '{city}' non trovata.", parse_mode="Markdown")
|
||||
|
||||
async def scheduled_morning_report(context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Funzione lanciata dallo scheduler alle 08:00"""
|
||||
logger.info("⏰ Invio report automatico meteo...")
|
||||
report = generate_weather_report(HOME_LAT, HOME_LON, HOME_NAME)
|
||||
|
||||
# Forza "SM" per casa -> Arome/IconD2 in base alla posizione
|
||||
report = generate_weather_report(HOME_LAT, HOME_LON, HOME_NAME, "SM")
|
||||
for uid in ALLOWED_IDS:
|
||||
try:
|
||||
await context.bot.send_message(chat_id=uid, text=report, parse_mode="Markdown")
|
||||
except Exception as e:
|
||||
logger.error(f"Errore invio report a {uid}: {e}")
|
||||
try: await context.bot.send_message(chat_id=uid, text=report, parse_mode="Markdown")
|
||||
except: pass
|
||||
|
||||
@restricted
|
||||
async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
@@ -314,7 +387,8 @@ async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
||||
|
||||
elif data == "req_meteo_home":
|
||||
await query.edit_message_text("⏳ **Scaricamento Meteo Casa...**", parse_mode="Markdown")
|
||||
report = generate_weather_report(HOME_LAT, HOME_LON, HOME_NAME)
|
||||
# Forza "SM" per casa
|
||||
report = generate_weather_report(HOME_LAT, HOME_LON, HOME_NAME, "SM")
|
||||
keyboard = [[InlineKeyboardButton("⬅️ Indietro", callback_data="main_menu")]]
|
||||
await query.edit_message_text(report, reply_markup=InlineKeyboardMarkup(keyboard), parse_mode="Markdown")
|
||||
|
||||
@@ -392,7 +466,7 @@ async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
||||
await query.edit_message_text(f"📜 **Log:**\n\n`{log_c}`", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("⬅️ Indietro", callback_data="menu_logs")]]), parse_mode="Markdown")
|
||||
|
||||
def main():
|
||||
logger.info("Avvio Loogle Bot v7.0 (Ultimate)...")
|
||||
logger.info("Avvio Loogle Bot v7.9 (Global GFS Fix)...")
|
||||
application = Application.builder().token(BOT_TOKEN).build()
|
||||
|
||||
# Handlers
|
||||
@@ -400,8 +474,7 @@ def main():
|
||||
application.add_handler(CommandHandler("meteo", meteo_command))
|
||||
application.add_handler(CallbackQueryHandler(button_handler))
|
||||
|
||||
# SCHEDULER (Sostituisce CRON)
|
||||
# Esegue il meteo tutti i giorni alle 08:00 Europe/Rome
|
||||
# SCHEDULER
|
||||
job_queue = application.job_queue
|
||||
job_queue.run_daily(scheduled_morning_report, time=datetime.time(hour=8, minute=0, tzinfo=TZINFO), days=(0, 1, 2, 3, 4, 5, 6))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user