mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-08-21 14:37:16 +00:00
Added tg bot autoconfirm
This commit is contained in:
@@ -8,11 +8,37 @@ from django import forms
|
||||
from django.contrib.admin.widgets import FilteredSelectMultiple
|
||||
from .models import BotSettings, TelegramMessage, AccessRequest
|
||||
from .localization import MessageLocalizer
|
||||
from vpn.models import User
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BotSettingsAdminForm(forms.ModelForm):
|
||||
"""Custom form for BotSettings with Telegram admin selection"""
|
||||
|
||||
class Meta:
|
||||
model = BotSettings
|
||||
fields = '__all__'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Show all users for telegram_admins selection
|
||||
if 'telegram_admins' in self.fields:
|
||||
self.fields['telegram_admins'].queryset = User.objects.all().order_by('username')
|
||||
self.fields['telegram_admins'].help_text = (
|
||||
"Select users who will have admin access in the bot. "
|
||||
"Users will get admin rights when they connect to the bot with their Telegram account."
|
||||
)
|
||||
|
||||
def clean_telegram_admins(self):
|
||||
"""Validate that selected admins have telegram_user_id or telegram_username"""
|
||||
admins = self.cleaned_data.get('telegram_admins')
|
||||
# No validation needed - admins can be selected even without telegram connection
|
||||
# They will get admin rights when they connect via bot
|
||||
return admins
|
||||
|
||||
|
||||
class AccessRequestAdminForm(forms.ModelForm):
|
||||
"""Custom form for AccessRequest with existing user selection"""
|
||||
|
||||
@@ -20,10 +46,6 @@ class AccessRequestAdminForm(forms.ModelForm):
|
||||
model = AccessRequest
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'selected_inbounds': FilteredSelectMultiple(
|
||||
verbose_name='Inbound Templates',
|
||||
is_stacked=False
|
||||
),
|
||||
'selected_subscription_groups': FilteredSelectMultiple(
|
||||
verbose_name='Subscription Groups',
|
||||
is_stacked=False
|
||||
@@ -43,13 +65,7 @@ class AccessRequestAdminForm(forms.ModelForm):
|
||||
telegram_user_id__isnull=True
|
||||
).order_by('username')
|
||||
|
||||
# Configure inbound and subscription group fields
|
||||
if 'selected_inbounds' in self.fields:
|
||||
from vpn.models_xray import Inbound
|
||||
self.fields['selected_inbounds'].queryset = Inbound.objects.all().order_by('name')
|
||||
self.fields['selected_inbounds'].label = 'Inbound Templates'
|
||||
self.fields['selected_inbounds'].help_text = 'Select inbound templates to assign to this user'
|
||||
|
||||
# Configure subscription group fields
|
||||
if 'selected_subscription_groups' in self.fields:
|
||||
from vpn.models_xray import SubscriptionGroup
|
||||
self.fields['selected_subscription_groups'].queryset = SubscriptionGroup.objects.filter(
|
||||
@@ -61,12 +77,17 @@ class AccessRequestAdminForm(forms.ModelForm):
|
||||
|
||||
@admin.register(BotSettings)
|
||||
class BotSettingsAdmin(admin.ModelAdmin):
|
||||
list_display = ('__str__', 'enabled', 'bot_token_display', 'updated_at')
|
||||
form = BotSettingsAdminForm
|
||||
list_display = ('__str__', 'enabled', 'bot_token_display', 'admin_count_display', 'updated_at')
|
||||
fieldsets = (
|
||||
('Bot Configuration', {
|
||||
'fields': ('bot_token', 'enabled', 'bot_status_display'),
|
||||
'description': 'Configure bot settings and view current status'
|
||||
}),
|
||||
('Admin Management', {
|
||||
'fields': ('telegram_admins', 'admin_info_display'),
|
||||
'description': 'Select users with linked Telegram accounts who will have admin access in the bot'
|
||||
}),
|
||||
('Connection Settings', {
|
||||
'fields': ('api_base_url', 'connection_timeout', 'use_proxy', 'proxy_url'),
|
||||
'classes': ('collapse',)
|
||||
@@ -76,7 +97,8 @@ class BotSettingsAdmin(admin.ModelAdmin):
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
readonly_fields = ('created_at', 'updated_at', 'bot_status_display')
|
||||
readonly_fields = ('created_at', 'updated_at', 'bot_status_display', 'admin_info_display')
|
||||
filter_horizontal = ('telegram_admins',)
|
||||
|
||||
def bot_token_display(self, obj):
|
||||
"""Mask bot token for security"""
|
||||
@@ -88,6 +110,60 @@ class BotSettingsAdmin(admin.ModelAdmin):
|
||||
return "No token set"
|
||||
bot_token_display.short_description = "Bot Token"
|
||||
|
||||
def admin_count_display(self, obj):
|
||||
"""Display count of Telegram admins"""
|
||||
count = obj.telegram_admins.count()
|
||||
if count == 0:
|
||||
return "No admins"
|
||||
elif count == 1:
|
||||
return "1 admin"
|
||||
else:
|
||||
return f"{count} admins"
|
||||
admin_count_display.short_description = "Telegram Admins"
|
||||
|
||||
def admin_info_display(self, obj):
|
||||
"""Display detailed admin information"""
|
||||
if not obj.pk:
|
||||
return "Save settings first to manage admins"
|
||||
|
||||
admins = obj.telegram_admins.all()
|
||||
|
||||
if not admins.exists():
|
||||
html = '<div style="background: #fff3cd; padding: 10px; border-radius: 4px; border-left: 4px solid #ffc107;">'
|
||||
html += '<p style="margin: 0; color: #856404;"><strong>⚠️ No Telegram admins configured</strong></p>'
|
||||
html += '<p style="margin: 5px 0 0 0; color: #856404;">Select users above to give them admin access in the Telegram bot.</p>'
|
||||
html += '</div>'
|
||||
else:
|
||||
html = '<div style="background: #d4edda; padding: 10px; border-radius: 4px; border-left: 4px solid #28a745;">'
|
||||
html += f'<p style="margin: 0; color: #155724;"><strong>✅ {admins.count()} Telegram admin(s) configured</strong></p>'
|
||||
html += '<div style="margin-top: 8px;">'
|
||||
|
||||
for admin in admins:
|
||||
html += '<div style="background: white; margin: 4px 0; padding: 6px 10px; border-radius: 3px; border: 1px solid #c3e6cb;">'
|
||||
html += f'<strong>{admin.username}</strong>'
|
||||
|
||||
if admin.telegram_username:
|
||||
html += f' (@{admin.telegram_username})'
|
||||
|
||||
html += f' <small style="color: #6c757d;">ID: {admin.telegram_user_id}</small>'
|
||||
|
||||
if admin.first_name or admin.last_name:
|
||||
name_parts = []
|
||||
if admin.first_name:
|
||||
name_parts.append(admin.first_name)
|
||||
if admin.last_name:
|
||||
name_parts.append(admin.last_name)
|
||||
html += f'<br><small style="color: #6c757d;">Name: {" ".join(name_parts)}</small>'
|
||||
|
||||
html += '</div>'
|
||||
|
||||
html += '</div>'
|
||||
html += '<p style="margin: 8px 0 0 0; color: #155724; font-size: 12px;">These users will receive notifications about new access requests and can approve/reject them directly in Telegram.</p>'
|
||||
html += '</div>'
|
||||
|
||||
return format_html(html)
|
||||
admin_info_display.short_description = "Admin Configuration"
|
||||
|
||||
def bot_status_display(self, obj):
|
||||
"""Display bot status with control buttons"""
|
||||
from .bot import TelegramBotManager
|
||||
@@ -365,10 +441,9 @@ class AccessRequestAdmin(admin.ModelAdmin):
|
||||
}),
|
||||
('VPN Access Configuration', {
|
||||
'fields': (
|
||||
'selected_inbounds',
|
||||
'selected_subscription_groups',
|
||||
),
|
||||
'description': 'Select inbound templates and subscription groups to assign to the user'
|
||||
'description': 'Select subscription groups to assign to the user'
|
||||
}),
|
||||
('Telegram User', {
|
||||
'fields': (
|
||||
@@ -620,11 +695,12 @@ class AccessRequestAdmin(admin.ModelAdmin):
|
||||
raise
|
||||
|
||||
def _assign_vpn_access(self, user, access_request):
|
||||
"""Assign selected inbounds and subscription groups to the user"""
|
||||
"""Assign selected subscription groups to the user"""
|
||||
try:
|
||||
from vpn.models_xray import UserSubscription, SubscriptionGroup
|
||||
from vpn.models_xray import UserSubscription
|
||||
|
||||
# Assign subscription groups
|
||||
group_count = 0
|
||||
for subscription_group in access_request.selected_subscription_groups.all():
|
||||
user_subscription, created = UserSubscription.objects.get_or_create(
|
||||
user=user,
|
||||
@@ -633,48 +709,16 @@ class AccessRequestAdmin(admin.ModelAdmin):
|
||||
)
|
||||
if created:
|
||||
logger.info(f"Assigned subscription group '{subscription_group.name}' to user {user.username}")
|
||||
group_count += 1
|
||||
else:
|
||||
# Ensure it's active if it already existed
|
||||
if not user_subscription.active:
|
||||
user_subscription.active = True
|
||||
user_subscription.save()
|
||||
logger.info(f"Re-activated subscription group '{subscription_group.name}' for user {user.username}")
|
||||
group_count += 1
|
||||
|
||||
# Handle individual inbounds - create a custom subscription group for them
|
||||
selected_inbounds = access_request.selected_inbounds.all()
|
||||
if selected_inbounds.exists():
|
||||
# Create a custom subscription group for this user's individual inbounds
|
||||
custom_group_name = f"Custom_{user.username}_{access_request.id}"
|
||||
custom_group, created = SubscriptionGroup.objects.get_or_create(
|
||||
name=custom_group_name,
|
||||
defaults={
|
||||
'description': f'Custom inbounds for {user.username} from Telegram request',
|
||||
'is_active': True
|
||||
}
|
||||
)
|
||||
|
||||
if created:
|
||||
# Add selected inbounds to the custom group
|
||||
custom_group.inbounds.set(selected_inbounds)
|
||||
logger.info(f"Created custom subscription group '{custom_group_name}' with {selected_inbounds.count()} inbounds")
|
||||
else:
|
||||
# Update existing custom group
|
||||
custom_group.inbounds.add(*selected_inbounds)
|
||||
logger.info(f"Updated custom subscription group '{custom_group_name}' with additional inbounds")
|
||||
|
||||
# Assign the custom group to the user
|
||||
user_subscription, created = UserSubscription.objects.get_or_create(
|
||||
user=user,
|
||||
subscription_group=custom_group,
|
||||
defaults={'active': True}
|
||||
)
|
||||
if created:
|
||||
logger.info(f"Assigned custom subscription group to user {user.username}")
|
||||
|
||||
inbound_count = selected_inbounds.count()
|
||||
group_count = access_request.selected_subscription_groups.count()
|
||||
|
||||
logger.info(f"Successfully assigned {group_count} subscription groups and {inbound_count} individual inbounds to user {user.username}")
|
||||
logger.info(f"Successfully assigned {group_count} subscription groups to user {user.username}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error assigning VPN access to user {user.username}: {e}")
|
||||
|
@@ -6,7 +6,7 @@ import os
|
||||
import fcntl
|
||||
from typing import Optional
|
||||
from telegram import Update, Bot
|
||||
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
|
||||
from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters, ContextTypes
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from asgiref.sync import sync_to_async
|
||||
@@ -125,6 +125,619 @@ class TelegramBotManager:
|
||||
self._release_lock()
|
||||
raise e
|
||||
|
||||
async def _is_telegram_admin(self, telegram_user):
|
||||
"""Check if user is a Telegram admin"""
|
||||
try:
|
||||
from django.db.models import Q
|
||||
bot_settings = await sync_to_async(BotSettings.get_settings)()
|
||||
|
||||
# Check by telegram_user_id first
|
||||
if hasattr(telegram_user, 'id'):
|
||||
telegram_user_id = telegram_user.id
|
||||
telegram_username = telegram_user.username if hasattr(telegram_user, 'username') else None
|
||||
else:
|
||||
# If just an ID was passed
|
||||
telegram_user_id = telegram_user
|
||||
telegram_username = None
|
||||
|
||||
# Check if admin by telegram_user_id
|
||||
admin_by_id = await sync_to_async(
|
||||
bot_settings.telegram_admins.filter(telegram_user_id=telegram_user_id).exists
|
||||
)()
|
||||
|
||||
if admin_by_id:
|
||||
return True
|
||||
|
||||
# Also check by telegram_username if available
|
||||
if telegram_username:
|
||||
admin_by_username = await sync_to_async(
|
||||
bot_settings.telegram_admins.filter(telegram_username=telegram_username).exists
|
||||
)()
|
||||
|
||||
if admin_by_username:
|
||||
# Update the user's telegram_user_id for future checks
|
||||
admin_user = await sync_to_async(
|
||||
bot_settings.telegram_admins.filter(telegram_username=telegram_username).first
|
||||
)()
|
||||
if admin_user and not admin_user.telegram_user_id:
|
||||
admin_user.telegram_user_id = telegram_user_id
|
||||
await sync_to_async(admin_user.save)()
|
||||
logger.info(f"Linked telegram_user_id {telegram_user_id} to admin {admin_user.username}")
|
||||
return True
|
||||
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking admin status: {e}")
|
||||
return False
|
||||
|
||||
async def _notify_admins_new_request(self, access_request):
|
||||
"""Notify all Telegram admins about new access request"""
|
||||
try:
|
||||
bot_settings = await sync_to_async(BotSettings.get_settings)()
|
||||
admins = await sync_to_async(list)(bot_settings.telegram_admins.all())
|
||||
|
||||
if not admins:
|
||||
logger.info("No Telegram admins configured, skipping notification")
|
||||
return
|
||||
|
||||
# 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}"
|
||||
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
|
||||
|
||||
# Send notification to each admin
|
||||
for admin in admins:
|
||||
try:
|
||||
if admin.telegram_user_id:
|
||||
# Get admin language (default to English if not set)
|
||||
admin_language = 'ru' if hasattr(admin, 'telegram_user_language') else 'en'
|
||||
|
||||
notification_text = MessageLocalizer.get_message(
|
||||
'admin_new_request_notification',
|
||||
admin_language,
|
||||
user_info=user_info,
|
||||
telegram_info=telegram_info,
|
||||
date=date,
|
||||
message=message
|
||||
)
|
||||
|
||||
await self._send_notification_to_admin(admin.telegram_user_id, notification_text)
|
||||
logger.info(f"Sent new request notification to admin {admin.username}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to notify admin {admin.username}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error notifying admins about new request: {e}")
|
||||
|
||||
async def _send_notification_to_admin(self, telegram_user_id, message_text):
|
||||
"""Send notification message to specific admin"""
|
||||
try:
|
||||
bot_settings = await sync_to_async(BotSettings.get_settings)()
|
||||
if not bot_settings.enabled or not bot_settings.bot_token:
|
||||
logger.warning("Bot not configured, skipping admin notification")
|
||||
return
|
||||
|
||||
# Create a simple Bot instance for sending notification
|
||||
from telegram import Bot
|
||||
from telegram.request import HTTPXRequest
|
||||
|
||||
request_kwargs = {
|
||||
'connection_pool_size': 1,
|
||||
'read_timeout': bot_settings.connection_timeout,
|
||||
'write_timeout': bot_settings.connection_timeout,
|
||||
'connect_timeout': bot_settings.connection_timeout,
|
||||
}
|
||||
|
||||
if bot_settings.use_proxy and bot_settings.proxy_url:
|
||||
request_kwargs['proxy'] = bot_settings.proxy_url
|
||||
|
||||
request = HTTPXRequest(**request_kwargs)
|
||||
bot = Bot(token=bot_settings.bot_token, request=request)
|
||||
|
||||
await bot.send_message(
|
||||
chat_id=telegram_user_id,
|
||||
text=message_text,
|
||||
parse_mode='Markdown'
|
||||
)
|
||||
|
||||
# Clean up bot connection
|
||||
try:
|
||||
await request.shutdown()
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send notification to admin {telegram_user_id}: {e}")
|
||||
|
||||
async def _create_main_keyboard(self, telegram_user):
|
||||
"""Create main keyboard for existing users, with admin buttons if user is admin"""
|
||||
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||
|
||||
# Get basic buttons
|
||||
access_button = get_localized_button(telegram_user, 'access')
|
||||
guide_button = get_localized_button(telegram_user, 'guide')
|
||||
|
||||
# Check if user is admin
|
||||
is_admin = await self._is_telegram_admin(telegram_user)
|
||||
|
||||
if is_admin:
|
||||
# Admin keyboard with additional admin button
|
||||
access_requests_button = get_localized_button(telegram_user, 'access_requests')
|
||||
keyboard = [
|
||||
[KeyboardButton(access_button), KeyboardButton(guide_button)],
|
||||
[KeyboardButton(access_requests_button)]
|
||||
]
|
||||
else:
|
||||
# Regular user keyboard
|
||||
keyboard = [
|
||||
[KeyboardButton(access_button), KeyboardButton(guide_button)]
|
||||
]
|
||||
|
||||
return ReplyKeyboardMarkup(
|
||||
keyboard,
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=False
|
||||
)
|
||||
|
||||
async def _handle_access_requests_command(self, update: Update):
|
||||
"""Handle access requests button - show pending requests to admin"""
|
||||
try:
|
||||
# Get pending access requests
|
||||
pending_requests = await sync_to_async(list)(
|
||||
AccessRequest.objects.filter(approved=False).order_by('-created_at')
|
||||
)
|
||||
|
||||
if not pending_requests:
|
||||
# No pending requests
|
||||
no_requests_msg = get_localized_message(update.message.from_user, 'admin_no_pending_requests')
|
||||
reply_markup = await self._create_main_keyboard(update.message.from_user)
|
||||
|
||||
sent_message = await update.message.reply_text(
|
||||
no_requests_msg,
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
|
||||
await self._save_outgoing_message(sent_message, update.message.from_user)
|
||||
return
|
||||
|
||||
# Show pending requests with inline keyboards for approve/reject
|
||||
title_msg = get_localized_message(update.message.from_user, 'admin_access_requests_title')
|
||||
|
||||
# Send title message with main keyboard
|
||||
reply_markup = await self._create_main_keyboard(update.message.from_user)
|
||||
title_sent = await update.message.reply_text(
|
||||
title_msg,
|
||||
reply_markup=reply_markup,
|
||||
parse_mode='Markdown'
|
||||
)
|
||||
await self._save_outgoing_message(title_sent, update.message.from_user)
|
||||
|
||||
# Send each request with inline keyboard for actions
|
||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
for request in pending_requests[:5]: # Show max 5 requests
|
||||
# Format request info
|
||||
user_info = request.display_name
|
||||
date = request.created_at.strftime("%Y-%m-%d %H:%M")
|
||||
message_preview = request.message_text[:100] + "..." if len(request.message_text) > 100 else request.message_text
|
||||
|
||||
request_text = get_localized_message(
|
||||
update.message.from_user,
|
||||
'admin_request_item',
|
||||
user_info=user_info,
|
||||
date=date,
|
||||
message_preview=message_preview
|
||||
)
|
||||
|
||||
# Create inline keyboard for this request
|
||||
approve_btn_text = get_localized_button(update.message.from_user, 'approve')
|
||||
reject_btn_text = get_localized_button(update.message.from_user, 'reject')
|
||||
details_btn_text = get_localized_button(update.message.from_user, 'details')
|
||||
|
||||
inline_keyboard = [
|
||||
[
|
||||
InlineKeyboardButton(approve_btn_text, callback_data=f"approve_{request.id}"),
|
||||
InlineKeyboardButton(reject_btn_text, callback_data=f"reject_{request.id}")
|
||||
],
|
||||
[InlineKeyboardButton(details_btn_text, callback_data=f"details_{request.id}")]
|
||||
]
|
||||
inline_markup = InlineKeyboardMarkup(inline_keyboard)
|
||||
|
||||
request_sent = await update.message.reply_text(
|
||||
request_text,
|
||||
reply_markup=inline_markup,
|
||||
parse_mode='Markdown'
|
||||
)
|
||||
await self._save_outgoing_message(request_sent, update.message.from_user)
|
||||
|
||||
if len(pending_requests) > 5:
|
||||
remaining_msg = f"... и еще {len(pending_requests) - 5} заявок" if get_user_language(update.message.from_user) == 'ru' else f"... and {len(pending_requests) - 5} more requests"
|
||||
await update.message.reply_text(remaining_msg)
|
||||
|
||||
logger.info(f"Showed {len(pending_requests)} pending requests to admin {update.message.from_user.username}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling access requests command: {e}")
|
||||
error_msg = get_localized_message(update.message.from_user, 'admin_error_processing', error=str(e))
|
||||
await update.message.reply_text(error_msg)
|
||||
|
||||
async def _handle_callback_query(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle inline keyboard button presses (callback queries)"""
|
||||
try:
|
||||
query = update.callback_query
|
||||
await query.answer() # Acknowledge the callback
|
||||
|
||||
# Check if user is admin
|
||||
if not await self._is_telegram_admin(query.from_user):
|
||||
await query.edit_message_text("❌ Access denied. Admin rights required.")
|
||||
return
|
||||
|
||||
# Parse callback data
|
||||
callback_data = query.data
|
||||
action, request_id = callback_data.split('_', 1)
|
||||
|
||||
if action == "approve":
|
||||
await self._handle_approve_callback(query, request_id)
|
||||
elif action == "reject":
|
||||
await self._handle_reject_callback(query, request_id)
|
||||
elif action == "details":
|
||||
await self._handle_details_callback(query, request_id)
|
||||
elif action == "sg": # Subscription group selection
|
||||
await self._handle_subscription_group_callback(query, callback_data)
|
||||
elif action == "confirm": # Confirm approval with selected groups
|
||||
await self._handle_confirm_approval_callback(query, request_id)
|
||||
elif action == "cancel": # Cancel approval process
|
||||
await self._handle_cancel_callback(query, request_id)
|
||||
else:
|
||||
await query.edit_message_text("❌ Unknown action")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling callback query: {e}")
|
||||
try:
|
||||
await query.edit_message_text(f"❌ Error: {str(e)}")
|
||||
except:
|
||||
pass
|
||||
|
||||
async def _handle_approve_callback(self, query, request_id):
|
||||
"""Handle approve button press - show subscription groups selection"""
|
||||
try:
|
||||
# Get the request
|
||||
request = await sync_to_async(AccessRequest.objects.get)(id=request_id)
|
||||
|
||||
# Check if already processed
|
||||
if request.approved:
|
||||
already_processed_msg = get_localized_message(query.from_user, 'admin_request_already_processed')
|
||||
await query.edit_message_text(already_processed_msg)
|
||||
return
|
||||
|
||||
# Get available subscription groups
|
||||
from vpn.models_xray import SubscriptionGroup
|
||||
groups = await sync_to_async(list)(
|
||||
SubscriptionGroup.objects.filter(is_active=True).order_by('name')
|
||||
)
|
||||
|
||||
if not groups:
|
||||
await query.edit_message_text("❌ No subscription groups available")
|
||||
return
|
||||
|
||||
# Create inline keyboard with subscription groups
|
||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
user_info = request.display_name
|
||||
choose_groups_msg = get_localized_message(
|
||||
query.from_user,
|
||||
'admin_choose_subscription_groups',
|
||||
user_info=user_info
|
||||
)
|
||||
|
||||
# Create buttons for each group (max 2 per row)
|
||||
keyboard = []
|
||||
for i in range(0, len(groups), 2):
|
||||
row = []
|
||||
for j in range(2):
|
||||
if i + j < len(groups):
|
||||
group = groups[i + j]
|
||||
button_text = f"⚪ {group.name}"
|
||||
callback_data = f"sg_toggle_{group.id}_{request_id}"
|
||||
row.append(InlineKeyboardButton(button_text, callback_data=callback_data))
|
||||
keyboard.append(row)
|
||||
|
||||
# Add confirm and cancel buttons
|
||||
confirm_btn_text = get_localized_button(query.from_user, 'confirm_approval')
|
||||
cancel_btn_text = get_localized_button(query.from_user, 'cancel')
|
||||
|
||||
keyboard.append([
|
||||
InlineKeyboardButton(confirm_btn_text, callback_data=f"confirm_{request_id}"),
|
||||
InlineKeyboardButton(cancel_btn_text, callback_data=f"cancel_{request_id}")
|
||||
])
|
||||
|
||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||
await query.edit_message_text(choose_groups_msg, reply_markup=reply_markup, parse_mode='Markdown')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling approve callback: {e}")
|
||||
error_msg = get_localized_message(query.from_user, 'admin_error_processing', error=str(e))
|
||||
await query.edit_message_text(error_msg)
|
||||
|
||||
async def _handle_subscription_group_callback(self, query, callback_data):
|
||||
"""Handle subscription group toggle button"""
|
||||
try:
|
||||
# Parse callback data: sg_toggle_{group_id}_{request_id}
|
||||
parts = callback_data.split('_')
|
||||
group_id = parts[2]
|
||||
request_id = parts[3]
|
||||
|
||||
# Get current message text and keyboard
|
||||
current_text = query.message.text
|
||||
current_keyboard = query.message.reply_markup.inline_keyboard
|
||||
|
||||
# Toggle the group selection
|
||||
updated_keyboard = []
|
||||
for row in current_keyboard:
|
||||
updated_row = []
|
||||
for button in row:
|
||||
if button.callback_data and button.callback_data.startswith(f"sg_toggle_{group_id}_"):
|
||||
# Toggle this button
|
||||
if button.text.startswith("⚪"):
|
||||
# Select it
|
||||
new_text = button.text.replace("⚪", "✅")
|
||||
else:
|
||||
# Deselect it
|
||||
new_text = button.text.replace("✅", "⚪")
|
||||
from telegram import InlineKeyboardButton
|
||||
updated_row.append(InlineKeyboardButton(new_text, callback_data=button.callback_data))
|
||||
else:
|
||||
updated_row.append(button)
|
||||
updated_keyboard.append(updated_row)
|
||||
|
||||
from telegram import InlineKeyboardMarkup
|
||||
updated_markup = InlineKeyboardMarkup(updated_keyboard)
|
||||
await query.edit_message_reply_markup(reply_markup=updated_markup)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling subscription group callback: {e}")
|
||||
|
||||
async def _handle_confirm_approval_callback(self, query, request_id):
|
||||
"""Handle confirm approval button - create user and assign groups"""
|
||||
try:
|
||||
# Get the request
|
||||
request = await sync_to_async(AccessRequest.objects.get)(id=request_id)
|
||||
|
||||
# Check if already processed
|
||||
if request.approved:
|
||||
already_processed_msg = get_localized_message(query.from_user, 'admin_request_already_processed')
|
||||
await query.edit_message_text(already_processed_msg)
|
||||
return
|
||||
|
||||
# Get selected groups from the keyboard
|
||||
selected_groups = []
|
||||
from vpn.models_xray import SubscriptionGroup
|
||||
|
||||
for row in query.message.reply_markup.inline_keyboard:
|
||||
for button in row:
|
||||
if button.callback_data and button.callback_data.startswith("sg_toggle_") and button.text.startswith("✅"):
|
||||
# Extract group_id from callback_data
|
||||
group_id = button.callback_data.split('_')[2]
|
||||
group = await sync_to_async(SubscriptionGroup.objects.get)(id=group_id)
|
||||
selected_groups.append(group)
|
||||
|
||||
if not selected_groups:
|
||||
await query.edit_message_text("❌ Please select at least one subscription group")
|
||||
return
|
||||
|
||||
# Save selected groups to the request
|
||||
await sync_to_async(request.selected_subscription_groups.set)(selected_groups)
|
||||
|
||||
try:
|
||||
# Create or get user
|
||||
from vpn.models import User
|
||||
from vpn.models_xray import UserSubscription
|
||||
import secrets
|
||||
import string
|
||||
|
||||
# Check if user already exists
|
||||
existing_user = await sync_to_async(
|
||||
User.objects.filter(telegram_user_id=request.telegram_user_id).first
|
||||
)()
|
||||
|
||||
if existing_user:
|
||||
user = existing_user
|
||||
logger.info(f"Using existing user {user.username} for telegram_user_id {request.telegram_user_id}")
|
||||
else:
|
||||
# Check if selected_existing_user is set
|
||||
if request.selected_existing_user:
|
||||
# Link telegram to existing user
|
||||
user = request.selected_existing_user
|
||||
user.telegram_user_id = request.telegram_user_id
|
||||
user.telegram_username = request.telegram_username
|
||||
await sync_to_async(user.save)()
|
||||
logger.info(f"Linked telegram account to existing user {user.username}")
|
||||
else:
|
||||
# Create new user
|
||||
username = request.desired_username or request.telegram_username or f"tg_{request.telegram_user_id}"
|
||||
|
||||
# Ensure unique username
|
||||
base_username = username
|
||||
counter = 1
|
||||
while await sync_to_async(User.objects.filter(username=username).exists)():
|
||||
username = f"{base_username}_{counter}"
|
||||
counter += 1
|
||||
|
||||
# Generate random password
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
password = ''.join(secrets.choice(alphabet) for _ in range(16))
|
||||
|
||||
# Create user
|
||||
user = await sync_to_async(User.objects.create_user)(
|
||||
username=username,
|
||||
password=password,
|
||||
telegram_user_id=request.telegram_user_id,
|
||||
telegram_username=request.telegram_username,
|
||||
first_name=request.telegram_first_name or '',
|
||||
last_name=request.telegram_last_name or '',
|
||||
comment=f"Created from Telegram request #{request.id}"
|
||||
)
|
||||
logger.info(f"Created new user {user.username} from Telegram request")
|
||||
|
||||
# Link user to request
|
||||
request.user = user
|
||||
await sync_to_async(request.save)()
|
||||
|
||||
# Assign subscription groups to user
|
||||
for subscription_group in selected_groups:
|
||||
user_subscription, created = await sync_to_async(UserSubscription.objects.get_or_create)(
|
||||
user=user,
|
||||
subscription_group=subscription_group,
|
||||
defaults={'active': True}
|
||||
)
|
||||
if created:
|
||||
logger.info(f"Assigned subscription group '{subscription_group.name}' to user {user.username}")
|
||||
else:
|
||||
# Ensure it's active if it already existed
|
||||
if not user_subscription.active:
|
||||
user_subscription.active = True
|
||||
await sync_to_async(user_subscription.save)()
|
||||
logger.info(f"Re-activated subscription group '{subscription_group.name}' for user {user.username}")
|
||||
|
||||
# Mark as approved
|
||||
request.approved = True
|
||||
await sync_to_async(request.save)()
|
||||
|
||||
# Send success message to admin
|
||||
groups_list = ", ".join([group.name for group in selected_groups])
|
||||
success_msg = get_localized_message(
|
||||
query.from_user,
|
||||
'admin_approval_success',
|
||||
user_info=request.display_name,
|
||||
groups=groups_list
|
||||
)
|
||||
await query.edit_message_text(success_msg, parse_mode='Markdown')
|
||||
|
||||
# Send approval notification to user
|
||||
# Create a dummy user object for localization
|
||||
class DummyUser:
|
||||
def __init__(self, lang):
|
||||
self.language_code = lang
|
||||
|
||||
user_lang = DummyUser(request.user_language if hasattr(request, 'user_language') else 'en')
|
||||
approval_msg = get_localized_message(user_lang, 'approval_notification')
|
||||
await self._send_notification_to_admin(request.telegram_user_id, approval_msg)
|
||||
|
||||
logger.info(f"Admin {query.from_user.username} approved request {request_id} with {len(selected_groups)} groups")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in approval process: {e}")
|
||||
error_msg = get_localized_message(query.from_user, 'admin_error_processing', error=str(e))
|
||||
await query.edit_message_text(error_msg)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling confirm approval callback: {e}")
|
||||
error_msg = get_localized_message(query.from_user, 'admin_error_processing', error=str(e))
|
||||
await query.edit_message_text(error_msg)
|
||||
|
||||
async def _handle_reject_callback(self, query, request_id):
|
||||
"""Handle reject button press"""
|
||||
try:
|
||||
# Get the request
|
||||
request = await sync_to_async(AccessRequest.objects.get)(id=request_id)
|
||||
|
||||
# Check if already processed
|
||||
if request.approved:
|
||||
already_processed_msg = get_localized_message(query.from_user, 'admin_request_already_processed')
|
||||
await query.edit_message_text(already_processed_msg)
|
||||
return
|
||||
|
||||
# Mark as rejected (we can add a rejected field or just delete)
|
||||
await sync_to_async(request.delete)()
|
||||
|
||||
# Send success message to admin
|
||||
success_msg = get_localized_message(
|
||||
query.from_user,
|
||||
'admin_rejection_success',
|
||||
user_info=request.display_name
|
||||
)
|
||||
await query.edit_message_text(success_msg, parse_mode='Markdown')
|
||||
|
||||
logger.info(f"Admin {query.from_user.username} rejected request {request_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling reject callback: {e}")
|
||||
error_msg = get_localized_message(query.from_user, 'admin_error_processing', error=str(e))
|
||||
await query.edit_message_text(error_msg)
|
||||
|
||||
async def _handle_details_callback(self, query, request_id):
|
||||
"""Handle details button press - show full request info"""
|
||||
try:
|
||||
# Get the request
|
||||
request = await sync_to_async(AccessRequest.objects.get)(id=request_id)
|
||||
|
||||
# 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"**📅 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"
|
||||
details += f"**💬 Full Message:**\n{request.message_text}"
|
||||
|
||||
# Create back button
|
||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
back_keyboard = [[
|
||||
InlineKeyboardButton("⬅️ Back", callback_data=f"approve_{request_id}"),
|
||||
InlineKeyboardButton("❌ Reject", callback_data=f"reject_{request_id}")
|
||||
]]
|
||||
reply_markup = InlineKeyboardMarkup(back_keyboard)
|
||||
|
||||
await query.edit_message_text(details, reply_markup=reply_markup, parse_mode='Markdown')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling details callback: {e}")
|
||||
error_msg = get_localized_message(query.from_user, 'admin_error_processing', error=str(e))
|
||||
await query.edit_message_text(error_msg)
|
||||
|
||||
async def _handle_cancel_callback(self, query, request_id):
|
||||
"""Handle cancel button press - return to original request view"""
|
||||
try:
|
||||
# Get the request
|
||||
request = await sync_to_async(AccessRequest.objects.get)(id=request_id)
|
||||
|
||||
# Recreate original request display
|
||||
user_info = request.display_name
|
||||
date = request.created_at.strftime("%Y-%m-%d %H:%M")
|
||||
message_preview = request.message_text[:100] + "..." if len(request.message_text) > 100 else request.message_text
|
||||
|
||||
request_text = get_localized_message(
|
||||
query.from_user,
|
||||
'admin_request_item',
|
||||
user_info=user_info,
|
||||
date=date,
|
||||
message_preview=message_preview
|
||||
)
|
||||
|
||||
# Create original inline keyboard
|
||||
from telegram import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
approve_btn_text = get_localized_button(query.from_user, 'approve')
|
||||
reject_btn_text = get_localized_button(query.from_user, 'reject')
|
||||
details_btn_text = get_localized_button(query.from_user, 'details')
|
||||
|
||||
inline_keyboard = [
|
||||
[
|
||||
InlineKeyboardButton(approve_btn_text, callback_data=f"approve_{request.id}"),
|
||||
InlineKeyboardButton(reject_btn_text, callback_data=f"reject_{request.id}")
|
||||
],
|
||||
[InlineKeyboardButton(details_btn_text, callback_data=f"details_{request.id}")]
|
||||
]
|
||||
inline_markup = InlineKeyboardMarkup(inline_keyboard)
|
||||
|
||||
await query.edit_message_text(request_text, reply_markup=inline_markup, parse_mode='Markdown')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling cancel callback: {e}")
|
||||
error_msg = get_localized_message(query.from_user, 'admin_error_processing', error=str(e))
|
||||
await query.edit_message_text(error_msg)
|
||||
|
||||
def stop(self):
|
||||
"""Stop the bot"""
|
||||
if not self._running:
|
||||
@@ -217,6 +830,7 @@ class TelegramBotManager:
|
||||
|
||||
# Add handlers
|
||||
self._application.add_handler(CommandHandler("start", self._handle_start))
|
||||
self._application.add_handler(CallbackQueryHandler(self._handle_callback_query))
|
||||
self._application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self._handle_message))
|
||||
self._application.add_handler(MessageHandler(filters.ALL & ~filters.COMMAND, self._handle_other))
|
||||
|
||||
@@ -276,19 +890,7 @@ class TelegramBotManager:
|
||||
|
||||
if user_response['action'] == 'existing_user':
|
||||
# User already exists - show keyboard with options
|
||||
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||
|
||||
# Create keyboard for registered users with localized buttons
|
||||
access_button = get_localized_button(update.message.from_user, 'access')
|
||||
guide_button = get_localized_button(update.message.from_user, 'guide')
|
||||
keyboard = [
|
||||
[KeyboardButton(access_button), KeyboardButton(guide_button)],
|
||||
]
|
||||
reply_markup = ReplyKeyboardMarkup(
|
||||
keyboard,
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=False
|
||||
)
|
||||
reply_markup = await self._create_main_keyboard(update.message.from_user)
|
||||
|
||||
help_text = get_localized_message(update.message.from_user, 'help_text')
|
||||
sent_message = await update.message.reply_text(
|
||||
@@ -331,6 +933,7 @@ class TelegramBotManager:
|
||||
# Get localized button texts for comparison
|
||||
access_btn = get_localized_button(update.message.from_user, 'access')
|
||||
guide_btn = get_localized_button(update.message.from_user, 'guide')
|
||||
access_requests_btn = get_localized_button(update.message.from_user, 'access_requests')
|
||||
all_in_one_btn = get_localized_button(update.message.from_user, 'all_in_one')
|
||||
back_btn = get_localized_button(update.message.from_user, 'back')
|
||||
group_prefix = get_localized_button(update.message.from_user, 'group_prefix')
|
||||
@@ -340,6 +943,12 @@ class TelegramBotManager:
|
||||
await self._handle_access_command(update, user_response['user'])
|
||||
elif update.message.text == guide_btn:
|
||||
await self._handle_guide_command(update)
|
||||
elif update.message.text == access_requests_btn:
|
||||
# Admin command - check permissions and handle
|
||||
if await self._is_telegram_admin(update.message.from_user):
|
||||
await self._handle_access_requests_command(update)
|
||||
else:
|
||||
await self._send_help_message(update)
|
||||
elif update.message.text.startswith(group_prefix):
|
||||
# Handle specific group selection
|
||||
group_name = update.message.text.replace(group_prefix, "")
|
||||
@@ -653,16 +1262,7 @@ class TelegramBotManager:
|
||||
)
|
||||
else:
|
||||
# No active subscriptions - show main keyboard
|
||||
access_button = get_localized_button(update.message.from_user, 'access')
|
||||
guide_button = get_localized_button(update.message.from_user, 'guide')
|
||||
keyboard = [
|
||||
[KeyboardButton(access_button), KeyboardButton(guide_button)],
|
||||
]
|
||||
reply_markup = ReplyKeyboardMarkup(
|
||||
keyboard,
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=False
|
||||
)
|
||||
reply_markup = await self._create_main_keyboard(update.message.from_user)
|
||||
|
||||
no_subs_msg = get_localized_message(update.message.from_user, 'no_subscriptions')
|
||||
sent_message = await update.message.reply_text(
|
||||
@@ -711,6 +1311,10 @@ class TelegramBotManager:
|
||||
)
|
||||
|
||||
logger.info(f"Created access request for user {telegram_user.username or telegram_user.id}")
|
||||
|
||||
# Notify admins about new request
|
||||
await self._notify_admins_new_request(request)
|
||||
|
||||
return request
|
||||
|
||||
except Exception as e:
|
||||
@@ -721,17 +1325,7 @@ class TelegramBotManager:
|
||||
"""Send help message for unrecognized commands"""
|
||||
try:
|
||||
# Create main keyboard for existing users with localized buttons
|
||||
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||
access_button = get_localized_button(update.message.from_user, 'access')
|
||||
guide_button = get_localized_button(update.message.from_user, 'guide')
|
||||
keyboard = [
|
||||
[KeyboardButton(access_button), KeyboardButton(guide_button)],
|
||||
]
|
||||
reply_markup = ReplyKeyboardMarkup(
|
||||
keyboard,
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=False
|
||||
)
|
||||
reply_markup = await self._create_main_keyboard(update.message.from_user)
|
||||
|
||||
help_text = get_localized_message(update.message.from_user, 'help_text')
|
||||
sent_message = await update.message.reply_text(
|
||||
@@ -978,19 +1572,8 @@ class TelegramBotManager:
|
||||
async def _handle_back_to_main(self, update: Update, user):
|
||||
"""Handle back button - return to main menu"""
|
||||
try:
|
||||
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||
|
||||
# Create main keyboard with localized buttons
|
||||
access_btn = get_localized_button(update.message.from_user, 'access')
|
||||
guide_btn = get_localized_button(update.message.from_user, 'guide')
|
||||
keyboard = [
|
||||
[KeyboardButton(access_btn), KeyboardButton(guide_btn)],
|
||||
]
|
||||
reply_markup = ReplyKeyboardMarkup(
|
||||
keyboard,
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=False
|
||||
)
|
||||
reply_markup = await self._create_main_keyboard(update.message.from_user)
|
||||
|
||||
help_text = get_localized_message(update.message.from_user, 'help_text')
|
||||
sent_message = await update.message.reply_text(
|
||||
|
@@ -41,6 +41,18 @@ MESSAGES = {
|
||||
'guide_choose_platform': "Select your device platform:",
|
||||
'web_portal_description': "_Web portal shows your access list on one convenient page with some statistics._",
|
||||
'servers_in_group': "🔒 **Servers in group:**",
|
||||
|
||||
# Admin messages
|
||||
'admin_new_request_notification': "🔔 **New Access Request**\n\n👤 **User:** {user_info}\n📱 **Telegram:** {telegram_info}\n📅 **Date:** {date}\n\n💬 **Message:** {message}",
|
||||
'admin_access_requests_title': "📋 **Pending Access Requests**",
|
||||
'admin_no_pending_requests': "✅ No pending access requests",
|
||||
'admin_request_item': "👤 **{user_info}**\n📅 {date}\n💬 _{message_preview}_",
|
||||
'admin_choose_subscription_groups': "📦 **Choose Subscription Groups for {user_info}:**\n\nSelect groups to assign to this user:",
|
||||
'admin_approval_success': "✅ **Request Approved!**\n\n👤 User: {user_info}\n📦 Groups: {groups}\n\nUser has been notified and given access.",
|
||||
'admin_rejection_success': "❌ **Request Rejected**\n\n👤 User: {user_info}\n\nUser has been notified.",
|
||||
'admin_request_already_processed': "⚠️ This request has already been processed by another admin.",
|
||||
'admin_error_processing': "❌ Error processing request: {error}",
|
||||
|
||||
'android_guide': "🤖 **Android Setup Guide**\n\n**Step 1: Install the app**\nDownload V2RayTUN from Google Play:\nhttps://play.google.com/store/apps/details?id=com.v2raytun.android\n\n**Step 2: Add subscription**\n• Open the app\n• Tap the **+** button in the top right corner\n• Paste your subscription link from the bot\n• The app will automatically load all VPN servers\n\n**Step 3: Connect**\n• Choose a server from the list\n• Tap **Connect**\n• All your traffic will now go through VPN\n\n**💡 Useful settings:**\n• In settings, enable direct access for banking apps and local sites\n• You can choose specific apps to use VPN while others use direct connection\n\n**🔄 If VPN stops working:**\nTap the refresh icon next to the server list to update your subscription.",
|
||||
'ios_guide': " **iOS Setup Guide**\n\n**Step 1: Install the app**\nDownload V2RayTUN from App Store:\nhttps://apps.apple.com/us/app/v2raytun/id6476628951\n\n**Step 2: Add subscription**\n• Open the app\n• Tap the **+** button in the top right corner\n• Paste your subscription link from the bot\n• The app will automatically load all VPN servers\n\n**Step 3: Connect**\n• Choose a server from the list\n• Tap **Connect**\n• All your traffic will now go through VPN\n\n**⚠️ Note for iOS users:**\nCurrently, only VLESS protocol works reliably on iOS. Other protocols may have connectivity issues.\n\n**💡 Useful settings:**\n• In settings, enable direct access for banking apps and local sites to improve performance\n\n**🔄 If VPN stops working:**\nTap the refresh icon next to the server list to update your subscription.",
|
||||
'buttons': {
|
||||
@@ -52,7 +64,15 @@ MESSAGES = {
|
||||
'all_in_one': "🌍 All-in-one",
|
||||
'back': "⬅️ Back",
|
||||
'group_prefix': "Group: ",
|
||||
'request_access': "🔑 Request Access"
|
||||
'request_access': "🔑 Request Access",
|
||||
# Admin buttons
|
||||
'access_requests': "📋 Access Requests",
|
||||
'approve': "✅ Approve",
|
||||
'reject': "❌ Reject",
|
||||
'details': "👁 Details",
|
||||
'confirm_approval': "✅ Confirm Approval",
|
||||
'confirm_rejection': "❌ Confirm Rejection",
|
||||
'cancel': "🚫 Cancel"
|
||||
}
|
||||
},
|
||||
'ru': {
|
||||
@@ -88,6 +108,18 @@ MESSAGES = {
|
||||
'guide_choose_platform': "Выберите платформу вашего устройства:",
|
||||
'web_portal_description': "_Веб-портал показывает список ваших доступов на одной удобной странице с некоторой статистикой._",
|
||||
'servers_in_group': "🔒 **Серверы в группе:**",
|
||||
|
||||
# Admin messages
|
||||
'admin_new_request_notification': "🔔 **Новый запрос на доступ**\n\n👤 **Пользователь:** {user_info}\n📱 **Telegram:** {telegram_info}\n📅 **Дата:** {date}\n\n💬 **Сообщение:** {message}",
|
||||
'admin_access_requests_title': "📋 **Ожидающие запросы на доступ**",
|
||||
'admin_no_pending_requests': "✅ Нет ожидающих запросов на доступ",
|
||||
'admin_request_item': "👤 **{user_info}**\n📅 {date}\n💬 _{message_preview}_",
|
||||
'admin_choose_subscription_groups': "📦 **Выберите группы подписки для {user_info}:**\n\nВыберите группы для назначения этому пользователю:",
|
||||
'admin_approval_success': "✅ **Запрос одобрен!**\n\n👤 Пользователь: {user_info}\n📦 Группы: {groups}\n\nПользователь уведомлен и получил доступ.",
|
||||
'admin_rejection_success': "❌ **Запрос отклонен**\n\n👤 Пользователь: {user_info}\n\nПользователь уведомлен.",
|
||||
'admin_request_already_processed': "⚠️ Этот запрос уже обработан другим администратором.",
|
||||
'admin_error_processing': "❌ Ошибка обработки запроса: {error}",
|
||||
|
||||
'android_guide': "🤖 **Руководство для Android**\n\n**Шаг 1: Установите приложение**\nСкачайте V2RayTUN из Google Play:\nhttps://play.google.com/store/apps/details?id=com.v2raytun.android\n\n**Шаг 2: Добавьте подписку**\n• Откройте приложение\n• Нажмите кнопку **+** в правом верхнем углу\n• Вставьте ссылку на подписку из бота\n• Приложение автоматически загрузит список VPN серверов\n\n**Шаг 3: Подключитесь**\n• Выберите сервер из списка\n• Нажмите **Подключиться**\n• Весь ваш трафик будет проходить через VPN\n\n**💡 Полезные настройки:**\n• В настройках включите прямой доступ для банковских приложений и местных сайтов\n• Вы можете выбрать конкретные приложения для использования VPN, в то время как остальные будут работать напрямую\n\n**🔄 Если VPN перестал работать:**\nНажмите иконку обновления рядом со списком серверов для обновления подписки.",
|
||||
'ios_guide': " **Руководство для iOS**\n\n**Шаг 1: Установите приложение**\nСкачайте V2RayTUN из App Store:\nhttps://apps.apple.com/us/app/v2raytun/id6476628951\n\n**Шаг 2: Добавьте подписку**\n• Откройте приложение\n• Нажмите кнопку **+** в правом верхнем углу\n• Вставьте ссылку на подписку из бота\n• Приложение автоматически загрузит список VPN серверов\n\n**Шаг 3: Подключитесь**\n• Выберите сервер из списка\n• Нажмите **Подключиться**\n• Весь ваш трафик будет проходить через VPN\n\n**⚠️ Важно для пользователей iOS:**\nВ настоящее время на iOS стабильно работает только протокол VLESS. Другие протоколы могут иметь проблемы с подключением.\n\n**💡 Полезные настройки:**\n• В настройках включите прямой доступ для банковских приложений и местных сайтов для улучшения производительности\n\n**🔄 Если VPN перестал работать:**\nНажмите иконку обновления рядом со списком серверов для обновления подписки.",
|
||||
'buttons': {
|
||||
@@ -99,7 +131,15 @@ MESSAGES = {
|
||||
'all_in_one': "🌍 Все в одном",
|
||||
'back': "⬅️ Назад",
|
||||
'group_prefix': "Группа: ",
|
||||
'request_access': "🔑 Запросить доступ"
|
||||
'request_access': "🔑 Запросить доступ",
|
||||
# Admin buttons
|
||||
'access_requests': "📋 Запросы на доступ",
|
||||
'approve': "✅ Одобрить",
|
||||
'reject': "❌ Отклонить",
|
||||
'details': "👁 Подробности",
|
||||
'confirm_approval': "✅ Подтвердить одобрение",
|
||||
'confirm_rejection': "❌ Подтвердить отклонение",
|
||||
'cancel': "🚫 Отмена"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
telegram_bot/migrations/0010_botsettings_telegram_admins.py
Normal file
20
telegram_bot/migrations/0010_botsettings_telegram_admins.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.1.7 on 2025-08-15 13:00
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('telegram_bot', '0009_accessrequest_selected_inbounds_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='botsettings',
|
||||
name='telegram_admins',
|
||||
field=models.ManyToManyField(blank=True, help_text='Users with linked Telegram accounts who will have admin access in the bot', related_name='bot_admin_settings', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
@@ -33,6 +33,12 @@ class BotSettings(models.Model):
|
||||
default=30,
|
||||
help_text="Connection timeout in seconds"
|
||||
)
|
||||
telegram_admins = models.ManyToManyField(
|
||||
User,
|
||||
blank=True,
|
||||
related_name='bot_admin_settings',
|
||||
help_text="Users with linked Telegram accounts who will have admin access in the bot"
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
|
Reference in New Issue
Block a user