# TourMonitor — мониторинг туров Level.Travel Ежедневно проверяет цены на туры из Москвы (2 взрослых + ребёнок 5 лет; количество ночей — своё для каждого отеля) в отели **Sunrise Tucana Resort** и **Posh Club By Sunrise Tucana Resort** (Макади Бей, Египет) и **Riu Sri Lanka** (Шри-Ланка) на окно с +30 дней от сегодня на 150 дней вперёд и отправляет в Telegram отчёт с топом дешёвых дат и дельтой цен. Работает напрямую с публичным веб-API level.travel (подпись запросов и расшифровка ответов воспроизведены с JS-бандла сайта), без партнёрского токена. Запросы к API выполняются из настоящего браузера **Camoufox** (запатченный Firefox) через Playwright: это обходит анти-бот периметр по фингерпринту, который режет даже корректно подписанные запросы из обычного HTTP-клиента. Браузер скачивается и устанавливается автоматически при первом запуске. ## Отчёт в Telegram ``` 📊 Мониторинг туров · 9 ночей, 2+1 чел, ребёнок 5 лет Заезды: 05.09–01.02 🏝 Sunrise Tucana Resort 🗓 календарь цен 📅 Октябрь 📅 05.10 — 475 447 ₽ 🔻 −12% (было 540 000 ₽) 🍽 AI · Полулюкс · Level.Travel 📅 06.10 — 501 000 ₽ ✨ новая минималка 📅 Ноябрь 📅 02.11 — 520 000 ₽ 🔺 +3% (было 505 000 ₽) ... ``` Секция показывается только если по отелю есть предложения. Каждый отель приходит **отдельным сообщением**; если сообщение длиннее лимита Telegram (4096 символов), оно автоматически разбивается на несколько. В отчёте — до `PricesPerMonth` дешёвых дат на каждый месяц, тип номера — ссылка на бронирование, рядом дельта к прошлому скану. ## Как запустить Требуется .NET 10 SDK. ```bash # первый скан сразу (наполнение базы) + отправка отчёта: dotnet run --project src/TourMonitor -- Monitor__RunOnStart=true \ --Telegram__BotToken=<токен> --Telegram__ChatId= ``` - Дашборд 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 | | `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/` — юнит- и интеграционные тесты