discordBot/README.md

293 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 — явные импорты
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 — проверка списка команд
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_rate_limiter.py` | `RateLimiter` (токен-бакет) | 5 |
**Итого: 209 тестов.**
## Запуск в 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`
## 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 форматов
- Извлечение ссылок из `<guid isPermaLink="true">` и авторов из `<dc:creator>`
- 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 символов с суффиксом `...`.
Ссылки отображаются в виде `<url>` для предотвращения 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) |