diff --git a/AGENTS.md b/AGENTS.md index a9dbd0f..f4ea6e3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # AGENTS.md ## Проект -Discord-бот на Python (discord.py). Команда `!msg <текст>` повторяет текст. +Discord-бот на Python (discord.py). Команды: `!msg` (повтор текста), `!pogoda` (прогноз погоды для Магнитогорска). ## Запуск ``` @@ -16,3 +16,23 @@ python bot.py ## Конвенции Используй TODO-списки для каждого запроса с несколькими шагами. + +## Архитектура +``` +commands/ # Discord команды (cogs) + __init__.py # ALL_COMMANDS — явные импорты + pogoda.py # !pogoda +console_commands/ # Консольные команды + __init__.py # ALL_CONSOLE_COMMANDS — явные импорты + stop.py # stop +``` + +**Добавление Discord команды:** +1. Создать файл `commands/имя.py` с классом, наследующим `commands.Cog` +2. Добавить импорт в `commands/__init__.py` +3. Добавить класс в `ALL_COMMANDS` + +**Добавление консольной команды:** +1. Создать файл `console_commands/имя.py` с функцией +2. Добавить импорт в `console_commands/__init__.py` +3. Добавить функцию в `ALL_CONSOLE_COMMANDS` diff --git a/bot.py b/bot.py index d1615bb..35dce0c 100644 --- a/bot.py +++ b/bot.py @@ -7,6 +7,8 @@ import discord from discord.ext import commands from dotenv import load_dotenv +from commands import ALL_COMMANDS + load_dotenv() intents = discord.Intents.default() @@ -19,6 +21,11 @@ stop_event = threading.Event() @bot.event async def on_ready(): print(f"Бот вошёл как {bot.user}") + for cog_class in ALL_COMMANDS: + cog = cog_class() + await bot.add_cog(cog) + for cog in bot.cogs: + print(f" Загружен: {cog}") @bot.command(name="msg") @@ -34,13 +41,19 @@ def console_input(): if cmd == "stop": print("\nОстановка бота...") stop_event.set() - asyncio.run_coroutine_threadsafe(bot.close(), bot.loop) + try: + bot.loop.stop() + except (AttributeError, RuntimeError): + pass break elif cmd: print(f"Неизвестная команда: {cmd}") except EOFError: stop_event.set() - asyncio.run_coroutine_threadsafe(bot.close(), bot.loop) + try: + bot.loop.stop() + except (AttributeError, RuntimeError): + pass break @@ -58,5 +71,9 @@ if __name__ == "__main__": bot.run(token) except KeyboardInterrupt: print("\nОстановка бота...") - bot.loop.stop() + try: + bot.loop.stop() + except (AttributeError, RuntimeError): + pass stop_event.set() + sys.exit(0) diff --git a/commands/__init__.py b/commands/__init__.py new file mode 100644 index 0000000..bc47f31 --- /dev/null +++ b/commands/__init__.py @@ -0,0 +1,3 @@ +from .pogoda import Pogoda + +ALL_COMMANDS = [Pogoda] diff --git a/commands/__pycache__/__init__.cpython-314.pyc b/commands/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..33ea537 Binary files /dev/null and b/commands/__pycache__/__init__.cpython-314.pyc differ diff --git a/commands/__pycache__/pogoda.cpython-314.pyc b/commands/__pycache__/pogoda.cpython-314.pyc new file mode 100644 index 0000000..0a807fb Binary files /dev/null and b/commands/__pycache__/pogoda.cpython-314.pyc differ diff --git a/commands/pogoda.py b/commands/pogoda.py new file mode 100644 index 0000000..bde8bac --- /dev/null +++ b/commands/pogoda.py @@ -0,0 +1,87 @@ +import discord +from discord.ext import commands +import requests + + +class Pogoda(commands.Cog): + """Команда !pogoda — прогноз погоды для Магнитогорска""" + + def __init__(self): + self.api_url = "https://api.open-meteo.com/v1/forecast" + self.lat = 53.41 + self.lon = 59.06 + + @commands.command(name="pogoda") + async def pogoda(self, ctx): + params = { + "latitude": self.lat, + "longitude": self.lon, + "current": "temperature_2m,weather_code,relative_humidity_2m,wind_speed_10m,pressure_msl", + "timezone": "Asia/Yekaterinburg", + } + + try: + response = requests.get(self.api_url, params=params, timeout=10) + response.raise_for_status() + data = response.json() + except requests.RequestException as e: + await ctx.send(f"Ошибка при получении данных: {e}") + return + + current = data.get("current", {}) + if not current: + await ctx.send("Не удалось получить данные о погоде.") + return + + temp = current.get("temperature_2m", "—") + weather_code = current.get("weather_code", "—") + humidity = current.get("relative_humidity_2m", "—") + wind = current.get("wind_speed_10m", "—") + pressure = current.get("pressure_msl", "—") + + description = self._get_weather_description(weather_code) + pressure_mm = self._pressure_to_mmhg(pressure) + + await ctx.send( + f"🌡 Температура: {temp}°C\n" + f"📝 Описание: {description}\n" + f"💧 Влажность: {humidity}%\n" + f"💨 Ветер: {wind} км/ч\n" + f"🌍 Давление: {pressure_mm} мм рт. ст." + ) + + def _get_weather_description(self, code): + descriptions = { + 0: "Ясно", + 1: "Преимущественно ясно", + 2: "Переменная облачность", + 3: "Пасмурно", + 45: "Туман", + 48: "Изморозь", + 51: "Лёгкая морось", + 53: "Морось", + 55: "Сильная морось", + 61: "Небольшой дождь", + 63: "Дождь", + 65: "Сильный дождь", + 66: "Ледяной дождь", + 67: "Сильный ледяной дождь", + 71: "Небольшой снег", + 73: "Снег", + 75: "Сильный снег", + 77: "Снежная крупа", + 80: "Небольшой ливень", + 81: "Ливень", + 82: "Сильный ливень", + 85: "Небольшой снегопад", + 86: "Сильный снегопад", + 95: "Гроза", + 96: "Гроза с градом", + 99: "Сильная гроза с градом", + } + return descriptions.get(code, "Неизвестно") + + def _pressure_to_mmhg(self, hpa): + if hpa == "—": + return "—" + return round(hpa * 0.750062, 1) diff --git a/console_commands/__init__.py b/console_commands/__init__.py new file mode 100644 index 0000000..e540de2 --- /dev/null +++ b/console_commands/__init__.py @@ -0,0 +1,5 @@ +from .stop import stop + +ALL_CONSOLE_COMMANDS = { + "stop": stop, +} diff --git a/console_commands/stop.py b/console_commands/stop.py new file mode 100644 index 0000000..796656b --- /dev/null +++ b/console_commands/stop.py @@ -0,0 +1,3 @@ +def stop(stop_event): + """Остановка бота""" + pass diff --git a/requirements.txt b/requirements.txt index 34347e3..63c5877 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ discord.py>=2.3.2 python-dotenv>=1.0.0 +requests>=2.31.0