feat(bot): implement HWID device list and deletion
This commit is contained in:
@@ -4,21 +4,31 @@ 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, construct_devices_list, main_menu, subscription_controls
|
||||
from bot.keyboards.client import (
|
||||
close_kb,
|
||||
construct_devices_list,
|
||||
main_menu,
|
||||
prompt_hwid_deletion_kb,
|
||||
subscription_controls,
|
||||
)
|
||||
from bot.keyboards.common import return_to_menu
|
||||
from bot.texts import (
|
||||
CALLBACK_FALLBACK,
|
||||
FAQ_TEXT,
|
||||
GENERAL_PROCESSING,
|
||||
HWID_LIST,
|
||||
NO_CONNECTIONS,
|
||||
NO_SUBSCRIPTION,
|
||||
PROMPT_HWID_DELETION,
|
||||
STANDARD_FALLBACK,
|
||||
SUCCESSFUL_HWID_DELETION,
|
||||
)
|
||||
from db.models.users import User
|
||||
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_hwid_list, get_user_by_telegram_id
|
||||
from services.rw import delete_hwid, get_hwid_list, get_user_by_telegram_id
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -159,6 +169,7 @@ async def sub_info(
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "hwid_control")
|
||||
async def hwid_control(cb: CallbackQuery, state: FSMContext, deps: DependenciesDTO):
|
||||
await state.clear()
|
||||
@@ -169,14 +180,46 @@ async def hwid_control(cb: CallbackQuery, state: FSMContext, deps: DependenciesD
|
||||
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))
|
||||
|
||||
hwid_limit = max(rw_user.hwid_device_limit, 3)
|
||||
|
||||
await cb.message.edit_text(
|
||||
text=HWID_LIST.format(count=len(devices), limit=hwid_limit),
|
||||
reply_markup=construct_devices_list(devices),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("hwid:"))
|
||||
async def prompt_delete_hwid(cb: CallbackQuery, state: FSMContext):
|
||||
await state.clear()
|
||||
hwid = cb.data.split(":")[1]
|
||||
|
||||
await cb.message.edit_text(PROMPT_HWID_DELETION, reply_markup=prompt_hwid_deletion_kb(hwid))
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("delete_hwid:"))
|
||||
async def delete_hwid_bot(cb: CallbackQuery, state: FSMContext, deps: DependenciesDTO):
|
||||
await state.clear()
|
||||
hwid = cb.data.split(":")[1]
|
||||
|
||||
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(STANDARD_FALLBACK, reply_markup=return_to_menu)
|
||||
return
|
||||
|
||||
status = await delete_hwid(deps.rw_sdk, rw_user.uuid, hwid)
|
||||
|
||||
if status:
|
||||
await cb.message.edit_text(SUCCESSFUL_HWID_DELETION, reply_markup=return_to_menu)
|
||||
else:
|
||||
await cb.message.edit_text(STANDARD_FALLBACK, reply_markup=return_to_menu)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "toggle:autorenewal")
|
||||
|
||||
@@ -3,7 +3,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from remnawave.models.hwid import HwidDeviceDto
|
||||
|
||||
from config import settings
|
||||
from misc.utils import convert_duration_to_int
|
||||
from misc.utils import convert_duration_to_int, format_hwid_device
|
||||
from schemas.subscriptions import SubscriptionDuration
|
||||
|
||||
from .common import _return_to_menu_btn
|
||||
@@ -146,10 +146,24 @@ def pay_url(url: str) -> InlineKeyboardMarkup:
|
||||
]
|
||||
).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(
|
||||
InlineKeyboardButton(
|
||||
text=format_hwid_device(device), callback_data=f"hwid:{device.hwid}"
|
||||
)
|
||||
)
|
||||
builder.row(_return_btn(data="sub_info"))
|
||||
return builder.as_markup()
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def prompt_hwid_deletion_kb(hwid: str) -> InlineKeyboardMarkup:
|
||||
return InlineKeyboardBuilder(
|
||||
[
|
||||
[InlineKeyboardButton(text="🗑️ Удалить", callback_data=f"delete_hwid:{hwid}")],
|
||||
[_return_btn("hwid_control", text="❌ Отмена")],
|
||||
]
|
||||
).as_markup()
|
||||
|
||||
12
bot/texts.py
12
bot/texts.py
@@ -74,6 +74,13 @@ NO_SQUADS = "отсутствуют"
|
||||
# — HWID лимит
|
||||
UNLIMITED_HWID = "без лимитов"
|
||||
|
||||
# — OS эмодзи
|
||||
WINDOWS = "💻"
|
||||
LINUX = "🐧"
|
||||
ANDROID = "💚"
|
||||
APPLE = "🍎"
|
||||
UNKNOWN_OS = "👤"
|
||||
|
||||
# — Трафик
|
||||
NO_TRAFFIC_LIMIT = "не ограничен"
|
||||
TRAFFIC_WITH_LIMIT = "{used} / {limit}"
|
||||
@@ -155,7 +162,10 @@ SUBSCRIPTION_INFORMATION = (
|
||||
|
||||
NO_SUBSCRIPTION = f"{MALENIA_WARN} <b>Подписка не найдена.</b>"
|
||||
|
||||
NO_CONNECTIONS = f"{MALENIA_SHIELD} <b>Вы ещё не подключались по своей ссылке.</b>"
|
||||
NO_CONNECTIONS = f"{MALENIA_SHIELD} <b>У вас нет подключённых устройств.</b>"
|
||||
HWID_LIST = f"{MALENIA_SHIELD} <b>Ваши устройства ({{count}} / {{limit}}):</b>"
|
||||
PROMPT_HWID_DELETION = f"{MALENIA_WARN} <b>Вы уверены что хотите удалить это устройство?</b>"
|
||||
SUCCESSFUL_HWID_DELETION = f"{MALENIA_HEART} <b>Устройство успешно удалено.</b>"
|
||||
|
||||
# ============================================================
|
||||
# РЕФЕРАЛЬНАЯ СИСТЕМА
|
||||
|
||||
@@ -19,6 +19,8 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
bot:
|
||||
env_file: .env
|
||||
|
||||
@@ -3,6 +3,8 @@ import urllib.parse
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from random import choice
|
||||
|
||||
from remnawave.models.hwid import HwidDeviceDto
|
||||
|
||||
from bot import texts
|
||||
from config import settings
|
||||
from schemas.subscriptions import InternalSquads, SubscriptionDuration, SubscriptionPlan
|
||||
@@ -271,3 +273,25 @@ def is_same_plan(rw_user: RWUserInfo, new_plan: SubscriptionPlan) -> bool:
|
||||
active_squad_uuids = {squad["uuid"] for squad in rw_user.active_squads}
|
||||
current_has_whitelist = InternalSquads.WHITELISTS in active_squad_uuids
|
||||
return current_has_whitelist == new_plan.whitelists
|
||||
|
||||
|
||||
def _format_os_emoji(os: str) -> str:
|
||||
os = os.lower()
|
||||
if os == "windows":
|
||||
return texts.WINDOWS
|
||||
if os == "linux":
|
||||
return texts.LINUX
|
||||
if os == "ios":
|
||||
return texts.APPLE
|
||||
if os == "android":
|
||||
return texts.ANDROID
|
||||
return texts.UNKNOWN_OS
|
||||
|
||||
|
||||
def format_hwid_device(device: HwidDeviceDto) -> str:
|
||||
os = device.platform
|
||||
model = device.device_model
|
||||
client = device.user_agent.split("/")[0]
|
||||
emoji = _format_os_emoji(os)
|
||||
|
||||
return f"{emoji} {os} | {model} | {client}"
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
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 import (
|
||||
CreateUserRequestDto,
|
||||
DeleteUserHwidDeviceResponseDto,
|
||||
HWIDDeleteRequest,
|
||||
UpdateUserRequestDto,
|
||||
)
|
||||
from remnawave.models.hwid import HwidDeviceDto
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -352,14 +356,21 @@ async def create_user(
|
||||
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:
|
||||
|
||||
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:
|
||||
except Exception:
|
||||
logger.exception("failed to fetch hwid list for user=%s", user_uuid)
|
||||
return
|
||||
|
||||
|
||||
|
||||
async def delete_hwid(sdk: RemnawaveSDK, user_uuid: str, hwid: str) -> bool:
|
||||
body = HWIDDeleteRequest(user_uuid=user_uuid, hwid=hwid)
|
||||
try:
|
||||
resp = await sdk.hwid.delete_hwid_to_user(body)
|
||||
return isinstance(resp, DeleteUserHwidDeviceResponseDto)
|
||||
except Exception:
|
||||
logger.exception("failed to fetch hwid list for user=%s", user_uuid)
|
||||
return False
|
||||
|
||||
@@ -2,9 +2,8 @@ import datetime
|
||||
import logging
|
||||
import math
|
||||
|
||||
from aiogram.types import InlineKeyboardMarkup, Message, User as TelegramUser
|
||||
from aiogram.types import 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
|
||||
|
||||
Reference in New Issue
Block a user