Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

9 Commits

Folders and files

Repository files navigation

VPN-бот для Telegram

Бот продаёт подписки на VPN: пользователь выбирает тариф, платит (Telegram Stars, СБП переводом по номеру, карта через ЮKassa или криптовалюта через CryptoBot) и сразу получает ключ. Ключи создаются и продлеваются в вашей VPN-панели автоматически, просроченные доступы отключаются по расписанию.

Стек: Python 3.10+, aiogram 3, SQLAlchemy 2 (SQLite или PostgreSQL), APScheduler, aiohttp.


Что уже работает

Для пользователя

  • Главное меню: доступ, покупка, профиль, пробный период, инструкция, поддержка
  • Пробный период (по умолчанию 3 дня / 10 ГБ), один раз на аккаунт; опционально — только после подписки на канал
  • Оплата: Telegram Stars, СБП переводом по номеру, карта (ЮKassa), криптовалюта (CryptoBot)
  • Покупка и продление тарифа; продление добавляется к остатку, а не обнуляет его
  • Всё для подключения приходит само — сразу после оплаты, продления и выдачи триала: QR-код ссылки-подписки, сама ссылка, срок, остаток трафика и пошаговая инструкция одним сообщением; отдельные ключи vless://… — по кнопке
  • Продление (/renew): витрина показывает текущую дату окончания и во что она превратится, дни прибавляются к остатку
  • Напоминания за 3 дня и за сутки до окончания, уведомление об истечении

Для администратора (/admin, доступ по ADMIN_IDS)

  • Редактор тарифов (/plans): цена, название, срок, трафик, число устройств, порядок; создание, скрытие и удаление — без перезапуска
  • Редактор настроек (/settings): триал, лимит трафика по умолчанию, курс звёзд, ссылки на поддержку и канал, реквизиты СБП — меняются на лету и хранятся в БД
  • Статистика: пользователи, активные подписки, выручка за сутки/30 дней/всё время
  • Список всех пользователей (/users): постраничный, с фильтрами «все / с подпиской / без» и выгрузкой в CSV; клиент открывается нажатием
  • Карточка клиента (/user 123456789 или /user @username, поиск в /admin): подписка, трафик, платежи — и правка в пару нажатий: прибавить/убавить дни, точная дата окончания, лимит трафика, сброс трафика, приостановка доступа, бан, повторный триал, подтверждение висящих счетов СБП, сообщение клиенту. Всё пишется сначала в VPN-панель, потом в базу — правки не откатываются при сверке
  • Выдача дней вручную (/give 123456789 30), бан/разбан (/ban 123456789)
  • Заявки по СБП: /claims или кнопка «🏦 Заявки СБП» — список открытых счетов с подтверждением
  • Рассылка по всем пользователям
  • Проверка связи с VPN-панелью, возврат Telegram Stars (/refund user_id charge_id)

Под капотом

  • Подтверждение платежа идемпотентно: повторный вебхук, кнопка «Я оплатил» и фоновый опрос не выдадут подписку дважды
  • Вебхуки платёжек перед выдачей подписки перепроверяются запросом в API платёжной системы — подделанный колбэк ничего не активирует
  • Панель — источник правды: срок, трафик и включённость сверяются с ней раз в час и при каждом открытии «Мой доступ», поэтому ручные правки в панели бот видит
  • Если панель недоступна, оплата не теряется: платёж сохранён, пользователь получает понятное сообщение
  • Фоновые задачи: напоминания, отключение просрочки, синхронизация трафика, добивка неподтверждённых платежей

Быстрый старт

python -m venv .venv && .venv/Scripts/activate   # Linux/macOS: source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env

Заполните в .env минимум два параметра:

BOT_TOKEN=токен от @BotFather
ADMIN_IDS=ваш_telegram_id       # узнать можно командой /id в боте

Запуск:

python -m bot.main

Короткий вариант python -m bot делает то же самое.

С VPN_PROVIDER=mock бот работает без реального сервера и выдаёт демонстрационные ключи — удобно, чтобы прокликать все сценарии. Проверить логику, не открывая Telegram:

