diff --git a/.gitignore b/.gitignore index 8722fed..6d62731 100644 --- a/.gitignore +++ b/.gitignore @@ -174,4 +174,6 @@ cython_debug/ .ruff_cache/ # PyPI configuration file -.pypirc \ No newline at end of file +.pypirc + +plans \ No newline at end of file diff --git a/bot/handlers/client/menus.py b/bot/handlers/client/menus.py index b7df211..a881ff2 100644 --- a/bot/handlers/client/menus.py +++ b/bot/handlers/client/menus.py @@ -4,12 +4,13 @@ from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, Message from sqlalchemy.ext.asyncio import AsyncSession -from bot.keyboards.client import close_kb, main_menu, subscription_controls +from bot.keyboards.client import close_kb, construct_devices_list, main_menu, subscription_controls from bot.keyboards.common import return_to_menu from bot.texts import ( CALLBACK_FALLBACK, FAQ_TEXT, GENERAL_PROCESSING, + NO_CONNECTIONS, NO_SUBSCRIPTION, ) from db.models.users import User @@ -17,7 +18,7 @@ from misc import utils from misc.utils import build_main_menu_text, format_subscription_link from schemas.di import DependenciesDTO from schemas.subscriptions import InternalSquads, SubscriptionPlan -from services.rw import get_user_by_telegram_id +from services.rw import get_hwid_list, get_user_by_telegram_id router = Router() @@ -34,7 +35,7 @@ async def fetch_referal( data = command.args.split("_") if not data: - await standard_start(msg, command, state) + await standard_start(msg, state, session, deps) return referal = int(data[1]) @@ -126,7 +127,7 @@ async def sub_info( return if not user: - await cb.answer("1" + CALLBACK_FALLBACK) + await cb.answer(CALLBACK_FALLBACK) return if rw_user and not sub: @@ -146,7 +147,7 @@ async def sub_info( plan_price_paid=utils.calculate_price(dto), ) except Exception: - await cb.answer("2" + CALLBACK_FALLBACK) + await cb.answer(CALLBACK_FALLBACK) return await cb.message.edit_text( @@ -158,6 +159,25 @@ async def sub_info( ), ) +@router.callback_query(F.data == "hwid_control") +async def hwid_control(cb: CallbackQuery, state: FSMContext, deps: DependenciesDTO): + await state.clear() + + await cb.message.edit_text(GENERAL_PROCESSING, reply_markup=close_kb) + rw_user = await get_user_by_telegram_id(deps.rw_sdk, cb.from_user.id) + + if not rw_user: + await cb.message.edit_text(NO_SUBSCRIPTION, reply_markup=return_to_menu) + return + + devices = await get_hwid_list(deps.rw_sdk, rw_user.uuid) + if not devices: + await cb.message.edit_text(NO_CONNECTIONS, reply_markup=return_to_menu) + return + + await cb.message.edit_text(text="pepe", reply_markup=construct_devices_list(devices)) + + @router.callback_query(F.data == "toggle:autorenewal") async def toggle_autorenewal( diff --git a/bot/keyboards/client.py b/bot/keyboards/client.py index 9ea7c5d..d15c709 100644 --- a/bot/keyboards/client.py +++ b/bot/keyboards/client.py @@ -1,5 +1,6 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.utils.keyboard import InlineKeyboardBuilder +from remnawave.models.hwid import HwidDeviceDto from config import settings from misc.utils import convert_duration_to_int @@ -63,6 +64,7 @@ def subscription_controls( ) builder.row(InlineKeyboardButton(text=autorenewal_text, callback_data="toggle:autorenewal")) + builder.row(InlineKeyboardButton(text="Управление устройствами", callback_data="hwid_control")) builder.row(_return_to_menu_btn) return builder.as_markup() @@ -143,3 +145,11 @@ def pay_url(url: str) -> InlineKeyboardMarkup: [InlineKeyboardButton(text="❌", callback_data="menu:main")], ] ).as_markup() + +def construct_devices_list(devices: list[HwidDeviceDto]) -> InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + + for device in devices: + builder.row(InlineKeyboardButton(text=f"{device.platform} | {device.device_model} | {device.user_agent.split('/')[0]}", callback_data=f"hwid:{device.hwid}")) + builder.row(_return_btn(data="sub_info")) + return builder.as_markup() \ No newline at end of file diff --git a/bot/texts.py b/bot/texts.py index d1a71df..a8d3208 100644 --- a/bot/texts.py +++ b/bot/texts.py @@ -155,6 +155,7 @@ SUBSCRIPTION_INFORMATION = ( NO_SUBSCRIPTION = f"{MALENIA_WARN} Подписка не найдена." +NO_CONNECTIONS = f"{MALENIA_SHIELD} Вы ещё не подключались по своей ссылке." # ============================================================ # РЕФЕРАЛЬНАЯ СИСТЕМА diff --git a/services/rw.py b/services/rw.py index 7abd37a..bf573e8 100644 --- a/services/rw.py +++ b/services/rw.py @@ -1,10 +1,12 @@ import logging from dataclasses import dataclass from datetime import UTC, datetime, timedelta +from typing import List from uuid import UUID from remnawave import RemnawaveSDK from remnawave.models import CreateUserRequestDto, UpdateUserRequestDto +from remnawave.models.hwid import HwidDeviceDto logger = logging.getLogger(__name__) @@ -349,3 +351,15 @@ async def create_user( except Exception as exc: logger.warning("Failed to create user username=%s: %s", username, exc) return None + +async def get_hwid_list( + sdk: RemnawaveSDK, + user_uuid: str +) -> List[HwidDeviceDto] | None: + try: + resp = await sdk.hwid.get_hwid_user(user_uuid) + return resp.devices + except Exception as exc: + logger.exception("failed to fetch hwid list for user=%s", user_uuid) + return + \ No newline at end of file diff --git a/services/user_service.py b/services/user_service.py index c67b7ea..3afa082 100644 --- a/services/user_service.py +++ b/services/user_service.py @@ -2,8 +2,9 @@ import datetime import logging import math -from aiogram.types import Message, User as TelegramUser +from aiogram.types import InlineKeyboardMarkup, Message, User as TelegramUser from remnawave import RemnawaveSDK +from remnawave.models.hwid import HwidDeviceDto from sqlalchemy.ext.asyncio import AsyncSession from bot.texts import NO_SUBSCRIPTION, NOTHING_PLACEHOLDER, SUBSCRIPTION_INFORMATION