# Discord Bot Discord-бот для Магнитогорска. Команды погоды, новостей, котиков и утреннего дайджеста. ## Установка ```bash pip install -r requirements.txt ``` ## Запуск ```bash python bot.py ``` Введите номер команды в терминале или `!команда` в Discord. ## Настройка 1. Скопируйте `.env.example` в `.env`: ```bash cp .env.example .env ``` 2. Вставьте токен бота в `.env`: ```env DISCORD_TOKEN=ваш_токен ``` Токен получите на [Discord Developer Portal](https://discord.com/developers/applications). ## Команды Discord | Команда | Описание | |---------|----------| | `!pg` | Прогноз погоды для Магнитогорска | | `!nw` | Топ-5 статей и топ-5 новостей по AI с Habr | | `!hp` | Список всех команд бота с описанием (автогенерация из `bot.commands`) | | `!morning` | Погода + топ-5 статей + топ-5 новостей + котик (утренний дайджест) | | `!cat` | Случайный котик | | `!msg <текст>` | Повторить текст в чате | ## Команды терминала | Номер | Команда | Описание | |-------|---------|----------| | 1 | `news` | Топ-5 статей + топ-5 новостей с Habr | | 2 | `pogoda` | Прогноз погоды для Магнитогорска | | 3 | `morning` | Погода + топ-5 статей + топ-5 новостей + котик | | 4 | `cat` | Вывести URL случайного котика | | 5 | `help` | Показать список всех команд | | 0 | `stop` | Остановка бота | > Номера команд генерируются автоматически из `ALL_CONSOLE_COMMANDS` в порядке определения в `console_commands/__init__.py`. ## Архитектура ``` bot.py # Точка входа, инициализация бота, console_input() 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) help.py # !hp — список команд (автогенерация из bot.commands) console_commands/ # Консольные команды __init__.py # ALL_CONSOLE_COMMANDS — явные импорты admin.py # admin — CLI для docker exec (pogoda, news, cat, morning, help) stop.py # stop — остановка бота news.py # news — новости с Habr pogoda.py # pogoda — погода в терминале morning.py # morning — утренний дайджест в терминале cat.py # cat — вывод URL котика help.py # help — список всех команд utils/ # Утилиты (API-клиенты, конвертации) __init__.py # __all__ — публичный API утилит pogoda.py # fetch_weather(), fetch_open_meteo(), wmo_to_russian(), translate_weather(), pressure_to_mmhg(), format_weather_data_for_console(), format_weather_for_embed() news.py # fetch_rss(), format_articles(), truncate_title() cat.py # fetch_cat() rate_limiter.py # RateLimiter (токен-бакет), cat/weather/meteo/rss лимитеры morning_runner.py # Scheduler, MorningData, gather_morning(), run_morning() tests/ # pytest-тесты test_pogoda.py # translate_weather, pressure_to_mmhg, wmo_to_russian, format_weather_data_for_console test_fetch_cat.py # fetch_cat test_fetch_rss.py # fetch_rss test_fetch_weather.py # fetch_weather, fetch_open_meteo test_format_articles.py # truncate_title, _parse_date, format_articles test_commands_pg.py # Pg cog test_bot.py # инициализация бота test_morning_runner.py# тесты morning runner-а test_help_discord.py # команда !hp — проверка формата вывода и контента test_help_console.py # консольная help — проверка списка команд test_logger.py # setup_logging — уровни, обработчики, формат test_admin.py # admin.py — CLI-скрипт для docker exec ISSUES.md # Задачи и баг-трекер проекта pytest.ini # Конфигурация pytest (asyncio_mode = auto) Dockerfile # Сборка образа бота (Python 3.14-slim, healthcheck) docker-compose.yml # Запуск бота в Docker .dockerignore # Исключения для Docker-контекста ``` ### Добавление Discord команды 1. Создать файл `commands/имя.py` с классом, наследующим `commands.Cog` 2. Добавить импорт в `commands/__init__.py` 3. Добавить класс в `ALL_COMMANDS` ### Добавление консольной команды 1. Создать файл `console_commands/имя.py` с функцией `func(stop_event, bot)` 2. Добавить импорт в `console_commands/__init__.py` 3. Добавить функцию в `ALL_CONSOLE_COMMANDS` ## Запуск тестов ```bash python -m pytest tests/ -v ``` ### Структура тестов | Файл | Что тестирует | Кол-во | |------|---------------|--------| | `test_pogoda.py` | `translate_weather()`, `pressure_to_mmhg()`, `wmo_to_russian()`, `format_weather_data_for_console()` | 93 | | `test_fetch_cat.py` | `fetch_cat()` | 10 | | `test_fetch_rss.py` | `fetch_rss()` | 20 | | `test_fetch_weather.py` | `fetch_weather()`, `fetch_open_meteo()` | 20 | | `test_format_articles.py` | `truncate_title()`, `_parse_date()`, `format_articles()` | 24 | | `test_commands_pg.py` | `Pg` cog, команда `!pg` | 13 | | `test_bot.py` | инициализация бота | 7 | | `test_morning_runner.py` | morning runner-а | 68 | | `test_help_discord.py` | команда `!hp` | 2 | | `test_help_console.py` | консольная `help` | 2 | | `test_logger.py` | `setup_logging` (уровни, обработчики, формат) | 9 | | `test_rate_limiter.py` | `RateLimiter` (токен-бакет) | 5 | | `test_admin.py` | `admin.py` — CLI для docker exec | 5 | **Итого: 223 теста.** ## Запуск в Docker ### Сборка и запуск ```bash docker-compose up --build ``` Передайте токен через переменную окружения: ```bash DISCORD_TOKEN=ваш_токен docker-compose up ``` ### Особенности - База: `python:3.14-slim` - Healthcheck: проверка каждые 30 сек (старт-период 60 сек) - Консольный ввод отключён в Docker (stdin недоступен) - Версия Python настраивается через `ARG PYTHON_VERSION` ### Администрирование через docker exec Для управления ботом из терминала (без Discord-чата) используйте `admin.py`: ```bash docker exec discord-bot python admin.py pogoda docker exec discord-bot python admin.py news docker exec discord-bot python admin.py cat docker exec discord-bot python admin.py morning docker exec discord-bot python admin.py help docker stop discord-bot # остановка бота ``` Результат выводится в stdout терминала. Команды используют те же `utils`, что и Discord-команды. ## API и внешние сервисы ### Погода (!pg, !morning) - **Основной**: `wttr.in/Magnitogorsk` (бесплатный, без ключа) - **Fallback**: `api.open-meteo.com` (бесплатный, без ключа) - Retry: 3 попытки с экспоненциальной задержкой при SSL/Connection/Timeout ошибках - Fallback срабатывает автоматически при неуспешных попытках - Rate-limiting: 1 req/sec, burst 3 (wttr.in); 2 req/sec, burst 5 (Open-Meteo). Настраивается через `.env` - WMO weather codes → русский перевод в `wmo_to_russian()` ### Конвертации - Давление: hPa → мм рт. ст. (`* 0.750062`) - Ветер: км/ч → м/с (`/ 3.6`) - Погодные описания: английский → русский (`translate_weather()`) ### Новости (!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 форматов - Извлечение ссылок из `` и авторов из `` - Rate-limiting: 1 req/sec, burst 2. Настраивается через `.env` - Формат вывода: заголовок → дата → ссылка ### Котики (!cat, !morning) - **API**: `https://api.thecatapi.com/v1/images/search` - Rate-limiting: 1 req/sec, burst 3. Настраивается через `.env` - Картинка встраивается в Discord Embed ## Структура данных погоды Команда `!pg` возвращает: ``` Температура: X°C (ощущается как Y°C) Описание: Z Влажность: X% Ветер: X м/с Давление: X мм рт. ст. ``` ## Формат дат Даты форматируются как `дд.мм.гггг` через `datetime.strptime` с форматом `%a, %d %b %Y %H:%M:%S %z`. ## Конфигурация | Переменная | Описание | Где взять | |------------|----------|-----------| | `DISCORD_TOKEN` | Токен бота | [Discord Developer Portal](https://discord.com/developers/applications) | | `MORNING_TIME` | Время запуска утреннего дайджеста | `.env` (формат `ЧЧ:ММ`, по умолчанию `07:00`) | | `MORNING_CHANNEL_ID` | ID канала для утреннего дайджеста | Правый клик по каналу → Копировать ID | | `CAT_API_RATE` | Rate-limit TheCatAPI (токенов/сек) | `.env`, по умолчанию `1` | | `CAT_API_BURST` | Burst-бакет TheCatAPI | `.env`, по умолчанию `3` | | `WEATHER_API_RATE` | Rate-limit wttr.in (токенов/сек) | `.env`, по умолчанию `1` | | `WEATHER_API_BURST` | Burst-бакет wttr.in | `.env`, по умолчанию `3` | | `OPEN_METEO_API_RATE` | Rate-limit Open-Meteo (токенов/сек) | `.env`, по умолчанию `2` | | `OPEN_METEO_API_BURST` | Burst-бакет Open-Meteo | `.env`, по умолчанию `5` | | `HABR_RSS_RATE` | Rate-limit Habr RSS (токенов/сек) | `.env`, по умолчанию `1` | | `HABR_RSS_BURST` | Burst-бакет Habr RSS | `.env`, по умолчанию `2` | ## Зависимости ```txt discord.py>=2.3.2 python-dotenv>=1.0.0 requests>=2.31.0 pytest>=7.4.0 pytest-asyncio>=0.21.0 ``` ## Безопасность - `.env` в `.gitignore` — токен никогда не должен попадать в репозиторий - Используйте `.env.example` как шаблон ## Формат новостей Каждая новость выводится в формате: ``` Заголовок статьи дд.мм.гггг https://habr.com/ru/articles/... ``` Заголовки обрезаются до 60 символов с суффиксом `...`. Ссылки отображаются в виде `` для предотвращения embed-превью в Discord. ## Основные функции утилит ### utils/pogoda.py | Функция | Описание | |---------|----------| | `fetch_weather()` | Основная функция получения погоды с wttr.in | | `fetch_open_meteo()` | Fallback при ошибках основного API | | `wmo_to_russian()` | Перевод WMO кодов погоды в русское описание | | `translate_weather()` | Перевод погодных описаний на русский язык | | `pressure_to_mmhg()` | Конвертация давления из hPa в мм рт. ст. | | `format_weather_data_for_console()` | Форматирование данных погоды для вывода в консоль | | `format_weather_for_embed()` | Форматирование погоды для Discord embed (с заголовком) | ### utils/news.py | Функция | Описание | |---------|----------| | `fetch_rss()` | Получение RSS ленты (статьи или новости) | | `truncate_title()` | Обрезка заголовка до заданной длины | | `format_articles()` | Форматирование списка статей для вывода | ### utils/cat.py | Функция | Описание | |---------|----------| | `fetch_cat()` | Получение URL случайного котика | ### utils/morning_runner.py | Функция / Класс | Описание | |----------------|----------| | `MorningData` | dataclass с полями weather, articles, posts, cat_url | | `gather_morning()` | Параллельный сбор всех данных для дайджеста | | `run_morning()` | Формирование и отправка embed в канал Discord | | `Scheduler` | Планировщик ежедневных задач (discord.ext.tasks.loop) | ### utils/rate_limiter.py | Функция / Класс | Описание | |----------------|----------| | `RateLimiter` | Токен-бакет: `rate` (токенов/сек), `burst` (макс. бакет) | | `RateLimiter.acquire()` | Асинхронно ждать освобождения токена перед запросом | | `cat_limiter` | Лимитер для TheCatAPI (1/s, burst 3) | | `weather_limiter` | Лимитер для wttr.in (1/s, burst 3) | | `open_meteo_limiter` | Лимитер для Open-Meteo (2/s, burst 5) | | `habr_rss_limiter` | Лимитер для Habr RSS (1/s, burst 2) |