import os from io import BytesIO import qrcode from aiogram import F, Router, types from aiogram.types import CallbackQuery, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from config import ( CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, INSTRUCTIONS_BUTTON, ) from database.models import Key from handlers.buttons import ( ANDROID, BACK, DOWNLOAD_ANDROID_BUTTON, DOWNLOAD_IOS_BUTTON, IMPORT_ANDROID, IMPORT_IOS, IPHONE, MAIN_MENU, MANUAL_INSTRUCTIONS, PC, TV, ) from handlers.texts import ( ANDROID_DESCRIPTION_TEMPLATE, CHOOSE_DEVICE_TEXT, IOS_DESCRIPTION_TEMPLATE, SUBSCRIPTION_DESCRIPTION, ) from handlers.utils import edit_or_send_message from hooks.hook_buttons import insert_hook_buttons from hooks.hooks import run_hooks from logger import logger router = Router() @router.callback_query(F.data.startswith("connect_device|")) async def handle_connect_device(callback_query: CallbackQuery, session: AsyncSession): try: key_name = callback_query.data.split("|")[1] builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=IPHONE, callback_data=f"connect_ios|{key_name}")) builder.row(InlineKeyboardButton(text=ANDROID, callback_data=f"connect_android|{key_name}")) builder.row(InlineKeyboardButton(text=PC, callback_data=f"connect_pc|{key_name}")) builder.row(InlineKeyboardButton(text=TV, callback_data=f"connect_tv|{key_name}")) # builder.row(InlineKeyboardButton(text=ROUTER, callback_data=f"connect_router|{key_name}")) builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{key_name}")) try: hook_builder = InlineKeyboardBuilder() hook_builder.attach(builder) hook_commands = await run_hooks( "connect_device_menu", chat_id=callback_query.from_user.id, admin=False, session=session ) if hook_commands: hook_builder = insert_hook_buttons(hook_builder, hook_commands) final_markup = hook_builder.as_markup() except Exception as e: logger.warning(f"[CONNECT_DEVICE] Ошибка при применении хуков: {e}") final_markup = builder.as_markup() await edit_or_send_message( target_message=callback_query.message, text=CHOOSE_DEVICE_TEXT, reply_markup=final_markup, media_path=None, ) except Exception as e: await callback_query.message.answer("❌ Ошибка при показе меню подключения.") logger.error(f"Ошибка в handle_connect_device: {e}") @router.callback_query(F.data.startswith("connect_phone|")) async def process_callback_connect_phone(callback_query: CallbackQuery, session: AsyncSession): email = callback_query.data.split("|")[1] try: result = await session.execute(select(Key.key).where(Key.email == email)) row = result.scalar_one_or_none() if not row: await callback_query.message.answer("❌ Ошибка: ключ не найден.") return key_link = row except Exception as e: logger.error(f"Ошибка при получении ключа для {email}: {e}") await callback_query.message.answer("❌ Произошла ошибка. Попробуйте позже.") return description = SUBSCRIPTION_DESCRIPTION.format(key_link=key_link) builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS), InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID), ) builder.row( InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key_link}"), InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key_link}"), ) if INSTRUCTIONS_BUTTON: builder.row(InlineKeyboardButton(text=MANUAL_INSTRUCTIONS, callback_data="instructions")) builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{email}")) await edit_or_send_message( target_message=callback_query.message, text=description, reply_markup=builder.as_markup(), media_path=None, ) @router.callback_query(F.data.startswith("connect_ios|")) async def process_callback_connect_ios(callback_query: CallbackQuery, session: AsyncSession): email = callback_query.data.split("|")[1] try: result = await session.execute(select(Key.key).where(Key.email == email)) key_link = result.scalar_one_or_none() if not key_link: await callback_query.message.answer("❌ Ошибка: ключ не найден.") return except Exception as e: logger.error(f"Ошибка при получении ключа для {email} (iOS): {e}") await callback_query.message.answer("❌ Произошла ошибка. Попробуйте позже.") return description = IOS_DESCRIPTION_TEMPLATE.format(key_link=key_link) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS)) builder.row(InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key_link}")) if INSTRUCTIONS_BUTTON: builder.row(InlineKeyboardButton(text=MANUAL_INSTRUCTIONS, callback_data="instructions")) builder.row(InlineKeyboardButton(text=BACK, callback_data=f"connect_device|{email}")) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) await edit_or_send_message( target_message=callback_query.message, text=description, reply_markup=builder.as_markup(), media_path=None, ) @router.callback_query(F.data.startswith("connect_android|")) async def process_callback_connect_android(callback_query: CallbackQuery, session: AsyncSession): email = callback_query.data.split("|")[1] try: result = await session.execute(select(Key.key).where(Key.email == email)) key_link = result.scalar_one_or_none() if not key_link: await callback_query.message.answer("❌ Ошибка: ключ не найден.") return except Exception as e: logger.error(f"Ошибка при получении ключа для {email} (Android): {e}") await callback_query.message.answer("❌ Произошла ошибка. Попробуйте позже.") return description = ANDROID_DESCRIPTION_TEMPLATE.format(key_link=key_link) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID)) builder.row(InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key_link}")) if INSTRUCTIONS_BUTTON: builder.row(InlineKeyboardButton(text=MANUAL_INSTRUCTIONS, callback_data="instructions")) builder.row(InlineKeyboardButton(text=BACK, callback_data=f"connect_device|{email}")) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) await edit_or_send_message( target_message=callback_query.message, text=description, reply_markup=builder.as_markup(), media_path=None, ) @router.callback_query(F.data.startswith("show_qr|")) async def show_qr_code(callback_query: types.CallbackQuery, session: AsyncSession): try: key_name = callback_query.data.split("|")[1] stmt = select(Key).where(Key.email == key_name) result = await session.execute(stmt) record = result.scalars().first() if not record: await callback_query.message.answer("❌ Подписка не найдена.") return qr_data = record.key or record.remnawave_link if not qr_data: await callback_query.message.answer("❌ У этой подписки отсутствует ссылка для подключения.") return qr = qrcode.QRCode(version=1, box_size=10, border=4) qr.add_data(qr_data) qr.make(fit=True) img = qr.make_image(fill_color="black", back_color="white") buffer = BytesIO() img.save(buffer, format="PNG") buffer.seek(0) qr_path = f"/tmp/qrcode_{record.email}.png" with open(qr_path, "wb") as f: f.write(buffer.read()) builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text=BACK, callback_data=f"view_key|{record.email}")) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) await edit_or_send_message( target_message=callback_query.message, text="🔲 Ваш QR-код для подключения", reply_markup=builder.as_markup(), media_path=qr_path, ) os.remove(qr_path) except Exception as e: logger.error(f"Ошибка при генерации QR: {e}", exc_info=True) await callback_query.message.answer("❌ Произошла ошибка при создании QR-кода.")