Template
246 lines
8.5 KiB
Python
246 lines
8.5 KiB
Python
"""Пользовательские хендлеры."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from aiogram import F, Router
|
||
from aiogram.filters import CommandObject, CommandStart
|
||
from aiogram.types import CallbackQuery, Message
|
||
from remnawave import RemnawaveSDK
|
||
|
||
from config import Settings
|
||
from keyboards import (
|
||
clients_links_inline,
|
||
clients_root_inline,
|
||
faq_back_inline,
|
||
faq_root_inline,
|
||
main_menu,
|
||
servers_inline,
|
||
)
|
||
from services.clients import CLIENTS_INTRO, client_card, get_client
|
||
from services.db import AuthError, Database
|
||
from services.faq import faq_answer_text, faq_intro, get_faq
|
||
from services.nodes_status import fetch_nodes_status, format_nodes_status
|
||
|
||
router = Router(name="user")
|
||
|
||
WELCOME = (
|
||
"👋 <b>Привет, {name}!</b>\n\n"
|
||
"Я бот VPN на Remnawave.\n"
|
||
"Открой <b>Mini App</b> или пользуйся кнопками ниже.\n\n"
|
||
"Баланс: <b>{balance:.0f} ₽</b>"
|
||
)
|
||
|
||
HELP = (
|
||
"<b>ℹ️ Помощь</b>\n\n"
|
||
"• <b>Mini App</b> — кабинет в Telegram\n"
|
||
"• <b>Тарифы</b> — оплата с баланса или криптой\n"
|
||
"• <b>Баланс</b> — Crypto Bot / Heleket / Lava / Digiseller / код\n"
|
||
"• <b>Моя подписка</b> — ссылка, срок, трафик, продление\n"
|
||
"• <b>Скачать клиент</b> — топ приложений\n"
|
||
"• <b>Статус серверов</b> — онлайн / оффлайн\n"
|
||
"• <b>FAQ</b> — частые вопросы и ответы\n"
|
||
"• <b>Тикеты</b> — написать в поддержку\n\n"
|
||
"Не нашёл ответ — создай тикет или открой FAQ."
|
||
)
|
||
|
||
|
||
def _is_admin(settings: Settings, user_id: int | None) -> bool:
|
||
return bool(user_id and user_id in settings.admin_ids)
|
||
|
||
|
||
def _kb(settings: Settings, is_admin: bool = False):
|
||
return main_menu(
|
||
is_admin=is_admin,
|
||
webapp_url=settings.webapp_url or None,
|
||
)
|
||
|
||
|
||
@router.message(CommandStart())
|
||
async def cmd_start(
|
||
message: Message,
|
||
command: CommandObject,
|
||
settings: Settings,
|
||
db: Database,
|
||
) -> None:
|
||
user = message.from_user
|
||
name = user.full_name if user else "друг"
|
||
telegram_id = user.id if user else 0
|
||
bot_user = db.ensure_user(
|
||
telegram_id,
|
||
username=user.username if user else None,
|
||
full_name=name if user else None,
|
||
)
|
||
admin = _is_admin(settings, telegram_id or None)
|
||
|
||
args = (command.args or "").strip()
|
||
if args.startswith("qrauth_") and telegram_id > 0:
|
||
token = args[len("qrauth_") :]
|
||
try:
|
||
db.approve_login_qr(token, telegram_id)
|
||
await message.answer(
|
||
"✅ <b>Вход на сайт подтверждён</b>\n\n"
|
||
"Можно вернуться в браузер — кабинет откроется автоматически.",
|
||
reply_markup=_kb(settings, admin),
|
||
parse_mode="HTML",
|
||
)
|
||
return
|
||
except AuthError as exc:
|
||
await message.answer(
|
||
f"⚠️ Не удалось подтвердить QR: {exc}\n\n"
|
||
+ WELCOME.format(name=name, balance=bot_user.balance),
|
||
reply_markup=_kb(settings, admin),
|
||
parse_mode="HTML",
|
||
)
|
||
return
|
||
|
||
if args.startswith("ref_") and telegram_id > 0:
|
||
code = args[len("ref_") :]
|
||
if db.attach_referrer(telegram_id, code):
|
||
await message.answer(
|
||
"🎁 Реферальная ссылка принята! Бонус начислим после первой покупки тарифа.",
|
||
parse_mode="HTML",
|
||
)
|
||
db.ensure_referral_code(telegram_id)
|
||
|
||
if bot_user.banned:
|
||
await message.answer(
|
||
"🚫 Аккаунт заблокирован. Напишите в поддержку, если это ошибка.",
|
||
parse_mode="HTML",
|
||
)
|
||
return
|
||
|
||
text = WELCOME.format(name=name, balance=bot_user.balance)
|
||
if not settings.webapp_url:
|
||
text += (
|
||
"\n\n<i>Mini App пока выключен: задай WEBAPP_URL (https) в .env</i>"
|
||
)
|
||
await message.answer(
|
||
text,
|
||
reply_markup=_kb(settings, admin),
|
||
parse_mode="HTML",
|
||
)
|
||
|
||
|
||
@router.message(F.text == "ℹ️ Помощь")
|
||
async def cmd_help(message: Message, settings: Settings) -> None:
|
||
admin = _is_admin(settings, message.from_user.id if message.from_user else None)
|
||
await message.answer(HELP, reply_markup=_kb(settings, admin), parse_mode="HTML")
|
||
|
||
|
||
@router.message(F.text == "❓ FAQ")
|
||
async def cmd_faq(message: Message) -> None:
|
||
await message.answer(
|
||
faq_intro(),
|
||
reply_markup=faq_root_inline(),
|
||
parse_mode="HTML",
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data == "faq:root")
|
||
async def cb_faq_root(callback: CallbackQuery) -> None:
|
||
await callback.answer()
|
||
if callback.message:
|
||
await callback.message.edit_text(
|
||
faq_intro(),
|
||
reply_markup=faq_root_inline(),
|
||
parse_mode="HTML",
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("faq:view:"))
|
||
async def cb_faq_view(callback: CallbackQuery) -> None:
|
||
item_id = (callback.data or "").split(":", 2)[2]
|
||
item = get_faq(item_id)
|
||
if not item:
|
||
await callback.answer("Вопрос не найден", show_alert=True)
|
||
return
|
||
await callback.answer()
|
||
if callback.message:
|
||
await callback.message.edit_text(
|
||
faq_answer_text(item),
|
||
reply_markup=faq_back_inline(),
|
||
parse_mode="HTML",
|
||
)
|
||
|
||
|
||
@router.message(F.text == "📥 Скачать клиент")
|
||
async def cmd_clients(message: Message) -> None:
|
||
await message.answer(
|
||
CLIENTS_INTRO,
|
||
reply_markup=clients_root_inline(),
|
||
parse_mode="HTML",
|
||
disable_web_page_preview=True,
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data == "clients:root")
|
||
async def cb_clients_root(callback: CallbackQuery) -> None:
|
||
await callback.answer()
|
||
if callback.message:
|
||
await callback.message.edit_text(
|
||
CLIENTS_INTRO,
|
||
reply_markup=clients_root_inline(),
|
||
parse_mode="HTML",
|
||
disable_web_page_preview=True,
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("clients:view:"))
|
||
async def cb_clients_view(callback: CallbackQuery) -> None:
|
||
client_id = (callback.data or "").split(":", 2)[2]
|
||
client = get_client(client_id)
|
||
if not client:
|
||
await callback.answer("Клиент не найден", show_alert=True)
|
||
return
|
||
await callback.answer()
|
||
if callback.message:
|
||
await callback.message.edit_text(
|
||
client_card(client),
|
||
reply_markup=clients_links_inline(client.links),
|
||
parse_mode="HTML",
|
||
disable_web_page_preview=True,
|
||
)
|
||
|
||
|
||
@router.message(F.text == "📡 Статус серверов")
|
||
async def cmd_servers(message: Message, sdk: RemnawaveSDK) -> None:
|
||
wait = await message.answer("⏳ Проверяю серверы…")
|
||
try:
|
||
nodes = await fetch_nodes_status(sdk)
|
||
text = format_nodes_status(nodes, detailed=False)
|
||
await wait.edit_text(text, reply_markup=servers_inline(), parse_mode="HTML")
|
||
except Exception as exc: # noqa: BLE001
|
||
await wait.edit_text(
|
||
f"Не удалось получить статус серверов.\n<code>{exc}</code>",
|
||
parse_mode="HTML",
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data == "servers:refresh")
|
||
async def cb_servers_refresh(callback: CallbackQuery, sdk: RemnawaveSDK) -> None:
|
||
await callback.answer("Обновляю…")
|
||
try:
|
||
nodes = await fetch_nodes_status(sdk)
|
||
text = format_nodes_status(nodes, detailed=False)
|
||
if callback.message:
|
||
await callback.message.edit_text(
|
||
text, reply_markup=servers_inline(), parse_mode="HTML"
|
||
)
|
||
except Exception as exc: # noqa: BLE001
|
||
if callback.message:
|
||
await callback.message.edit_text(
|
||
f"Ошибка обновления.\n<code>{exc}</code>",
|
||
parse_mode="HTML",
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data == "menu:home")
|
||
async def cb_menu_home(callback: CallbackQuery, settings: Settings) -> None:
|
||
await callback.answer()
|
||
admin = _is_admin(settings, callback.from_user.id if callback.from_user else None)
|
||
if callback.message:
|
||
await callback.message.answer(
|
||
"🏠 Главное меню",
|
||
reply_markup=_kb(settings, admin),
|
||
)
|