Compare commits

..

2 Commits

4 changed files with 28 additions and 11 deletions

View File

@ -2,3 +2,11 @@ DISCORD_TOKEN=your_bot_token_here
MORNING_TIME=07:00
MORNING_CHANNEL_ID=channel_id
LOG_LEVEL=INFO
CAT_API_RATE=1
CAT_API_BURST=3
WEATHER_API_RATE=1
WEATHER_API_BURST=3
OPEN_METEO_API_RATE=2
OPEN_METEO_API_BURST=5
HABR_RSS_RATE=1
HABR_RSS_BURST=2

View File

@ -46,7 +46,7 @@ python bot.py
## Архитектура
```
bot.py # Точка входа, инициализация бота
bot.py # Точка входа, инициализация бота, команда !msg (в BotRunner._setup_events)
commands/ # Discord команды (cogs)
__init__.py # ALL_COMMANDS — явные импорты
pg.py # !pg — погода (обёртка над utils.pogoda)
@ -100,20 +100,20 @@ python -m pytest tests/ -v
| Файл | Что тестирует | Кол-во |
|------|---------------|--------|
| `test_pogoda.py` | `translate_weather()`, `pressure_to_mmhg()`, `wmo_to_russian()`, `format_weather_data_for_console()` | 93 |
| `test_pogoda.py` | `translate_weather()`, `pressure_to_mmhg()`, `wmo_to_russian()`, `format_weather_data_for_console()` | 27 |
| `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_fetch_rss.py` | `fetch_rss()` | 21 |
| `test_fetch_weather.py` | `fetch_weather()`, `fetch_open_meteo()` | 19 |
| `test_format_articles.py` | `truncate_title()`, `_parse_date()`, `format_articles()` | 20 |
| `test_commands_pg.py` | `Pg` cog, команда `!pg` | 13 |
| `test_bot.py` | инициализация бота | 7 |
| `test_morning_runner.py` | morning runner-а | 68 |
| `test_bot.py` | инициализация бота | 5 |
| `test_morning_runner.py` | morning runner | 12 |
| `test_help_discord.py` | команда `!hp` | 2 |
| `test_logger.py` | `setup_logging` (уровни, обработчики, формат) | 9 |
| `test_rate_limiter.py` | `RateLimiter` (токен-бакет) | 5 |
| `test_commands_status.py` | команда `!status` (embed, uptime) | 6 |
| `test_commands_stats.py` | команда `!stats` (серверы, каналы) | 4 |
**Итого: 227 теста.**
| `test_commands_stats.py` | команда `!stats` (серверы, каналы) | 5 |
**Итого: 154 теста.**
## Запуск в Docker
@ -194,6 +194,7 @@ DISCORD_TOKEN=ваш_токен docker-compose up
| `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` |
| `LOG_LEVEL` | Уровень логирования | `.env`, по умолчанию `INFO` (DEBUG, WARNING, ERROR, CRITICAL) |
## Логирование
@ -208,10 +209,17 @@ DISCORD_TOKEN=ваш_токен docker-compose up
## Зависимости
### Production (`requirements.txt`)
```txt
discord.py>=2.3.2
python-dotenv>=1.0.0
requests>=2.31.0
```
### Development (`requirements-dev.txt`)
```txt
pytest>=7.4.0
pytest-asyncio>=0.21.0
```

View File

@ -1,3 +1,4 @@
discord.py>=2.3.2
python-dotenv>=1.0.0
requests>=2.31.0
defusedxml>=7.0.0

View File

@ -17,12 +17,12 @@ _session = requests.Session()
async def fetch_rss(url):
"""Скачать и распарсить RSS-ленту (RSS 2.0 / Atom)."""
await habr_rss_limiter.acquire()
from xml.etree import ElementTree
from defusedxml.ElementTree import fromstring
try:
response = await asyncio.to_thread(_session.get, url, timeout=10)
response.raise_for_status()
root = ElementTree.fromstring(response.content)
root = fromstring(response.content)
# RSS 2.0
ns_dc = {"dc": "http://purl.org/dc/elements/1.1/"}