python scripts/smoke_test.py    # БД, оплата, продление, истечение
python scripts/dialog_test.py   # реальные апдейты Telegram через Dispatcher с подставным Bot
python scripts/renew_test.py    # продление, сброс трафика, СБП из «Мой доступ»
python scripts/admin_test.py    # редакторы тарифов и настроек
python scripts/safety_test.py   # гонки и защитные проверки

Скрипты работают на отдельных БД (data/smoke.db, data/dialog.db и т. д.) и ничего не отправляют наружу — их можно запускать на боевом сервере.


Подключение VPN-панели

Marzban

VPN_PROVIDER=marzban
MARZBAN_URL=https://panel.example.com
MARZBAN_USERNAME=admin
MARZBAN_PASSWORD=пароль
MARZBAN_PROXIES=vless           # можно vless,vmess,trojan
MARZBAN_INBOUNDS=               # пусто = все inbound'ы панели
MARZBAN_SUB_DOMAIN=             # если ссылка-подписка отдаётся с другого домена

Бот создаёт пользователей с именем tg<telegram_id> через /api/user, продлевает их при оплате и переводит в статус disabled при истечении срока.

3x-ui

VPN_PROVIDER=xui
XUI_URL=https://panel.example.com:2053/секретный_путь   # без /panel в конце
XUI_USERNAME=admin
XUI_PASSWORD=пароль
XUI_INBOUND_IDS=                                        # пусто = все inbound'ы панели
XUI_SUB_BASE=https://panel.example.com:2096/sub         # сервис подписок

Клиент заводится с email = tg<telegram_id> сразу во всех inbound'ах (или в тех, что перечислены в XUI_INBOUND_IDS) и получает единый subId. Благодаря этому ссылка-подписка отдаёт пользователю все протоколы разом — vless, hysteria2 и что там ещё настроено, а при добавлении нового сервера в панель ссылка у клиентов обновится сама.

При продлении идентификаторы не меняются: бот подхватывает уже выданные uuid, пароль и subId, так что ранее розданные ключи продолжают работать.

Клиенты заводятся через обновление inbound целиком (/panel/api/inbounds/update/{id}): бот читает inbound, подставляет запись клиента и записывает обратно. Этот маршрут есть и в классических сборках, и в свежих, где отдельных ручек addClient уже нет.

Свежие сборки панели защищены CSRF: бот забирает токен из <meta name="csrf-token"> страницы входа и шлёт его заголовком X-CSRF-Token. Без этого панель отвечает пустым 403.

Диагностика — покажет inbound'ы, их ID и проверит выдачу ключа:

python scripts/panel_check.py          # логин + список inbound'ов
python scripts/panel_check.py --full   # плюс создание и удаление тестового клиента

Проверить связь с панелью: /admin → «🩺 Проверить панель».

Панель как источник правды

Бот не живёт «своей» датой окончания. Перед выдачей и продлением он читает клиента из панели и отсчитывает срок от того, что там реально стоит; раз в час и при каждом открытии «🔑 Мой доступ» сверяет трафик, срок и включённость. Практические следствия:

  • продлили клиента руками в панели — бот покажет новую дату и продлит уже от неё;
  • выключили клиента в панели — пользователь увидит «Доступ приостановлен», а не «активна»;
  • удалили клиента из панели — подписка в боте помечается истёкшей.

Клиентские приложения

Рекомендуемый клиент — Happ: есть под iOS, Android, Windows, macOS, Linux и TV, понимает ссылку-подписку и поддерживает маршрутизацию (российские сайты напрямую, остальное через VPN).

Маршрутизацию раздаёт панель, а не бот: Happ принимает профиль правил через deep-link happ://routing/add/{base64} либо прямо из ответа подписки — заголовком routing: или строкой в теле. Настраивается это на стороне 3x-ui, боту делать ничего не нужно: он отдаёт ссылку-подписку, а всё остальное приезжает вместе с ней.


Платежи

Telegram Stars (PAY_STARS_ENABLED=true)

Работает сразу, без договоров и подключения платёжек: Telegram сам проводит оплату. Цена тарифа хранится в рублях и пересчитывается в звёзды по курсу STARS_RUB_RATE (сколько рублей в одной звезде — уточните актуальный курс вывода в @BotFather). Возврат звёзд: /refund <user_id> <charge_id> (charge_id хранится в поле external_id платежа).

