discordBot/commands/pogoda.py
deadzilla db2ac21b41 fix: исправить дублирование кода, добавить retry и fallback для погоды
- Убран дублирующийся on_command_error и импорт CommandNotFound
- stop.py: добавлены аргументы stop_event, bot + обработка ошибок
- console_commands интегрирован в console_input()
- pogoda.py: retry (3 попытки), fallback на Open-Meteo при SSL-ошибках
- pogoda.py: безопасная обработка wind_kmh и давления
- pogoda.py: сортировка translate-словаря по длине ключа
- Добавлен _wmo_to_russian() для WMO weather code
2026-05-25 00:07:20 +05:00

175 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import discord
from discord.ext import commands
import requests
from requests.exceptions import ConnectionError, Timeout, SSLError
class Pogoda(commands.Cog):
"""Команда !pogoda — прогноз погоды для Магнитогорска"""
def __init__(self):
self.api_url = "https://wttr.in/Magnitogorsk?format=j1&lang=ru"
@commands.command(name="pogoda")
async def pogoda(self, ctx):
data = await self._fetch_weather(ctx)
if data is None:
return
current = data.get("current_condition", [{}])[0]
if not current:
await ctx.send("Не удалось получить данные о погоде.")
return
temp = current.get("temp_C", "")
feels_like = current.get("FeelsLikeC", "")
description = self._translate_weather(current.get("weatherDesc", [{}])[0].get("value", ""))
humidity = current.get("humidity", "")
wind_kmh = current.get("windspeedKmph", "")
try:
wind = round(int(wind_kmh) / 3.6, 1) if wind_kmh != "" else ""
except (ValueError, TypeError):
wind = ""
pressure_mb = current.get("pressure", "")
pressure_mm = self._pressure_to_mmhg(pressure_mb)
await ctx.send(
f"[TEMP] Температура: {temp}°C (ощущается как {feels_like}°C)\n"
f"[DESC] Описание: {description}\n"
f"[HUMID] Влажность: {humidity}%\n"
f"[WIND] Ветер: {wind} м/с\n"
f"[PRESS] Давление: {pressure_mm} мм рт. ст."
)
async def _fetch_weather(self, ctx):
"""Получить данные о погоде с retry и fallback."""
# Пробуем wttr.in с retry
for attempt in range(3):
try:
response = requests.get(self.api_url, timeout=10)
response.raise_for_status()
return response.json()
except (SSLError, ConnectionError, Timeout):
if attempt < 2:
continue
break
except requests.RequestException as e:
await ctx.send(f"Ошибка при получении данных: {e}")
return None
# Fallback: Open-Meteo API (без ключа, HTTPS)
return await self._fetch_open_meteo(ctx)
async def _fetch_open_meteo(self, ctx):
"""Fallback на Open-Meteo API."""
url = (
"https://api.open-meteo.com/v1/forecast?"
"latitude=53.4069&longitude=58.9797&current=temperature,"
"apparent_temperature,weather_code,wind_speed_10m,"
"relative_humidity_2m,pressure_msl&timezone=Asia/Chelyabinsk"
)
for attempt in range(3):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
data = response.json()
current = data.get("current", {})
# weather_code WMO код → перевод (https://open-meteo.com/en/docs)
weather_code = current.get("weather_code", None)
desc = self._wmo_to_russian(weather_code)
return {
"current_condition": [{
"temp_C": current.get("temperature", ""),
"FeelsLikeC": current.get("apparent_temperature", ""),
"weatherDesc": [{"value": desc}],
"humidity": current.get("relative_humidity_2m", ""),
"windspeedKmph": current.get("wind_speed_10m", ""),
"pressure": current.get("pressure_msl", ""),
}]
}
except (SSLError, ConnectionError, Timeout):
if attempt < 2:
continue
break
except requests.RequestException as e:
await ctx.send(f"Ошибка при получении данных: {e}")
return None
await ctx.send("Не удалось получить данные о погоде. Попробуйте позже.")
return None
def _wmo_to_russian(self, code):
"""Перевод WMO weather code в русский."""
mapping = {
0: "Ясно",
1: "Ясно", 2: "Переменная облачность",
3: "Пасмурно",
45: "Туман", 48: "Туман",
51: "Лёгкая морось", 53: "Морось", 55: "Сильная морось",
56: "Ледяная морось", 57: "Сильная ледяная морось",
61: "Небольшой дождь", 63: "Дождь", 65: "Сильный дождь",
66: "Ледяной дождь", 67: "Сильный ледяной дождь",
71: "Небольшой снег", 73: "Снег", 75: "Сильный снег",
77: "Снежная крупа",
80: "Небольшой ливень", 81: "Ливень", 82: "Сильный ливень",
85: "Снежный ливень", 86: "Сильный снежный ливень",
95: "Гроза", 96: "Гроза с градом", 99: "Сильная гроза с градом",
}
return mapping.get(code, "Неизвестно")
def _translate_weather(self, en):
if not en:
return ""
mapping = {
"Moderate or heavy freezing rain in area": "Ледяной дождь",
"Moderate or heavy sleet in area": "Слякоть",
"Moderate or heavy snow in area": "Снег",
"Moderate or heavy rain in area": "Дождь",
"Thundery outbreaks in nearby": "Гроза вблизи",
"Patchy rain nearby": "Местами дождь",
"Patchy snow nearby": "Местами снег",
"Patchy sleet nearby": "Местами слякоть",
"Heavy freezing rain": "Сильный ледяной дождь",
"Heavy snow": "Сильный снег",
"Heavy rain": "Сильный дождь",
"Moderate or heavy rain at times": "Дождь",
"Moderate or heavy snow at times": "Снег",
"Blowing snow": "Метель",
"Patchy light drizzle": "Местами лёгкая морось",
"Moderate or heavy freezing rain at a distance": "Ледяной дождь",
"Moderate or heavy sleet at a distance": "Слякоть",
"Light rain shower": "Небольшой дождь",
"Heavy rain shower": "Сильный дождь",
"Moderate rain": "Умеренный дождь",
"Light rain": "Небольшой дождь",
"Moderate rain at times": "Умеренный дождь",
"Heavy rain at times": "Сильный дождь",
"Light snow": "Небольшой снег",
"Moderate snow": "Умеренный снег",
"Patchy light snow": "Местами лёгкий снег",
"Partly cloudy": "Переменная облачность",
"Moderate or light sleet": "Слякоть",
"Light freezing rain": "Лёгкий ледяной дождь",
"Foggy": "Туманно",
"Fog": "Туман",
"Mist": "Туман",
"Haze": "Дымка",
"Overcast": "Пасмурно",
"Cloudy": "Облачно",
"Clear": "Ясно",
"Sunny": "Ясно",
}
for key, value in mapping.items():
if key.lower() in en.lower():
return value
return en
def _pressure_to_mmhg(self, mb):
if mb == "" or not mb:
return ""
try:
return round(float(mb) * 0.750062, 1)
except (ValueError, TypeError):
return ""