Files

246 lines
8.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Пользовательские хендлеры."""
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),
)