discordBot/README.md
deadzilla 517bb4b9fa docs: update README to reflect current project state
- Weather API: wttr.in + Open-Meteo → Yandex Weather API (api.weather.yandex.ru)
- Add required YANDEX_WEATHER_API_KEY env var and config validation section
- Add new env vars: CAT_API_KEY, YANDEX_WEATHER_API_RATE/BURST
- Document new functions: yandex_condition_to_russian(), get_weather_description()
- Document new file: utils/compat.py (Python 3.14+ monkey-patch)
- Update rate limiters: cat_limiter, yandex_weather_limiter, habr_rss_limiter + factory functions
- Remove outdated: fetch_open_meteo(), open_meteo_limiter, weather_limiter (wttr.in)
- Update requirements versions
- Add BotRunner and TextHelpCommand sections
- Note morning digest format: plain text + cat as separate message
2026-07-20 23:38:57 +05:00

19 KiB
Raw Blame History

Discord Bot

Discord-бот для Магнитогорска. Команды погоды, новостей, котиков и утреннего дайджеста.

Установка

pip install -r requirements.txt

Запуск

python bot.py

Используйте !команда в Discord.

Настройка

  1. Скопируйте .env.example в .env:

    cp .env.example .env
    
  2. Заполните обязательные переменные в .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 команды

  1. Создать файл commands/имя.py с классом, наследующим commands.Cog
  2. Добавить импорт в commands/__init__.py
  3. Добавить класс в 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)

Валидация конфигурации

При запуске бот проверяет:

  1. DISCORD_TOKEN — наличие
  2. YANDEX_WEATHER_API_KEY — наличие
  3. MORNING_TIME — формат ЧЧ:ММ (0-23, 0-59)
  4. MORNING_CHANNEL_ID — числовой формат (если задан)

При ошибке валидации бот завершается с sys.exit(1).

Логирование

При запуске бота автоматически создаётся директория logs/ и файл logs/bot.log.

  • Консоль: все сообщения выводятся в stdout
  • Файл: logs/bot.log с ротацией по размером
    • maxBytes: 5 МБ — при достижении файл архивируется
    • backupCount: 5 — хранится до 5 бэкапов (bot.log.1bot.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.iscoroutinefunctioninspect.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-командам