From 89f17d53d85c4d155fad3e5cbd86fcd3f8b3cfa7 Mon Sep 17 00:00:00 2001 From: deadzilla Date: Sun, 24 May 2026 15:27:29 +0500 Subject: [PATCH] Refactor: add commands/, console_commands/, !pogoda command --- AGENTS.md | 22 ++++- bot.py | 23 ++++- commands/__init__.py | 3 + commands/__pycache__/__init__.cpython-314.pyc | Bin 0 -> 235 bytes commands/__pycache__/pogoda.cpython-314.pyc | Bin 0 -> 4820 bytes commands/pogoda.py | 87 ++++++++++++++++++ console_commands/__init__.py | 5 + console_commands/stop.py | 3 + requirements.txt | 1 + 9 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 commands/__init__.py create mode 100644 commands/__pycache__/__init__.cpython-314.pyc create mode 100644 commands/__pycache__/pogoda.cpython-314.pyc create mode 100644 commands/pogoda.py create mode 100644 console_commands/__init__.py create mode 100644 console_commands/stop.py 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 0000000000000000000000000000000000000000..33ea5373ad6514d92c4eb5b241988266ffc83da9 GIT binary patch literal 235 zcmdPqD z`6VMzT9fe>TR?t#eoCUBCi5+}0x+|P87Om$$I-_p-r3*R*U`@Mv>NpXx~Kv8~HYBG@JHKrsOA C20TLm literal 0 HcmV?d00001 diff --git a/commands/__pycache__/pogoda.cpython-314.pyc b/commands/__pycache__/pogoda.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a807fb1826d363c79fe982f9b13e325ebbdd17b GIT binary patch literal 4820 zcmZ`-Yj7LY6~3$0VoiW-d(qb#M9_#~x z!*twaI#C9!sZO{e3Tq?EV|Cd6J8 zmPUYy!`x33nc|3Si#cUFNU~swZ0BqtB0HXT4)`j&T0C0A*&n7XiF1U=)*?t|I4AUT zb1tABj*+VdiOh0tu*#!52eQN2WL$X)!0Ip6dG&@mqkgDfQ)j|0Ia3w>>-ei-^$*%O zDBlCsy!t+frZBHw(_RV#__}r>to{mmB4AECV`;TP(_&lxdF*D7c1M+62rv z;m9D#m#A!*WDe@rMj{?A+s?p$U?RYyxRTT=I&>* zqL7G7@^Ia|hrb-V_WehOk9;{+_wKpJ9y$6Bt-GKTuZW{MJsOvRWHYcKc1ak0M#5R! z^!@w$hK@;sC=DqZfa~vnY$%zM5?L|1FDnm~c5}$`R#)x? zo#FXZCMEN{?zg<#Rcdg?vl`8{7rpR-5p1Fn0NAab^DvbDH^oHOcdFG7ab z5wea%sezgm`nb0i571%4F(<*oQ`9)!@9==HIA%SGI#-;@!&Q}Ma;<`4D=^F|7;go} zy$Z%xf$^+@@mFA~qGX`<{*^|V(n@nIAnI2@RFx6lRlHRlb+Lr2iLyWgQ8&=qs0V0W z)C;tJOL>Hqy&0}NKPyBfCrL(duX2u6=66LMEZJf!*=f!KC(_z87r|X?f_=+5_Be3& zEG|W*$PT)Rq$n5Z{|EdX>7`RtB&aiRty8j+6m&P-?O}^`B@|JFOFY&u3+bF7#$`nm z_$}$q=Y_aDB8YqyKe4(?I&QKnJk&G)7SHofTEm*}3 zND#A-F=oK1v-sN=R^ZN(k7xFKj4Q9a0kAg#G6(qq*Ey%o0-zB9pndsm(7vo*2hHz5 zhYN+3fxZs(tq7Pl0ke>1U=C2UfFcYYz0s|m1O3F$K>sRq|1At+c@&4oSP=%xZiFE( zo#_hWxLuJd5o5B5C4tU}!n2AX$&yYF3$lnwU9T47DM{cVt?-fzCr8rlC#7sgujVZ5 z!BYt#C#RqV(nuz$H}KGvFZnYmNC`1z>1+71$K))ZPLB*nd?E%-oleN7M9d0kft1T; zBtf?)<8oZ51QD;bZpV!i(Lp*>I-j~7cSL7Oqv}#j7s)=*&U)sRqNgK9&k;dTT(d8grX zx@$iVHD7-5m^ zy4-Q8V{-e{iG@HU@A`{BSP0Z(liJ8H1dcDV$5ryH6nvf2(dk15p?ca2?OUeOz-^5bBAcf@ z*w#{LX`ONc*KTRmo&$#4?ma{;(N4eTTjF$kzDDxhH?5?)VbR~A`8&QLHg|1lolTtj zL{#ODsf~jRfhQN)CpGrTFWA6;4F`b!bB86~ICZB1M) z1N=vO>VSS+OC6%@AJ^?`1L2cC>JV-JWQP;@n^gV5-So|1=-_tx=0@t^Cg;sA2l73p zcDn<$yX}DdBTWHaR%T$jf#|?2%>gq`y3Mlp1Qnw=8&D`9fI<-gbR*{gx`}fF?crQN zdpQQ^!<-xFF3tmV4_5_rFUJDi$9aM7=X^j9bAF(Ya@9b8z|{bKj0?mDxZ2p`TwRRg z>SM>aU~G^J#dxkEcAQ%iOK@wu-TSGWZO}GQO!Hu1`5UCT#!?!GYH^*2#e;$uS^YJ{ z8i*ze))49yh)J^$c5Xm)IuB6S0YZr=GNL_$6BN@gtOFx6P?mUEWh)@u+ z%MnV!+XUzt=#1lzo6})nQQv~FZPwKmW>S40#CbqKEv-!`YY_9gb_uFyNDDZ8QHwEN z!J7)TwFx&HFbt?y!TRgk1k7Hk-({Rey3;?QLg5>r@x50o$(jCw;cPhojPYq6+@!vjUcy=csm=I+md8dhtbZP>O5 zC5Ay2p{<-_$D&7PD^i9eVL1S4!paS;An47d_sc3t4NDrS3oat$3R`1?r@(siV+IY} zZPu&$w;2v#F^l+Af1lw(fiZ;^!;ON6c>NbyqY8P5cYd|{LBorJ&lENoeiW(^4h0+iB7rcY2{~o;9|S>UE3$X3gI`WmnsF zF8Ft;%r4Onep?oAq2#sVZ67e>1mYOk22b)NY{@!!Bd;ezwsn=Vqx$PIRsmB-m?JCo5E=R=o+tG*@U%&Wjh1ai#=hNAwGKxIQ z^Uo^r(W1uB^UtJ2NghpQgiIFHE}la6vGu%JiKP;J zT$aVu2}Kqp7@>ye6QglSN~RKWLKq$8d04!+q7k~G4=OB_h*OAsox=1KaB7?v(xg`Zt*aPXsHwiV$c*teMWp= bknm^3|Bdr;it5Nebz$iI(7y=s<_P}>$p8{; literal 0 HcmV?d00001 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