Если run_morning выбрасывает исключение, ошибка теперь
перехватывается: логируется с exc_info, пользователю
отправлено сообщение об ошибке. Тест обновлён — проверяется
что исключение не пробрасывается наружу.
Город больше не захардкожен в коде. Переопределяется через
WEATHER_CITY (по умолчанию "Магнитогорск"). Шаблон изменён на
"Погода: {city}:" для обхода проблемы падежей в русском языке.
Файлы: utils/pogoda.py, .env.example, tests/test_morning_runner.py
ruff format --check падает на 7 файлах (не 5 как было указано).
Изменения — чистое форматирование: line wrapping, trailing commas, blank lines.
Никаких логических изменений.
Файлы: bot.py, tests/test_commands_news.py, tests/test_commands_pg.py,
tests/test_fetch_weather.py, tests/test_help_command.py,
tests/test_morning_runner.py, utils/pogoda.py
- fetch_weather кэширует результат на 3600 сек (WEATHER_CACHE_TTL)
- Провал запроса тоже кэшируется — не долбит API повторно
- bypass_cache=True для форсирования свежего запроса
- clear_weather_cache() для сброса кэша
- asyncio.Lock для thread-safety
- 5 новых тестов (cache hit, bypass, failure cache, clear, TTL expiry)
- JSONDecodeError теперь ловится в fetch_weather (был краш при невалидном JSON от API)
- Порывы ветра больше не теряются при отсутствии base wind (gust fallback)
- Устранено дублирование 'м/с' при порывах + отсутствии направления ветра
- Переименован windspeedKmph → wind_speed_mps (ключ хранил м/с, а не км/ч)
- Обновлены тесты, добавлены 6 новых (gust-only, no-duplication, JSONDecodeError)
- Переименован format_weather_for_embed -> format_weather_for_message
- Убран markdown жирный (**text**) из заголовков погоды и статей
- Утренний дайджест отправляется как обычное текстовое сообщение
- Котик отправляется отдельным сообщением перед дайджестом
- Truncation через truncate_message (лимит 2000 символов)
- Обновлены тесты: проверка 2 send() вместо embed
Проблема: cmd.__doc__ возвращал docstring класса Command из discord.py
(одинаковый для всех команд), а не docstring callback-функции.
Решение:
- Использовать cmd.help вместо cmd.__doc__ для получения описания
- Скрыть !hp из списка через hidden=True
- Пропускать hidden-команды и команды без описания
- Обновить тесты под новый интерфейс
- test_fetch_cat.py: 10 тестов (def → async def, asyncio.run → await)
- test_fetch_rss.py: 21 тест (def → async def, asyncio.run → await)
- test_fetch_weather.py: 19 тест (def → async def, asyncio.run → await)
- Удалён лишний import asyncio из тестовых файлов
- Больше не создаётся новый event loop на каждый тест
- RateLimiter (asyncio.Lock) теперь корректно работает в одном loop
230 тестов проходят.
- Заменён dict на _WEATHER_MAPPING (list of tuples, отсортирован по длине ключей)
- Длинные фразы проверяются первыми, исключая ложные подстроочные совпадения
- "Moderate or heavy rain at times" теперь корректно -> "Дождь" вместо "Сильный дождь"
- Обновлены тесты: test_translate_known и test_translate_longer_key_priority
230 тестов проходят.
- Защита от пустого списка [] и пустого dict [{}] в current_condition
- Возврат None вместо краша, команда !pg показывает fallback-сообщение
- Обновлён тест test_pg_empty_current_condition (graceful fallback вместо IndexError)
230 тестов проходят.
- Удалён dead code except KeyboardInterrupt (никогда не срабатывал в bot.run())
- Добавлен _signal_handler() для SIGINT/SIGTERM с остановкой scheduler и bot.close()
- Заменён bot.run(token) на asyncio.run(main()) с async with self.bot:
- Добавлен reconnect=True для устойчивости к разрывам gateway
- Тесты: string-matching заменены на поведенческие mock-тесты (5 тестов, все OK)
- Добавлен CODE_REVIEW.md с результатами ревью
- Scheduler больше не опрашивает каждую секунду (tasks.loop мог пропустить
момент срабатывания из-за неточности тайминга). Теперь спит ровно до
целевого времени через asyncio.sleep, затем запускает morning и повторяет.
- Добавлено логирование ожидания и срабатывания расписания.
- Перенес инициализацию планировщика из on_ready в on_guild_available —
гарантирует, что кэш гильдии загружен до запуска Scheduler.
- Обновлены тесты под новый Scheduler (asyncio.create_task вместо tasks.loop).
- README.md: исправлены пути admin.py → console_commands/admin.py для docker exec.
- docker-compose.yml: добавлены переменные LOG_LEVEL и rate-limit конфиги.
- console_commands/__init__.py: переименованы help → console_help,
reload → reload_cogs (избежал конфликта с built-in и уточнил имена).
guild.channels содержит все типы включая CategoryChannel.
Категории — это контейнеры для группировки, а не каналы.
Изменено:
- commands/stats.py: filter по isinstance(CategoryChannel)
- console_commands/stats.py: аналогичный фильтр
- test_commands_stats.py: тест test_stats_excludes_categories
- Итого: 234 теста
Проблема: _start_time хранился в Status Cog, но консольная
команда искала его на объекте bot — всегда получала fallback
time.time() и показывала 0с.
Решение: _start_time устанавливается в BotRunner.__init__ на
объекте bot. Cog и console_command читают getattr(bot, '_start_time').
Изменено: bot.py, commands/status.py, console_commands/status.py,
tests/test_commands_status.py