СБП переводом по номеру (без эквайринга)

PAY_SBP_ENABLED=true
SBP_PHONE=+7 900 000-00-00
SBP_BANK=Т-Банк
SBP_RECEIVER=Иван И.
ADMIN_IDS=ваш_telegram_id

Самый простой способ, когда клиентов немного: бот показывает ваш номер для перевода по СБП, сумму и код платежа (VPN-<номер счёта>). Клиент переводит и жмёт «✅ Я перевёл» — вам в личку прилетает заявка с кнопками «Деньги пришли» / «Отклонить». По нажатию подписка выдаётся автоматически, клиент получает ключ; при отказе он видит код платежа и предложение написать в поддержку.

Заявку может подать только владелец счёта, а подтвердить — только ADMIN_IDS. Повторное подтверждение ничего не удваивает. Если заявка не дошла в личку (админ не нажимал /start в боте или в ADMIN_IDS неверный ID), счёт не теряется — он виден по команде /claims и подтверждается оттуда теми же кнопками.

Ограничения, о которых стоит знать: подтверждение ручное (нужно смотреть в банк), поступления на карту физлица регулярными платежами — зона вашей ответственности перед налоговой, а при росте до сотни клиентов это станет утомительным — тогда переходите на ЮKassa.

ЮKassa

PAY_YOOKASSA_ENABLED=true
YOOKASSA_SHOP_ID=...
YOOKASSA_SECRET_KEY=...
YOOKASSA_RETURN_URL=https://t.me/ваш_бот

Нужен подтверждённый магазин в ЮKassa (юрлицо, ИП или самозанятый). СБП там доступен как способ оплаты «из коробки»: включите его в личном кабинете ЮKassa — на странице оплаты появится QR-код, никаких правок в боте не потребуется. Бот создаёт платёж через API v3 и подтверждает его двумя путями: вебхук payment.succeeded и фоновый опрос каждые 3 минуты — то есть даже без вебхука подписка выдастся сама.

54-ФЗ: если вы обязаны выдавать чеки, добавьте объект receipt в тело запроса в bot/services/payments/yookassa.py:create_invoice (или включите чеки на стороне ЮKassa).

CryptoBot

PAY_CRYPTOBOT_ENABLED=true
CRYPTOBOT_TOKEN=...        # получить у @CryptoBot -> Crypto Pay -> Create App
CRYPTOBOT_ASSET=USDT
USD_RUB_RATE=95            # курс для пересчёта цены из рублей

Вебхуки (необязательно)

Без вебхуков оплаты подтверждаются опросом (до 3 минут задержки). Чтобы получать подтверждение мгновенно:

WEBHOOK_ENABLED=true
WEBHOOK_PORT=8080
WEBHOOK_BASE_URL=https://bot.example.com
WEBHOOK_SECRET=длинная_случайная_строка

Адреса, которые нужно указать в панелях платёжек (они печатаются в лог при старте):

  • ЮKassa: https://bot.example.com/pay/yookassa/<WEBHOOK_SECRET>
  • CryptoBot: https://bot.example.com/pay/cryptobot/<WEBHOOK_SECRET>

Поставьте перед ботом nginx с TLS — платёжки принимают только HTTPS.


Доступ к базе данных

По умолчанию это файл SQLite data/vpnbot.db. Подключаться можно, не останавливая бота (включён режим WAL — параллельные чтение и запись разрешены).

Встроенная консоль — сама находит файл по DATABASE_URL:

python scripts/db.py --tables                          # таблицы и число строк
python scripts/db.py "SELECT id, title, price_rub FROM plans"
python scripts/db.py                                   # интерактивный SQL

Полезные запросы:

-- кто и до когда подписан
SELECT u.id, u.username, s.expires_at, s.status FROM subscriptions s JOIN users u ON u.id = s.user_id;
-- открытые счета по СБП (их же показывает /claims)
SELECT id, user_id, amount, created_at FROM payments WHERE provider='sbp' AND status='pending';
-- выручка по месяцам
SELECT substr(paid_at,1,7) AS month, sum(amount) FROM payments WHERE status='paid' GROUP BY month;

