feat: add admin HWID device limit overrides

This commit is contained in:
3252a8
2026-06-03 14:47:47 +03:00
parent 7b8feee99a
commit a06884d816
20 changed files with 1782 additions and 14 deletions
+316 -2
View File
@@ -236,6 +236,10 @@ def get_user_card_keyboard(
text=_(key="admin_user_traffic_grant_button"),
callback_data=f"user_action:traffic_grant:{user_id}",
)
builder.button(
text=_(key="admin_user_hwid_limit_button"),
callback_data=f"user_action:hwid_limit:{user_id}",
)
# Row 4: Quick links — only for users with a real Telegram profile
# (synthetic email-only users have a negative user_id with no tg profile).
@@ -261,9 +265,9 @@ def get_user_card_keyboard(
quick_links_count = (1 if has_self_link else 0) + (1 if has_referrer_link else 0)
if quick_links_count == 0:
builder.adjust(2, 2, 2, 1, 2, 1, 2)
builder.adjust(2, 2, 2, 1, 3, 1, 2)
else:
builder.adjust(2, 2, 2, 1, 2, quick_links_count, 1, 2)
builder.adjust(2, 2, 2, 1, 3, quick_links_count, 1, 2)
return builder
@@ -403,6 +407,26 @@ async def format_user_card(
f"{_('admin_user_traffic_label')} {hcode(f'{used_display} / {limit_display}')}"
)
max_devices = subscription_details.get("max_devices")
extra_hwid_devices = int(subscription_details.get("extra_hwid_devices") or 0)
if max_devices is not None:
if int(max_devices) == 0:
devices_display = _("admin_hwid_limit_state_unlimited")
elif extra_hwid_devices > 0:
base_hwid_limit = subscription_details.get("base_hwid_device_limit")
if base_hwid_limit is None:
devices_display = _("admin_hwid_limit_state_count", count=int(max_devices))
else:
devices_display = _(
"admin_hwid_limit_state_with_extra",
total=int(max_devices),
base=int(base_hwid_limit),
extra=extra_hwid_devices,
)
else:
devices_display = _("admin_hwid_limit_state_count", count=int(max_devices))
card_parts.append(f"{_('admin_user_hwid_limit_label')} {hcode(devices_display)}")
premium_unlimited = bool(subscription_details.get("premium_unlimited_override"))
premium_bonus_bytes = int(subscription_details.get("premium_bonus_bytes") or 0)
if premium_unlimited:
@@ -706,6 +730,32 @@ async def user_action_handler(
await handle_traffic_grant_prompt(callback, state, user, "regular", i18n, current_lang)
elif action == "traffic_grant_premium":
await handle_traffic_grant_prompt(callback, state, user, "premium", i18n, current_lang)
elif action == "hwid_limit":
await handle_hwid_limit_menu(callback, state, user, session, i18n, current_lang)
elif action == "hwid_limit_set_unlimited":
await handle_hwid_limit_apply(
callback,
user,
subscription_service,
session,
settings,
i18n,
current_lang,
hwid_device_limit=0,
)
elif action == "hwid_limit_reset":
await handle_hwid_limit_apply(
callback,
user,
subscription_service,
session,
settings,
i18n,
current_lang,
hwid_device_limit=None,
)
elif action == "hwid_limit_set_number":
await handle_hwid_limit_prompt(callback, state, user, i18n, current_lang)
else:
await callback.answer(_("admin_unknown_action"), show_alert=True)
@@ -850,6 +900,162 @@ async def handle_premium_override_bonus_prompt(
await callback.answer()
def _admin_hwid_limit_state_text(
get_text: Callable[..., str],
hwid_device_limit: Optional[int],
extra_hwid_devices: int = 0,
) -> str:
if hwid_device_limit is None:
return get_text("admin_hwid_limit_state_default")
base_limit = int(hwid_device_limit)
if base_limit == 0:
return get_text("admin_hwid_limit_state_unlimited")
extra = max(0, int(extra_hwid_devices or 0))
if extra > 0:
return get_text(
"admin_hwid_limit_state_with_extra",
total=base_limit + extra,
base=base_limit,
extra=extra,
)
return get_text("admin_hwid_limit_state_count", count=base_limit)
async def handle_hwid_limit_menu(
callback: types.CallbackQuery,
state: FSMContext,
user: User,
session: AsyncSession,
i18n_instance,
lang: str,
) -> None:
"""Show HWID device limit override controls."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
active_sub = await subscription_dal.get_active_subscription_by_user_id(session, user.user_id)
if not active_sub:
await callback.answer(_("admin_hwid_limit_no_subscription"), show_alert=True)
return
current_text = _admin_hwid_limit_state_text(
_,
getattr(active_sub, "hwid_device_limit", None),
int(getattr(active_sub, "extra_hwid_devices", 0) or 0),
)
text = "\n".join(
[
f"<b>{_('admin_hwid_limit_title')}</b>",
"",
_("admin_hwid_limit_hint"),
"",
_("admin_hwid_limit_current", current=current_text),
]
)
builder = InlineKeyboardBuilder()
builder.button(
text=_("admin_hwid_limit_btn_set_number"),
callback_data=f"user_action:hwid_limit_set_number:{user.user_id}",
)
builder.button(
text=_("admin_hwid_limit_btn_unlimited"),
callback_data=f"user_action:hwid_limit_set_unlimited:{user.user_id}",
)
builder.button(
text=_("admin_hwid_limit_btn_reset"),
callback_data=f"user_action:hwid_limit_reset:{user.user_id}",
)
builder.button(
text=_("admin_user_back_to_card_button"),
callback_data=f"user_action:refresh:{user.user_id}",
)
builder.adjust(1, 1, 1, 1)
try:
await callback.message.edit_text(text, reply_markup=builder.as_markup(), parse_mode="HTML")
except Exception:
await callback.message.answer(text, reply_markup=builder.as_markup(), parse_mode="HTML")
await state.update_data(target_user_id=user.user_id)
await callback.answer()
async def handle_hwid_limit_apply(
callback: types.CallbackQuery,
user: User,
subscription_service: SubscriptionService,
session: AsyncSession,
settings: Settings,
i18n_instance,
lang: str,
*,
hwid_device_limit: Optional[int],
) -> None:
"""Persist a HWID device base limit override and push it to the panel."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
try:
active_sub = await subscription_dal.get_active_subscription_by_user_id(
session, user.user_id
)
if not active_sub:
await callback.answer(_("admin_hwid_limit_no_subscription"), show_alert=True)
return
active_sub.hwid_device_limit = hwid_device_limit
effective_limit = await subscription_service.sync_hwid_device_limit_to_panel(
session, user.user_id
)
await message_log_dal.create_message_log_no_commit(
session,
{
"user_id": callback.from_user.id if callback.from_user else user.user_id,
"event_type": "admin:hwid_device_limit",
"content": (
f"hwid_device_limit={hwid_device_limit!r} "
f"effective_hwid_device_limit={effective_limit!r}"
),
"is_admin_event": True,
"target_user_id": user.user_id,
"timestamp": datetime.now(timezone.utc),
},
)
await session.commit()
await callback.answer(_("admin_hwid_limit_saved"), show_alert=False)
await handle_refresh_user_card(
callback, user, subscription_service, session, settings, i18n_instance, lang
)
except Exception as exc:
logging.error(
"Failed to apply HWID device limit for user %s: %s",
user.user_id,
exc,
exc_info=True,
)
await session.rollback()
await callback.answer(_("admin_hwid_limit_save_error"), show_alert=True)
async def handle_hwid_limit_prompt(
callback: types.CallbackQuery,
state: FSMContext,
user: User,
i18n_instance,
lang: str,
) -> None:
"""Ask admin for an explicit HWID device limit."""
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
await state.update_data(target_user_id=user.user_id)
await state.set_state(AdminStates.waiting_for_hwid_device_limit)
prompt = _("admin_hwid_limit_prompt", user_id=user.user_id)
try:
await callback.message.edit_text(prompt)
except Exception:
await callback.message.answer(prompt)
await callback.answer()
async def handle_traffic_grant_menu(
callback: types.CallbackQuery,
user: User,
@@ -1971,6 +2177,114 @@ async def process_premium_override_bonus_handler(
await state.clear()
@router.message(AdminStates.waiting_for_hwid_device_limit, F.text)
async def process_hwid_device_limit_handler(
message: types.Message,
state: FSMContext,
settings: Settings,
i18n_data: dict,
subscription_service: SubscriptionService,
session: AsyncSession,
):
"""Read explicit HWID device limit and apply it."""
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
if not i18n:
await message.reply("Language service error.")
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
data = await state.get_data()
target_user_id = data.get("target_user_id")
if not target_user_id:
await message.answer(_("admin_hwid_limit_state_missing"))
await state.clear()
return
raw = (message.text or "").strip()
try:
hwid_device_limit = int(raw)
if hwid_device_limit < 0 or hwid_device_limit > 1_000_000:
raise ValueError("out_of_range")
except (TypeError, ValueError):
await message.answer(_("admin_hwid_limit_invalid"))
return
target_user = await user_dal.get_user_by_id(session, target_user_id)
if not target_user:
await message.answer(_("admin_user_not_found_action"))
await state.clear()
return
try:
active_sub = await subscription_dal.get_active_subscription_by_user_id(
session, target_user_id
)
if not active_sub:
await message.answer(_("admin_hwid_limit_no_subscription"))
await state.clear()
return
active_sub.hwid_device_limit = hwid_device_limit
effective_limit = await subscription_service.sync_hwid_device_limit_to_panel(
session, target_user_id
)
await message_log_dal.create_message_log_no_commit(
session,
{
"user_id": message.from_user.id if message.from_user else target_user_id,
"event_type": "admin:hwid_device_limit",
"content": (
f"hwid_device_limit={hwid_device_limit!r} "
f"effective_hwid_device_limit={effective_limit!r}"
),
"is_admin_event": True,
"target_user_id": target_user_id,
"timestamp": datetime.now(timezone.utc),
},
)
await session.commit()
current_text = _admin_hwid_limit_state_text(_, hwid_device_limit)
await message.answer(
_("admin_hwid_limit_set", current=current_text, user_id=target_user_id)
)
referral_service = ReferralService(settings, subscription_service, message.bot, i18n)
bot_username = await _resolve_bot_username(message.bot)
user_card_text = await format_user_card(
target_user,
session,
subscription_service,
i18n,
current_lang,
referral_service,
settings=settings,
bot_username=bot_username,
)
keyboard = get_user_card_keyboard(
target_user.user_id, i18n, current_lang, target_user.referred_by_id
)
await _send_with_profile_link_fallback(
message.answer,
text=user_card_text,
markup=keyboard.as_markup(),
user_id=target_user.user_id,
parse_mode="HTML",
)
except Exception as exc:
logging.error(
"Error setting HWID device limit for user %s: %s",
target_user_id,
exc,
exc_info=True,
)
await session.rollback()
await message.answer(_("admin_hwid_limit_save_error"))
finally:
await state.clear()
@router.message(AdminStates.waiting_for_traffic_grant_gb, F.text)
async def process_traffic_grant_gb_handler(
message: types.Message,