# AutoriaBot

Telegram-бот для мониторинга объявлений **Auto.RIA**: поиск по фильтрам пользователя, открытие карточек в **headless Chrome (Selenium)**, извлечение телефонов и рассылка SMS через **TurboSMS**.

---

## Возможности

- Фильтры в Telegram: тип авто, **один или несколько регионов**, год, цена
- **Единые «Настройки»**: фильтр / расписание / SMS / общие параметры (понятные описания)
- **Авторабота по расписанию**: дни недели + окно времени (например Пн–Пт 09:00–20:00); ручной «Запуск» всегда доступен
- Пагинация выдачи Auto.RIA (`PARSING_SEARCH_PAGES`)
- Настраиваемая «свежесть» объявлений (`PARSING_REPUBLISHED_LAST`)
- Несколько регионов: для каждого региона — отдельный API-запрос, результаты объединяются и дедуплицируются
- Дедупликация SMS по номеру + cooldown (`SMS_RESEND_AFTER_DAYS`)
- Стабильность Chrome: безопасный quit, сбор zombie, плановый рестарт каждые N объявлений
- Укороченные таймауты Selenium (настраиваются в `.env`)
- SQLite в Docker в именованном томе `autoria_database`
- Удалённое управление сервером: `scripts/remote.py`

**Ограничение:** одновременно один активный сеанс парсинга на процесс бота (один Chrome).

---

## Как это работает

```text
Telegram UI (фильтры / старт)
        │
        ▼
  Auto.RIA API  ──►  список объявлений
  (по регионам × страницам)
        │
        ▼
  Selenium/Chrome ──►  «Показать телефон» ──► номера
        │
        ▼
  SQLite (autoinfo, sent_phones, логи)
        │
        ▼
  TurboSMS ──► SMS продавцу
```

1. Пользователь задаёт фильтр и нажимает «Запуск».
2. Бот запрашивает `form/extended` Auto.RIA:
   - до `PARSING_SEARCH_PAGES` страниц **на каждый** выбранный регион;
   - с `republished_last = PARSING_REPUBLISHED_LAST`.
3. Для объявлений без сохранённых телефонов открывается Chrome, ищутся номера.
4. Новые номера резервируются в БД и уходят в TurboSMS (с учётом `SMS_RESEND_AFTER_DAYS`).
5. Каждые `CHROME_RESTART_EVERY_ADS` реально открытых объявлений Chrome полностью перезапускается.

> Auto.RIA **не умеет** OR по нескольким `state` в одном запросе. Поэтому при выборе, например, Киев + Житомир + Сумы бот делает отдельные запросы и мержит уникальные объявления.

---

## Структура проекта

```text
AutoriaBot/
├── run.py / start.py          # точка входа бота
├── config.py                  # чтение .env → константы
├── test.py                    # Auto.RIA API (search_extended*)
├── spam_sms.py                # TurboSMS
├── parsing.py                 # вспомогательный/legacy URL
├── app/
│   ├── functions.py           # основной цикл парсинга + Selenium
│   ├── user/usermenu.py       # меню и фильтры
│   ├── keyboards.py
│   ├── state.py
│   └── database/              # модели + CRUD
├── scripts/remote.py          # SSH: deploy / restart / logs / status
├── docker-compose.yml
├── Dockerfile
├── env.example                # шаблон переменных
├── install.sh                 # установка на сервере
└── doc/                       # расширенная документация
```

---

## Требования

### Сервер (рекомендуется)

| Ресурс | Минимум | Рекомендация |
|--------|---------|--------------|
| RAM | 2 GB | 4 GB |
| Swap | — | **4 GB** (обязательно при 2–4 GB RAM) |
| CPU | 1 vCPU | 2 vCPU |
| Disk | 10 GB | 20 GB+ |
| OS | Debian/Ubuntu 22.04+ | Docker + docker-compose |

Без swap Chrome часто получает OOM / `tab crashed`.

### Локально / в Docker

- Docker + docker-compose
- Файл `.env` с `BOT_TOKEN` и `TOKEN_PHONE`

---

## Быстрый старт (Docker)

```bash
# 1. Клонировать / скопировать проект
cd /path/to/AutoriaBot

# 2. Конфиг
cp env.example .env
nano .env   # BOT_TOKEN, TOKEN_PHONE, при необходимости остальные параметры

# 3. Каталоги
mkdir -p logs temp

# 4. Запуск
docker-compose up -d --build

# 5. Логи
docker-compose logs -f autoria-bot
# или
docker logs -f autoria_telegram_bot
```

Через Makefile:

```bash
make setup
make up
make logs
make status
```

Путь на текущем проде (пример):

```bash
cd /home/cdn_revany/public_html/autoria
docker-compose down
docker-compose up -d --build
docker-compose logs -f autoria-bot
```

