Files
tours_monitor/README.md
T
kislovdm e9cdbbea05
Create and publish a Docker image / build-and-push-image (push) Successful in 3m22s
add rixos
2026-08-21 10:18:49 +03:00

115 lines
7.1 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.
# TourMonitor — мониторинг туров Level.Travel
Ежедневно проверяет цены на туры из Москвы (2 взрослых + ребёнок 5 лет; количество ночей —
своё для каждого отеля) в отели **Sunrise Tucana Resort** и **Posh Club By Sunrise Tucana Resort**
(Макади Бей, Египет) и **Riu Sri Lanka** (Шри-Ланка) и **Rixos Premium Alamein** (Эль-Аламейн, Египет) на окно с +30 дней от сегодня на
150 дней вперёд и отправляет в Telegram отчёт с топом дешёвых дат и дельтой цен.
Работает напрямую с публичным веб-API level.travel (подпись запросов и расшифровка
ответов воспроизведены с JS-бандла сайта), без партнёрского токена.
Запросы к API выполняются из настоящего браузера **Camoufox** (запатченный Firefox)
через Playwright: это обходит анти-бот периметр по фингерпринту, который режет даже
корректно подписанные запросы из обычного HTTP-клиента. Браузер скачивается и
устанавливается автоматически при первом запуске.
## Отчёт в Telegram
```
📊 <b>Мониторинг туров</b> · 9 ночей, 2+1 чел, ребёнок 5 лет
Заезды: 05.09–01.02
🏝 <b>Sunrise Tucana Resort</b>
🗓 <a href="http://192.168.1.6:8084/prices/9099454">календарь цен</a>
📅 <b>Октябрь</b>
📅 05.10 — <b>475 447 ₽</b> 🔻 −12% (было 540 000 ₽)
🍽 AI · <a href="https://level.travel/package_details/…">Полулюкс</a> · Level.Travel
📅 06.10 — <b>501 000 ₽</b> ✨ новая минималка
📅 <b>Ноябрь</b>
📅 02.11 — <b>520 000 ₽</b> 🔺 +3% (было 505 000 ₽)
...
```
Секция показывается только если по отелю есть предложения. Каждый отель приходит
**отдельным сообщением**; если сообщение длиннее лимита Telegram (4096 символов), оно
автоматически разбивается на несколько. В отчёте — до `PricesPerMonth` дешёвых дат
на каждый месяц, тип номера — ссылка на бронирование, рядом дельта к прошлому скану.
## Как запустить
Требуется .NET 10 SDK.
```bash
# первый скан сразу (наполнение базы) + отправка отчёта:
dotnet run --project src/TourMonitor -- Monitor__RunOnStart=true \
--Telegram__BotToken=<токен> --Telegram__ChatId=<id>
```
- Дашборд Hangfire (журнал задач, кнопка «Run now»): http://localhost:5199/hangfire
- Календарь цен: http://localhost:5199/prices (список отелей → сетка дат с ценой из
последнего скана, диапазоном минимальной цены по датам сканирования и дельтой)
- Ежедневный скан по умолчанию в 09:15 по Москве (`Schedule:Cron`, `Schedule:TimeZoneId`)
- База SQLite: `Data/tour_monitor.db` (история цен + ежедневные минимумы)
## Конфигурация
Всё через `appsettings.json` или переменные окружения (`LevelTravel__…`, `Monitor__…`,
`Telegram__…`, `Schedule__…`):
| Ключ | По умолчанию |
|---|---|
| `Telegram:BotToken`, `Telegram:ChatId` | пусто (отчёт не отправляется) |
| `Monitor:Hotels` | Sunrise Tucana 9099454, Posh Club 9151153, Riu Sri Lanka 9067553, Rixos Premium Alamein 9148149 |
| `Monitor:Adults`, `Monitor:KidsAges` | 2, `[5]` |
| `Monitor:DateRangeDays` | 150 |
| `Monitor:StartOffsetDays` | 30 (окно начинается с +30 дней от сегодня) |
| `Monitor:PricesPerMonth` | 3 (сколько дешёвых дат показывать в отчёте для каждого месяца) |
| `Monitor:CalendarUrlBase` | `http://192.168.1.6:8084` (ссылка на календарь цен в отчёте) |
| `Monitor:RunOnStart` | false |
| `Monitor:MaxParallelDates` | 5 (даты сканируются параллельно) |
| `Camoufox:Version` | `v152.0.4-beta.28` |
| `Camoufox:InstallDirectory` | `Data/camoufox` |
| `Camoufox:Headless` | true |
| `Schedule:Cron` | `0 15 9 * * ?` |
| `Schedule:TimeZoneId` | `Europe/Moscow` |
Ключи API и расшифровки (`LevelTravel:ApiKey`, `LevelTravel:SecretBoxKeys`) — из открытого
JS-бандла сайта; менять не нужно.
У каждого отеля (`Monitor:Hotels`) свои направление и ночи: `ToCity`, `ToCountry`, `Nights`
(например, Riu Sri Lanka — `"ToCity": "Sri Lanka", "ToCountry": "LK", "Nights": 9`).
## Docker
```bash
cp .env.example .env # заполнить TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID
docker compose up -d --build
```
Данные — в volume `tour-monitor-data` (`/app/Data/tour_monitor.db`).
## Тесты
```bash
dotnet test # юнит: подпись, расшифровка, хранилище, отчёт
LT_LIVE_TESTS=1 dotnet test # + живые проверки против api.level.travel (через браузер Camoufox)
```
Живые тесты ходят в реальный API и поэтому опциональны: `LT_LIVE_TESTS=1` включает их,
без переменной они «проходят» без запросов. API защищён анти-бот периметром по
фингепринту/IP: в моменты блокировки он отвечает decoy'ами — `403`,
`{"message":"partner not found"}` (с кодом 200) или HTML-404 вместо данных.
Браузерный транспорт (Camoufox) эти блоки обходит и распознаёт decoy'ы.
## Структура
- `src/TourMonitor/LevelTravel/` — подпись (`SignHelper`), расшифровка (`LevelTravelCrypto`,
XSalsa20-Poly1305 + zlib), клиент `multi_enqueue`/`status`/`room_rates` поверх
транспорта `IApiTransport` (браузер Camoufox)
- `src/TourMonitor/Camoufox/` — установка Camoufox (скачивание из релизов), сессия
браузера и fetch-транспорт через Playwright
- `src/TourMonitor/Storage/PriceStore.cs` — SQLite: история цен, ежедневные минимумы
- `src/TourMonitor/Jobs/DailyScanJob.cs` — сам скан: 150 дат × отели из конфига, параллельно
- `src/TourMonitor/Notifications/` — Telegram и сборка отчёта
- `tests/TourMonitor.Tests/` — юнит- и интеграционные тесты