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
This commit is contained in:
parent
48e64c2bc8
commit
517bb4b9fa
141
README.md
141
README.md
@ -23,12 +23,14 @@ python bot.py
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Вставьте токен бота в `.env`:
|
||||
2. Заполните обязательные переменные в `.env`:
|
||||
```env
|
||||
DISCORD_TOKEN=ваш_токен
|
||||
YANDEX_WEATHER_API_KEY=ваш_ключ_яндекс_погоды
|
||||
```
|
||||
|
||||
Токен получите на [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
`DISCORD_TOKEN` получите на [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
`YANDEX_WEATHER_API_KEY` — в [Яндекс Погода API](https://yandex.ru/dev/weather/).
|
||||
|
||||
## Команды Discord
|
||||
|
||||
@ -39,12 +41,12 @@ python bot.py
|
||||
| `!morning` | Погода + топ-5 статей + топ-5 новостей + котик (утренний дайджест) |
|
||||
| `!cat` | Случайный котик |
|
||||
| `!status` | Статус бота: пинг к Discord gateway, uptime |
|
||||
| `!stats` | Количество серверов, каналов, пользователей |
|
||||
| `!stats` | Количество серверов, каналов, пользователей, пинг |
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
bot.py # Точка входа, инициализация бота
|
||||
bot.py # Точка входа, BotRunner, TextHelpCommand, валидация конфига
|
||||
commands/ # Discord команды (cogs)
|
||||
__init__.py # ALL_COMMANDS — явные импорты
|
||||
pg.py # !pg — погода (обёртка над utils.pogoda)
|
||||
@ -52,20 +54,21 @@ commands/ # Discord команды (cogs)
|
||||
cat.py # !cat — случайный котик
|
||||
morning.py # !morning — утренний дайджест (обёртка над utils.morning_runner)
|
||||
status.py # !status — статус бота: пинг, uptime
|
||||
stats.py # !stats — серверы, каналы, пользователи
|
||||
stats.py # !stats — серверы, каналы, пользователи, пинг
|
||||
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_message()
|
||||
news.py # fetch_rss(), format_articles(), truncate_title()
|
||||
__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/weather/meteo/rss лимитеры
|
||||
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
|
||||
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, fetch_open_meteo
|
||||
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
|
||||
@ -104,10 +107,10 @@ python -m pytest tests/ -v
|
||||
|
||||
| Файл | Что тестирует | Кол-во |
|
||||
|------|---------------|--------|
|
||||
| `test_pogoda.py` | `translate_weather()`, `pressure_to_mmhg()`, `wmo_to_russian()`, `format_weather_data_for_console()` | 27 |
|
||||
| `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()`, `fetch_open_meteo()` | 19 |
|
||||
| `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 |
|
||||
@ -148,28 +151,33 @@ DISCORD_TOKEN=ваш_токен docker-compose up
|
||||
## API и внешние сервисы
|
||||
|
||||
### Погода (!pg, !morning)
|
||||
- **Основной**: `wttr.in/Magnitogorsk` (бесплатный, без ключа)
|
||||
- **Fallback**: `api.open-meteo.com` (бесплатный, без ключа)
|
||||
- **API**: `api.weather.yandex.ru/v1/informers` (Яндекс Погода API)
|
||||
- Требуется API-ключ в `YANDEX_WEATHER_API_KEY`
|
||||
- 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()`
|
||||
- Rate-limiting: 1 req/sec, burst 3. Настраивается через `.env` (`YANDEX_WEATHER_API_RATE`, `YANDEX_WEATHER_API_BURST`)
|
||||
- Координаты: Магнитогорск (53.40716, 58.980289)
|
||||
- API возвращает давление в мм рт. ст. и ветер в м/с — конвертация не требуется
|
||||
|
||||
### Конвертации
|
||||
- Давление: hPa → мм рт. ст. (`* 0.750062`)
|
||||
- Ветер: км/ч → м/с (`/ 3.6`)
|
||||
- Погодные описания: английский → русский (`translate_weather()`)
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `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
|
||||
|
||||
@ -181,51 +189,66 @@ DISCORD_TOKEN=ваш_токен docker-compose up
|
||||
Температура: X°C (ощущается как Y°C)
|
||||
Описание: Z
|
||||
Влажность: X%
|
||||
Ветер: X м/с
|
||||
Ветер: X м/с (порывы Y м/с), направление
|
||||
Давление: X мм рт. ст.
|
||||
```
|
||||
|
||||
Яндекс Погода API возвращает ветер в м/с, порывы ветра, направление ветра (n/ne/e/se/s/sw/w/nw → русский перевод).
|
||||
|
||||
## Формат дат
|
||||
|
||||
Даты форматируются как `дд.мм.гггг` через `datetime.strptime` с форматом `%a, %d %b %Y %H:%M:%S %z`.
|
||||
Даты форматируются как `дд.мм.гггг` через `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](https://discord.com/developers/applications) |
|
||||
| `DISCORD_TOKEN` | Токен бота (обязательна) | [Discord Developer Portal](https://discord.com/developers/applications) |
|
||||
| `YANDEX_WEATHER_API_KEY` | API-ключ Яндекс Погоды (обязателен) | [Яндекс Погода API](https://yandex.ru/dev/weather/) |
|
||||
| `MORNING_TIME` | Время запуска утреннего дайджеста | `.env` (формат `ЧЧ:ММ`, по умолчанию `07:00`) |
|
||||
| `MORNING_CHANNEL_ID` | ID канала для утреннего дайджеста | Правый клик по каналу → Копировать ID |
|
||||
| `CAT_API_KEY` | API-ключ TheCatAPI (опционально) | [TheCatAPI](https://thecatapi.com/) |
|
||||
| `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` |
|
||||
| `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` с ротацией по размеру
|
||||
- **Файл**: `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`)
|
||||
|
||||
```txt
|
||||
discord.py>=2.3.2
|
||||
python-dotenv>=1.0.0
|
||||
requests>=2.31.0
|
||||
defusedxml>=0.7.0
|
||||
discord.py~=2.7.1
|
||||
python-dotenv~=1.2.2
|
||||
requests~=2.34.2
|
||||
defusedxml~=0.7.1
|
||||
```
|
||||
|
||||
### Development (`requirements-dev.txt`)
|
||||
@ -239,7 +262,7 @@ ruff>=0.8.0
|
||||
|
||||
## Безопасность
|
||||
|
||||
- `.env` в `.gitignore` — токен никогда не должен попадать в репозиторий
|
||||
- `.env` в `.gitignore` — токены и ключи никогда не должны попадать в репозиторий
|
||||
- Используйте `.env.example` как шаблон
|
||||
|
||||
## Формат новостей
|
||||
@ -261,21 +284,21 @@ ruff>=0.8.0
|
||||
|
||||
| Функция | Описание |
|
||||
|---------|----------|
|
||||
| `fetch_weather()` | Основная функция получения погоды с wttr.in |
|
||||
| `fetch_open_meteo()` | Fallback при ошибках основного API |
|
||||
| `wmo_to_russian()` | Перевод WMO кодов погоды в русское описание |
|
||||
| `translate_weather()` | Перевод погодных описаний на русский язык |
|
||||
| `pressure_to_mmhg()` | Конвертация давления из hPa в мм рт. ст. |
|
||||
| `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 ленты (статьи или новости) |
|
||||
| `fetch_rss()` | Получение RSS ленты (статьи или новости), до 10 записей |
|
||||
| `truncate_title()` | Обрезка заголовка до заданной длины |
|
||||
| `format_articles()` | Форматирование списка статей для вывода |
|
||||
| `format_articles()` | Форматирование списка статей для вывода (топ-5) |
|
||||
| `truncate_message()` | Обрезка plain text сообщения до заданной длины (дефолт 2000) |
|
||||
| `truncate_embed_text()` | Обрезка embed.description до заданной длины (дефолт 4096) |
|
||||
| `truncate_embed_field()` | Обрезка embed field value до заданной длины (дефолт 1024) |
|
||||
@ -292,7 +315,7 @@ ruff>=0.8.0
|
||||
|----------------|----------|
|
||||
| `MorningData` | dataclass с полями weather, articles, posts, cat_url |
|
||||
| `gather_morning()` | Параллельный сбор всех данных для дайджеста |
|
||||
| `run_morning()` | Формирование и отправка embed в канал Discord |
|
||||
| `run_morning()` | Формирование и отправка plain text в канал Discord (котик — отдельным сообщением) |
|
||||
| `Scheduler` | Планировщик ежедневных задач (asyncio.sleep до целевого времени) |
|
||||
|
||||
### utils/rate_limiter.py
|
||||
@ -302,6 +325,36 @@ ruff>=0.8.0
|
||||
| `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) |
|
||||
| `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-командам
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user