---

## Переменные окружения

Шаблон: [`env.example`](env.example). Рабочий файл: `.env` (не коммитить).

### Обязательные

| Переменная | Описание |
|------------|----------|
| `BOT_TOKEN` | Токен от [@BotFather](https://t.me/BotFather) |
| `TOKEN_PHONE` | TurboSMS: `login:password` шлюза **или** готовая Base64-строка Basic Auth |

### Парсинг и объём

| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `PARSING_SEARCH_PAGES` | `5` | Страниц выдачи **на каждый регион** (~20 объявлений/стр.) |
| `PARSING_REPUBLISHED_LAST` | `7` | Свежесть Auto.RIA: `1` / `2` / `7`; `0` — без фильтра |
| `MAX_PARSING_LIMIT` | `100` | Мягкий лимит (исторический параметр) |
| `PARSING_PAUSE_BETWEEN_ADS_SEC_MIN/MAX` | `180`/`300` | Пауза между объявлениями (сек.) |
| `PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MIN/MAX` | `1200`/`2700` | Пауза между полными проходами |
| `SMS_PAUSE_AFTER_SEND_SEC_MIN/MAX` | `300`/`1200` | Пауза после успешной SMS; `0`/`0` — выключить |

### SMS и дедупликация

| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `SMS_RESEND_AFTER_DAYS` | `7` | `0` — никогда повторно; `N` — не чаще 1 раза в N дней |
| `SMS_MAX_RETRIES` | `3` | Повторы отправки |
| `SMS_RETRY_DELAY` | `5` | Пауза между попытками |
| `PARSING_START_SMS_TEST_ENABLED` | `false` | Тестовая SMS при каждом «Запуск» |
| `PARSING_START_SMS_TEST_NUMBER` | — | Номер для тестовой SMS |

### Стабильность Chrome / Selenium

| Переменная | По умолчанию | Описание |
|------------|--------------|----------|
| `CHROME_RESTART_EVERY_ADS` | `15` | Плановый рестарт Chrome каждые N открытых объявлений (`0` = выкл.) |
| `PARSING_PAGE_WAIT_SEC` | `10` | Ожидание после загрузки страницы |
| `PARSING_AFTER_CLICK_WAIT_SEC` | `8` | Ожидание после клика «телефон» |
| `PARSING_PHONE_FIND_WAIT_SEC` | `6` | WebDriverWait при поиске номеров |
| `PARSING_RECONNECT_WAIT_SEC` | `10` | Пауза после reconnect драйвера |
| `PARSING_SCROLL_WAIT_SEC` | `2` | Пауза после скролла |
| `PARSING_STAGE_PAUSE_SEC` | `1` | Пауза между этапами поиска телефона |
| `CHROME_HEADLESS` | `true` | Headless-режим |
| `CHROME_OPTIONS` | `--no-sandbox,...` | Доп. флаги Chrome |
| `SCHEDULE_TICK_SEC` | `30` | Как часто проверять авторасписание |

### Прочее

| Переменная | Описание |
|------------|----------|
| `DATABASE_URL` | Локально: `sqlite+aiosqlite:///database.db`. В Docker переопределяется на `/app/data/database.db` |
| `ADMIN_USER_ID` | Telegram ID админа |
| `TZ` | Часовой пояс, например `Europe/Kiev` |
| `LOG_LEVEL` / `LOG_FILE` | Логирование |

После изменения `.env` на сервере нужен **recreate**, не только restart:

```bash
cd /path/to/autoria
docker-compose up -d
# затем, если код копировали через docker cp — задеплоить код снова
```

---

## Управление и отчёты

В главном меню:
- **Запуск / Стоп** — старт и мягкая остановка парсинга
- **Статус** — режим, окно расписания, идёт ли парсинг, SMS за сегодня / лимит

В **Настройки → SMS**:
- **Тест SMS** — отправка на указанный номер
- **Дневной отчёт сейчас** — сводка за день по кнопке

Автоотчёт каждый день в `DAILY_REPORT_HOUR` (по умолчанию 20:00).  
`SMS_DAILY_LIMIT=0` — без лимита; `N>0` — не больше N успешных SMS в сутки.  
Ошибки SMS/лимит дублируются админу (`ADMIN_USER_ID`), если `ERROR_ALERTS_TO_ADMIN=true`.

## Авторабота по расписанию

В Telegram: **Настройки → Расписание работы**.

| Режим | Поведение |
|-------|-----------|
| **Вручную** | Только кнопка «Запуск» (как раньше) |
| **По расписанию** | В выбранные дни и часы бот **сам стартует**, по окончании окна — **сам останавливает** |

- Время — в пределах одного дня (`09:00`–`20:00`), без перехода через полночь
- Дни недели — toggle Пн…Вс (есть пресеты «Пн–Пт» и «Все дни»)
- Кнопка **«Запуск»** работает **всегда**, даже вне окна
- Часовой пояс: переменная `TZ` (по умолчанию `Europe/Kiev`)
- Интервал проверки: `SCHEDULE_TICK_SEC` (по умолчанию 30)

После перезапуска контейнера расписание восстанавливается из БД (поля в `filterUser`).

## Регионы и глубина поиска

### Регионы

В меню можно выбрать несколько областей. Бот:

1. нормализует список ID;
2. для каждого ID запрашивает до `PARSING_SEARCH_PAGES` страниц;
3. склеивает и убирает дубли по `id` объявления.

Пустой список / `0` → все регионы.

Пример нагрузки: 3 региона × 5 страниц ≈ до **15** API-запросов за итерацию и до ~300 карточек до дедупликации.

### `PARSING_REPUBLISHED_LAST`

| Значение | Смысл |
|----------|--------|
| `1` | Только самые свежие |
| `2` | Узкая свежесть (как раньше было захардкожено) |
| `7` | Неделя (баланс объём/актуальность) |
| `0` | Без фильтра свежести — максимум объёма |

---

## Стабильность на сервере

### Swap 4 GB (рекомендуется)

```bash
fallocate -l 4G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=4096
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
sysctl vm.swappiness=10
echo 'vm.swappiness=10' >> /etc/sysctl.conf
free -h
swapon --show
```

### Что уже сделано в коде

- `_safe_quit_driver` + принудительный kill Chrome/ChromeDriver
- сбор zombie (`waitpid`)
- немедленный рестарт драйвера при `tab crashed` / dead session
- плановый рестарт каждые `CHROME_RESTART_EVERY_ADS`
- укороченные waits вместо «спать по 30 секунд»

### Типичные симптомы

| Симптом | Что проверить |
|---------|----------------|
| `tab crashed` / `session deleted` | RAM/swap, `CHROME_RESTART_EVERY_ADS`, логи контейнера |
| Мало SMS в день | `PARSING_SEARCH_PAGES`, `PARSING_REPUBLISHED_LAST`, несколько регионов, `SMS_RESEND_AFTER_DAYS`, паузы |
| Нет телефонов | селекторы на сайте, anticheck Auto.RIA, логи `DIAG` |
| Контейнер healthy, бот молчит | `docker logs`, polling Telegram, `BOT_TOKEN` |

Подробнее: [doc/LINUX_SERVER_STABILITY_FIX.md](doc/LINUX_SERVER_STABILITY_FIX.md), [doc/WHY_CHROMEDRIVER_CRASHES.md](doc/WHY_CHROMEDRIVER_CRASHES.md).

---

## Удалённое управление (`scripts/remote.py`)

Управление продом по SSH (Paramiko). Секреты — локально в `.cursor/server.secrets` (в `.gitignore`) или в env:

```env
AUTORIA_SSH_HOST=...
AUTORIA_SSH_USER=root
AUTORIA_SSH_PASS=...
AUTORIA_REMOTE_ROOT=/home/cdn_revany/public_html/autoria
AUTORIA_DOCKER_CONTAINER=autoria_telegram_bot
```

Команды (из корня репозитория, Windows: `py -3`):

```bash
py -3 scripts/remote.py status
py -3 scripts/remote.py logs 100
py -3 scripts/remote.py restart
py -3 scripts/remote.py cmd "free -h; swapon --show"

# Быстрый деплой файлов в контейнер (docker cp + restart)
py -3 scripts/remote.py deploy app/functions.py config.py test.py --no-rebuild

# Деплой .env (нужно имя именно ".env")
py -3 scripts/remote.py deploy .env --no-rebuild
# затем recreate, чтобы env_file подхватился:
py -3 scripts/remote.py cmd "cd /home/cdn_revany/public_html/autoria && docker-compose up -d"
# и снова docker cp кода, т.к. recreate поднимает образ
py -3 scripts/remote.py deploy app/functions.py config.py test.py --no-rebuild
```

Без `--no-rebuild` скрипт делает `docker-compose up -d --build` (дольше, но собирает образ с файлов хоста).

---

## База данных

В Docker:

- `DATABASE_URL=sqlite+aiosqlite:////app/data/database.db`
- том: `autoria_database` → `/app/data`
- логи: bind `./logs` → `/app/logs`

Основные сущности: пользователи, фильтры, `autoinfo` (объявления/телефоны), `sent_phones`, логи SMS/счётчики.

Миграции: Alembic (`alembic/`). На проде таблицы также создаются при старте через SQLAlchemy.

---

## TurboSMS

`TOKEN_PHONE` принимают в двух видах:

1. Открыто: `login:gateway_password` (будет закодировано в Basic Auth)
2. Уже Base64 из кабинета TurboSMS

Баланс, статус и ошибки — в логах и документах SMS:

- [doc/SMS_MONITORING_GUIDE.md](doc/SMS_MONITORING_GUIDE.md)
- [doc/DUPLICATE_SMS_PREVENTION.md](doc/DUPLICATE_SMS_PREVENTION.md)

---

## Команды Docker / Make

| Команда | Действие |
|---------|----------|
| `make setup` | `logs/`, `temp/`, `.env` из шаблона |
| `make build` / `make rebuild` | Сборка образа |
| `make up` | Запуск в фоне |
| `make down` | Остановка |
| `make restart` | Restart контейнера |
| `make logs` | Логи |
| `make status` / `make health` | Статус / healthcheck |
| `make shell` | Shell внутри контейнера |
| `make clean` | Удалить контейнеры/образы |
| `make clean-all` | То же + volumes (**сотрёт БД в томе**) |

Эквивалент без Make:

```bash
docker-compose up -d --build
docker-compose restart
docker-compose logs -f autoria-bot
docker-compose down
docker ps --filter name=autoria
```

---

## Запуск без Docker (dev)

Не рекомендуется для прода (Chrome/системные зависимости). Для отладки:

```bash
python -m venv .venv
# Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp env.example .env
# заполнить BOT_TOKEN, TOKEN_PHONE
python run.py
```

Нужны системный Chrome/Chromium и совместимый ChromeDriver. См. [doc/INSTALL_CHROME_UBUNTU.md](doc/INSTALL_CHROME_UBUNTU.md).

---

## Документация в `doc/`

| Документ | Назначение |
|----------|------------|
| [doc/README.md](doc/README.md) | Оглавление |
| [doc/QUICK_START.md](doc/QUICK_START.md) | Быстрый старт |
| [doc/DOCKER_README.md](doc/DOCKER_README.md) | Docker, тома, типовые сбои |
| [doc/UBUNTU_SERVER_SETUP.md](doc/UBUNTU_SERVER_SETUP.md) | Настройка Ubuntu |
| [doc/INSTALL_CHROME_UBUNTU.md](doc/INSTALL_CHROME_UBUNTU.md) | Chrome / ChromeDriver / swap |
| [doc/LINUX_SERVER_STABILITY_FIX.md](doc/LINUX_SERVER_STABILITY_FIX.md) | Стабильность на Linux |
| [doc/WHY_CHROMEDRIVER_CRASHES.md](doc/WHY_CHROMEDRIVER_CRASHES.md) | Почему падает Chrome |
| [doc/PARSING_IMPROVEMENTS.md](doc/PARSING_IMPROVEMENTS.md) | Улучшения парсинга |
| [doc/REGION_FILTER_FIX.md](doc/REGION_FILTER_FIX.md) | История фильтра регионов |
| [doc/PHONE_EXTRACTION_FIX.md](doc/PHONE_EXTRACTION_FIX.md) | Извлечение телефонов |
| [doc/SMS_MONITORING_GUIDE.md](doc/SMS_MONITORING_GUIDE.md) | Мониторинг SMS |
| [doc/DUPLICATE_SMS_PREVENTION.md](doc/DUPLICATE_SMS_PREVENTION.md) | Антидубли SMS |
| [doc/WSL_DOCKER_SETUP_GUIDE.md](doc/WSL_DOCKER_SETUP_GUIDE.md) | WSL + Docker |

---

## Рекомендуемые настройки «под объём»

Для большего числа SMS в день (при стабильном сервере):

```env
PARSING_SEARCH_PAGES=5
PARSING_REPUBLISHED_LAST=7
SMS_RESEND_AFTER_DAYS=0
CHROME_RESTART_EVERY_ADS=15
PARSING_PAUSE_BETWEEN_ADS_SEC_MIN=60
PARSING_PAUSE_BETWEEN_ADS_SEC_MAX=120
PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MIN=900
PARSING_PAUSE_BETWEEN_ITERATIONS_SEC_MAX=1800
```

В Telegram выберите **несколько нужных регионов**, а не один.

Для более «щадящего» режима увеличьте паузы и поставьте `PARSING_REPUBLISHED_LAST=2`, `SMS_RESEND_AFTER_DAYS=7`.

---

## Безопасность

- Не коммитьте `.env`, пароли SSH, токены.
- Локальные SSH-секреты: `.cursor/server.secrets` (уже в `.gitignore`).
- `ADMIN_USER_ID` ограничивает админские уведомления.
- На сервере держите Docker и ОС обновлёнными; ограничивайте доступ по SSH.

---

## Лицензия / назначение

Внутренний служебный проект. Используйте в соответствии с правилами Auto.RIA, TurboSMS и законодательством о рассылках.
