Ветки if/else по cog_or_none выполняли идентичную логику. Объединены в один цикл — standalone-команды и команды из cog обрабатываются одинаково.
Discord Bot
Discord-бот для Магнитогорска. Команды погоды, новостей, котиков и утреннего дайджеста.
Установка
pip install -r requirements.txt
Запуск
python bot.py
Используйте !команда в Discord.
Настройка
-
Скопируйте
.env.exampleв.env:cp .env.example .env -
Заполните обязательные переменные в
.env:DISCORD_TOKEN=ваш_токен YANDEX_WEATHER_API_KEY=ваш_ключ_яндекс_погоды
DISCORD_TOKEN получите на Discord Developer Portal.
YANDEX_WEATHER_API_KEY — в Яндекс Погода API.
Команды Discord
| Команда | Описание |
|---|---|
!pg |
Прогноз погоды для Магнитогорска |
!nw |
Топ-5 статей и топ-5 новостей по AI с Habr |
!morning |
Погода + топ-5 статей + топ-5 новостей + котик (утренний дайджест) |
!cat |
Случайный котик |
!status |
Статус бота: пинг к Discord gateway, uptime |
!stats |
Количество серверов, каналов, пользователей, пинг |
Архитектура
bot.py # Точка входа, BotRunner, TextHelpCommand, валидация конфига
commands/ # Discord команды (cogs)
__init__.py # ALL_COMMANDS — явные импорты
pg.py # !pg — погода (обёртка над utils.pogoda)
news.py # !nw — статьи + новости с Habr
cat.py # !cat — случайный котик
morning.py # !morning — утренний дайджест (обёртка над utils.morning_runner)
status.py # !status — статус бота: пинг, uptime
stats.py # !stats — серверы, каналы, пользователи, пинг
utils/ # Утилиты (API-клиенты, конвертации)
__init__.py # __all__ — публичный API утилит + close_all_sessions()
pogoda.py # fetch_weather(), yandex_condition_to_russian(), get_weather_description(), wmo_to_russian(), translate_weather(), pressure_to_mmhg(), format_weather_data_for_console(), format_weather_for_message()
news.py # fetch_rss(), format_articles(), truncate_title(), truncate_message(), truncate_embed_text(), truncate_embed_field()
cat.py # fetch_cat()
rate_limiter.py # RateLimiter (токен-бакет), cat/yandex_weather/habr_rss лимитеры
morning_runner.py # Scheduler, MorningData, gather_morning(), run_morning()
logger.py # setup_logging() — консоль + файл с ротацией по размеру
compat.py # Monkey-patch asyncio.iscoroutinefunction (Python 3.14+ / discord.py 2.7.1)
tests/ # pytest-тесты
test_pogoda.py # translate_weather, pressure_to_mmhg, wmo_to_russian, format_weather_data_for_console, yandex_condition_to_russian
test_fetch_cat.py # fetch_cat
test_fetch_rss.py # fetch_rss
test_fetch_weather.py # fetch_weather (Яндекс Погода API)
test_format_articles.py # truncate_title, _parse_date, format_articles, truncate_message, truncate_embed_text, truncate_embed_field
test_commands_pg.py # Pg cog
test_commands_cat.py # Cat cog, команда !cat
test_commands_news.py # News cog, команда !nw
test_commands_morning.py # Morning cog, команда !morning
test_bot.py # инициализация бота, обработка ошибок запуска
test_morning_runner.py# тесты morning runner-а
test_help_command.py # TextHelpCommand — текстовая справка по командам
test_logger.py # setup_logging — уровни, обработчики, формат
test_commands_status.py # команда !status — embed и uptime
test_commands_stats.py # команда !stats — подсчёт серверов и каналов
test_integration.py # интеграционные тесты (загрузка когов, поток команд)
conftest.py # monkey-patch asyncio.iscoroutinefunction (Python 3.14+ / discord.py 2.7.1)
ISSUES.md # Задачи и баг-трекер проекта
pytest.ini # Конфигурация pytest (asyncio_mode = auto)
Dockerfile # Сборка образа бота (Python 3.14-slim, healthcheck)
docker-compose.yml # Запуск бота в Docker
.dockerignore # Исключения для Docker-контекста
.gitignore # Исключения для Git (venv, .env, логи, IDE)
.pre-commit-config.yaml # pre-commit хуки (ruff lint + ruff-format)
Добавление Discord команды
- Создать файл
commands/имя.pyс классом, наследующимcommands.Cog - Добавить импорт в
commands/__init__.py - Добавить класс в
ALL_COMMANDS
Запуск тестов
python -m pytest tests/ -v
Структура тестов
| Файл | Что тестирует | Кол-во |
|---|---|---|
test_pogoda.py |
translate_weather(), pressure_to_mmhg(), wmo_to_russian(), format_weather_data_for_console(), yandex_condition_to_russian() |
27 |
test_fetch_cat.py |
fetch_cat() |
10 |
test_fetch_rss.py |
fetch_rss() |
21 |
test_fetch_weather.py |
fetch_weather() (Яндекс Погода) |
19 |
test_format_articles.py |
truncate_title(), _parse_date(), format_articles(), truncate_message(), truncate_embed_text(), truncate_embed_field() |
20 |
test_commands_pg.py |
Pg cog, команда !pg |
13 |
test_commands_cat.py |
Cat cog, команда !cat (embed, fallback) |
8 |
test_commands_news.py |
News cog, команда !nw (статьи, посты, fallback) |
8 |
test_commands_morning.py |
Morning cog, команда !morning (run_morning) |
5 |
test_bot.py |
инициализация бота, обработка ошибок запуска | 5 |
test_morning_runner.py |
morning runner | 12 |
test_logger.py |
setup_logging (уровни, обработчики, формат) |
9 |
test_help_command.py |
TextHelpCommand (справка, алиасы, скрытые команды) |
11 |
test_rate_limiter.py |
RateLimiter (токен-бакет) |
5 |
test_commands_status.py |
команда !status (embed, uptime) |
6 |
test_commands_stats.py |
команда !stats (серверы, каналы) |
5 |
test_integration.py |
загрузка когов, поток команд (моки API) | 9 |
| Итого: 206 функций (282 тестов с учётом parametrized). |
Запуск в Docker
Сборка и запуск
docker-compose up --build
Передайте токен через переменную окружения:
DISCORD_TOKEN=ваш_токен docker-compose up
Особенности
- База:
python:3.14-slim - Healthcheck: проверка каждые 30 сек (старт-период 60 сек)
- Версия Python настраивается через
ARG PYTHON_VERSION - Часовой пояс:
TZ=Asia/Yekaterinburg(в docker-compose.yml) - Установлены
tzdataиprocpsдля healthcheck и корректных дат
API и внешние сервисы
Погода (!pg, !morning)
- API:
api.weather.yandex.ru/v1/informers(Яндекс Погода API) - Требуется API-ключ в
YANDEX_WEATHER_API_KEY - Retry: 3 попытки с экспоненциальной задержкой при SSL/Connection/Timeout ошибках
- Rate-limiting: 1 req/sec, burst 3. Настраивается через
.env(YANDEX_WEATHER_API_RATE,YANDEX_WEATHER_API_BURST) - Координаты: Магнитогорск (53.40716, 58.980289)
- API возвращает давление в мм рт. ст. и ветер в м/с — конвертация не требуется
Конвертации
| Функция | Описание |
|---|---|
yandex_condition_to_russian() |
Перевод Яндекс condition-кодов в русское описание |
pressure_to_mmhg() |
hPa → мм рт. ст. (* 0.750062) — для обратной совместимости с тестами |
wmo_to_russian() |
WMO weather codes → русский — для обратной совместимости с тестами |
Новости (!nw, !morning)
- Articles:
https://habr.com/ru/rss/hubs/artificial_intelligence/articles/top/daily/?fl=ru - News:
https://habr.com/ru/rss/hubs/artificial_intelligence/news/top/daily/?fl=ru - Парсинг RSS 2.0 и Atom форматов
- Извлечение ссылок из
<guid isPermaLink="true">и авторов из<dc:creator> - Возвращает до 10 статей/постов, выводит топ-5
- Rate-limiting: 1 req/sec, burst 2. Настраивается через
.env - Формат вывода: заголовок → дата → ссылка
Котики (!cat, !morning)
- API:
https://api.thecatapi.com/v1/images/search - Опциональный ключ
CAT_API_KEY(заголовокx-api-key) - Rate-limiting: 1 req/sec, burst 3. Настраивается через
.env - Картинка встраивается в Discord Embed
Структура данных погоды
Команда !pg возвращает:
Температура: X°C (ощущается как Y°C)
Описание: Z
Влажность: X%
Ветер: X м/с (порывы Y м/с), направление
Давление: X мм рт. ст.
Яндекс Погода API возвращает ветер в м/с, порывы ветра, направление ветра (n/ne/e/se/s/sw/w/nw → русский перевод).
Формат дат
Даты форматируются как дд.мм.гггг через datetime.strptime:
- RFC 822:
"Mon, 01 Jan 2024 12:00:00 GMT" - ISO 8601:
"2024-01-01T12:00:00+00:00"или"2024-01-01"
Конфигурация
| Переменная | Описание | Где взять |
|---|---|---|
DISCORD_TOKEN |
Токен бота (обязательна) | Discord Developer Portal |
YANDEX_WEATHER_API_KEY |
API-ключ Яндекс Погоды (обязателен) | Яндекс Погода API |
MORNING_TIME |
Время запуска утреннего дайджеста | .env (формат ЧЧ:ММ, по умолчанию 07:00) |
MORNING_CHANNEL_ID |
ID канала для утреннего дайджеста | Правый клик по каналу → Копировать ID |
CAT_API_KEY |
API-ключ TheCatAPI (опционально) | TheCatAPI |
CAT_API_RATE |
Rate-limit TheCatAPI (токенов/сек) | .env, по умолчанию 1 |
CAT_API_BURST |
Burst-бакет TheCatAPI | .env, по умолчанию 3 |
YANDEX_WEATHER_API_RATE |
Rate-limit Яндекс Погоды (токенов/сек) | .env, по умолчанию 1 |
YANDEX_WEATHER_API_BURST |
Burst-бакет Яндекс Погоды | .env, по умолчанию 3 |
HABR_RSS_RATE |
Rate-limit Habr RSS (токенов/сек) | .env, по умолчанию 1 |
HABR_RSS_BURST |
Burst-бакет Habr RSS | .env, по умолчанию 2 |
LOG_LEVEL |
Уровень логирования | .env, по умолчанию INFO (DEBUG, WARNING, ERROR, CRITICAL) |
WEATHER_CACHE_TTL |
TTL кэша погоды (сек) | .env, по умолчанию 3600 |
Валидация конфигурации
При запуске бот проверяет:
DISCORD_TOKEN— наличиеYANDEX_WEATHER_API_KEY— наличиеMORNING_TIME— форматЧЧ:ММ(0-23, 0-59)MORNING_CHANNEL_ID— числовой формат (если задан)
При ошибке валидации бот завершается с sys.exit(1).
Логирование
При запуске бота автоматически создаётся директория logs/ и файл logs/bot.log.
- Консоль: все сообщения выводятся в stdout
- Файл:
logs/bot.logс ротацией по размером- maxBytes: 5 МБ — при достижении файл архивируется
- backupCount: 5 — хранится до 5 бэкапов (
bot.log.1…bot.log.5) - Максимальный объём: ~25 МБ
- Уровень: настраивается через
LOG_LEVELв.env(по умолчаниюINFO) - Шум:
aiohttp— WARNING,discord— INFO
Зависимости
Production (requirements.txt)
discord.py~=2.7.1
python-dotenv~=1.2.2
requests~=2.34.2
defusedxml~=0.7.1
Development (requirements-dev.txt)
pre-commit>=3.5.0
pytest>=7.4.0
pytest-asyncio>=0.21.0
ruff>=0.8.0
Безопасность
.envв.gitignore— токены и ключи никогда не должны попадать в репозиторий- Используйте
.env.exampleкак шаблон
Формат новостей
Каждая новость выводится в формате:
Заголовок статьи
дд.мм.гггг https://habr.com/ru/articles/...
Заголовки обрезаются до 60 символов с суффиксом ....
Ссылки отображаются в виде <url> для предотвращения embed-превью в Discord.
Основные функции утилит
utils/pogoda.py
| Функция | Описание |
|---|---|
fetch_weather() |
Получение погоды через Яндекс Погода API |
yandex_condition_to_russian() |
Перевод Яндекс condition-кодов в русское описание |
get_weather_description() |
Извлечение описания погоды из weatherDesc |
format_weather_data_for_console() |
Форматирование данных погоды для вывода в консоль |
format_weather_for_message() |
Форматирование погоды для plain text сообщения (с заголовком) |
pressure_to_mmhg() |
Конвертация давления из hPa в мм рт. ст. (для обратной совместимости) |
wmo_to_russian() |
Перевод WMO weather codes в русский (для обратной совместимости) |
utils/news.py
| Функция | Описание |
|---|---|
fetch_rss() |
Получение RSS ленты (статьи или новости), до 10 записей |
truncate_title() |
Обрезка заголовка до заданной длины |
format_articles() |
Форматирование списка статей для вывода (топ-5) |
truncate_message() |
Обрезка plain text сообщения до заданной длины (дефолт 2000) |
truncate_embed_text() |
Обрезка embed.description до заданной длины (дефолт 4096) |
truncate_embed_field() |
Обрезка embed field value до заданной длины (дефолт 1024) |
utils/cat.py
| Функция | Описание |
|---|---|
fetch_cat() |
Получение URL случайного котика |
utils/morning_runner.py
| Функция / Класс | Описание |
|---|---|
MorningData |
dataclass с полями weather, articles, posts, cat_url |
gather_morning() |
Параллельный сбор всех данных для дайджеста |
run_morning() |
Формирование и отправка plain text в канал Discord (котик — отдельным сообщением) |
Scheduler |
Планировщик ежедневных задач (asyncio.sleep до целевого времени) |
utils/rate_limiter.py
| Функция / Класс | Описание |
|---|---|
RateLimiter |
Токен-бакет: rate (токенов/сек), burst (макс. бакет) |
RateLimiter.acquire() |
Асинхронно ждать освобождения токена перед запросом |
cat_limiter |
Лимитер для TheCatAPI (1/s, burst 3) |
yandex_weather_limiter |
Лимитер для Яндекс Погоды (1/s, burst 3) |
habr_rss_limiter |
Лимитер для Habr RSS (1/s, burst 2) |
make_*_limiter() |
Factory-функции для создания изолированных экземпляров в тестах |
utils/compat.py
| Описание |
|---|
Monkey-patch asyncio.iscoroutinefunction → inspect.iscoroutinefunction для совместимости Python 3.14+ с discord.py 2.7.1 |
utils/logger.py
| Функция | Описание |
|---|---|
setup_logging() |
Настройка root-логгера: консоль (stdout) + файл (logs/bot.log) с ротацией |
BotRunner
BotRunner управляет жизненным циклом бота:
- Graceful shutdown:
async with self.bot(context manager),on_shutdownсобытие,KeyboardInterrupt - Cog loading: Защита от дублирования при reconnect (
on_readyможет сработать несколько раз) - Scheduler: Запуск через
on_guild_available(после загрузки guild-кэша) - Error handling:
on_command_error— логирование + пользовательское сообщение в Discord - Session cleanup:
close_all_sessions()при завершении для освобождения сокетов
TextHelpCommand
Кастомная команда справки (!help) — выводит список команд простым текстом вместо embed. Поддерживает:
- Список всех команд с описаниями
- Справку по отдельной команде с алиасами
- Справку по cog-модулю
- Справку по group-командам