Tune users rating

This commit is contained in:
3252a8
2026-02-09 10:29:40 +03:00
parent 3b9043332c
commit 18a6b3e18d
8 changed files with 118 additions and 1 deletions
+19 -1
View File
@@ -315,13 +315,16 @@ async def show_user_ratings_handler(
e_get_me)
traffic_top = await user_dal.get_top_users_by_traffic_used(session, limit=top_limit)
lifetime_traffic_top = await user_dal.get_top_users_by_lifetime_traffic_used(
session, limit=top_limit
)
invited_top = await user_dal.get_top_users_by_referrals_count(session, limit=top_limit)
revenue_top = await user_dal.get_top_users_by_referral_revenue(session, limit=top_limit)
text_parts: List[str] = [
_("admin_user_ratings_header", top_limit=top_limit),
"",
f"<b>{_('admin_user_ratings_traffic_title')}</b>",
f"<b>{_('admin_user_ratings_traffic_month_title')}</b>",
]
if traffic_top:
@@ -338,6 +341,21 @@ async def show_user_ratings_handler(
else:
text_parts.append(_("admin_user_ratings_empty"))
text_parts.extend(["", f"<b>{_('admin_user_ratings_traffic_lifetime_title')}</b>"])
if lifetime_traffic_top:
for idx, row in enumerate(lifetime_traffic_top, start=1):
traffic_gb = float(row.get("lifetime_used_traffic_bytes") or 0) / (1024**3)
text_parts.append(
_(
"admin_user_ratings_traffic_item",
rank=idx,
user=_format_rating_user_label(row, bot_username),
traffic_gb=f"{traffic_gb:.2f}",
)
)
else:
text_parts.append(_("admin_user_ratings_empty"))
text_parts.extend(["", f"<b>{_('admin_user_ratings_invited_title')}</b>"])
if invited_top:
for idx, row in enumerate(invited_top, start=1):
+26
View File
@@ -18,6 +18,24 @@ from bot.middlewares.i18n import JsonI18n
router = Router(name="admin_sync_router")
def _extract_lifetime_used_traffic_bytes(panel_user_data: dict) -> Optional[int]:
user_traffic = panel_user_data.get("userTraffic") or {}
raw_value = (
user_traffic.get("lifetimeUsedTrafficBytes")
if isinstance(user_traffic, dict)
else None
)
if raw_value is None:
raw_value = panel_user_data.get("lifetimeUsedTrafficBytes")
try:
if raw_value is None:
return None
return int(raw_value)
except (TypeError, ValueError):
return None
async def perform_sync(
panel_service: PanelApiService,
session: AsyncSession,
@@ -176,6 +194,14 @@ async def perform_sync(
f"Updated panel UUID for user {actual_user_id}: {panel_uuid}"
)
lifetime_used = _extract_lifetime_used_traffic_bytes(panel_user_dict)
if (
lifetime_used is not None
and existing_user.lifetime_used_traffic_bytes != lifetime_used
):
existing_user.lifetime_used_traffic_bytes = lifetime_used
user_was_updated = True
# Ensure panel description contains Telegram fields
try:
if panel_uuid and existing_user:
+25
View File
@@ -68,6 +68,20 @@ class SubscriptionService:
strategy = traffic_stats.get("trafficLimitStrategy")
return used, limit, strategy
def _extract_lifetime_used_traffic(
self, panel_user_data: Dict[str, Any]
) -> Optional[int]:
traffic_stats = panel_user_data.get("userTraffic") or {}
lifetime = traffic_stats.get("lifetimeUsedTrafficBytes")
if lifetime is None:
lifetime = panel_user_data.get("lifetimeUsedTrafficBytes")
try:
if lifetime is None:
return None
return int(lifetime)
except (TypeError, ValueError):
return None
async def _notify_admin_panel_user_creation_failed(self, user_id: int):
if not self.bot or not self.i18n or not self.settings.ADMIN_IDS:
return
@@ -853,6 +867,17 @@ class SubscriptionService:
await user_dal.update_user(session, user_id, {"panel_user_uuid": None})
return None
panel_lifetime_used = self._extract_lifetime_used_traffic(panel_user_data)
if (
panel_lifetime_used is not None
and db_user.lifetime_used_traffic_bytes != panel_lifetime_used
):
await user_dal.update_user(
session,
user_id,
{"lifetime_used_traffic_bytes": panel_lifetime_used},
)
if local_active_sub:
update_payload_local = {}
panel_status = panel_user_data.get("status", "UNKNOWN").upper()
+25
View File
@@ -391,6 +391,31 @@ async def get_top_users_by_traffic_used(
return [dict(row._mapping) for row in result]
async def get_top_users_by_lifetime_traffic_used(
session: AsyncSession,
*,
limit: int = 10,
) -> List[Dict[str, Any]]:
"""Return top users by lifetime used traffic from panel data."""
safe_limit = max(1, limit)
lifetime_used = func.coalesce(User.lifetime_used_traffic_bytes, 0)
stmt = (
select(
User.user_id,
User.username,
User.first_name,
lifetime_used.label("lifetime_used_traffic_bytes"),
)
.where(lifetime_used > 0)
.order_by(desc("lifetime_used_traffic_bytes"), User.user_id.asc())
.limit(safe_limit)
)
result = await session.execute(stmt)
return [dict(row._mapping) for row in result]
async def get_top_users_by_referrals_count(
session: AsyncSession,
*,
+18
View File
@@ -112,6 +112,19 @@ def _migration_0003_normalize_referral_codes(connection: Connection) -> None:
)
)
def _migration_0004_add_lifetime_used_traffic(connection: Connection) -> None:
inspector = inspect(connection)
columns: Set[str] = {col["name"] for col in inspector.get_columns("users")}
if "lifetime_used_traffic_bytes" in columns:
return
connection.execute(
text(
"ALTER TABLE users ADD COLUMN lifetime_used_traffic_bytes BIGINT"
)
)
MIGRATIONS: List[Migration] = [
Migration(
id="0001_add_channel_subscription_fields",
@@ -128,6 +141,11 @@ MIGRATIONS: List[Migration] = [
description="Normalize referral codes to uppercase for consistent lookups",
upgrade=_migration_0003_normalize_referral_codes,
),
Migration(
id="0004_add_lifetime_used_traffic",
description="Store lifetime traffic usage for users",
upgrade=_migration_0004_add_lifetime_used_traffic,
),
]
+1
View File
@@ -25,6 +25,7 @@ class User(Base):
referred_by_id = Column(BigInteger,
ForeignKey("users.user_id"),
nullable=True)
lifetime_used_traffic_bytes = Column(BigInteger, nullable=True)
channel_subscription_verified = Column(Boolean, nullable=True)
channel_subscription_checked_at = Column(DateTime(timezone=True),
nullable=True)
+2
View File
@@ -136,6 +136,8 @@
"back_to_stats_monitoring_button": "⬅️ To Statistics",
"admin_user_ratings_header": "🏆 <b>User Ratings (TOP {top_limit})</b>",
"admin_user_ratings_traffic_title": "📊 By used traffic",
"admin_user_ratings_traffic_month_title": "📊 By used traffic (month)",
"admin_user_ratings_traffic_lifetime_title": "📊 By used traffic (lifetime)",
"admin_user_ratings_invited_title": "👥 By invited users",
"admin_user_ratings_revenue_title": "💸 By referral revenue",
"admin_user_ratings_traffic_item": "{rank}. {user} — <b>{traffic_gb} GB</b>",
+2
View File
@@ -136,6 +136,8 @@
"back_to_stats_monitoring_button": "⬅️ К статистике",
"admin_user_ratings_header": "🏆 <b>Рейтинг пользователей (ТОП {top_limit})</b>",
"admin_user_ratings_traffic_title": "📊 По использованному трафику",
"admin_user_ratings_traffic_month_title": "📊 По использованному трафику за месяц",
"admin_user_ratings_traffic_lifetime_title": "📊 По использованному трафику за всё время",
"admin_user_ratings_invited_title": "👥 По количеству приглашенных",
"admin_user_ratings_revenue_title": "💸 По доходу с приглашенных",
"admin_user_ratings_traffic_item": "{rank}. {user} — <b>{traffic_gb} ГБ</b>",