This commit is contained in:
2026-04-22 18:34:13 +03:00
parent 3992121397
commit ce7a1f70b2
60 changed files with 10243 additions and 3443 deletions

848
README.md
View File

@@ -1,115 +1,785 @@
# OreolRP Subscription Bot
# OREOL VPN Bot
Telegram-бот для продажи подписок на **aiogram 3.x** с поддержкой нескольких языков.
Telegram-бот на `aiogram 3` с интеграцией в `Remnawave API`, локальным кэшем в `MariaDB`, пользовательской панелью, тикетами поддержки, реферальной системой и ручной оплатой с подтверждением админом.
Этот README написан как рабочий handoff-документ: по нему можно поднять проект, понять текущую архитектуру и продолжить разработку в новой сессии без восстановления контекста по кускам.
## Что умеет бот
- Показывает стартовую панель пользователя через `/start`
- Подтягивает аккаунты Remnawave по `telegramId`
- Хранит локальный кэш пользователей и истории подписок в MariaDB
- Поддерживает привязку существующего аккаунта Remnawave по `short_uuid`
- Показывает профиль, статус подписки, срок и трафик
- Работает с реферальными кодами и скидкой перед оплатой
- Поддерживает ручную оплату переводом с отправкой чека в бот
- Отправляет чек в отдельный Telegram-канал/топик на проверку
- Даёт админу или модератору подтвердить или отклонить оплату
- После подтверждения создаёт или продлевает доступ в Remnawave
- Поддерживает тикеты в отдельный канал и ответы только от админа/модератора
- Показывает админам отдельную Telegram-панель с метриками и быстрыми действиями
## Текущий сценарий оплаты
Сейчас в проекте не используется Telegram Stars. Оплата работает вручную:
1. Пользователь нажимает `Купить подписку`
2. Бот показывает список тарифов из `PAYMENT_PLANS`
3. Если у пользователя применён реферальный код, скидка учитывается до выбора тарифа
4. После выбора тарифа бот показывает реквизиты из `PAYMENT_TRANSFER_TEXT`
5. Пользователь отправляет чек следующим сообщением в бота
6. Бот пересылает чек в review-чат оплаты
7. Админ или модератор нажимает `Подтвердить` или `Отклонить`
8. При подтверждении бот создаёт или продлевает доступ в Remnawave и отправляет пользователю подписку
### Тарифы
Тарифы задаются одной переменной:
```env
PAYMENT_PLANS=30:250,180:600,365:1000
```
Формат:
- `дни:цена_в_рублях`
- элементы разделяются запятой
Пример выше означает:
- 30 дней = 250 ₽
- 180 дней = 600 ₽
- 365 дней = 1000 ₽
### Логин в Remnawave
Логин создаётся в формате:
```text
Oreol-<telegram_id>-<username>
```
Пример:
```text
Oreol-591220249-slkes
```
Если username отсутствует, используется `first_name`, а если и его нет, то `user`.
Важно:
- логин обрезается до 36 символов, чтобы соответствовать ограничениям Remnawave
- повторная покупка у того же пользователя не должна создавать новый логин, а должна продлевать уже существующий доступ
## Важное замечание по базе
Если таблица `payment_orders` уже была создана старой версией проекта, в ней мог остаться `UNIQUE` на `provision_username`.
Для текущей логики это неверно, потому что один и тот же логин может использоваться в нескольких заказах одного пользователя при продлении.
Правильное состояние:
- в ORM `provision_username` обычный индекс без `UNIQUE`
- в `sql/schema.sql` тоже без `UNIQUE`
Если таблица уже существует в MariaDB, может потребоваться вручную убрать старое уникальное ограничение.
## Архитектура
### Основные слои
- `app/main.py`
- инициализация приложения
- подключение к БД
- создание клиента Remnawave
- создание `SyncService`, `SupportTicketService`, `PaymentService`
- запуск aiogram
- `app/config.py`
- все настройки из `.env`
- парсинг ID админов и модераторов
- парсинг ссылок Telegram вида `https://t.me/c/.../.../...`
- парсинг тарифов оплаты
- `app/services/remnawave_client.py`
- прямой HTTP-клиент к Remnawave API через `httpx`
- `GET /users/by-telegram-id/{telegramId}`
- `GET /users/{uuid}`
- `GET /users/by-short-uuid/{shortUuid}`
- `GET /users/by-username/{username}`
- `POST /users`
- `PATCH /users`
- `POST /users/resolve`
- `GET /users`
- `GET /users/{uuid}/subscription-request-history`
- `app/services/sync_service.py`
- регистрация Telegram-пользователей
- синхронизация пользователей из Remnawave в MariaDB
- привязка аккаунтов по `short_uuid`
- реферальные коды и приглашения
- `app/services/payment_service.py`
- список доступных тарифов
- создание ручного заказа
- расчёт скидки по реферальному коду
- хранение статусов заказа
- создание нового доступа в Remnawave
- продление существующего доступа в Remnawave
- `app/services/support_ticket_service.py`
- создание тикетов
- поиск тикета по support message ID
- отметка, что тикет обработан
- `app/bot/handlers/main.py`
- все команды и callback-и
- стартовая панель
- меню оплаты
- FSM для тикетов, рефкодов и чека оплаты
- приём ответа модератора на тикет
- приём кнопок `Подтвердить / Отклонить` в review-канале оплаты
- `app/bot/ui/panel.py`
- рендер панели пользователя
- инлайн-кнопки разделов
- тексты профиля, подписки, рефералки, правил и поддержки
- `app/utils/formatters.py`
- форматирование дат
- форматирование трафика
- форматирование карточек доступа
## Структура проекта
```
botyobshik/
├── main.py # Точка входа
├── requirements.txt # Зависимости
├── .env # Конфигурация (не в git)
├── .env.example # Пример конфигурации
├── README.md # Документация
├── config/
── __init__.py # Загрузка настроек из .env
├── core/
└── __init__.py # Работа с БД (авто-создание)
├── handlers/
── __init__.py # Обработчики команд
├── keyboards/
── __init__.py # Клавиатуры
└── locales/
├── __init__.py # Локализация
├── ru.json # Русский
├── en.json # English
── kz.json # Қазақша
```text
telegabot/
├── app/
│ ├── bot/
├── handlers/
└── main.py
└── ui/
│ │ └── panel.py
── db/
│ │ ├── base.py
│ ├── models.py
│ │ └── session.py
── schemas/
│ │ └── remnawave.py
── services/
│ │ ├── payment_service.py
│ │ ├── remnawave_client.py
│ │ ├── support_ticket_service.py
│ └── sync_service.py
── utils/
│ │ └── formatters.py
│ ├── config.py
│ └── main.py
├── assets/
│ └── main.png
├── sql/
│ └── schema.sql
├── tests/
│ ├── test_config.py
│ ├── test_formatters.py
│ ├── test_panel_ui.py
│ └── test_remnawave_client.py
├── .env.example
├── docker-compose.yml
├── Dockerfile
├── main.py
├── pyproject.toml
├── README.md
└── run.bat
```
## Быстрый старт
## Команды бота
### Пользовательские
- `/start` — открыть стартовую панель
### Админские
- `Админ-панель` в UI — сводка по боту, поиск пользователя, полная синхронизация, быстрые переходы в рабочие чаты
- `/lookup <uuid|id|username|short_uuid>` — найти и синхронизировать пользователя
- `/sync_all` — массовая синхронизация пользователей Remnawave в MariaDB
## База данных
### Основные таблицы
- `telegram_users`
- Telegram-пользователи, взаимодействовавшие с ботом
- `remnawave_users`
- локальный кэш пользователей Remnawave
- `internal_squads`
- справочник внутренних групп
- `remnawave_user_internal_squads`
- связь many-to-many пользователей и групп
- `subscription_request_logs`
- история запросов подписки
- `support_tickets`
- тикеты поддержки
- `referral_codes`
- персональные коды пользователей
- `referral_invites`
- кто кого пригласил
- `referral_bonuses`
- начисленные и ожидающие применения бонусы реферерам
- `payment_orders`
- ручные платежи, статусы и выданные доступы
### Статусы заказов оплаты
Сейчас используются:
- `PENDING` — заказ создан, чек ещё не отправлен
- `REVIEW` — чек отправлен на проверку
- `REJECTED` — платёж отклонён модератором
- `FULFILLED` — доступ выдан или продлён
## Реферальная система
Что реализовано:
- каждому пользователю создаётся персональный код
- можно сгенерировать ссылку вида `https://t.me/<bot_username>?start=ref_<CODE>`
- сам переход по ссылке скидку не активирует
- чтобы получить скидку, пользователь должен вручную ввести код до оплаты
- размер скидки задаётся в `REFERRAL_DISCOUNT_PERCENT`
- за каждую подтверждённую оплату по рефкоду реферер получает бонус в днях
- размер бонуса задаётся в `REFERRAL_BONUS_DAYS`
## Тикеты поддержки
Сценарий:
1. Пользователь открывает раздел `Поддержка`
2. Нажимает `Создать тикет`
3. Отправляет следующим сообщением вопрос или описание проблемы
4. Бот отправляет тикет в support-чат
5. Админ или модератор отвечает реплаем на сообщение с тикетом
6. Бот доставляет ответ пользователю в личный чат
Доступ к ответу на тикет есть только у:
- пользователей из `BOT_ADMIN_IDS`
- пользователей из `BOT_MODERATOR_IDS`
## Переменные окружения
Полный шаблон лежит в `.env.example`.
Критический минимум для запуска:
- `BOT_TOKEN`
- `REMNAWAVE_BASE_URL`
- `REMNAWAVE_API_TOKEN`
- `DB_HOST`
- `DB_PORT`
- `DB_NAME`
- `DB_USER`
- `DB_PASSWORD`
- `PAYMENT_INTERNAL_SQUAD_UUIDS`
Для полного сценария поддержки и оплаты также нужны:
- `BOT_PUBLIC_USERNAME`
- `BOT_SUPPORT_TICKET_LINK` или `BOT_SUPPORT_TICKET_CHAT_ID`
- `PAYMENT_TRANSFER_TEXT`
- `PAYMENT_REVIEW_LINK` или `PAYMENT_REVIEW_CHAT_ID`
## Как настроить `.env`
1. Скопировать шаблон:
```bat
copy .env.example .env
```
2. Заполнить обязательные значения
3. Проверить:
- корректен ли `BOT_TOKEN`
- доступен ли `REMNAWAVE_BASE_URL`
- валиден ли `REMNAWAVE_API_TOKEN`
- существует ли MariaDB и есть ли права на создание БД, если `CREATE_DATABASE_ON_START=true`
- указаны ли реальные UUID групп в `PAYMENT_INTERNAL_SQUAD_UUIDS`
- указаны ли реальные реквизиты в `PAYMENT_TRANSFER_TEXT`
## Пример важных настроек
```env
BOT_BRAND_NAME=OREOL VPN
BOT_PUBLIC_USERNAME=oreol_vpn_bot
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=oreolvpn
DB_USER=oreolvpn
DB_PASSWORD=strong_password
PAYMENT_PLANS=30:250,180:600,365:1000
PAYMENT_TRANSFER_TEXT=Карта 0000 0000 0000 0000; банк OREOL; получатель OREOL VPN
PAYMENT_REVIEW_LINK=https://t.me/c/3646494169/56/57
PAYMENT_INTERNAL_SQUAD_UUIDS=11111111-1111-1111-1111-111111111111
REFERRAL_DISCOUNT_PERCENT=5
PAYMENT_USERNAME_PREFIX=Oreol
```
## Поддержка Telegram-ссылок `t.me/c/...`
Проект умеет разбирать приватные ссылки Telegram вида:
```text
https://t.me/c/3646494169/56/57
```
Разбор происходит так:
- chat id = `-1003646494169`
- thread id = `56`
- последний сегмент `57` — это message id, для маршрутизации топика он не нужен
Это используется для:
- `BOT_SUPPORT_TICKET_LINK`
- `PAYMENT_REVIEW_LINK`
## Как запускать
### Вариант 1. Windows через `run.bat`
```bat
run.bat
```
Что делает батник:
- переходит в папку проекта
- проверяет `.env`
- создаёт `.venv`, если его нет
- ставит зависимости
- запускает `main.py`
### Вариант 2. Ручной локальный запуск
```bat
py -3 -m venv .venv
.venv\Scripts\python -m pip install -e .[dev]
.venv\Scripts\python main.py
```
### Вариант 3. Docker
1. **Установите зависимости:**
```bash
pip install -r requirements.txt
docker compose up --build
```
2. **Создайте `.env` файл:**
Это полный запуск вместе с MariaDB. База данных уже есть в проекте: сервис `db` описан в `docker-compose.yml`, данные сохраняются в volume `mariadb_data`.
#### Что нужно установить заранее
- Docker Desktop на Windows/macOS
- или Docker Engine + Docker Compose plugin на Linux
Проверьте, что команды доступны:
```bash
docker --version
docker compose version
```
#### Ubuntu Server 24.04: установка Docker Engine
Для `Ubuntu Server 24.04` рекомендуется ставить Docker Engine из официального Docker-репозитория.
1. Обновить пакеты и поставить базовые утилиты:
```bash
sudo apt update
sudo apt install -y ca-certificates curl git
```
2. Добавить официальный GPG-ключ Docker:
```bash
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
```
3. Подключить Docker repository:
```bash
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
```
4. Установить Docker Engine и Compose plugin:
```bash
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```
5. Включить Docker после перезагрузки:
```bash
sudo systemctl enable docker --now
```
6. Разрешить запуск Docker без `sudo`:
```bash
sudo usermod -aG docker $USER
```
После этого выйдите из SSH-сессии и зайдите снова, либо выполните:
```bash
newgrp docker
```
7. Проверить установку:
```bash
docker --version
docker compose version
docker ps
```
Если проект будет стоять на VPS или выделенном сервере, этого достаточно: отдельный Docker Desktop на Linux Server не нужен.
#### Что нужно подготовить перед первым запуском
1. Скопировать пример конфига:
```bash
cp .env.example .env
```
3. **Настройте `.env`:**
```ini
BOT_TOKEN=1234567890:AAH...
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=ваш_пароль
DB_NAME=botyobshik
SUPPORT_USERNAME=@support_username
На Windows PowerShell:
```powershell
Copy-Item .env.example .env
```
4. **Запустите бота:**
2. Открыть `.env` и заполнить обязательные значения:
- `BOT_TOKEN`
- `BOT_ADMIN_IDS`
- `REMNAWAVE_BASE_URL`
- `REMNAWAVE_API_TOKEN`
- `DB_NAME`
- `DB_USER`
- `DB_PASSWORD`
- `DB_ROOT_PASSWORD`
- `PAYMENT_INTERNAL_SQUAD_UUIDS`
Обычно ещё сразу настраивают:
- `BOT_PUBLIC_USERNAME`
- `BOT_SUPPORT_URL`
- `BOT_SUPPORT_TICKET_LINK` или `BOT_SUPPORT_TICKET_CHAT_ID`
- `PAYMENT_TRANSFER_TEXT`
- `PAYMENT_REVIEW_LINK` или `PAYMENT_REVIEW_CHAT_ID`
#### Важный момент по базе данных в Docker
При запуске через `docker compose` контейнер бота сам получает:
```env
DB_HOST=db
DB_PORT=3306
```
Это уже задано в `docker-compose.yml`. В `.env` для Docker главное задать:
- `DB_NAME`
- `DB_USER`
- `DB_PASSWORD`
- `DB_ROOT_PASSWORD`
Если вы запускаете проект только в Docker, `DB_HOST` и `DB_PORT` можно не менять вручную.
MariaDB в текущем `docker-compose.yml` не публикуется наружу на хост, чтобы база не торчала во внешний интернет по `3306`. Бот подключается к ней по внутренней Docker-сети через hostname `db`.
#### Первый запуск
Запустите сборку и контейнеры:
```bash
python main.py
docker compose up -d --build
```
🎉 База данных и таблицы создадутся **автоматически**!
Будут подняты два сервиса:
## Возможности
- `db` — MariaDB 11.7
- `bot` — приложение на Python
- ✅ Авто-создание БД и таблиц
- ✅ Команда `/start` с меню
- ✅ Проверка статуса подписки
- ✅ Покупка подписки (тарифы)
- ✅ Техподдержка
- ✅ Правила сервиса
- ✅ Смена языка (RU/EN/KZ)
- ✅ Асинхронная работа с БД
- ✅ Конфигурация через `.env`
Полный сценарий для Ubuntu Server обычно выглядит так:
## Редактирование
### Переменные окружения (`.env`)
| Переменная | Описание |
|------------|----------|
| `BOT_TOKEN` | Токен от @BotFather |
| `DB_HOST` | Хост MySQL |
| `DB_USER` | Пользователь MySQL |
| `DB_PASSWORD` | Пароль MySQL |
| `DB_NAME` | Имя базы данных |
| `SUPPORT_USERNAME` | Контакт поддержки |
### Тексты и кнопки
Все тексты редактируются в `locales/*.json`:
| Ключ | Описание |
|------|----------|
| `welcome` | Приветствие |
| `subscription_active/inactive` | Статусы подписки |
| `buttons.*` | Текст кнопок (buy, support, rules, language, back) |
| `messages.rules_title` | Заголовок правил |
| `messages.rules_text` | Текст правил (многострочный) |
| `messages.support` | Сообщение поддержки |
| `messages.select_tariff` | Выбор тарифа |
| `tariffs.*` | Названия тарифов |
| `languages.*` | Названия языков |
### Пример редактирования правил
Откройте `locales/ru.json` и измените:
```json
"messages": {
"rules_title": "📜 Правила сервиса",
"rules_text": "1. Первое правило\n2. Второе правило\n3. Третье правило"
}
```bash
git clone <URL_репозитория> telegabot
cd telegabot
cp .env.example .env
nano .env
docker compose up -d --build
```
Используйте `\n` для переноса строки.
#### Как проверить, что всё поднялось
## База данных
Посмотреть список контейнеров:
При запуске бот автоматически создаёт:
- Базу данных `botyobshik`
- Таблицу `subscriptions` (подписки)
- Таблицу `user_languages` (языки пользователей)
```bash
docker compose ps
```
Посмотреть логи бота:
```bash
docker compose logs -f bot
```
Посмотреть логи базы:
```bash
docker compose logs -f db
```
Проверить, что бот видит базу и база отвечает:
```bash
docker compose exec db mariadb -u"$DB_USER" -p"$DB_PASSWORD" -e "SHOW DATABASES;"
```
Если нужно зайти в MariaDB вручную:
```bash
docker compose exec db mariadb -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME"
```
Если всё настроено правильно, сценарий такой:
1. поднимается `db`
2. healthcheck MariaDB становится `healthy`
3. стартует `bot`
4. бот при старте создаёт БД и таблицы, если включены:
- `CREATE_DATABASE_ON_START=true`
- `CREATE_TABLES_ON_START=true`
#### Остановка и повторный запуск
Остановить контейнеры:
```bash
docker compose down
```
Запустить снова без пересборки:
```bash
docker compose up -d
```
Пересобрать после изменения кода:
```bash
docker compose up -d --build
```
#### Как удалить всё вместе с базой
Если нужен полный сброс, включая MariaDB volume:
```bash
docker compose down -v
```
После этого база будет создана заново при следующем запуске.
#### Обновление на Ubuntu Server
Если вы обновили код проекта:
```bash
git pull
docker compose up -d --build
```
Если меняли только `.env`, обычно достаточно:
```bash
docker compose up -d
```
#### Если бот не стартует
Проверьте по пунктам:
- заполнен ли `BOT_TOKEN`
- доступен ли `REMNAWAVE_BASE_URL`
- корректен ли `REMNAWAVE_API_TOKEN`
- заполнен ли `PAYMENT_INTERNAL_SQUAD_UUIDS`
- не пустые ли `DB_PASSWORD` и `DB_ROOT_PASSWORD`
- есть ли у сервиса `db` статус `healthy` в `docker compose ps`
Если хотите открыть MariaDB наружу для внешнего клиента, это нужно делать осознанно: добавьте `ports` обратно в сервис `db` и отдельно ограничьте доступ через firewall или private network.
#### Минимальный сценарий запуска в Docker
1. Скопировать `.env.example` в `.env`
2. Заполнить Telegram, Remnawave и DB-переменные
3. Выполнить `docker compose up -d --build`
4. Проверить `docker compose logs -f bot`
## Проверка проекта
### Синтаксис
```bat
py -3 -m compileall app main.py tests
```
### Тесты
```bat
.venv\Scripts\python -m pytest
```
Если основная `.venv` невалидна из-за переезда проекта между машинами, пересоздай её.
## Что уже протестировано
Покрыто тестами:
- парсинг конфигурации
- разбор support/review ссылок Telegram
- парсинг тарифов
- форматтеры текста
- UI панели
- базовые методы `RemnawaveApiClient`
Последняя локальная проверка в этой сетевой копии:
- `py -3 -m compileall app main.py tests` — успешно
- `pytest``26 passed`
## Известные нюансы
### 1. Перенос `.venv` между машинами
Если скопировать проект вместе с уже готовой `.venv` в другую папку, на другой компьютер или на сетевой диск, окружение может ссылаться на старый путь Python.
Типичная ошибка:
```text
did not find executable at ...python.exe
```
Решение:
1. удалить `.venv`
2. создать её заново
3. снова установить зависимости
### 2. Ошибка `Unknown database`
Если MariaDB доступна, но самой базы из `DB_NAME` ещё нет, можно увидеть:
```text
OperationalError: (1049, "Unknown database '...'" )
```
Что делать:
- оставить `CREATE_DATABASE_ON_START=true`, если у пользователя есть права на создание БД
- либо создать БД вручную
### 3. Прокси Telegram
Для `TELEGRAM_PROXY_URL` подходят обычные proxy URL:
- `http://user:pass@host:port`
- `socks5://host:port`
Не подходят клиентские ссылки вроде:
- `vless://...`
- `vmess://...`
- `trojan://...`
- `tg://proxy?...` для aiogram напрямую
### 4. Review-чат оплаты должен быть настроен
Если `PAYMENT_REVIEW_LINK` и `PAYMENT_REVIEW_CHAT_ID` пустые, пользователь сможет выбрать тариф, но бот не сможет отправить чек на модерацию.
### 5. Internal squads обязательны для выдачи доступа
Если `PAYMENT_INTERNAL_SQUAD_UUIDS` пустой, бот не сможет корректно создать доступ в Remnawave.
## Безопасность
Нельзя публиковать:
- реальный `.env`
- `BOT_TOKEN`
- `REMNAWAVE_API_TOKEN`
- `REMNAWAVE_CADDY_API_KEY`
- банковские реквизиты из рабочего `PAYMENT_TRANSFER_TEXT`
Передавать между сессиями безопасно:
- код проекта
- `README.md`
- `.env.example`
## Что логично делать дальше
Следующие разумные шаги по проекту:
- добавить миграции через Alembic
- хранить отдельный audit log по модерации оплат
- сделать уведомление модераторам о новых чеках
- добавить историю заказов пользователя в панели
- сделать админ-панель со списком платежей
- добавить продление конкретного существующего аккаунта по выбору, если у пользователя их несколько
- добавить отдельный статус `APPROVED_BUT_NOT_DELIVERED`, если бот не смог отправить результат пользователю
- покрыть тестами `PaymentService` и FSM оплаты
## Быстрый handoff
Если проект открывает новая сессия, порядок такой:
1. Прочитать этот README
2. Проверить `.env`
3. Проверить, рабочая ли `.venv`
4. Убедиться, что доступны MariaDB и Remnawave
5. Запустить `run.bat` или `python main.py`
6. Проверить руками:
- `/start`
- открытие раздела подписки через кнопку в панели
- ввод реферального кода
- отправку чека
- подтверждение оплаты модератором
- выдачу ссылки пользователю
- поддержку через тикет