Альтернативы: GUI DB Browser for SQLite (открыть data/vpnbot.db), любой клиент с поддержкой SQLite или консольный sqlite3.

Клиентов правьте в карточке (/user <id>), а не SQL-запросами: срок, трафик и включённость бот при каждой сверке берёт из VPN-панели, поэтому правка subscriptions «только в базе» откатится в течение часа. Карточка пишет сначала в панель, потом в базу.


Тарифы и настройки

Всё это правится прямо в боте: /admin → «💼 Тарифы» и «⚙️ Настройки». Изменения применяются сразу — пользователи видят новую цену со следующего нажатия, перезапуск не нужен.

Тарифы (/plans) — цена, название, срок, лимит трафика, число устройств, порядок в списке. Тариф можно скрыть кнопкой «Выключить», создать новый строкой вида Неделя | 7 | 59 или удалить. Тариф, по которому уже были платежи, удалить нельзя — иначе развалится статистика; такой скрывается.

Настройки (/settings) — пробный период и его длительность, трафик триала, лимит трафика по умолчанию, курс рублей к звезде, ссылки на поддержку и канал, реквизиты СБП. Хранятся в таблице settings и переживают перезапуск; значение из .env работает как значение по умолчанию, пока настройку не поменяли в боте.

Ссылки на поддержку и канал

