feat: tune visual of web app admin panel, tune limit messages and etc
This commit is contained in:
@@ -87,7 +87,7 @@ class CryptoPayService:
|
||||
"currency": self.settings.CRYPTOPAY_ASSET,
|
||||
"status": "pending_cryptopay",
|
||||
"description": description,
|
||||
"subscription_duration_months": int(months),
|
||||
"subscription_duration_months": int(months) if sale_base == "subscription" else None,
|
||||
"provider": "cryptopay",
|
||||
"sale_mode": sale_mode,
|
||||
"tariff_key": sale_mode.split("@", 1)[1] if "@" in sale_mode else None,
|
||||
@@ -263,6 +263,10 @@ class CryptoPayService:
|
||||
logging.exception("Failed to send CryptoPay success message.")
|
||||
|
||||
# Send notification about payment
|
||||
try:
|
||||
payment_row = await payment_dal.get_payment_by_db_id(session, payment_db_id)
|
||||
except Exception:
|
||||
payment_row = None
|
||||
try:
|
||||
notification_service = NotificationService(bot, settings, i18n)
|
||||
user = await user_dal.get_user_by_id(session, user_id)
|
||||
@@ -273,7 +277,9 @@ class CryptoPayService:
|
||||
months=int(months) if sale_base == "subscription" else 0,
|
||||
traffic_gb=traffic_gb if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else None,
|
||||
payment_provider="crypto_pay",
|
||||
username=user.username if user else None
|
||||
username=user.username if user else None,
|
||||
traffic_is_premium=sale_base == "premium_topup",
|
||||
tariff_key=getattr(payment_row, "tariff_key", None) if payment_row else None,
|
||||
)
|
||||
except Exception:
|
||||
logging.exception("Failed to send crypto_pay payment notification.")
|
||||
|
||||
@@ -347,7 +347,7 @@ def render_payment_success(
|
||||
lang = _normalize_lang(language_code, settings)
|
||||
accent = _safe_color(settings.WEBAPP_PRIMARY_COLOR)
|
||||
brand = _brand_title(settings)
|
||||
is_traffic = sale_mode == "traffic"
|
||||
is_traffic = (sale_mode or "").split("@", 1)[0].split("|", 1)[0] in {"traffic", "traffic_package", "topup", "premium_topup"}
|
||||
amount_text = _format_amount(amount, currency)
|
||||
safe_dashboard_url = (dashboard_url or "").strip()
|
||||
end_date = end_date_text or "—"
|
||||
|
||||
@@ -315,6 +315,7 @@ class FreeKassaService:
|
||||
final_end = activation.get("end_date") if activation else None
|
||||
months = payment.purchased_gb or payment.subscription_duration_months or 1
|
||||
sale_mode = payment.sale_mode or ("traffic" if self.settings.traffic_sale_mode else "subscription")
|
||||
sale_base = sale_mode.split("@", 1)[0].split("|", 1)[0]
|
||||
|
||||
applied_days = 0
|
||||
if referral_bonus and referral_bonus.get("referee_new_end_date"):
|
||||
@@ -395,10 +396,12 @@ class FreeKassaService:
|
||||
user_id=payment.user_id,
|
||||
amount=float(payment.amount),
|
||||
currency=self.default_currency,
|
||||
months=int(months) if sale_mode != "traffic" else 0,
|
||||
traffic_gb=months if sale_mode == "traffic" else None,
|
||||
months=int(months) if sale_base == "subscription" else 0,
|
||||
traffic_gb=float(months) if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else None,
|
||||
payment_provider="freekassa",
|
||||
username=db_user.username if db_user else None,
|
||||
traffic_is_premium=sale_base == "premium_topup",
|
||||
tariff_key=getattr(payment, "tariff_key", None),
|
||||
)
|
||||
except Exception:
|
||||
logging.exception("FreeKassa notification: failed to notify admins.")
|
||||
|
||||
@@ -324,10 +324,31 @@ class NotificationService:
|
||||
profile_keyboard = self._build_profile_keyboard(_, telegram_id)
|
||||
await self._send_to_log_channel(message, reply_markup=profile_keyboard)
|
||||
|
||||
def _format_traffic_gb_admin(self, traffic_gb: float) -> str:
|
||||
value = float(traffic_gb)
|
||||
if value.is_integer():
|
||||
return str(int(value))
|
||||
return f"{value:g}"
|
||||
|
||||
def _tariff_display_for_log(self, tariff_key: Optional[str]) -> str:
|
||||
if not tariff_key:
|
||||
return ""
|
||||
cfg = getattr(self.settings, "tariffs_config", None)
|
||||
if not cfg:
|
||||
return str(tariff_key)
|
||||
try:
|
||||
tariff = cfg.require(str(tariff_key))
|
||||
return str(tariff.name(self.settings.DEFAULT_LANGUAGE))
|
||||
except Exception:
|
||||
return str(tariff_key)
|
||||
|
||||
async def notify_payment_received(self, user_id: int, amount: float, currency: str,
|
||||
months: int, payment_provider: str,
|
||||
username: Optional[str] = None,
|
||||
traffic_gb: Optional[float] = None):
|
||||
traffic_gb: Optional[float] = None,
|
||||
*,
|
||||
traffic_is_premium: bool = False,
|
||||
tariff_key: Optional[str] = None):
|
||||
"""Send notification about successful payment"""
|
||||
if not self.settings.LOG_PAYMENTS:
|
||||
return
|
||||
@@ -350,14 +371,25 @@ class NotificationService:
|
||||
}.get(payment_provider.lower(), "💰")
|
||||
|
||||
if traffic_gb is not None:
|
||||
traffic_label = str(int(traffic_gb)) if float(traffic_gb).is_integer() else f"{traffic_gb:g}"
|
||||
traffic_label = self._format_traffic_gb_admin(float(traffic_gb))
|
||||
traffic_kind = _(
|
||||
"log_payment_traffic_kind_premium" if traffic_is_premium else "log_payment_traffic_kind_regular",
|
||||
)
|
||||
traffic_summary = _("log_payment_traffic_purchase_line", gb=traffic_label, kind=traffic_kind)
|
||||
tariff_name = self._tariff_display_for_log(tariff_key)
|
||||
tariff_line = (
|
||||
_("log_payment_tariff_line", name=hd.quote(tariff_name))
|
||||
if tariff_name
|
||||
else ""
|
||||
)
|
||||
message = _(
|
||||
"log_payment_received_traffic",
|
||||
provider_emoji=provider_emoji,
|
||||
user_display=user_display,
|
||||
amount=amount,
|
||||
currency=currency,
|
||||
traffic_gb=traffic_label,
|
||||
traffic_summary=traffic_summary,
|
||||
tariff_line=tariff_line,
|
||||
payment_provider=payment_provider,
|
||||
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
)
|
||||
|
||||
@@ -318,10 +318,12 @@ class PlategaService:
|
||||
user_id=payment.user_id,
|
||||
amount=float(payment.amount),
|
||||
currency=currency,
|
||||
months=int(payment_months) if sale_mode != "traffic" else 0,
|
||||
traffic_gb=payment_months if sale_mode == "traffic" else None,
|
||||
months=int(payment_months) if sale_base == "subscription" else 0,
|
||||
traffic_gb=float(payment_months) if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else None,
|
||||
payment_provider="platega",
|
||||
username=db_user.username if db_user else None,
|
||||
traffic_is_premium=sale_base == "premium_topup",
|
||||
tariff_key=getattr(payment, "tariff_key", None),
|
||||
)
|
||||
except Exception:
|
||||
logging.exception("Platega webhook: failed to notify admins.")
|
||||
|
||||
@@ -314,10 +314,12 @@ class SeverPayService:
|
||||
user_id=payment.user_id,
|
||||
amount=float(payment.amount),
|
||||
currency=payment.currency,
|
||||
months=int(payment_months) if sale_mode != "traffic" else 0,
|
||||
traffic_gb=payment_months if sale_mode == "traffic" else None,
|
||||
months=int(payment_months) if sale_base == "subscription" else 0,
|
||||
traffic_gb=float(payment_months) if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else None,
|
||||
payment_provider="severpay",
|
||||
username=db_user.username if db_user else None,
|
||||
traffic_is_premium=sale_base == "premium_topup",
|
||||
tariff_key=getattr(payment, "tariff_key", None),
|
||||
)
|
||||
except Exception:
|
||||
logging.exception("SeverPay webhook: failed to notify admins.")
|
||||
|
||||
@@ -35,7 +35,7 @@ class StarsService:
|
||||
"currency": "XTR",
|
||||
"status": "pending_stars",
|
||||
"description": description,
|
||||
"subscription_duration_months": int(months),
|
||||
"subscription_duration_months": int(months) if sale_base == "subscription" else None,
|
||||
"provider": "telegram_stars",
|
||||
"sale_mode": sale_mode,
|
||||
"tariff_key": sale_mode.split("@", 1)[1] if "@" in sale_mode else None,
|
||||
@@ -201,7 +201,9 @@ class StarsService:
|
||||
months=int(months) if sale_base == "subscription" else 0,
|
||||
payment_provider="stars",
|
||||
username=user.username if user else None,
|
||||
traffic_gb=months if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else None,
|
||||
traffic_gb=float(months) if sale_base in {"traffic", "traffic_package", "topup", "premium_topup"} else None,
|
||||
traffic_is_premium=sale_base == "premium_topup",
|
||||
tariff_key=getattr(payment_record, "tariff_key", None),
|
||||
)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to send stars payment notification: {e}")
|
||||
|
||||
@@ -85,10 +85,25 @@ class SubscriptionService:
|
||||
return list(dict.fromkeys(squads))
|
||||
return self.settings.parsed_user_squad_uuids
|
||||
|
||||
def _traffic_limit_for_period_tariff(self, tariff: Optional[Tariff], topup_balance_bytes: int = 0) -> int:
|
||||
def _traffic_limit_for_period_tariff(
|
||||
self,
|
||||
tariff: Optional[Tariff],
|
||||
topup_balance_bytes: int = 0,
|
||||
regular_bonus_bytes: int = 0,
|
||||
regular_unlimited_override: bool = False,
|
||||
traffic_used_bytes: int = 0,
|
||||
) -> int:
|
||||
if tariff:
|
||||
return int(tariff.monthly_bytes + max(0, topup_balance_bytes))
|
||||
return self.settings.user_traffic_limit_bytes
|
||||
baseline = int(tariff.monthly_bytes or 0)
|
||||
else:
|
||||
baseline = int(self.settings.user_traffic_limit_bytes)
|
||||
return self._compute_main_traffic_limit_bytes(
|
||||
tier_baseline_bytes=baseline,
|
||||
topup_balance_bytes=topup_balance_bytes,
|
||||
regular_bonus_bytes=regular_bonus_bytes,
|
||||
regular_unlimited_override=regular_unlimited_override,
|
||||
traffic_used_bytes=traffic_used_bytes,
|
||||
)
|
||||
|
||||
def _premium_limit_for_tariff(self, tariff: Optional[Tariff], topup_balance_bytes: int = 0) -> int:
|
||||
if not tariff:
|
||||
@@ -100,11 +115,35 @@ class SubscriptionService:
|
||||
premium_baseline_bytes: int,
|
||||
premium_topup_balance_bytes: int = 0,
|
||||
premium_topup_used_bytes: int = 0,
|
||||
premium_bonus_bytes: int = 0,
|
||||
) -> int:
|
||||
return int(premium_baseline_bytes or 0) + max(0, int(premium_topup_balance_bytes or 0)) + max(
|
||||
0, int(premium_topup_used_bytes or 0)
|
||||
return (
|
||||
int(premium_baseline_bytes or 0)
|
||||
+ max(0, int(premium_topup_balance_bytes or 0))
|
||||
+ max(0, int(premium_topup_used_bytes or 0))
|
||||
+ max(0, int(premium_bonus_bytes or 0))
|
||||
)
|
||||
|
||||
def _compute_main_traffic_limit_bytes(
|
||||
self,
|
||||
*,
|
||||
tier_baseline_bytes: int,
|
||||
topup_balance_bytes: int,
|
||||
regular_bonus_bytes: int,
|
||||
regular_unlimited_override: bool,
|
||||
traffic_used_bytes: int,
|
||||
) -> int:
|
||||
"""Numeric cap sent to the panel; ``regular_unlimited_override`` uses a large practical ceiling."""
|
||||
floor = (
|
||||
int(tier_baseline_bytes or 0)
|
||||
+ max(0, int(topup_balance_bytes or 0))
|
||||
+ max(0, int(regular_bonus_bytes or 0))
|
||||
)
|
||||
if regular_unlimited_override:
|
||||
used = max(0, int(traffic_used_bytes or 0))
|
||||
return max(floor, used + 512 * (1024 ** 3), 1024 ** 5)
|
||||
return floor
|
||||
|
||||
async def premium_access_for_tariff(self, tariff: Optional[Tariff]) -> Dict[str, Any]:
|
||||
if not tariff or not tariff.premium_squad_uuids:
|
||||
return {"squad_uuids": [], "squad_labels": [], "node_labels": []}
|
||||
@@ -949,7 +988,17 @@ class SubscriptionService:
|
||||
|
||||
purchase_bytes = self.gb_to_bytes(traffic_gb)
|
||||
new_topup_balance = int(sub.topup_balance_bytes or 0) + purchase_bytes
|
||||
new_limit = int(sub.tier_baseline_bytes or tariff.monthly_bytes) + new_topup_balance
|
||||
baseline = int(sub.tier_baseline_bytes or tariff.monthly_bytes)
|
||||
rb = int(getattr(sub, "regular_bonus_bytes", 0) or 0)
|
||||
runl = bool(getattr(sub, "regular_unlimited_override", False))
|
||||
used_for_lim = int(getattr(sub, "traffic_used_bytes", 0) or 0)
|
||||
new_limit = self._compute_main_traffic_limit_bytes(
|
||||
tier_baseline_bytes=baseline,
|
||||
topup_balance_bytes=new_topup_balance,
|
||||
regular_bonus_bytes=rb,
|
||||
regular_unlimited_override=runl,
|
||||
traffic_used_bytes=used_for_lim,
|
||||
)
|
||||
base_hwid_limit = (
|
||||
int(sub.hwid_device_limit)
|
||||
if sub.hwid_device_limit is not None
|
||||
@@ -1084,6 +1133,277 @@ class SubscriptionService:
|
||||
"tariff_key": tariff.key,
|
||||
}
|
||||
|
||||
async def admin_grant_topup(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
user_id: int,
|
||||
traffic_gb: float,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Credit regular traffic to a user as if they purchased a top-up.
|
||||
|
||||
Mirrors :meth:`activate_topup` but skips payment context and tariff
|
||||
resolution: the grant simply increases ``topup_balance_bytes`` and
|
||||
recomputes ``traffic_limit_bytes`` from the subscription's current
|
||||
tier baseline. The audit row in ``traffic_topups`` is stored with
|
||||
``kind="admin_topup"`` and ``payment_id=NULL`` so reports stay clean.
|
||||
"""
|
||||
try:
|
||||
gb_value = float(traffic_gb)
|
||||
except (TypeError, ValueError):
|
||||
logging.error("admin_grant_topup: invalid traffic_gb=%r", traffic_gb)
|
||||
return None
|
||||
if gb_value <= 0:
|
||||
return None
|
||||
|
||||
db_user = await user_dal.get_user_by_id(session, user_id)
|
||||
if not db_user or not db_user.panel_user_uuid:
|
||||
return None
|
||||
sub = await subscription_dal.get_active_subscription_by_user_id(
|
||||
session, user_id, db_user.panel_user_uuid
|
||||
)
|
||||
if not sub:
|
||||
return None
|
||||
|
||||
tariff = self._resolve_tariff(sub.tariff_key) if sub.tariff_key else None
|
||||
purchase_bytes = self.gb_to_bytes(gb_value)
|
||||
baseline_bytes = int(sub.tier_baseline_bytes or (tariff.monthly_bytes if tariff else 0) or 0)
|
||||
new_topup_balance = int(sub.topup_balance_bytes or 0) + purchase_bytes
|
||||
rb = int(getattr(sub, "regular_bonus_bytes", 0) or 0)
|
||||
runl = bool(getattr(sub, "regular_unlimited_override", False))
|
||||
used_for_lim = int(getattr(sub, "traffic_used_bytes", 0) or 0)
|
||||
new_limit = self._compute_main_traffic_limit_bytes(
|
||||
tier_baseline_bytes=baseline_bytes,
|
||||
topup_balance_bytes=new_topup_balance,
|
||||
regular_bonus_bytes=rb,
|
||||
regular_unlimited_override=runl,
|
||||
traffic_used_bytes=used_for_lim,
|
||||
)
|
||||
base_hwid_limit = (
|
||||
int(sub.hwid_device_limit)
|
||||
if sub.hwid_device_limit is not None
|
||||
else self._base_hwid_limit_for_tariff(tariff)
|
||||
)
|
||||
effective_hwid_limit = self._effective_hwid_limit(
|
||||
base_hwid_limit,
|
||||
int(sub.extra_hwid_devices or 0),
|
||||
)
|
||||
updated_sub = await subscription_dal.update_subscription(
|
||||
session,
|
||||
sub.subscription_id,
|
||||
{
|
||||
"topup_balance_bytes": new_topup_balance,
|
||||
"traffic_limit_bytes": new_limit,
|
||||
"is_throttled": False,
|
||||
"hwid_device_limit": base_hwid_limit,
|
||||
},
|
||||
)
|
||||
panel_payload = self._build_panel_update_payload(
|
||||
panel_user_uuid=db_user.panel_user_uuid,
|
||||
expire_at=updated_sub.end_date,
|
||||
status="ACTIVE",
|
||||
traffic_limit_bytes=new_limit,
|
||||
hwid_device_limit=effective_hwid_limit,
|
||||
)
|
||||
if tariff is not None:
|
||||
panel_payload["activeInternalSquads"] = self._panel_squads_for_tariff(
|
||||
tariff,
|
||||
include_premium=not bool(getattr(updated_sub, "premium_is_limited", False)),
|
||||
)
|
||||
panel_payload.update(self._panel_identity_payload_for_user(db_user))
|
||||
try:
|
||||
await self.panel_service.update_user_details_on_panel(
|
||||
db_user.panel_user_uuid, panel_payload
|
||||
)
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"admin_grant_topup: failed to push panel update for user %s", user_id
|
||||
)
|
||||
await tariff_dal.create_traffic_topup(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
payment_id=None,
|
||||
purchased_bytes=purchase_bytes,
|
||||
kind="admin_topup",
|
||||
)
|
||||
return {
|
||||
"subscription_id": sub.subscription_id,
|
||||
"traffic_limit_bytes": new_limit,
|
||||
"topup_balance_bytes": new_topup_balance,
|
||||
"granted_bytes": purchase_bytes,
|
||||
}
|
||||
|
||||
async def sync_main_traffic_limit_to_panel(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
user_id: int,
|
||||
) -> None:
|
||||
"""Recompute main traffic limit from tier + topups + regular_bonus_bytes and push to panel."""
|
||||
db_user = await user_dal.get_user_by_id(session, user_id)
|
||||
if not db_user or not db_user.panel_user_uuid:
|
||||
return
|
||||
sub = await subscription_dal.get_active_subscription_by_user_id(
|
||||
session, user_id, db_user.panel_user_uuid
|
||||
)
|
||||
if not sub:
|
||||
return
|
||||
tariff = self._resolve_tariff(sub.tariff_key) if sub.tariff_key else None
|
||||
baseline = int(sub.tier_baseline_bytes or (tariff.monthly_bytes if tariff else 0) or 0)
|
||||
rb = int(getattr(sub, "regular_bonus_bytes", 0) or 0)
|
||||
runl = bool(getattr(sub, "regular_unlimited_override", False))
|
||||
used_now = int(getattr(sub, "traffic_used_bytes", 0) or 0)
|
||||
new_limit = self._compute_main_traffic_limit_bytes(
|
||||
tier_baseline_bytes=baseline,
|
||||
topup_balance_bytes=int(sub.topup_balance_bytes or 0),
|
||||
regular_bonus_bytes=rb,
|
||||
regular_unlimited_override=runl,
|
||||
traffic_used_bytes=used_now,
|
||||
)
|
||||
sub.traffic_limit_bytes = new_limit
|
||||
if runl:
|
||||
sub.is_throttled = False
|
||||
base_hwid_limit = (
|
||||
int(sub.hwid_device_limit)
|
||||
if sub.hwid_device_limit is not None
|
||||
else self._base_hwid_limit_for_tariff(tariff)
|
||||
)
|
||||
effective_hwid_limit = self._effective_hwid_limit(
|
||||
base_hwid_limit,
|
||||
int(sub.extra_hwid_devices or 0),
|
||||
)
|
||||
panel_payload = self._build_panel_update_payload(
|
||||
panel_user_uuid=db_user.panel_user_uuid,
|
||||
expire_at=sub.end_date,
|
||||
status="ACTIVE",
|
||||
traffic_limit_bytes=new_limit,
|
||||
hwid_device_limit=effective_hwid_limit,
|
||||
)
|
||||
if tariff is not None:
|
||||
panel_payload["activeInternalSquads"] = self._panel_squads_for_tariff(
|
||||
tariff,
|
||||
include_premium=not bool(getattr(sub, "premium_is_limited", False)),
|
||||
)
|
||||
panel_payload.update(self._panel_identity_payload_for_user(db_user))
|
||||
try:
|
||||
await self.panel_service.update_user_details_on_panel(
|
||||
db_user.panel_user_uuid, panel_payload
|
||||
)
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"sync_main_traffic_limit_to_panel failed for user %s", user_id
|
||||
)
|
||||
|
||||
async def admin_grant_premium_topup(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
user_id: int,
|
||||
traffic_gb: float,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Credit premium-squad traffic to a user as if they purchased a premium top-up.
|
||||
|
||||
Mirrors :meth:`activate_premium_topup` but skips payment context.
|
||||
Requires the user's current tariff to expose premium squads. The
|
||||
balance is absorbed into ``premium_topup_balance_bytes`` (backfilling
|
||||
any current overuse first), ``premium_is_limited`` is recomputed and,
|
||||
if access becomes available again, the premium squads are returned to
|
||||
the user on the panel. The audit row in ``traffic_topups`` is stored
|
||||
with ``kind="admin_premium_topup"`` and ``payment_id=NULL``.
|
||||
"""
|
||||
try:
|
||||
gb_value = float(traffic_gb)
|
||||
except (TypeError, ValueError):
|
||||
logging.error("admin_grant_premium_topup: invalid traffic_gb=%r", traffic_gb)
|
||||
return None
|
||||
if gb_value <= 0:
|
||||
return None
|
||||
|
||||
db_user = await user_dal.get_user_by_id(session, user_id)
|
||||
if not db_user or not db_user.panel_user_uuid:
|
||||
return None
|
||||
sub = await subscription_dal.get_active_subscription_by_user_id(
|
||||
session, user_id, db_user.panel_user_uuid
|
||||
)
|
||||
if not sub:
|
||||
return None
|
||||
tariff = self._resolve_tariff(sub.tariff_key) if sub.tariff_key else None
|
||||
if not tariff or not tariff.premium_squad_uuids:
|
||||
logging.error(
|
||||
"admin_grant_premium_topup: tariff %s has no premium squads (user %s)",
|
||||
getattr(tariff, "key", None),
|
||||
user_id,
|
||||
)
|
||||
return None
|
||||
|
||||
purchase_bytes = self.gb_to_bytes(gb_value)
|
||||
now = datetime.now(timezone.utc)
|
||||
premium_period_start = month_start(now)
|
||||
current_period_start = getattr(sub, "premium_period_start_at", None)
|
||||
same_period = bool(current_period_start and current_period_start == premium_period_start)
|
||||
previous_topup_used = int(sub.premium_topup_used_bytes or 0) if same_period else 0
|
||||
premium_used = int(sub.premium_used_bytes or 0) if same_period else 0
|
||||
premium_baseline = int(tariff.premium_monthly_bytes or sub.premium_baseline_bytes or 0)
|
||||
premium_bonus = max(0, int(getattr(sub, "premium_bonus_bytes", 0) or 0))
|
||||
premium_topup_balance = int(sub.premium_topup_balance_bytes or 0) + purchase_bytes
|
||||
overflow_to_cover = max(
|
||||
0, premium_used - premium_baseline - previous_topup_used - premium_bonus
|
||||
)
|
||||
consume_now = min(premium_topup_balance, overflow_to_cover)
|
||||
premium_topup_balance -= consume_now
|
||||
premium_topup_used = previous_topup_used + consume_now
|
||||
premium_limit = self._premium_effective_limit_bytes(
|
||||
premium_baseline,
|
||||
premium_topup_balance,
|
||||
premium_topup_used,
|
||||
premium_bonus,
|
||||
)
|
||||
premium_unlimited = bool(getattr(sub, "premium_unlimited_override", False))
|
||||
premium_is_limited = (
|
||||
not premium_unlimited and premium_limit > 0 and premium_used >= premium_limit
|
||||
)
|
||||
|
||||
updated_sub = await subscription_dal.update_subscription(
|
||||
session,
|
||||
sub.subscription_id,
|
||||
{
|
||||
"premium_baseline_bytes": premium_baseline,
|
||||
"premium_topup_balance_bytes": premium_topup_balance,
|
||||
"premium_topup_used_bytes": premium_topup_used,
|
||||
"premium_used_bytes": premium_used,
|
||||
"premium_is_limited": premium_is_limited,
|
||||
"premium_period_start_at": premium_period_start,
|
||||
},
|
||||
)
|
||||
panel_payload = {
|
||||
"uuid": db_user.panel_user_uuid,
|
||||
"activeInternalSquads": self._panel_squads_for_tariff(
|
||||
tariff,
|
||||
include_premium=not premium_is_limited,
|
||||
),
|
||||
}
|
||||
try:
|
||||
await self.panel_service.update_user_details_on_panel(
|
||||
db_user.panel_user_uuid, panel_payload
|
||||
)
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"admin_grant_premium_topup: failed to push panel update for user %s",
|
||||
user_id,
|
||||
)
|
||||
await tariff_dal.create_traffic_topup(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
payment_id=None,
|
||||
purchased_bytes=purchase_bytes,
|
||||
kind="admin_premium_topup",
|
||||
)
|
||||
return {
|
||||
"subscription_id": sub.subscription_id,
|
||||
"premium_limit_bytes": premium_limit,
|
||||
"premium_topup_balance_bytes": premium_topup_balance,
|
||||
"premium_topup_used_bytes": premium_topup_used,
|
||||
"premium_is_limited": premium_is_limited,
|
||||
"granted_bytes": purchase_bytes,
|
||||
}
|
||||
|
||||
async def activate_hwid_device_topup(
|
||||
self,
|
||||
session: AsyncSession,
|
||||
@@ -1272,7 +1592,16 @@ class SubscriptionService:
|
||||
|
||||
if target.billing_model == "period":
|
||||
update_data["tier_baseline_bytes"] = target.monthly_bytes
|
||||
update_data["traffic_limit_bytes"] = target.monthly_bytes + int(sub.topup_balance_bytes or 0)
|
||||
rb = int(getattr(sub, "regular_bonus_bytes", 0) or 0)
|
||||
runl = bool(getattr(sub, "regular_unlimited_override", False))
|
||||
used_sub = int(sub.traffic_used_bytes or 0)
|
||||
update_data["traffic_limit_bytes"] = self._compute_main_traffic_limit_bytes(
|
||||
tier_baseline_bytes=target.monthly_bytes,
|
||||
topup_balance_bytes=int(sub.topup_balance_bytes or 0),
|
||||
regular_bonus_bytes=rb,
|
||||
regular_unlimited_override=runl,
|
||||
traffic_used_bytes=used_sub,
|
||||
)
|
||||
update_data["period_start_at"] = None
|
||||
update_data["effective_monthly_price_rub"] = target.period_price(1, "rub") or target.min_period_price_rub()
|
||||
if mode == "recalc_days" and options.get("recalc_days") is not None:
|
||||
@@ -1282,15 +1611,24 @@ class SubscriptionService:
|
||||
converted_bytes = self.gb_to_bytes(converted_gb)
|
||||
old_topup = int(sub.topup_balance_bytes or 0)
|
||||
new_balance = old_topup + converted_bytes
|
||||
rb = int(getattr(sub, "regular_bonus_bytes", 0) or 0)
|
||||
runl = bool(getattr(sub, "regular_unlimited_override", False))
|
||||
panel_user = await self.panel_service.get_user_by_uuid(db_user.panel_user_uuid, log_response=False) or {}
|
||||
current_used, _, _ = self._extract_panel_traffic_details(panel_user)
|
||||
cur_used_int = int(current_used or 0)
|
||||
update_data.update(
|
||||
{
|
||||
"end_date": self._far_future(),
|
||||
"period_start_at": None,
|
||||
"tier_baseline_bytes": 0,
|
||||
"topup_balance_bytes": new_balance,
|
||||
"traffic_limit_bytes": int(current_used or 0) + new_balance,
|
||||
"traffic_limit_bytes": self._compute_main_traffic_limit_bytes(
|
||||
tier_baseline_bytes=0,
|
||||
topup_balance_bytes=new_balance,
|
||||
regular_bonus_bytes=rb,
|
||||
regular_unlimited_override=runl,
|
||||
traffic_used_bytes=cur_used_int,
|
||||
),
|
||||
"traffic_used_bytes": current_used,
|
||||
"effective_monthly_price_rub": None,
|
||||
"auto_renew_enabled": False,
|
||||
@@ -1575,7 +1913,15 @@ class SubscriptionService:
|
||||
premium_topup_used_bytes,
|
||||
)
|
||||
effective_monthly_price = float(payment_amount) / max(1, months_int)
|
||||
traffic_limit_bytes = self._traffic_limit_for_period_tariff(tariff, topup_balance_bytes)
|
||||
regular_bonus_carry = int(getattr(current_active_sub, "regular_bonus_bytes", 0) or 0)
|
||||
regular_unl_carry = bool(getattr(current_active_sub, "regular_unlimited_override", False))
|
||||
traffic_limit_bytes = self._traffic_limit_for_period_tariff(
|
||||
tariff,
|
||||
topup_balance_bytes,
|
||||
regular_bonus_carry,
|
||||
regular_unlimited_override=regular_unl_carry,
|
||||
traffic_used_bytes=0,
|
||||
)
|
||||
base_hwid_limit = self._base_hwid_limit_for_tariff(tariff)
|
||||
effective_hwid_limit = self._effective_hwid_limit(base_hwid_limit, extra_hwid_devices)
|
||||
premium_is_limited = bool(premium_limit_bytes > 0 and premium_used_bytes >= premium_limit_bytes)
|
||||
@@ -1595,6 +1941,8 @@ class SubscriptionService:
|
||||
"tariff_key": tariff.key if tariff else None,
|
||||
"tier_baseline_bytes": tier_baseline_bytes,
|
||||
"topup_balance_bytes": topup_balance_bytes,
|
||||
"regular_bonus_bytes": regular_bonus_carry,
|
||||
"regular_unlimited_override": regular_unl_carry,
|
||||
"premium_baseline_bytes": premium_baseline_bytes,
|
||||
"premium_topup_balance_bytes": premium_topup_balance_bytes,
|
||||
"premium_topup_used_bytes": premium_topup_used_bytes,
|
||||
@@ -1905,6 +2253,10 @@ class SubscriptionService:
|
||||
premium_baseline = int(local_active_sub.premium_baseline_bytes or 0) if local_active_sub else 0
|
||||
premium_topup_balance = int(local_active_sub.premium_topup_balance_bytes or 0) if local_active_sub else 0
|
||||
premium_topup_used = int(getattr(local_active_sub, "premium_topup_used_bytes", 0) or 0) if local_active_sub else 0
|
||||
premium_bonus_bytes = int(getattr(local_active_sub, "premium_bonus_bytes", 0) or 0) if local_active_sub else 0
|
||||
premium_unlimited_override = bool(getattr(local_active_sub, "premium_unlimited_override", False)) if local_active_sub else False
|
||||
regular_bonus_bytes = int(getattr(local_active_sub, "regular_bonus_bytes", 0) or 0) if local_active_sub else 0
|
||||
regular_unlimited_override = bool(getattr(local_active_sub, "regular_unlimited_override", False)) if local_active_sub else False
|
||||
|
||||
return {
|
||||
"user_id": panel_user_data.get("uuid"),
|
||||
@@ -1922,14 +2274,19 @@ class SubscriptionService:
|
||||
"billing_model": billing_model_display,
|
||||
"tier_baseline_bytes": local_active_sub.tier_baseline_bytes if local_active_sub else None,
|
||||
"topup_balance_bytes": local_active_sub.topup_balance_bytes if local_active_sub else 0,
|
||||
"regular_bonus_bytes": regular_bonus_bytes,
|
||||
"regular_unlimited_override": regular_unlimited_override,
|
||||
"premium_baseline_bytes": premium_baseline,
|
||||
"premium_topup_balance_bytes": premium_topup_balance,
|
||||
"premium_topup_used_bytes": premium_topup_used,
|
||||
"premium_used_bytes": local_active_sub.premium_used_bytes if local_active_sub else 0,
|
||||
"premium_bonus_bytes": premium_bonus_bytes,
|
||||
"premium_unlimited_override": premium_unlimited_override,
|
||||
"premium_limit_bytes": self._premium_effective_limit_bytes(
|
||||
premium_baseline,
|
||||
premium_topup_balance,
|
||||
premium_topup_used,
|
||||
premium_bonus_bytes,
|
||||
),
|
||||
"premium_is_limited": bool(local_active_sub.premium_is_limited) if local_active_sub else False,
|
||||
"premium_period_start_at": getattr(local_active_sub, "premium_period_start_at", None) if local_active_sub else None,
|
||||
|
||||
+197
-49
@@ -4,7 +4,8 @@ from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, WebAppInfo
|
||||
from aiogram.utils.text_decorations import html_decoration as hd
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
@@ -14,10 +15,13 @@ from bot.services.panel_api_service import PanelApiService
|
||||
from bot.services.subscription_service import SubscriptionService
|
||||
from bot.utils.date_utils import month_start
|
||||
from config.settings import Settings
|
||||
from db.dal import subscription_dal, tariff_dal
|
||||
from bot.utils.mini_app_url import subscription_mini_app_topup_url
|
||||
from db.dal import subscription_dal, tariff_dal, user_dal
|
||||
from db.models import Subscription
|
||||
|
||||
PREMIUM_WARNING_LEVEL_OFFSET = 1000
|
||||
# Single warning per premium billing period when usage reached or exceeded the quota.
|
||||
PREMIUM_WARNING_DEPLETED_LEVEL = PREMIUM_WARNING_LEVEL_OFFSET + 100
|
||||
|
||||
|
||||
class TariffTrafficWorker:
|
||||
@@ -39,6 +43,47 @@ class TariffTrafficWorker:
|
||||
self._stopped = asyncio.Event()
|
||||
self._premium_nodes_cache = {}
|
||||
|
||||
async def _user_lang(self, session: AsyncSession, user_id: int) -> str:
|
||||
try:
|
||||
row = await user_dal.get_user_by_id(session, user_id)
|
||||
if row and getattr(row, "language_code", None):
|
||||
code = str(row.language_code or "").strip()
|
||||
if code:
|
||||
return code
|
||||
except Exception:
|
||||
logging.exception("TariffTrafficWorker: failed to load user language for %s", user_id)
|
||||
return self.settings.DEFAULT_LANGUAGE
|
||||
|
||||
def _usage_placeholders(self, used_bytes: int, limit_bytes: int) -> dict:
|
||||
"""Formatted traffic stats for warning messages (HTML-safe quoted)."""
|
||||
used_b = max(0, int(used_bytes or 0))
|
||||
lim_b = max(0, int(limit_bytes or 0))
|
||||
remaining_b = max(0, lim_b - used_b)
|
||||
return {
|
||||
"used": hd.quote(self._fmt_bytes(used_b)),
|
||||
"remaining": hd.quote(self._fmt_bytes(remaining_b)),
|
||||
"limit_total": hd.quote(self._fmt_bytes(lim_b)),
|
||||
}
|
||||
|
||||
def _traffic_topup_markup(self, user_lang: str, kind: str) -> Optional[InlineKeyboardMarkup]:
|
||||
if not self.bot:
|
||||
return None
|
||||
_ = lambda k, **kw: self.i18n.gettext(user_lang, k, **kw) if self.i18n else (lambda key, **_: key)
|
||||
normalized = "premium" if str(kind or "").lower() == "premium" else "regular"
|
||||
url = subscription_mini_app_topup_url(self.settings, normalized)
|
||||
if normalized == "premium":
|
||||
label_key = "traffic_warn_btn_topup_webapp_premium"
|
||||
fallback_key = "traffic_warn_btn_topup_premium"
|
||||
else:
|
||||
label_key = "traffic_warn_btn_topup_webapp_regular"
|
||||
fallback_key = "traffic_warn_btn_topup_regular"
|
||||
# Mini App inside Telegram when SUBSCRIPTION_MINI_APP_URL is configured.
|
||||
if url:
|
||||
button = InlineKeyboardButton(text=_(label_key), web_app=WebAppInfo(url=url))
|
||||
else:
|
||||
button = InlineKeyboardButton(text=_(fallback_key), callback_data="tariff_topup:list")
|
||||
return InlineKeyboardMarkup(inline_keyboard=[[button]])
|
||||
|
||||
async def run(self) -> None:
|
||||
if not self.settings.tariffs_config:
|
||||
return
|
||||
@@ -108,7 +153,22 @@ class TariffTrafficWorker:
|
||||
) -> None:
|
||||
if str(panel_strategy or "").upper() == "MONTH":
|
||||
return
|
||||
traffic_limit_bytes = int(limit or sub.traffic_limit_bytes or (tariff.monthly_bytes + int(sub.topup_balance_bytes or 0)))
|
||||
rb = int(getattr(sub, "regular_bonus_bytes", 0) or 0)
|
||||
if bool(getattr(sub, "regular_unlimited_override", False)):
|
||||
baseline = int(sub.tier_baseline_bytes or (tariff.monthly_bytes if tariff else 0) or 0)
|
||||
traffic_limit_bytes = self.subscription_service._compute_main_traffic_limit_bytes(
|
||||
tier_baseline_bytes=baseline,
|
||||
topup_balance_bytes=int(sub.topup_balance_bytes or 0),
|
||||
regular_bonus_bytes=rb,
|
||||
regular_unlimited_override=True,
|
||||
traffic_used_bytes=int(sub.traffic_used_bytes or 0),
|
||||
)
|
||||
else:
|
||||
traffic_limit_bytes = int(
|
||||
limit
|
||||
or sub.traffic_limit_bytes
|
||||
or (tariff.monthly_bytes + int(sub.topup_balance_bytes or 0) + rb)
|
||||
)
|
||||
payload = self.subscription_service._build_panel_update_payload(
|
||||
panel_user_uuid=sub.panel_user_uuid,
|
||||
expire_at=sub.end_date,
|
||||
@@ -131,6 +191,8 @@ class TariffTrafficWorker:
|
||||
*,
|
||||
warning_period_start: Optional[datetime] = None,
|
||||
) -> None:
|
||||
if bool(getattr(sub, "regular_unlimited_override", False)):
|
||||
return
|
||||
used_val = int(used or sub.traffic_used_bytes or 0)
|
||||
limit_val = int(limit or sub.traffic_limit_bytes or 0)
|
||||
if limit_val <= 0:
|
||||
@@ -159,22 +221,35 @@ class TariffTrafficWorker:
|
||||
)
|
||||
if self.bot:
|
||||
try:
|
||||
user_lang = await self._user_lang(session, sub.user_id)
|
||||
_ = (
|
||||
(lambda k, **kw: self.i18n.gettext(user_lang, k, **kw))
|
||||
if self.i18n
|
||||
else (lambda k, **kw: k)
|
||||
)
|
||||
left_pct = max(0, 100 - level)
|
||||
tariff_name = hd.quote(str(tariff.name(user_lang)))
|
||||
usage = self._usage_placeholders(used_val, limit_val)
|
||||
if level < 100:
|
||||
text = f"Трафик тарифа {tariff.name(self.settings.DEFAULT_LANGUAGE)} почти закончился. Осталось около {left_pct}%."
|
||||
text = _(
|
||||
"traffic_warning_regular_almost",
|
||||
tariff_name=tariff_name,
|
||||
left_pct=left_pct,
|
||||
**usage,
|
||||
)
|
||||
else:
|
||||
text = "Трафик закончился. Доступ временно ограничен до сброса или докупки пакета."
|
||||
markup = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text="Докупить трафик",
|
||||
callback_data="tariff_topup:list",
|
||||
)
|
||||
]
|
||||
]
|
||||
text = _(
|
||||
"traffic_warning_regular_depleted",
|
||||
tariff_name=tariff_name,
|
||||
**usage,
|
||||
)
|
||||
markup = self._traffic_topup_markup(user_lang, "regular")
|
||||
await self.bot.send_message(
|
||||
sub.user_id,
|
||||
text,
|
||||
reply_markup=markup,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await self.bot.send_message(sub.user_id, text, reply_markup=markup)
|
||||
except Exception:
|
||||
logging.exception("Failed to send traffic warning to user %s", sub.user_id)
|
||||
if ratio >= 1.0 and not sub.is_throttled:
|
||||
@@ -214,8 +289,11 @@ class TariffTrafficWorker:
|
||||
premium_baseline = int(tariff.premium_monthly_bytes or 0)
|
||||
premium_topup_balance = int(sub.premium_topup_balance_bytes or 0)
|
||||
premium_topup_used = int(getattr(sub, "premium_topup_used_bytes", 0) or 0) if same_period else 0
|
||||
premium_limit = premium_baseline + premium_topup_balance + premium_topup_used
|
||||
if premium_limit <= 0:
|
||||
# Admin-side overrides for free gifted premium traffic.
|
||||
premium_unlimited_override = bool(getattr(sub, "premium_unlimited_override", False))
|
||||
premium_bonus = max(0, int(getattr(sub, "premium_bonus_bytes", 0) or 0))
|
||||
premium_limit = premium_baseline + premium_topup_balance + premium_topup_used + premium_bonus
|
||||
if premium_limit <= 0 and not premium_unlimited_override:
|
||||
return
|
||||
|
||||
node_uuids = await self._premium_node_uuids_for_tariff(tariff)
|
||||
@@ -235,15 +313,22 @@ class TariffTrafficWorker:
|
||||
if premium_used is None:
|
||||
return
|
||||
|
||||
overflow = max(0, int(premium_used) - premium_baseline)
|
||||
# Consume paid top-up balance only for overflow beyond baseline+bonus.
|
||||
# Admin-granted bonus is "spent" against usage first along with baseline,
|
||||
# so the user's paid top-up survives longer.
|
||||
free_quota = premium_baseline + premium_bonus
|
||||
overflow = max(0, int(premium_used) - free_quota)
|
||||
delta_overflow = max(0, overflow - premium_topup_used)
|
||||
consume_from_topup = min(premium_topup_balance, delta_overflow)
|
||||
if consume_from_topup > 0:
|
||||
premium_topup_balance -= consume_from_topup
|
||||
premium_topup_used += consume_from_topup
|
||||
premium_limit = premium_baseline + premium_topup_balance + premium_topup_used
|
||||
premium_limit = premium_baseline + premium_topup_balance + premium_topup_used + premium_bonus
|
||||
|
||||
should_limit = premium_used >= premium_limit
|
||||
if premium_unlimited_override:
|
||||
should_limit = False
|
||||
else:
|
||||
should_limit = premium_used >= premium_limit
|
||||
changed = (
|
||||
int(sub.premium_baseline_bytes or 0) != premium_baseline
|
||||
or int(sub.premium_topup_balance_bytes or 0) != premium_topup_balance
|
||||
@@ -258,14 +343,15 @@ class TariffTrafficWorker:
|
||||
sub.premium_used_bytes = int(premium_used)
|
||||
sub.premium_is_limited = bool(should_limit)
|
||||
sub.premium_period_start_at = premium_period_start
|
||||
await self._maybe_warn_premium_squad_limit(
|
||||
session,
|
||||
sub,
|
||||
tariff,
|
||||
premium_used,
|
||||
premium_limit,
|
||||
premium_period_start,
|
||||
)
|
||||
if not premium_unlimited_override:
|
||||
await self._maybe_warn_premium_squad_limit(
|
||||
session,
|
||||
sub,
|
||||
tariff,
|
||||
premium_used,
|
||||
premium_limit,
|
||||
premium_period_start,
|
||||
)
|
||||
if not changed:
|
||||
return
|
||||
|
||||
@@ -307,9 +393,67 @@ class TariffTrafficWorker:
|
||||
) -> None:
|
||||
if limit <= 0:
|
||||
return
|
||||
ratio = int(used or 0) / int(limit)
|
||||
used_val = int(used or 0)
|
||||
limit_val = int(limit)
|
||||
ratio = used_val / limit_val
|
||||
levels = list(getattr(self.settings, "tariff_traffic_warning_levels", [85, 90, 95]))
|
||||
|
||||
# Fully exhausted or over quota — one message per period (same idea as regular traffic at 100%).
|
||||
if ratio >= 1.0:
|
||||
depleted_existing = await tariff_dal.get_warning(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
period_start_at=period_start_at,
|
||||
level=PREMIUM_WARNING_DEPLETED_LEVEL,
|
||||
)
|
||||
if depleted_existing:
|
||||
return
|
||||
await tariff_dal.create_warning(
|
||||
session,
|
||||
subscription_id=sub.subscription_id,
|
||||
period_start_at=period_start_at,
|
||||
level=PREMIUM_WARNING_DEPLETED_LEVEL,
|
||||
traffic_limit_bytes=None,
|
||||
)
|
||||
if self.bot:
|
||||
try:
|
||||
user_lang = await self._user_lang(session, sub.user_id)
|
||||
_ = (
|
||||
(lambda k, **kw: self.i18n.gettext(user_lang, k, **kw))
|
||||
if self.i18n
|
||||
else (lambda k, **kw: k)
|
||||
)
|
||||
access = await self.subscription_service.premium_access_for_tariff(tariff)
|
||||
labels = access.get("node_labels") or access.get("squad_labels") or []
|
||||
if labels:
|
||||
visible = [hd.quote(str(x)) for x in labels[:8]]
|
||||
servers = "\n".join(f"• {label}" for label in visible)
|
||||
if len(labels) > len(visible):
|
||||
more = len(labels) - len(visible)
|
||||
servers += "\n" + _("traffic_warning_premium_servers_more", count=more)
|
||||
else:
|
||||
servers = _("traffic_warning_premium_generic_servers")
|
||||
usage = self._usage_placeholders(used_val, limit_val)
|
||||
text = _(
|
||||
"traffic_warning_premium_depleted",
|
||||
tariff_name=hd.quote(str(tariff.name(user_lang))),
|
||||
servers=servers,
|
||||
**usage,
|
||||
)
|
||||
markup = self._traffic_topup_markup(user_lang, "premium")
|
||||
await self.bot.send_message(
|
||||
sub.user_id,
|
||||
text,
|
||||
reply_markup=markup,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
except Exception:
|
||||
logging.exception("Failed to send premium traffic depleted warning to user %s", sub.user_id)
|
||||
return
|
||||
|
||||
for level in levels:
|
||||
if level >= 100:
|
||||
continue
|
||||
if ratio < level / 100:
|
||||
continue
|
||||
storage_level = PREMIUM_WARNING_LEVEL_OFFSET + int(level)
|
||||
@@ -331,34 +475,38 @@ class TariffTrafficWorker:
|
||||
if not self.bot:
|
||||
continue
|
||||
try:
|
||||
user_lang = await self._user_lang(session, sub.user_id)
|
||||
_ = (
|
||||
(lambda k, **kw: self.i18n.gettext(user_lang, k, **kw))
|
||||
if self.i18n
|
||||
else (lambda k, **kw: k)
|
||||
)
|
||||
access = await self.subscription_service.premium_access_for_tariff(tariff)
|
||||
labels = access.get("node_labels") or access.get("squad_labels") or []
|
||||
if labels:
|
||||
visible = labels[:8]
|
||||
visible = [hd.quote(str(x)) for x in labels[:8]]
|
||||
servers = "\n".join(f"• {label}" for label in visible)
|
||||
if len(labels) > len(visible):
|
||||
servers += f"\n• ... еще {len(labels) - len(visible)}"
|
||||
more = len(labels) - len(visible)
|
||||
servers += "\n" + _("traffic_warning_premium_servers_more", count=more)
|
||||
else:
|
||||
servers = "• premium-серверы тарифа"
|
||||
text = (
|
||||
"⚠️ Отдельный лимит premium-серверов почти закончился.\n\n"
|
||||
f"Тариф: {tariff.name(self.settings.DEFAULT_LANGUAGE)}\n"
|
||||
f"Использовано: {self._fmt_bytes(used)} из {self._fmt_bytes(limit)} ({level}%).\n\n"
|
||||
"Этот лимит действует на:\n"
|
||||
f"{servers}\n\n"
|
||||
"Можно докупить premium-трафик. Докупленный остаток переносится на следующие месяцы, пока не израсходуется."
|
||||
servers = _("traffic_warning_premium_generic_servers")
|
||||
left_pct = max(0, 100 - int(level))
|
||||
usage = self._usage_placeholders(used_val, limit_val)
|
||||
text = _(
|
||||
"traffic_warning_premium_almost",
|
||||
tariff_name=hd.quote(str(tariff.name(user_lang))),
|
||||
left_pct=left_pct,
|
||||
servers=servers,
|
||||
**usage,
|
||||
)
|
||||
markup = InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text="Докупить premium-трафик",
|
||||
callback_data="tariff_topup:list",
|
||||
)
|
||||
]
|
||||
]
|
||||
markup = self._traffic_topup_markup(user_lang, "premium")
|
||||
await self.bot.send_message(
|
||||
sub.user_id,
|
||||
text,
|
||||
reply_markup=markup,
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await self.bot.send_message(sub.user_id, text, reply_markup=markup)
|
||||
except Exception:
|
||||
logging.exception("Failed to send premium traffic warning to user %s", sub.user_id)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user