Move user links to id's from buttons
This commit is contained in:
@@ -13,17 +13,27 @@ from bot.services.panel_api_service import PanelApiService
|
|||||||
|
|
||||||
from bot.keyboards.inline.admin_keyboards import (
|
from bot.keyboards.inline.admin_keyboards import (
|
||||||
get_back_to_admin_panel_keyboard,
|
get_back_to_admin_panel_keyboard,
|
||||||
get_user_ratings_keyboard,
|
get_back_to_user_management_keyboard,
|
||||||
)
|
)
|
||||||
from bot.middlewares.i18n import JsonI18n
|
from bot.middlewares.i18n import JsonI18n
|
||||||
|
|
||||||
router = Router(name="admin_statistics_router")
|
router = Router(name="admin_statistics_router")
|
||||||
|
|
||||||
|
|
||||||
def _format_rating_user_label(user_row: Dict[str, object]) -> str:
|
def _format_rating_user_label(user_row: Dict[str, object],
|
||||||
|
bot_username: Optional[str] = None) -> str:
|
||||||
user_id = int(user_row.get("user_id", 0) or 0)
|
user_id = int(user_row.get("user_id", 0) or 0)
|
||||||
username = user_row.get("username")
|
username = user_row.get("username")
|
||||||
first_name = user_row.get("first_name")
|
first_name = user_row.get("first_name")
|
||||||
|
user_id_text = str(user_id)
|
||||||
|
user_id_html = html.escape(user_id_text)
|
||||||
|
|
||||||
|
if bot_username:
|
||||||
|
safe_bot_username = html.escape(bot_username)
|
||||||
|
user_id_html = (
|
||||||
|
f'<a href="https://t.me/{safe_bot_username}?start=admin_user_{user_id_text}">'
|
||||||
|
f"{user_id_html}</a>"
|
||||||
|
)
|
||||||
|
|
||||||
parts: List[str] = []
|
parts: List[str] = []
|
||||||
if username:
|
if username:
|
||||||
@@ -32,25 +42,13 @@ def _format_rating_user_label(user_row: Dict[str, object]) -> str:
|
|||||||
parts.append(html.escape(str(first_name)))
|
parts.append(html.escape(str(first_name)))
|
||||||
|
|
||||||
if not parts:
|
if not parts:
|
||||||
parts.append(f"ID {user_id}")
|
parts.append(f"ID {user_id_html}")
|
||||||
else:
|
else:
|
||||||
parts.append(f"(ID {user_id})")
|
parts.append(f"(ID {user_id_html})")
|
||||||
|
|
||||||
return " ".join(parts)
|
return " ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
def _format_rating_user_short_label(user_row: Dict[str, object]) -> str:
|
|
||||||
username = user_row.get("username")
|
|
||||||
first_name = user_row.get("first_name")
|
|
||||||
user_id = int(user_row.get("user_id", 0) or 0)
|
|
||||||
|
|
||||||
if username:
|
|
||||||
return f"@{html.escape(str(username))}"
|
|
||||||
if first_name:
|
|
||||||
return html.escape(str(first_name))
|
|
||||||
return f"ID {user_id}"
|
|
||||||
|
|
||||||
|
|
||||||
async def show_statistics_handler(callback: types.CallbackQuery,
|
async def show_statistics_handler(callback: types.CallbackQuery,
|
||||||
i18n_data: dict, settings: Settings,
|
i18n_data: dict, settings: Settings,
|
||||||
session: AsyncSession):
|
session: AsyncSession):
|
||||||
@@ -308,6 +306,14 @@ async def show_user_ratings_handler(
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
top_limit = 10
|
top_limit = 10
|
||||||
|
bot_username: Optional[str] = None
|
||||||
|
try:
|
||||||
|
me = await callback.bot.get_me()
|
||||||
|
bot_username = me.username
|
||||||
|
except Exception as e_get_me:
|
||||||
|
logging.warning("Failed to resolve bot username for ratings links: %s",
|
||||||
|
e_get_me)
|
||||||
|
|
||||||
traffic_top = await user_dal.get_top_users_by_traffic_used(session, limit=top_limit)
|
traffic_top = await user_dal.get_top_users_by_traffic_used(session, limit=top_limit)
|
||||||
invited_top = await user_dal.get_top_users_by_referrals_count(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)
|
revenue_top = await user_dal.get_top_users_by_referral_revenue(session, limit=top_limit)
|
||||||
@@ -325,7 +331,7 @@ async def show_user_ratings_handler(
|
|||||||
_(
|
_(
|
||||||
"admin_user_ratings_traffic_item",
|
"admin_user_ratings_traffic_item",
|
||||||
rank=idx,
|
rank=idx,
|
||||||
user=_format_rating_user_label(row),
|
user=_format_rating_user_label(row, bot_username),
|
||||||
traffic_gb=f"{traffic_gb:.2f}",
|
traffic_gb=f"{traffic_gb:.2f}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -339,7 +345,7 @@ async def show_user_ratings_handler(
|
|||||||
_(
|
_(
|
||||||
"admin_user_ratings_invited_item",
|
"admin_user_ratings_invited_item",
|
||||||
rank=idx,
|
rank=idx,
|
||||||
user=_format_rating_user_label(row),
|
user=_format_rating_user_label(row, bot_username),
|
||||||
invited_count=int(row.get("invited_count") or 0),
|
invited_count=int(row.get("invited_count") or 0),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -353,29 +359,15 @@ async def show_user_ratings_handler(
|
|||||||
_(
|
_(
|
||||||
"admin_user_ratings_revenue_item",
|
"admin_user_ratings_revenue_item",
|
||||||
rank=idx,
|
rank=idx,
|
||||||
user=_format_rating_user_label(row),
|
user=_format_rating_user_label(row, bot_username),
|
||||||
revenue=f"{float(row.get('referral_revenue') or 0):.2f}",
|
revenue=f"{float(row.get('referral_revenue') or 0):.2f}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text_parts.append(_("admin_user_ratings_empty"))
|
text_parts.append(_("admin_user_ratings_empty"))
|
||||||
|
|
||||||
unique_users: Dict[int, Dict[str, object]] = {}
|
|
||||||
for row in traffic_top + invited_top + revenue_top:
|
|
||||||
user_id = int(row.get("user_id", 0) or 0)
|
|
||||||
if user_id <= 0 or user_id in unique_users:
|
|
||||||
continue
|
|
||||||
unique_users[user_id] = {
|
|
||||||
"user_id": user_id,
|
|
||||||
"label": _format_rating_user_short_label(row),
|
|
||||||
}
|
|
||||||
|
|
||||||
await callback.message.edit_text(
|
await callback.message.edit_text(
|
||||||
"\n".join(text_parts),
|
"\n".join(text_parts),
|
||||||
reply_markup=get_user_ratings_keyboard(
|
reply_markup=get_back_to_user_management_keyboard(current_lang, i18n),
|
||||||
list(unique_users.values()),
|
|
||||||
current_lang,
|
|
||||||
i18n,
|
|
||||||
),
|
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -305,7 +305,8 @@ async def ensure_required_channel_subscription(
|
|||||||
@router.message(CommandStart())
|
@router.message(CommandStart())
|
||||||
@router.message(CommandStart(magic=F.args.regexp(r"^ref_((?:[uU][A-Za-z0-9]{9})|(?:[A-Za-z0-9]{9})|\d+)$").as_("ref_match")))
|
@router.message(CommandStart(magic=F.args.regexp(r"^ref_((?:[uU][A-Za-z0-9]{9})|(?:[A-Za-z0-9]{9})|\d+)$").as_("ref_match")))
|
||||||
@router.message(CommandStart(magic=F.args.regexp(r"^promo_(\w+)$").as_("promo_match")))
|
@router.message(CommandStart(magic=F.args.regexp(r"^promo_(\w+)$").as_("promo_match")))
|
||||||
@router.message(CommandStart(magic=F.args.regexp(r"^(?!ref_|promo_)([A-Za-z0-9_\-]{2,64})$").as_("ad_param_match")))
|
@router.message(CommandStart(magic=F.args.regexp(r"^admin_user_(\d+)$").as_("admin_user_match")))
|
||||||
|
@router.message(CommandStart(magic=F.args.regexp(r"^(?!ref_|promo_|admin_user_)([A-Za-z0-9_\-]{2,64})$").as_("ad_param_match")))
|
||||||
async def start_command_handler(message: types.Message,
|
async def start_command_handler(message: types.Message,
|
||||||
state: FSMContext,
|
state: FSMContext,
|
||||||
settings: Settings,
|
settings: Settings,
|
||||||
@@ -314,7 +315,8 @@ async def start_command_handler(message: types.Message,
|
|||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
ref_match: Optional[re.Match] = None,
|
ref_match: Optional[re.Match] = None,
|
||||||
promo_match: Optional[re.Match] = None,
|
promo_match: Optional[re.Match] = None,
|
||||||
ad_param_match: Optional[re.Match] = None):
|
ad_param_match: Optional[re.Match] = None,
|
||||||
|
admin_user_match: Optional[re.Match] = None):
|
||||||
await state.clear()
|
await state.clear()
|
||||||
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
|
||||||
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
|
||||||
@@ -324,6 +326,58 @@ async def start_command_handler(message: types.Message,
|
|||||||
user = message.from_user
|
user = message.from_user
|
||||||
user_id = user.id
|
user_id = user.id
|
||||||
|
|
||||||
|
if admin_user_match and user_id in settings.ADMIN_IDS:
|
||||||
|
target_user_id = int(admin_user_match.group(1))
|
||||||
|
target_user = await user_dal.get_user_by_id(session, target_user_id)
|
||||||
|
if not target_user:
|
||||||
|
await message.answer(
|
||||||
|
_("admin_user_not_found", input=hd.quote(str(target_user_id)))
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from bot.handlers.admin.user_management import (
|
||||||
|
format_user_card,
|
||||||
|
get_user_card_keyboard,
|
||||||
|
_send_with_profile_link_fallback,
|
||||||
|
)
|
||||||
|
|
||||||
|
referral_service = ReferralService(
|
||||||
|
settings, subscription_service, message.bot, i18n
|
||||||
|
)
|
||||||
|
user_card_text = await format_user_card(
|
||||||
|
target_user,
|
||||||
|
session,
|
||||||
|
subscription_service,
|
||||||
|
i18n,
|
||||||
|
current_lang,
|
||||||
|
referral_service,
|
||||||
|
)
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except Exception as e_admin_card:
|
||||||
|
logging.error(
|
||||||
|
"Failed to open admin user card via deep-link for %s: %s",
|
||||||
|
target_user_id,
|
||||||
|
e_admin_card,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
await message.answer(_("admin_user_card_error"))
|
||||||
|
return
|
||||||
|
|
||||||
referred_by_user_id: Optional[int] = None
|
referred_by_user_id: Optional[int] = None
|
||||||
promo_code_to_apply: Optional[str] = None
|
promo_code_to_apply: Optional[str] = None
|
||||||
ad_start_param: Optional[str] = None
|
ad_start_param: Optional[str] = None
|
||||||
|
|||||||
@@ -480,34 +480,3 @@ def get_back_to_user_management_keyboard(lang: str,
|
|||||||
builder.button(text=_(key="back_to_user_management_button"),
|
builder.button(text=_(key="back_to_user_management_button"),
|
||||||
callback_data="admin_section:user_management")
|
callback_data="admin_section:user_management")
|
||||||
return builder.as_markup()
|
return builder.as_markup()
|
||||||
|
|
||||||
|
|
||||||
def get_user_ratings_keyboard(rating_users: List[dict],
|
|
||||||
lang: str,
|
|
||||||
i18n_instance) -> InlineKeyboardMarkup:
|
|
||||||
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
|
|
||||||
builder = InlineKeyboardBuilder()
|
|
||||||
|
|
||||||
for entry in rating_users:
|
|
||||||
user_id = int(entry.get("user_id", 0) or 0)
|
|
||||||
if user_id <= 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
label = str(entry.get("label", "")).strip()
|
|
||||||
if label:
|
|
||||||
button_text = _("admin_user_ratings_open_user_button",
|
|
||||||
user_id=user_id,
|
|
||||||
user_label=label)
|
|
||||||
else:
|
|
||||||
button_text = _("admin_user_ratings_open_user_button_simple",
|
|
||||||
user_id=user_id)
|
|
||||||
|
|
||||||
builder.button(
|
|
||||||
text=button_text,
|
|
||||||
callback_data=f"admin_user_card_from_list:{user_id}:0",
|
|
||||||
)
|
|
||||||
|
|
||||||
builder.button(text=_(key="back_to_user_management_button"),
|
|
||||||
callback_data="admin_section:user_management")
|
|
||||||
builder.adjust(1)
|
|
||||||
return builder.as_markup()
|
|
||||||
|
|||||||
@@ -142,8 +142,6 @@
|
|||||||
"admin_user_ratings_invited_item": "{rank}. {user} — <b>{invited_count}</b>",
|
"admin_user_ratings_invited_item": "{rank}. {user} — <b>{invited_count}</b>",
|
||||||
"admin_user_ratings_revenue_item": "{rank}. {user} — <b>{revenue} RUB</b>",
|
"admin_user_ratings_revenue_item": "{rank}. {user} — <b>{revenue} RUB</b>",
|
||||||
"admin_user_ratings_empty": "No data yet.",
|
"admin_user_ratings_empty": "No data yet.",
|
||||||
"admin_user_ratings_open_user_button": "👤 ID {user_id} ({user_label})",
|
|
||||||
"admin_user_ratings_open_user_button_simple": "👤 ID {user_id}",
|
|
||||||
"admin_payments_header": "💰 <b>All Payments</b>",
|
"admin_payments_header": "💰 <b>All Payments</b>",
|
||||||
"admin_no_payments_found": "No payments found.",
|
"admin_no_payments_found": "No payments found.",
|
||||||
"admin_export_payments_csv": "📊 Export CSV",
|
"admin_export_payments_csv": "📊 Export CSV",
|
||||||
|
|||||||
@@ -142,8 +142,6 @@
|
|||||||
"admin_user_ratings_invited_item": "{rank}. {user} — <b>{invited_count}</b>",
|
"admin_user_ratings_invited_item": "{rank}. {user} — <b>{invited_count}</b>",
|
||||||
"admin_user_ratings_revenue_item": "{rank}. {user} — <b>{revenue} RUB</b>",
|
"admin_user_ratings_revenue_item": "{rank}. {user} — <b>{revenue} RUB</b>",
|
||||||
"admin_user_ratings_empty": "Пока нет данных.",
|
"admin_user_ratings_empty": "Пока нет данных.",
|
||||||
"admin_user_ratings_open_user_button": "👤 ID {user_id} ({user_label})",
|
|
||||||
"admin_user_ratings_open_user_button_simple": "👤 ID {user_id}",
|
|
||||||
"admin_payments_header": "💰 <b>Все платежи</b>",
|
"admin_payments_header": "💰 <b>Все платежи</b>",
|
||||||
"admin_no_payments_found": "Платежи не найдены.",
|
"admin_no_payments_found": "Платежи не найдены.",
|
||||||
"admin_export_payments_csv": "📊 Экспорт CSV",
|
"admin_export_payments_csv": "📊 Экспорт CSV",
|
||||||
|
|||||||
Reference in New Issue
Block a user