Исправление: удаление эмодзи из сообщений и логов morning_runner
This commit is contained in:
parent
5a11fb2b5d
commit
ec0cee6c68
33
ISSUES.md
33
ISSUES.md
@ -1,18 +1,35 @@
|
|||||||
# ISSUES — Задачи и баг-трекер
|
# 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` (без файла бот падает с непонятной ошибкой)
|
- [ ] **Type hints в production-коде** — 17 функций без аннотаций:
|
||||||
- [ ] **Graceful shutdown (SIGTERM/SIGINT)** — нет обработчиков сигналов, бот завершается резко без закрытия handler-ов и cleanup
|
- `commands/`: `cat()`, `hp()`, `morning()`, `nw()`, `pg()`, `stats()`, `status()` — все методы Cog
|
||||||
- [ ] **`asyncio.iscoroutinefunction` deprecated** — 35 warnings, функция удалена в Python 3.16; заменить на `inspect.iscoroutinefunction` (решение: обновление discord.py или monkey-patch в `bot.py`)
|
- `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()`
|
||||||
- [ ] **Утечка корутины в тестах** — `RuntimeWarning: coroutine 'trigger_morning.<locals>._run' was never awaited` в `test_trigger_morning.py`
|
- `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 в тестах** — 144 функции `test_*()` без `-> None` (не влияет на работу, но зашумляет warnings)
|
||||||
- [ ] **Добавить type hints** повсеместно — не все функции имеют аннотации (отсутствуют в `utils/pogoda.py`, `utils/news.py`, присутствуют в `utils/cat.py`, `utils/morning_runner.py`)
|
- [ ] **`import` внутри функций в тестах** — 23 вхождения в `tests/` (приемлемо, но лучше вынести наверх)
|
||||||
|
- [ ] **Добавить pre-commit хуки** — lint (ruff), форматирование (black)
|
||||||
- [ ] **Интеграционные тесты** — сейчас только unit-тесты с моками
|
- [ ] **Интеграционные тесты** — сейчас только 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`~~ — файл существует
|
||||||
|
|||||||
@ -39,14 +39,13 @@ python bot.py
|
|||||||
| `!hp` | Список всех команд бота с описанием (автогенерация из `bot.commands`) |
|
| `!hp` | Список всех команд бота с описанием (автогенерация из `bot.commands`) |
|
||||||
| `!morning` | Погода + топ-5 статей + топ-5 новостей + котик (утренний дайджест) |
|
| `!morning` | Погода + топ-5 статей + топ-5 новостей + котик (утренний дайджест) |
|
||||||
| `!cat` | Случайный котик |
|
| `!cat` | Случайный котик |
|
||||||
| `!msg <текст>` | Повторить текст в чате |
|
|
||||||
| `!status` | Статус бота: пинг к Discord gateway, uptime |
|
| `!status` | Статус бота: пинг к Discord gateway, uptime |
|
||||||
| `!stats` | Количество серверов, каналов, пользователей |
|
| `!stats` | Количество серверов, каналов, пользователей |
|
||||||
|
|
||||||
## Архитектура
|
## Архитектура
|
||||||
|
|
||||||
```
|
```
|
||||||
bot.py # Точка входа, инициализация бота, команда !msg (в BotRunner._setup_events)
|
bot.py # Точка входа, инициализация бота
|
||||||
commands/ # Discord команды (cogs)
|
commands/ # Discord команды (cogs)
|
||||||
__init__.py # ALL_COMMANDS — явные импорты
|
__init__.py # ALL_COMMANDS — явные импорты
|
||||||
pg.py # !pg — погода (обёртка над utils.pogoda)
|
pg.py # !pg — погода (обёртка над utils.pogoda)
|
||||||
|
|||||||
5
bot.py
5
bot.py
@ -100,11 +100,6 @@ class BotRunner:
|
|||||||
except (discord.NotFound, discord.Forbidden):
|
except (discord.NotFound, discord.Forbidden):
|
||||||
pass # Бот не может писать в канал — игнорируем
|
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:
|
def _on_shutdown(self) -> None:
|
||||||
"""Остановить планировщик при завершении работы бота."""
|
"""Остановить планировщик при завершении работы бота."""
|
||||||
if self.scheduler:
|
if self.scheduler:
|
||||||
|
|||||||
@ -44,7 +44,6 @@ class TestHelpCommandDiscord:
|
|||||||
self._make_mock_command("nw", "Топ-5 статей и топ-5 новостей AI с Habr"),
|
self._make_mock_command("nw", "Топ-5 статей и топ-5 новостей AI с Habr"),
|
||||||
self._make_mock_command("morning", "Утренний дайджест: погода + новости + котик"),
|
self._make_mock_command("morning", "Утренний дайджест: погода + новости + котик"),
|
||||||
self._make_mock_command("cat", "Случайный котик"),
|
self._make_mock_command("cat", "Случайный котик"),
|
||||||
self._make_mock_command("msg", "Повторить текст в чате"),
|
|
||||||
]
|
]
|
||||||
mock_ctx.send = AsyncMock(side_effect=send_side_effect)
|
mock_ctx.send = AsyncMock(side_effect=send_side_effect)
|
||||||
|
|
||||||
@ -59,13 +58,13 @@ class TestHelpCommandDiscord:
|
|||||||
assert "=" * 40 in message
|
assert "=" * 40 in message
|
||||||
|
|
||||||
# Проверяем наличие всех команд без кавычек
|
# Проверяем наличие всех команд без кавычек
|
||||||
commands = ["!pg", "!nw", "!morning", "!cat", "!msg"]
|
commands = ["!pg", "!nw", "!morning", "!cat"]
|
||||||
for cmd in commands:
|
for cmd in commands:
|
||||||
assert cmd in message, f"Команда {cmd} не найдена"
|
assert cmd in message, f"Команда {cmd} не найдена"
|
||||||
|
|
||||||
# Проверяем разделение тире между командой и описанием
|
# Проверяем разделение тире между командой и описанием
|
||||||
lines = [l.strip() for l in message.split("\n") if "—" in l]
|
lines = [l.strip() for l in message.split("\n") if "—" in l]
|
||||||
assert len(lines) >= 5
|
assert len(lines) >= 4
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@ -58,7 +58,7 @@ async def run_morning(bot: "commands.Bot", channel: discord.TextChannel):
|
|||||||
data = await gather_morning()
|
data = await gather_morning()
|
||||||
|
|
||||||
# --- Формируем embed ---
|
# --- Формируем embed ---
|
||||||
embed = discord.Embed(title="🌅 Утренний дайджест!", color=0xF4A460)
|
embed = discord.Embed(title="Утренний дайджест", color=0xF4A460)
|
||||||
|
|
||||||
# Котик как thumbnail
|
# Котик как thumbnail
|
||||||
if data.cat_url:
|
if data.cat_url:
|
||||||
@ -115,12 +115,12 @@ async def run_morning(bot: "commands.Bot", channel: discord.TextChannel):
|
|||||||
description = "\n".join(description_lines)
|
description = "\n".join(description_lines)
|
||||||
embed.description = truncate_embed_text(description)
|
embed.description = truncate_embed_text(description)
|
||||||
await channel.send(embed=embed)
|
await channel.send(embed=embed)
|
||||||
logger.info("✅ Утренний дайджест отправлен в #%s", channel.name)
|
logger.info("Утренний дайджест отправлен в #%s", channel.name)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Ошибка при выполнении утреннего дайджеста: %s", e, exc_info=True)
|
logger.error("Ошибка при выполнении утреннего дайджеста: %s", e, exc_info=True)
|
||||||
try:
|
try:
|
||||||
await channel.send("❌ Не удалось выполнить утренний дайджест.")
|
await channel.send("Не удалось выполнить утренний дайджест.")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user