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
This commit is contained in:
parent
e78d34e3cb
commit
486e3124ed
23
bot.py
23
bot.py
@ -6,10 +6,10 @@ import threading
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord.ext.commands import CommandNotFound
|
from discord.ext.commands import CommandNotFound
|
||||||
from discord.ext.commands import CommandNotFound
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from commands import ALL_COMMANDS
|
from commands import ALL_COMMANDS
|
||||||
|
from console_commands import ALL_CONSOLE_COMMANDS
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@ -37,13 +37,6 @@ async def on_command_error(ctx, error):
|
|||||||
print(f"Ошибка команды: {error}")
|
print(f"Ошибка команды: {error}")
|
||||||
|
|
||||||
|
|
||||||
@bot.event
|
|
||||||
async def on_command_error(ctx, error):
|
|
||||||
if isinstance(error, CommandNotFound):
|
|
||||||
return
|
|
||||||
print(f"Ошибка команды: {error}")
|
|
||||||
|
|
||||||
|
|
||||||
@bot.command(name="msg")
|
@bot.command(name="msg")
|
||||||
async def msg(ctx, *, text: str):
|
async def msg(ctx, *, text: str):
|
||||||
"""Повторяет текст после !msg"""
|
"""Повторяет текст после !msg"""
|
||||||
@ -56,14 +49,20 @@ def console_input():
|
|||||||
cmd = input().strip().lower()
|
cmd = input().strip().lower()
|
||||||
if cmd == "stop":
|
if cmd == "stop":
|
||||||
print("\nОстановка бота...")
|
print("\nОстановка бота...")
|
||||||
stop_event.set()
|
if "stop" in ALL_CONSOLE_COMMANDS:
|
||||||
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop).result()
|
ALL_CONSOLE_COMMANDS["stop"](stop_event, bot)
|
||||||
break
|
break
|
||||||
elif cmd:
|
elif cmd:
|
||||||
|
if cmd in ALL_CONSOLE_COMMANDS:
|
||||||
|
ALL_CONSOLE_COMMANDS[cmd](stop_event, bot)
|
||||||
|
else:
|
||||||
print(f"Неизвестная команда: {cmd}")
|
print(f"Неизвестная команда: {cmd}")
|
||||||
except EOFError:
|
except (EOFError, KeyboardInterrupt):
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop).result()
|
try:
|
||||||
|
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop).result(timeout=5)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при остановке бота: {e}")
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
import requests
|
import requests
|
||||||
|
from requests.exceptions import ConnectionError, Timeout, SSLError
|
||||||
|
|
||||||
|
|
||||||
class Pogoda(commands.Cog):
|
class Pogoda(commands.Cog):
|
||||||
@ -11,12 +12,8 @@ class Pogoda(commands.Cog):
|
|||||||
|
|
||||||
@commands.command(name="pogoda")
|
@commands.command(name="pogoda")
|
||||||
async def pogoda(self, ctx):
|
async def pogoda(self, ctx):
|
||||||
try:
|
data = await self._fetch_weather(ctx)
|
||||||
response = requests.get(self.api_url, timeout=10)
|
if data is None:
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json()
|
|
||||||
except requests.RequestException as e:
|
|
||||||
await ctx.send(f"Ошибка при получении данных: {e}")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
current = data.get("current_condition", [{}])[0]
|
current = data.get("current_condition", [{}])[0]
|
||||||
@ -29,7 +26,10 @@ class Pogoda(commands.Cog):
|
|||||||
description = self._translate_weather(current.get("weatherDesc", [{}])[0].get("value", "—"))
|
description = self._translate_weather(current.get("weatherDesc", [{}])[0].get("value", "—"))
|
||||||
humidity = current.get("humidity", "—")
|
humidity = current.get("humidity", "—")
|
||||||
wind_kmh = current.get("windspeedKmph", "—")
|
wind_kmh = current.get("windspeedKmph", "—")
|
||||||
|
try:
|
||||||
wind = round(int(wind_kmh) / 3.6, 1) if wind_kmh != "—" else "—"
|
wind = round(int(wind_kmh) / 3.6, 1) if wind_kmh != "—" else "—"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
wind = "—"
|
||||||
pressure_mb = current.get("pressure", "—")
|
pressure_mb = current.get("pressure", "—")
|
||||||
|
|
||||||
pressure_mm = self._pressure_to_mmhg(pressure_mb)
|
pressure_mm = self._pressure_to_mmhg(pressure_mb)
|
||||||
@ -42,32 +42,123 @@ class Pogoda(commands.Cog):
|
|||||||
f"[PRESS] Давление: {pressure_mm} мм рт. ст."
|
f"[PRESS] Давление: {pressure_mm} мм рт. ст."
|
||||||
)
|
)
|
||||||
|
|
||||||
def _translate_weather(self, en):
|
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¤t=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 = {
|
mapping = {
|
||||||
"Sunny": "Ясно",
|
|
||||||
"Clear": "Ясно",
|
|
||||||
"Partly cloudy": "Переменная облачность",
|
|
||||||
"Cloudy": "Облачно",
|
|
||||||
"Overcast": "Пасмурно",
|
|
||||||
"Mist": "Туман",
|
|
||||||
"Fog": "Туман",
|
|
||||||
"Patchy rain nearby": "Местами дождь",
|
|
||||||
"Light rain": "Небольшой дождь",
|
|
||||||
"Moderate rain": "Умеренный дождь",
|
|
||||||
"Heavy rain": "Сильный дождь",
|
|
||||||
"Patchy snow nearby": "Местами снег",
|
|
||||||
"Light snow": "Небольшой снег",
|
|
||||||
"Heavy snow": "Сильный снег",
|
|
||||||
"Patchy sleet nearby": "Местами слякоть",
|
|
||||||
"Blowing snow": "Метель",
|
|
||||||
"Thundery outbreaks in nearby": "Гроза вблизи",
|
|
||||||
"Moderate or heavy snow in area": "Снег",
|
|
||||||
"Moderate or heavy rain in area": "Дождь",
|
|
||||||
"Moderate or heavy freezing rain in area": "Ледяной дождь",
|
"Moderate or heavy freezing rain in area": "Ледяной дождь",
|
||||||
"Moderate or heavy sleet in area": "Слякоть",
|
"Moderate or heavy sleet in area": "Слякоть",
|
||||||
"Ice pellets": "Ледяные кристаллы",
|
"Moderate or heavy snow in area": "Снег",
|
||||||
"Haze": "Дымка",
|
"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": "Туманно",
|
"Foggy": "Туманно",
|
||||||
|
"Fog": "Туман",
|
||||||
|
"Mist": "Туман",
|
||||||
|
"Haze": "Дымка",
|
||||||
|
"Overcast": "Пасмурно",
|
||||||
|
"Cloudy": "Облачно",
|
||||||
|
"Clear": "Ясно",
|
||||||
|
"Sunny": "Ясно",
|
||||||
}
|
}
|
||||||
for key, value in mapping.items():
|
for key, value in mapping.items():
|
||||||
if key.lower() in en.lower():
|
if key.lower() in en.lower():
|
||||||
@ -75,6 +166,9 @@ class Pogoda(commands.Cog):
|
|||||||
return en
|
return en
|
||||||
|
|
||||||
def _pressure_to_mmhg(self, mb):
|
def _pressure_to_mmhg(self, mb):
|
||||||
if mb == "—":
|
if mb == "—" or not mb:
|
||||||
|
return "—"
|
||||||
|
try:
|
||||||
|
return round(float(mb) * 0.750062, 1)
|
||||||
|
except (ValueError, TypeError):
|
||||||
return "—"
|
return "—"
|
||||||
return round(int(mb) * 0.750062, 1)
|
|
||||||
|
|||||||
@ -1,3 +1,10 @@
|
|||||||
def stop(stop_event):
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
def stop(stop_event, bot):
|
||||||
"""Остановка бота"""
|
"""Остановка бота"""
|
||||||
pass
|
stop_event.set()
|
||||||
|
try:
|
||||||
|
asyncio.run_coroutine_threadsafe(bot.close(), bot.loop).result(timeout=5)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при остановке бота: {e}")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user