import os import urllib.parse from typing import Any from aiogram import F, Router from aiogram.types import CallbackQuery, InlineKeyboardButton, Message from aiogram.utils.keyboard import InlineKeyboardBuilder from config import ( CONNECT_MACOS, CONNECT_WINDOWS, DOWNLOAD_MACOS, DOWNLOAD_PC, HAPP_CRYPTOLINK, REMNAWAVE_WEBAPP, REMNAWAVE_WEBAPP_OPEN_IN_BROWSER, SUPPORT_CHAT_URL, WEBHOOK_HOST, ) from core.bootstrap import MODES_CONFIG from database import get_key_details, get_subscription_link from handlers.buttons import ( BACK, CONNECT_MACOS_BUTTON, CONNECT_WINDOWS_BUTTON, DOWNLOAD_MACOS_BUTTON, DOWNLOAD_PC_BUTTON, MAIN_MENU, PC_MACOS, PC_PC, SUPPORT, TV_CONTINUE, ) from handlers.keys.utils import build_key_callback, key_owned_by_user, resolve_key from handlers.texts import ( CHOOSE_DEVICE_TEXT, CONNECT_TV_TEXT, INSTRUCTIONS, INSTRUCTION_MACOS, INSTRUCTION_PC, KEY_MESSAGE, ROUTER_MESSAGE, SUBSCRIPTION_DETAILS_TEXT, ) from handlers.utils import edit_or_send_message, is_full_remnawave_cluster from hooks.processors import process_remnawave_webapp_override router = Router() @router.callback_query(F.data == "instructions") @router.message(F.text == "/instructions") async def send_instructions(callback_query_or_message: CallbackQuery | Message): instructions_message = INSTRUCTIONS image_path = os.path.join("img", "instructions.jpg") builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL)) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) if isinstance(callback_query_or_message, CallbackQuery): target_message = callback_query_or_message.message else: target_message = callback_query_or_message await edit_or_send_message( target_message=target_message, text=instructions_message, reply_markup=builder.as_markup(), media_path=image_path, ) @router.callback_query(F.data.startswith("connect_pc|")) async def process_connect_pc(callback_query: CallbackQuery, session: Any): key_ref = callback_query.data.split("|", 1)[1] key_obj = await resolve_key(session, callback_query.from_user.id, key_ref) key_name = key_obj.email if key_obj else key_ref record = await get_key_details(session, key_name) if not key_owned_by_user(record, callback_query.from_user.id): await callback_query.answer("Доступ запрещён.", show_alert=True) return key_link = await get_subscription_link(session, key_name) if not key_link: builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) await edit_or_send_message( target_message=callback_query.message, text="❌ Ключ не найден. Проверьте имя ключа. 🔍", reply_markup=builder.as_markup(), media_path=None, ) return builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton( text=PC_PC, callback_data=build_key_callback("windows_menu", record.get("client_id"), key_name) ) ) builder.row( InlineKeyboardButton( text=PC_MACOS, callback_data=build_key_callback("macos_menu", record.get("client_id"), key_name) ) ) builder.row( InlineKeyboardButton( text=BACK, callback_data=build_key_callback("connect_device", record.get("client_id"), key_name) ) ) await edit_or_send_message( target_message=callback_query.message, text=CHOOSE_DEVICE_TEXT, reply_markup=builder.as_markup(), media_path=None, ) @router.callback_query(F.data.startswith("windows_menu|")) async def process_windows_menu(callback_query: CallbackQuery, session: Any): key_ref = callback_query.data.split("|", 1)[1] key_obj = await resolve_key(session, callback_query.from_user.id, key_ref) key_name = key_obj.email if key_obj else key_ref record = await get_key_details(session, key_name) if not key_owned_by_user(record, callback_query.from_user.id): await callback_query.answer("Доступ запрещён.", show_alert=True) return key_link = await get_subscription_link(session, key_name) if not key_link: await callback_query.message.answer("❌ Ошибка: ключ не найден.") return key_message_text = KEY_MESSAGE.format(key_link) instruction_message = f"{key_message_text}{INSTRUCTION_PC}" builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=DOWNLOAD_PC_BUTTON, url=DOWNLOAD_PC)) if "happ://crypt" in key_link: processed_link = urllib.parse.quote(key_link, safe="") windows_url = f"{WEBHOOK_HOST}/?url={processed_link}" else: processed_link = key_link windows_url = f"{CONNECT_WINDOWS}{processed_link}" builder.row(InlineKeyboardButton(text=CONNECT_WINDOWS_BUTTON, url=windows_url)) builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL)) builder.row( InlineKeyboardButton( text=BACK, callback_data=build_key_callback("connect_pc", record.get("client_id"), key_name) ) ) await edit_or_send_message( target_message=callback_query.message, text=instruction_message, reply_markup=builder.as_markup(), media_path=None, ) @router.callback_query(F.data.startswith("macos_menu|")) async def process_macos_menu(callback_query: CallbackQuery, session: Any): key_ref = callback_query.data.split("|", 1)[1] key_obj = await resolve_key(session, callback_query.from_user.id, key_ref) key_name = key_obj.email if key_obj else key_ref record = await get_key_details(session, key_name) if not key_owned_by_user(record, callback_query.from_user.id): await callback_query.answer("Доступ запрещён.", show_alert=True) return key_link = await get_subscription_link(session, key_name) if not key_link: await callback_query.message.answer("❌ Ошибка: ключ не найден.") return key_message_text = KEY_MESSAGE.format(key_link) instruction_message = f"{key_message_text}{INSTRUCTION_MACOS}" builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=DOWNLOAD_MACOS_BUTTON, url=DOWNLOAD_MACOS)) if "happ://crypt" in key_link: processed_link = urllib.parse.quote(key_link, safe="") macos_url = f"{WEBHOOK_HOST}/?url={processed_link}" else: processed_link = key_link macos_url = f"{CONNECT_MACOS}{processed_link}" builder.row(InlineKeyboardButton(text=CONNECT_MACOS_BUTTON, url=macos_url)) builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL)) builder.row( InlineKeyboardButton( text=BACK, callback_data=build_key_callback("connect_pc", record.get("client_id"), key_name) ) ) await edit_or_send_message( target_message=callback_query.message, text=instruction_message, reply_markup=builder.as_markup(), media_path=None, ) @router.callback_query(F.data.startswith("connect_tv|")) async def process_connect_tv(callback_query: CallbackQuery, session: Any): key_ref = callback_query.data.split("|", 1)[1] key_obj = await resolve_key(session, callback_query.from_user.id, key_ref) key_name = key_obj.email if key_obj else key_ref record = await get_key_details(session, key_name) if not key_owned_by_user(record, callback_query.from_user.id): await callback_query.answer("Доступ запрещён.", show_alert=True) return final_link = None is_full_remnawave = False use_webapp = False is_remnawave_webapp = False if record: server_name = record.get("server_id") final_link = record.get("key") or record.get("remnawave_link") if server_name: is_full_remnawave = await is_full_remnawave_cluster(server_name, session) remnawave_webapp_enabled = bool(MODES_CONFIG.get("REMNAWAVE_WEBAPP_ENABLED", REMNAWAVE_WEBAPP)) happ_cryptolink_enabled = bool(MODES_CONFIG.get("HAPP_CRYPTOLINK_ENABLED", HAPP_CRYPTOLINK)) use_webapp = remnawave_webapp_enabled if is_full_remnawave and final_link and remnawave_webapp_enabled and not happ_cryptolink_enabled: use_webapp = await process_remnawave_webapp_override( remnawave_webapp=remnawave_webapp_enabled, final_link=final_link, session=session, ) is_remnawave_webapp = bool(is_full_remnawave and final_link and use_webapp and not happ_cryptolink_enabled) if not final_link: await callback_query.answer("❌ Ссылка подписки не найдена", show_alert=True) return back_callback = ( build_key_callback("view_key", record.get("client_id"), key_name) if is_remnawave_webapp else build_key_callback("connect_device", record.get("client_id"), key_name) ) builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton( text=TV_CONTINUE, callback_data=build_key_callback("continue_tv", record.get("client_id"), key_name) ) ) builder.row(InlineKeyboardButton(text=BACK, callback_data=back_callback)) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) text = CONNECT_TV_TEXT.format(subscription_link=final_link) await edit_or_send_message( target_message=callback_query.message, text=text, reply_markup=builder.as_markup(), media_path=None, disable_web_page_preview=True, ) @router.callback_query(F.data.startswith("continue_tv|")) async def process_continue_tv(callback_query: CallbackQuery, session: Any): key_ref = callback_query.data.split("|", 1)[1] key_obj = await resolve_key(session, callback_query.from_user.id, key_ref) key_name = key_obj.email if key_obj else key_ref record = await get_key_details(session, key_name) if not key_owned_by_user(record, callback_query.from_user.id): await callback_query.answer("Доступ запрещён.", show_alert=True) return key_link = await get_subscription_link(session, key_name) message_text = SUBSCRIPTION_DETAILS_TEXT.format(subscription_link=key_link) builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton( text=BACK, callback_data=build_key_callback("connect_tv", record.get("client_id"), key_name) ) ) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) await edit_or_send_message( target_message=callback_query.message, text=message_text, reply_markup=builder.as_markup(), media_path=None, ) @router.callback_query(F.data.startswith("connect_router|")) async def process_connect_router(callback_query: CallbackQuery, session: Any): key_ref = callback_query.data.split("|", 1)[1] key_obj = await resolve_key(session, callback_query.from_user.id, key_ref) key_name = key_obj.email if key_obj else key_ref record = await get_key_details(session, key_name) if not key_owned_by_user(record, callback_query.from_user.id): await callback_query.answer("Доступ запрещён.", show_alert=True) return key_link = await get_subscription_link(session, key_name) if not key_link: builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) await edit_or_send_message( target_message=callback_query.message, text="❌ Ключ не найден.", reply_markup=builder.as_markup(), media_path=None, ) return message_text = ROUTER_MESSAGE.format(subscription_link=key_link) builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton(text=BACK, callback_data=build_key_callback("view_key", record.get("client_id"), key_name)) ) await edit_or_send_message( target_message=callback_query.message, text=message_text, reply_markup=builder.as_markup(), media_path=None, )