OREOL VPN Bot
Telegram-бот на aiogram 3 с интеграцией в Remnawave API, локальным кэшем в MariaDB, пользовательской панелью, тикетами поддержки, реферальной системой и ручной оплатой с подтверждением админом.
Этот README написан как рабочий handoff-документ: по нему можно поднять проект, понять текущую архитектуру и продолжить разработку в новой сессии без восстановления контекста по кускам.
Что умеет бот
- Показывает стартовую панель пользователя через
/start - Подтягивает аккаунты Remnawave по
telegramId - Хранит локальный кэш пользователей и истории подписок в MariaDB
- Поддерживает привязку существующего аккаунта Remnawave по
short_uuid - Показывает профиль, статус подписки, срок и трафик
- Работает с реферальными кодами и скидкой перед оплатой
- Поддерживает ручную оплату переводом с отправкой чека в бот
- Отправляет чек в отдельный Telegram-канал/топик на проверку
- Даёт админу или модератору подтвердить или отклонить оплату
- После подтверждения создаёт или продлевает доступ в Remnawave
- Поддерживает тикеты в отдельный канал и ответы только от админа/модератора
- Показывает админам отдельную Telegram-панель с метриками и быстрыми действиями
Текущий сценарий оплаты
Сейчас в проекте не используется Telegram Stars. Оплата работает вручную:
- Пользователь нажимает
Купить подписку - Бот показывает список тарифов из
PAYMENT_PLANS - Если у пользователя применён реферальный код, скидка учитывается до выбора тарифа
- После выбора тарифа бот показывает реквизиты из
PAYMENT_TRANSFER_TEXT - Пользователь отправляет чек следующим сообщением в бота
- Бот пересылает чек в review-чат оплаты
- Админ или модератор нажимает
ПодтвердитьилиОтклонить - При подтверждении бот создаёт или продлевает доступ в Remnawave и отправляет пользователю подписку
Тарифы
Тарифы задаются одной переменной:
PAYMENT_PLANS=30:250,180:600,365:1000
Формат:
дни:цена_в_рублях- элементы разделяются запятой
Пример выше означает:
- 30 дней = 250 ₽
- 180 дней = 600 ₽
- 365 дней = 1000 ₽
Логин в Remnawave
Логин создаётся в формате:
Oreol-<telegram_id>-<username>
Пример:
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 /usersPATCH /usersPOST /users/resolveGET /usersGET /users/{uuid}/subscription-request-history
- прямой HTTP-клиент к Remnawave API через
-
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- форматирование дат
- форматирование трафика
- форматирование карточек доступа
Структура проекта
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
Тикеты поддержки
Сценарий:
- Пользователь открывает раздел
Поддержка - Нажимает
Создать тикет - Отправляет следующим сообщением вопрос или описание проблемы
- Бот отправляет тикет в support-чат
- Админ или модератор отвечает реплаем на сообщение с тикетом
- Бот доставляет ответ пользователю в личный чат
Доступ к ответу на тикет есть только у:
- пользователей из
BOT_ADMIN_IDS - пользователей из
BOT_MODERATOR_IDS
Переменные окружения
Полный шаблон лежит в .env.example.
Критический минимум для запуска:
BOT_TOKENREMNAWAVE_BASE_URLREMNAWAVE_API_TOKENDB_HOSTDB_PORTDB_NAMEDB_USERDB_PASSWORDPAYMENT_INTERNAL_SQUAD_UUIDS
Для полного сценария поддержки и оплаты также нужны:
BOT_PUBLIC_USERNAMEBOT_SUPPORT_TICKET_LINKилиBOT_SUPPORT_TICKET_CHAT_IDPAYMENT_TRANSFER_TEXTPAYMENT_REVIEW_LINKилиPAYMENT_REVIEW_CHAT_ID
Как настроить .env
- Скопировать шаблон:
copy .env.example .env
-
Заполнить обязательные значения
-
Проверить:
- корректен ли
BOT_TOKEN - доступен ли
REMNAWAVE_BASE_URL - валиден ли
REMNAWAVE_API_TOKEN - существует ли MariaDB и есть ли права на создание БД, если
CREATE_DATABASE_ON_START=true - указаны ли реальные UUID групп в
PAYMENT_INTERNAL_SQUAD_UUIDS - указаны ли реальные реквизиты в
PAYMENT_TRANSFER_TEXT
Пример важных настроек
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 вида:
https://t.me/c/3646494169/56/57
Разбор происходит так:
- chat id =
-1003646494169 - thread id =
56 - последний сегмент
57— это message id, для маршрутизации топика он не нужен
Это используется для:
BOT_SUPPORT_TICKET_LINKPAYMENT_REVIEW_LINK
Как запускать
Вариант 1. Windows через run.bat
run.bat
Что делает батник:
- переходит в папку проекта
- проверяет
.env - создаёт
.venv, если его нет - ставит зависимости
- запускает
main.py
Вариант 2. Ручной локальный запуск
py -3 -m venv .venv
.venv\Scripts\python -m pip install -e .[dev]
.venv\Scripts\python main.py
Вариант 3. Docker
docker compose up --build
Это полный запуск вместе с MariaDB. База данных уже есть в проекте: сервис db описан в docker-compose.yml, данные сохраняются в volume mariadb_data.
Что нужно установить заранее
- Docker Desktop на Windows/macOS
- или Docker Engine + Docker Compose plugin на Linux
Проверьте, что команды доступны:
docker --version
docker compose version
Ubuntu Server 24.04: установка Docker Engine
Для Ubuntu Server 24.04 рекомендуется ставить Docker Engine из официального Docker-репозитория.
- Обновить пакеты и поставить базовые утилиты:
sudo apt update
sudo apt install -y ca-certificates curl git
- Добавить официальный GPG-ключ Docker:
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
- Подключить Docker repository:
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
- Установить Docker Engine и Compose plugin:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- Включить Docker после перезагрузки:
sudo systemctl enable docker --now
- Разрешить запуск Docker без
sudo:
sudo usermod -aG docker $USER
После этого выйдите из SSH-сессии и зайдите снова, либо выполните:
newgrp docker
- Проверить установку:
docker --version
docker compose version
docker ps
Если проект будет стоять на VPS или выделенном сервере, этого достаточно: отдельный Docker Desktop на Linux Server не нужен.
Что нужно подготовить перед первым запуском
- Скопировать пример конфига:
cp .env.example .env
На Windows PowerShell:
Copy-Item .env.example .env
- Открыть
.envи заполнить обязательные значения:
BOT_TOKENBOT_ADMIN_IDSREMNAWAVE_BASE_URLREMNAWAVE_API_TOKENDB_NAMEDB_USERDB_PASSWORDDB_ROOT_PASSWORDPAYMENT_INTERNAL_SQUAD_UUIDS
Обычно ещё сразу настраивают:
BOT_PUBLIC_USERNAMEBOT_SUPPORT_URLBOT_SUPPORT_TICKET_LINKилиBOT_SUPPORT_TICKET_CHAT_IDPAYMENT_TRANSFER_TEXTPAYMENT_REVIEW_LINKилиPAYMENT_REVIEW_CHAT_ID
Важный момент по базе данных в Docker
При запуске через docker compose контейнер бота сам получает:
DB_HOST=db
DB_PORT=3306
Это уже задано в docker-compose.yml. В .env для Docker главное задать:
DB_NAMEDB_USERDB_PASSWORDDB_ROOT_PASSWORD
Если вы запускаете проект только в Docker, DB_HOST и DB_PORT можно не менять вручную.
DB_ROOT_PASSWORD нужен только контейнеру MariaDB. Сам бот подключается к базе исключительно через:
DB_USERDB_PASSWORD
Это важно:
DB_ROOT_PASSWORDне используется приложением для SQLAlchemy-подключения- если в
.envуказатьDB_USER=root, но оставитьDB_PASSWORDпустым, бот попытается зайти какrootбез пароля и упадёт сAccess denied
Для production рекомендуется не использовать root для бота, а создать отдельного пользователя БД через:
DB_USERDB_PASSWORD
MariaDB в текущем docker-compose.yml не публикуется наружу на хост, чтобы база не торчала во внешний интернет по 3306. Бот подключается к ней по внутренней Docker-сети через hostname db.
Первый запуск
Запустите сборку и контейнеры:
docker compose up -d --build
Будут подняты два сервиса:
db— MariaDB 11.7bot— приложение на Python
Полный сценарий для Ubuntu Server обычно выглядит так:
git clone <URL_репозитория> telegabot
cd telegabot
cp .env.example .env
nano .env
docker compose up -d --build
Как проверить, что всё поднялось
Посмотреть список контейнеров:
docker compose ps
Посмотреть логи бота:
docker compose logs -f bot
Посмотреть логи базы:
docker compose logs -f db
Проверить, что бот видит базу и база отвечает:
docker compose exec db mariadb -u"$DB_USER" -p"$DB_PASSWORD" -e "SHOW DATABASES;"
Если нужно зайти в MariaDB вручную:
docker compose exec db mariadb -u"$DB_USER" -p"$DB_PASSWORD" "$DB_NAME"
Если всё настроено правильно, сценарий такой:
- поднимается
db - healthcheck MariaDB становится
healthy - стартует
bot - бот при старте создаёт БД и таблицы, если включены:
CREATE_DATABASE_ON_START=trueCREATE_TABLES_ON_START=true
Остановка и повторный запуск
Остановить контейнеры:
docker compose down
Запустить снова без пересборки:
docker compose up -d
Пересобрать после изменения кода:
docker compose up -d --build
Как удалить всё вместе с базой
Если нужен полный сброс, включая MariaDB volume:
docker compose down -v
После этого база будет создана заново при следующем запуске.
Обновление на Ubuntu Server
Если вы обновили код проекта:
git pull
docker compose up -d --build
Если меняли только .env, обычно достаточно:
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
Если видите ошибку вида:
Access denied for user 'root' ... (using password: NO)
проверьте:
- не стоит ли в
.envDB_USER=root - не пустой ли
DB_PASSWORD - совпадают ли
DB_USERиDB_PASSWORDс пользователем MariaDB, который был создан при первом старте контейнера
Если контейнер db уже один раз поднимался с неправильными DB-переменными, volume MariaDB мог сохранить старого пользователя и старый пароль. Тогда есть два варианта:
- если данных ещё не жалко:
docker compose down -vи затемdocker compose up -d --build - если данные нужно сохранить: зайти в MariaDB и вручную создать/выдать права нужному пользователю
Если хотите открыть MariaDB наружу для внешнего клиента, это нужно делать осознанно: добавьте ports обратно в сервис db и отдельно ограничьте доступ через firewall или private network.
Минимальный сценарий запуска в Docker
- Скопировать
.env.exampleв.env - Заполнить Telegram, Remnawave и DB-переменные
- Выполнить
docker compose up -d --build - Проверить
docker compose logs -f bot
Проверка проекта
Синтаксис
py -3 -m compileall app main.py tests
Тесты
.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.
Типичная ошибка:
did not find executable at ...python.exe
Решение:
- удалить
.venv - создать её заново
- снова установить зависимости
2. Ошибка Unknown database
Если MariaDB доступна, но самой базы из DB_NAME ещё нет, можно увидеть:
OperationalError: (1049, "Unknown database '...'" )
Что делать:
- оставить
CREATE_DATABASE_ON_START=true, если у пользователя есть права на создание БД - либо создать БД вручную
3. Прокси Telegram
Для TELEGRAM_PROXY_URL подходят обычные proxy URL:
http://user:pass@host:portsocks5://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_TOKENREMNAWAVE_API_TOKENREMNAWAVE_CADDY_API_KEY- банковские реквизиты из рабочего
PAYMENT_TRANSFER_TEXT
Передавать между сессиями безопасно:
- код проекта
README.md.env.example
Что логично делать дальше
Следующие разумные шаги по проекту:
- добавить миграции через Alembic
- хранить отдельный audit log по модерации оплат
- сделать уведомление модераторам о новых чеках
- добавить историю заказов пользователя в панели
- сделать админ-панель со списком платежей
- добавить продление конкретного существующего аккаунта по выбору, если у пользователя их несколько
- добавить отдельный статус
APPROVED_BUT_NOT_DELIVERED, если бот не смог отправить результат пользователю - покрыть тестами
PaymentServiceи FSM оплаты
Быстрый handoff
Если проект открывает новая сессия, порядок такой:
- Прочитать этот README
- Проверить
.env - Проверить, рабочая ли
.venv - Убедиться, что доступны MariaDB и Remnawave
- Запустить
run.batилиpython main.py - Проверить руками:
/start- открытие раздела подписки через кнопку в панели
- ввод реферального кода
- отправку чека
- подтверждение оплаты модератором
- выдачу ссылки пользователю
- поддержку через тикет