Задаются в /settings («Ссылка на поддержку», «Ссылка на канал») или в .env (SUPPORT_URL, CHANNEL_URL). Значения-заглушки из шаблона (https://t.me/your_support и подобные) бот распознаёт и кнопку не показывает — лучше без кнопки, чем ведущая в никуда. Чтобы убрать уже заданную ссылку, отправьте в редакторе -.

Как считается лимит трафика

  • 0 в любом месте означает безлимит;
  • у каждого тарифа свой лимит (поле «📊 Трафик» в карточке) — он и уезжает в панель как totalGB;
  • DEFAULT_TRAFFIC_GB («Лимит трафика по умолчанию») применяется к новым тарифам, созданным через админку, и к ручной выдаче дней командой /give;
  • у пробного периода лимит отдельный — TRIAL_TRAFFIC_GB;
  • израсходованное подтягивается из панели раз в час и видно пользователю в «🔑 Мой доступ»;
  • при оплате (покупка или продление) израсходованный трафик обнуляется в панели — новый период начинается с полного лимита; ручная выдача дней /give расход не трогает.

В .env остаётся то, что менять на лету небезопасно: токен бота, ADMIN_IDS, доступы к VPN-панели, ключи платёжных систем, включение способов оплаты. После их правки — перезапуск бота.


Деплой на сервер

Подойдёт любой VPS с Ubuntu 22.04/24.04 или Debian 12 — боту хватает 1 vCPU и 512 МБ RAM. Его можно поставить на тот же сервер, где крутится 3x-ui: нагрузки он почти не создаёт. Открытые порты не нужны — бот работает на long polling и сам ходит в Telegram.

Шаг 1. Загрузить файлы на сервер

Через git (удобно для обновлений):

ssh root@ВАШ_СЕРВЕР
apt update && apt install -y git
git clone ВАШ_РЕПОЗИТОРИЙ /root/vpn && cd /root/vpn

Или напрямую с Windows — в PowerShell из папки проекта (.env копируется отдельно, чтобы секреты не попали в репозиторий):

scp -r bot scripts deploy requirements.txt .env.example root@ВАШ_СЕРВЕР:/root/vpn/
scp .env root@ВАШ_СЕРВЕР:/root/vpn/.env

Шаг 2. Установить (systemd, рекомендуемый способ)

sudo bash deploy/install.sh

Скрипт создаёт системного пользователя vpnbot, разворачивает проект в /opt/vpn-bot, ставит зависимости в venv, регистрирует сервис и запускает его. Повторный запуск обновляет код и перезапускает бота, не трогая .env и базу.

Шаг 3. Проверить настройки на сервере

sudo nano /opt/vpn-bot/.env
sudo systemctl restart vpn-bot
journalctl -u vpn-bot -f

В логе при старте должно быть Бот @имя запущен, а в Telegram придёт «🤖 Бот запущен». Если админ не получил сообщение — в логе будет строка с причиной.

Если бот стоит на одном сервере с 3x-ui, укажите панель по локальному адресу: XUI_URL=http://127.0.0.1:2053/секретный_путь. Так запросы не выходят наружу и не зависят от сертификата.

Альтернатива: Docker

cd /root/vpn
cp .env.example .env && nano .env      # если .env ещё не скопирован
docker compose up -d --build
docker compose logs -f

База лежит в ./data на хосте, поэтому переживает пересборку образа.

Эксплуатация

Задача systemd Docker
Логи journalctl -u vpn-bot -f docker compose logs -f
Перезапуск systemctl restart vpn-bot docker compose restart
Остановить systemctl stop vpn-bot docker compose down
Обновить код залить файлы, sudo bash deploy/install.sh git pull && docker compose up -d --build
База sudo -u vpnbot /opt/vpn-bot/.venv/bin/python /opt/vpn-bot/scripts/db.py --tables docker compose exec bot python scripts/db.py --tables

Резервные копии базы (горячий бэкап, бота останавливать не нужно):

sudo bash /opt/vpn-bot/deploy/backup.sh
sudo crontab -e   # 0 4 * * * bash /opt/vpn-bot/deploy/backup.sh

Копии складываются в /opt/vpn-bot/backups, хранятся 14 дней. Файл data/vpnbot.db — это все ваши пользователи, подписки и платежи, так что бэкап стоит настроить сразу.

Перед тем как звать первых клиентов

  • BOT_TOKEN и ADMIN_IDS заполнены реальными значениями, админ нажал /start
  • VPN_PROVIDER не mock, «🩺 Проверить панель» в /admin отвечает «панель отвечает»
  • Куплен ключ самому себе через тестовый тариф — доступ пришёл и подключается
  • Настроен бэкап базы

Структура

bot/
  main.py               точка входа, сборка Dispatcher, запуск планировщика и вебхуков
  config.py             все настройки из .env
  tasks.py              фоновые задачи (напоминания, отключение, опрос платежей)
  webserver.py          aiohttp-сервер для вебхуков платёжных систем
  db/                   модели, сессия, запросы
  handlers/             admin, admin_users (карточка клиента), admin_config, common,
                        access, plans, payments, profile, trial
  keyboards/            inline- и reply-клавиатуры, callback-данные
  middlewares/          сессия БД, регистрация пользователя, антифлуд
  services/
    subscriptions.py    выдача/продление/отключение — единая точка правды
    vpn/                панели: base, marzban, xui, mock + фабрика
    payments/           провайдеры: yookassa, cryptobot + реестр
  texts/ru.py           все пользовательские тексты
  utils/                рендер сообщений, логи, проверка подписки на канал
scripts/db.py           консоль к базе бота
scripts/panel_check.py  диагностика VPN-панели
scripts/renew_test.py   продление подписки и лимиты трафика
scripts/admin_test.py   проверка админки: редактор тарифов и настроек
scripts/smoke_test.py   сквозная проверка логики без Telegram
scripts/safety_test.py  гонки и защитные проверки: двойные нажатия, просрочка, бан, ошибки
scripts/migrate_legacy.py  чистка базы от столбцов рефералки и промокодов

Добавить свою панель или платёжку — значит написать один класс: VpnPanel (bot/services/vpn/base.py) или PaymentProvider (bot/services/payments/base.py) и зарегистрировать его в фабрике.


Что стоит сделать перед продакшеном

  • Хранить .env только на сервере, права chmod 600; токены в репозиторий не коммитить.
  • Перейти на PostgreSQL, если ожидается больше нескольких тысяч пользователей (DATABASE_URL=postgresql+asyncpg://…), и настроить бэкап БД.
  • FSM-состояния хранятся в памяти: при рестарте недозаполненные диалоги сбрасываются. Для нескольких реплик подключите Redis-хранилище aiogram.
  • Проверить юридическую сторону приёма платежей (статус самозанятого/ИП, чеки, оферта, возвраты) — текст условий лежит в /terms в bot/handlers/common.py.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages