From ec0cee6c68071dc1e3a400c5e595d5aff2ce6354 Mon Sep 17 00:00:00 2001 From: deadzilla Date: Tue, 7 Jul 2026 23:08:58 +0500 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=8D=D0=BC=D0=BE=D0=B4=D0=B7=D0=B8=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=BE=D0=B2=20morning=5Fru?= =?UTF-8?q?nner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ISSUES.md | 33 +++++++++++++++++++++++++-------- README.md | 3 +-- bot.py | 5 ----- tests/test_help_discord.py | 5 ++--- utils/morning_runner.py | 6 +++--- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/ISSUES.md b/ISSUES.md index 0fe468d..05d0b8e 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -1,18 +1,35 @@ # ISSUES — Задачи и баг-трекер +## Высокий приоритет + +- [ ] **Эмодзи в embed-сообщениях** — `utils/morning_runner.py` (строки 61, 118, 123): `🌅`, `✅`, `❌` нарушают правило AGENTS.md «Никогда не используй эмодзи" +- [ ] **f-string в logger** — `bot.py` (строка 60): `f"Ошибка команды {cmd_name}: {error}"` — использовать `%`-формат для lazy evaluation +- [ ] **`import time` внутри `__init__`** — `bot.py` (строка 31): импорты должны быть на уровне модуля + ## Средний приоритет -- [ ] **Проверка наличия `.env`** — есть проверка DISCORD_TOKEN в `_validate_config()`, но нет проверки существования самого файла `.env` (без файла бот падает с непонятной ошибкой) -- [ ] **Graceful shutdown (SIGTERM/SIGINT)** — нет обработчиков сигналов, бот завершается резко без закрытия handler-ов и cleanup -- [ ] **`asyncio.iscoroutinefunction` deprecated** — 35 warnings, функция удалена в Python 3.16; заменить на `inspect.iscoroutinefunction` (решение: обновление discord.py или monkey-patch в `bot.py`) -- [ ] **Утечка корутины в тестах** — `RuntimeWarning: coroutine 'trigger_morning.._run' was never awaited` в `test_trigger_morning.py` +- [ ] **Type hints в production-коде** — 17 функций без аннотаций: + - `commands/`: `cat()`, `hp()`, `morning()`, `nw()`, `pg()`, `stats()`, `status()` — все методы Cog + - `utils/pogoda.py`: `fetch_weather()`, `fetch_open_meteo()`, `wmo_to_russian()`, `translate_weather()`, `format_weather_data_for_console()`, `format_weather_for_embed()`, `pressure_to_mmhg()` + - `utils/news.py`: `fetch_rss()`, `truncate_title()`, `format_articles()` + - `utils/morning_runner.py`: `run_morning()`, `start()`, `stop()` +- [ ] **`asyncio.iscoroutinefunction` deprecated** — 53 warnings при запуске тестов, функция удалена в Python 3.16; причина — discord.py использует deprecated API (решение: обновление discord.py или monkey-patch в `bot.py`) +- [ ] **Утечка корутины Scheduler в тестах** — `RuntimeWarning: coroutine 'Scheduler._scheduler_loop' was never awaited` в `test_morning_runner.py` (строки 105, 137, 166 — моки не останавливают task) +- [ ] **Graceful shutdown (SIGTERM)** — нет обработки `SIGTERM`, бот завершается резко в Docker/K8s без закрытия handler-ов и cleanup +- [ ] **Пустой `__init__` в Morning** — `commands/morning.py` (строка 13): `def __init__(self): pass` можно убрать +- [ ] **Global RateLimiter на модульном уровне** — `utils/rate_limiter.py` (строки 70-75): экземпляры создаются при импорте, нельзя подменить в тестах ## Низкий приоритет -- [ ] **Добавить pre-commit хуки** — lint (flake8/ruff), форматирование (black) -- [ ] **Добавить type hints** повсеместно — не все функции имеют аннотации (отсутствуют в `utils/pogoda.py`, `utils/news.py`, присутствуют в `utils/cat.py`, `utils/morning_runner.py`) +- [ ] **Type hints в тестах** — 144 функции `test_*()` без `-> None` (не влияет на работу, но зашумляет warnings) +- [ ] **`import` внутри функций в тестах** — 23 вхождения в `tests/` (приемлемо, но лучше вынести наверх) +- [ ] **Добавить pre-commit хуки** — lint (ruff), форматирование (black) - [ ] **Интеграционные тесты** — сейчас только unit-тесты с моками -## Замечания +## Исправлено -- [ ] В тестовых файлах используется `asyncio.run()` внутри синхронных тестов (50 вхождений: `test_fetch_cat.py` — 10, `test_fetch_rss.py` — 21, `test_fetch_weather.py` — 19) — конфликт с `pytest-asyncio` event loop, рекомендуется `async def` тесты +- [x] ~~Проверка наличия `.env`~~ — `.env` в `.gitignore`, есть `.env.example` +- [x] ~~Команда `!msg` в BotRunner~~ — удалена из проекта +- [x] ~~Отсутствует `.gitignore`~~ — файл существует +- [x] ~~Отсутствует Dockerfile~~ — файл существует +- [x] ~~Отсутствует `.env.example`~~ — файл существует diff --git a/README.md b/README.md index c07cf6d..5b3360e 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,13 @@ python bot.py | `!hp` | Список всех команд бота с описанием (автогенерация из `bot.commands`) | | `!morning` | Погода + топ-5 статей + топ-5 новостей + котик (утренний дайджест) | | `!cat` | Случайный котик | -| `!msg <текст>` | Повторить текст в чате | | `!status` | Статус бота: пинг к Discord gateway, uptime | | `!stats` | Количество серверов, каналов, пользователей | ## Архитектура ``` -bot.py # Точка входа, инициализация бота, команда !msg (в BotRunner._setup_events) +bot.py # Точка входа, инициализация бота commands/ # Discord команды (cogs) __init__.py # ALL_COMMANDS — явные импорты pg.py # !pg — погода (обёртка над utils.pogoda) diff --git a/bot.py b/bot.py index d3c57c1..91c29fa 100644 --- a/bot.py +++ b/bot.py @@ -100,11 +100,6 @@ class BotRunner: except (discord.NotFound, discord.Forbidden): pass # Бот не может писать в канал — игнорируем - @self.bot.command(name="msg") - async def msg(ctx: commands.Context, *, text: str) -> None: - """Повторяет текст после !msg""" - await ctx.send(text) - def _on_shutdown(self) -> None: """Остановить планировщик при завершении работы бота.""" if self.scheduler: diff --git a/tests/test_help_discord.py b/tests/test_help_discord.py index 7cbbeee..8dadf53 100644 --- a/tests/test_help_discord.py +++ b/tests/test_help_discord.py @@ -44,7 +44,6 @@ class TestHelpCommandDiscord: self._make_mock_command("nw", "Топ-5 статей и топ-5 новостей AI с Habr"), self._make_mock_command("morning", "Утренний дайджест: погода + новости + котик"), self._make_mock_command("cat", "Случайный котик"), - self._make_mock_command("msg", "Повторить текст в чате"), ] mock_ctx.send = AsyncMock(side_effect=send_side_effect) @@ -59,13 +58,13 @@ class TestHelpCommandDiscord: assert "=" * 40 in message # Проверяем наличие всех команд без кавычек - commands = ["!pg", "!nw", "!morning", "!cat", "!msg"] + commands = ["!pg", "!nw", "!morning", "!cat"] for cmd in commands: assert cmd in message, f"Команда {cmd} не найдена" # Проверяем разделение тире между командой и описанием lines = [l.strip() for l in message.split("\n") if "—" in l] - assert len(lines) >= 5 + assert len(lines) >= 4 if __name__ == "__main__": diff --git a/utils/morning_runner.py b/utils/morning_runner.py index ae3fe4c..0a4597c 100644 --- a/utils/morning_runner.py +++ b/utils/morning_runner.py @@ -58,7 +58,7 @@ async def run_morning(bot: "commands.Bot", channel: discord.TextChannel): data = await gather_morning() # --- Формируем embed --- - embed = discord.Embed(title="🌅 Утренний дайджест!", color=0xF4A460) + embed = discord.Embed(title="Утренний дайджест", color=0xF4A460) # Котик как thumbnail if data.cat_url: @@ -115,12 +115,12 @@ async def run_morning(bot: "commands.Bot", channel: discord.TextChannel): description = "\n".join(description_lines) embed.description = truncate_embed_text(description) await channel.send(embed=embed) - logger.info("✅ Утренний дайджест отправлен в #%s", channel.name) + logger.info("Утренний дайджест отправлен в #%s", channel.name) except Exception as e: logger.error("Ошибка при выполнении утреннего дайджеста: %s", e, exc_info=True) try: - await channel.send("❌ Не удалось выполнить утренний дайджест.") + await channel.send("Не удалось выполнить утренний дайджест.") except Exception: pass