From 486e3124ede06780201b5810312da102d1bbff94 Mon Sep 17 00:00:00 2001 From: deadzilla Date: Mon, 25 May 2026 00:07:20 +0500 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D0=B4=D1=83=D0=B1=D0=BB=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B0,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82=D1=8C=20retry=20?= =?UTF-8?q?=D0=B8=20fallback=20=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Убран дублирующийся 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 --- bot.py | 25 +++---- commands/pogoda.py | 156 +++++++++++++++++++++++++++++++-------- console_commands/stop.py | 11 ++- 3 files changed, 146 insertions(+), 46 deletions(-) diff --git a/bot.py b/bot.py index e8cb953..1164cc9 100644 --- a/bot.py +++ b/bot.py @@ -6,10 +6,10 @@ import threading import discord from discord.ext import commands from discord.ext.commands import CommandNotFound -from discord.ext.commands import CommandNotFound from dotenv import load_dotenv from commands import ALL_COMMANDS +from console_commands import ALL_CONSOLE_COMMANDS load_dotenv() @@ -37,13 +37,6 @@ async def on_command_error(ctx, 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") async def msg(ctx, *, text: str): """Повторяет текст после !msg""" @@ -56,14 +49,20 @@ def console_input(): cmd = input().strip().lower() if cmd == "stop": print("\nОстановка бота...") - stop_event.set() - asyncio.run_coroutine_threadsafe(bot.close(), bot.loop).result() + if "stop" in ALL_CONSOLE_COMMANDS: + ALL_CONSOLE_COMMANDS["stop"](stop_event, bot) break elif cmd: - print(f"Неизвестная команда: {cmd}") - except EOFError: + if cmd in ALL_CONSOLE_COMMANDS: + ALL_CONSOLE_COMMANDS[cmd](stop_event, bot) + else: + print(f"Неизвестная команда: {cmd}") + except (EOFError, KeyboardInterrupt): 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 diff --git a/commands/pogoda.py b/commands/pogoda.py index 9b9087c..1681a23 100644 --- a/commands/pogoda.py +++ b/commands/pogoda.py @@ -1,6 +1,7 @@ import discord from discord.ext import commands import requests +from requests.exceptions import ConnectionError, Timeout, SSLError class Pogoda(commands.Cog): @@ -11,12 +12,8 @@ class Pogoda(commands.Cog): @commands.command(name="pogoda") async def pogoda(self, ctx): - try: - response = requests.get(self.api_url, timeout=10) - response.raise_for_status() - data = response.json() - except requests.RequestException as e: - await ctx.send(f"Ошибка при получении данных: {e}") + data = await self._fetch_weather(ctx) + if data is None: return current = data.get("current_condition", [{}])[0] @@ -29,7 +26,10 @@ class Pogoda(commands.Cog): description = self._translate_weather(current.get("weatherDesc", [{}])[0].get("value", "—")) humidity = current.get("humidity", "—") wind_kmh = current.get("windspeedKmph", "—") - wind = round(int(wind_kmh) / 3.6, 1) if wind_kmh != "—" else "—" + 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) @@ -42,32 +42,123 @@ class Pogoda(commands.Cog): 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 = { - "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 sleet in area": "Слякоть", - "Ice pellets": "Ледяные кристаллы", - "Haze": "Дымка", + "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(): @@ -75,6 +166,9 @@ class Pogoda(commands.Cog): return en 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 round(int(mb) * 0.750062, 1) diff --git a/console_commands/stop.py b/console_commands/stop.py index 796656b..ee6fd10 100644 --- a/console_commands/stop.py +++ b/console_commands/stop.py @@ -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}")