From c4057180b94effad5144f99b37fd1b7c4e38e34a Mon Sep 17 00:00:00 2001 From: Ultradesu Date: Wed, 17 Sep 2025 13:20:20 +0300 Subject: [PATCH] Fixed TG messages quotes. Fixed sync tasks loop. --- telegram_bot/bot.py | 4 +-- telegram_bot/models.py | 4 +-- vpn/admin/server.py | 32 ++++++++++++++++++++ vpn/server_plugins/xray_v2.py | 9 ++++-- vpn/tasks.py | 55 ++++++++++++++++++++++++++++++----- 5 files changed, 91 insertions(+), 13 deletions(-) diff --git a/telegram_bot/bot.py b/telegram_bot/bot.py index 8602f0a..ef05ebb 100644 --- a/telegram_bot/bot.py +++ b/telegram_bot/bot.py @@ -182,7 +182,7 @@ class TelegramBotManager: # Prepare user info user_info = access_request.display_name - telegram_info = f"@{access_request.telegram_username}" if access_request.telegram_username else f"ID: {access_request.telegram_user_id}" + telegram_info = f"`@{access_request.telegram_username}`" if access_request.telegram_username else f"ID: {access_request.telegram_user_id}" date = access_request.created_at.strftime("%Y-%m-%d %H:%M") message = access_request.message_text[:200] + "..." if len(access_request.message_text) > 200 else access_request.message_text @@ -703,7 +703,7 @@ class TelegramBotManager: # Format detailed information details = f"**📋 Request Details**\n\n" details += f"**👤 User:** {request.display_name}\n" - details += f"**📱 Telegram:** @{request.telegram_username}" if request.telegram_username else f"**📱 Telegram ID:** {request.telegram_user_id}\n" + details += f"**📱 Telegram:** `@{request.telegram_username}`" if request.telegram_username else f"**📱 Telegram ID:** {request.telegram_user_id}\n" details += f"**📅 Date:** {request.created_at.strftime('%Y-%m-%d %H:%M:%S')}\n" details += f"**🆔 Request ID:** {request.id}\n" details += f"**👤 Desired Username:** {request.desired_username}\n\n" diff --git a/telegram_bot/models.py b/telegram_bot/models.py index 6dbeb1b..4db112c 100644 --- a/telegram_bot/models.py +++ b/telegram_bot/models.py @@ -155,7 +155,7 @@ class TelegramMessage(models.Model): def display_name(self): """Get best available display name""" if self.telegram_username: - return f"@{self.telegram_username}" + return f"`@{self.telegram_username}`" return self.full_name @@ -294,7 +294,7 @@ class AccessRequest(models.Model): def display_name(self): """Get best available display name""" if self.telegram_username: - return f"@{self.telegram_username}" + return f"`@{self.telegram_username}`" name_parts = [] if self.telegram_first_name: diff --git a/vpn/admin/server.py b/vpn/admin/server.py index 7460768..ce2116d 100644 --- a/vpn/admin/server.py +++ b/vpn/admin/server.py @@ -469,13 +469,45 @@ class ServerAdmin(PolymorphicParentModelAdmin, BaseVPNAdmin): try: from vpn.tasks import sync_server_users + from celery import current_app tasks_started = 0 errors = [] + scheduled_tasks = set() # Track already scheduled tasks to avoid duplicates for server in queryset: try: + # Check if a task is already running for this server + task_key = f"sync_server_{server.id}" + + # Use Celery's inspect to check active tasks (optional, for better UX) + inspect = current_app.control.inspect() + active_tasks = inspect.active() + + # Check if task is already scheduled for this server + task_already_running = False + if active_tasks: + for worker, tasks in active_tasks.items(): + for task_info in tasks: + if task_info.get('name') == 'sync_server_users' and \ + server.id in str(task_info.get('args', [])): + task_already_running = True + break + + if task_already_running: + self.message_user( + request, + f"⏳ Sync already in progress for '{server.name}'", + level=messages.WARNING + ) + continue + + # Avoid scheduling duplicate tasks in this batch + if server.id in scheduled_tasks: + continue + task = sync_server_users.delay(server.id) + scheduled_tasks.add(server.id) tasks_started += 1 self.message_user( request, diff --git a/vpn/server_plugins/xray_v2.py b/vpn/server_plugins/xray_v2.py index b8c802e..2588cec 100644 --- a/vpn/server_plugins/xray_v2.py +++ b/vpn/server_plugins/xray_v2.py @@ -815,9 +815,14 @@ class XrayServerV2Admin(PolymorphicChildModelAdmin): actions = ['sync_users', 'sync_inbounds', 'get_status'] def sync_users(self, request, queryset): + from vpn.tasks import sync_server_users + scheduled_count = 0 for server in queryset: - server.sync_users() - self.message_user(request, f"Scheduled user sync for {queryset.count()} servers") + # Directly schedule the task instead of calling server.sync_users() + # to avoid potential recursion issues + sync_server_users.delay(server.id) + scheduled_count += 1 + self.message_user(request, f"Scheduled user sync for {scheduled_count} servers") sync_users.short_description = "Sync users for selected servers" def sync_inbounds(self, request, queryset): diff --git a/vpn/tasks.py b/vpn/tasks.py index dbb9575..f0b2acc 100644 --- a/vpn/tasks.py +++ b/vpn/tasks.py @@ -123,8 +123,29 @@ def sync_xray_users(self, server_id): create_task_log(task_id, "sync_xray_users", f"Starting user sync for {server.name}", 'STARTED', server=server) logger.info(f"Starting user sync for Xray server {server.name}") + # Don't call sync_users() which would create another task - directly perform the sync + from vpn.models import User + from vpn.models_xray import UserSubscription + + # Get all users who should have access to this server + users_to_sync = User.objects.filter( + xray_subscriptions__active=True, + xray_subscriptions__subscription_group__is_active=True + ).distinct() + real_server = server.get_real_instance() - user_result = real_server.sync_users() + + added_count = 0 + failed_count = 0 + for user in users_to_sync: + try: + if real_server.add_user(user): + added_count += 1 + except Exception as e: + failed_count += 1 + logger.error(f"Failed to sync user {user.username} on server {server.name}: {e}") + + user_result = {"users_added": added_count, "total_users": users_to_sync.count(), "failed": failed_count} success_message = f"Successfully synced {user_result.get('users_added', 0)} users for {server.name}" logger.info(f"{success_message}. Result: {user_result}") @@ -247,16 +268,36 @@ def sync_users(self, server_id): create_task_log(task_id, "sync_all_users_on_server", f"Found {user_count} users to sync", 'STARTED', server=server, message=f"Users: {user_list}") - # For Xray servers, use the new sync methods + # For Xray servers, use the sync_server_users task to avoid recursion from vpn.server_plugins.xray_v2 import XrayServerV2 if isinstance(server.get_real_instance(), XrayServerV2): logger.info(f"Using XrayServerV2 sync for server {server.name}") - # Just call the sync method which schedules tasks asynchronously - sync_result = server.sync_users() - logger.info(f"Scheduled async sync for Xray server {server.name}") + # Call sync_server_users directly to perform the actual sync + # Avoid calling server.sync_users() which would create another task + from vpn.models import User + from vpn.models_xray import UserSubscription + + # Get all users who should have access to this server + users_to_sync = User.objects.filter( + xray_subscriptions__active=True, + xray_subscriptions__subscription_group__is_active=True + ).distinct() + + added_count = 0 + failed_count = 0 + for user in users_to_sync: + try: + if server.add_user(user): + added_count += 1 + except Exception as e: + failed_count += 1 + logger.error(f"Failed to sync user {user.username} on server {server.name}: {e}") + + sync_result = {"users_added": added_count, "total_users": users_to_sync.count(), "failed": failed_count} + logger.info(f"Directly synced {added_count} users for Xray server {server.name}") else: - # For non-Xray servers, just sync users - sync_result = server.sync_users() + # For non-Xray servers, sync users directly (non-Xray servers should not create tasks) + sync_result = server.sync_all_users() # Check if sync was successful (can be boolean or dict/string) sync_successful = bool(sync_result) and (