mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-08-21 14:37:16 +00:00
Added TG bot
This commit is contained in:
@@ -280,8 +280,9 @@ class TelegramBotManager:
|
|||||||
|
|
||||||
# Create keyboard for registered users with localized buttons
|
# Create keyboard for registered users with localized buttons
|
||||||
access_button = get_localized_button(update.message.from_user, 'access')
|
access_button = get_localized_button(update.message.from_user, 'access')
|
||||||
|
guide_button = get_localized_button(update.message.from_user, 'guide')
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[KeyboardButton(access_button)],
|
[KeyboardButton(access_button), KeyboardButton(guide_button)],
|
||||||
]
|
]
|
||||||
reply_markup = ReplyKeyboardMarkup(
|
reply_markup = ReplyKeyboardMarkup(
|
||||||
keyboard,
|
keyboard,
|
||||||
@@ -329,6 +330,7 @@ class TelegramBotManager:
|
|||||||
if user_response['action'] == 'existing_user':
|
if user_response['action'] == 'existing_user':
|
||||||
# Get localized button texts for comparison
|
# Get localized button texts for comparison
|
||||||
access_btn = get_localized_button(update.message.from_user, 'access')
|
access_btn = get_localized_button(update.message.from_user, 'access')
|
||||||
|
guide_btn = get_localized_button(update.message.from_user, 'guide')
|
||||||
all_in_one_btn = get_localized_button(update.message.from_user, 'all_in_one')
|
all_in_one_btn = get_localized_button(update.message.from_user, 'all_in_one')
|
||||||
back_btn = get_localized_button(update.message.from_user, 'back')
|
back_btn = get_localized_button(update.message.from_user, 'back')
|
||||||
group_prefix = get_localized_button(update.message.from_user, 'group_prefix')
|
group_prefix = get_localized_button(update.message.from_user, 'group_prefix')
|
||||||
@@ -336,6 +338,8 @@ class TelegramBotManager:
|
|||||||
# Check if this is a keyboard command
|
# Check if this is a keyboard command
|
||||||
if update.message.text == access_btn:
|
if update.message.text == access_btn:
|
||||||
await self._handle_access_command(update, user_response['user'])
|
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.startswith(group_prefix):
|
elif update.message.text.startswith(group_prefix):
|
||||||
# Handle specific group selection
|
# Handle specific group selection
|
||||||
group_name = update.message.text.replace(group_prefix, "")
|
group_name = update.message.text.replace(group_prefix, "")
|
||||||
@@ -346,6 +350,15 @@ class TelegramBotManager:
|
|||||||
elif update.message.text == back_btn:
|
elif update.message.text == back_btn:
|
||||||
# Handle back button
|
# Handle back button
|
||||||
await self._handle_back_to_main(update, user_response['user'])
|
await self._handle_back_to_main(update, user_response['user'])
|
||||||
|
elif update.message.text == get_localized_button(update.message.from_user, 'android'):
|
||||||
|
# Handle Android guide
|
||||||
|
await self._handle_android_guide(update)
|
||||||
|
elif update.message.text == get_localized_button(update.message.from_user, 'ios'):
|
||||||
|
# Handle iOS guide
|
||||||
|
await self._handle_ios_guide(update)
|
||||||
|
elif update.message.text == get_localized_button(update.message.from_user, 'web_portal'):
|
||||||
|
# Handle web portal
|
||||||
|
await self._handle_web_portal(update, user_response['user'])
|
||||||
else:
|
else:
|
||||||
# Unrecognized command - send help message
|
# Unrecognized command - send help message
|
||||||
await self._send_help_message(update)
|
await self._send_help_message(update)
|
||||||
@@ -641,8 +654,9 @@ class TelegramBotManager:
|
|||||||
else:
|
else:
|
||||||
# No active subscriptions - show main keyboard
|
# No active subscriptions - show main keyboard
|
||||||
access_button = get_localized_button(update.message.from_user, 'access')
|
access_button = get_localized_button(update.message.from_user, 'access')
|
||||||
|
guide_button = get_localized_button(update.message.from_user, 'guide')
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[KeyboardButton(access_button)],
|
[KeyboardButton(access_button), KeyboardButton(guide_button)],
|
||||||
]
|
]
|
||||||
reply_markup = ReplyKeyboardMarkup(
|
reply_markup = ReplyKeyboardMarkup(
|
||||||
keyboard,
|
keyboard,
|
||||||
@@ -709,8 +723,9 @@ class TelegramBotManager:
|
|||||||
# Create main keyboard for existing users with localized buttons
|
# Create main keyboard for existing users with localized buttons
|
||||||
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||||
access_button = get_localized_button(update.message.from_user, 'access')
|
access_button = get_localized_button(update.message.from_user, 'access')
|
||||||
|
guide_button = get_localized_button(update.message.from_user, 'guide')
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[KeyboardButton(access_button)],
|
[KeyboardButton(access_button), KeyboardButton(guide_button)],
|
||||||
]
|
]
|
||||||
reply_markup = ReplyKeyboardMarkup(
|
reply_markup = ReplyKeyboardMarkup(
|
||||||
keyboard,
|
keyboard,
|
||||||
@@ -751,17 +766,62 @@ class TelegramBotManager:
|
|||||||
# Also generate user portal link
|
# Also generate user portal link
|
||||||
portal_link = f"{base_url}/u/{user.hash}"
|
portal_link = f"{base_url}/u/{user.hash}"
|
||||||
|
|
||||||
|
# Get server information for this group
|
||||||
|
from vpn.models_xray import SubscriptionGroup, ServerInbound
|
||||||
|
group_servers_info = ""
|
||||||
|
try:
|
||||||
|
# Find the subscription group by name
|
||||||
|
subscription_group = await sync_to_async(
|
||||||
|
SubscriptionGroup.objects.filter(
|
||||||
|
name=group_name,
|
||||||
|
is_active=True
|
||||||
|
).prefetch_related('inbounds').first
|
||||||
|
)()
|
||||||
|
|
||||||
|
if subscription_group:
|
||||||
|
# Get servers where this group's inbounds are deployed
|
||||||
|
deployed_servers = await sync_to_async(
|
||||||
|
lambda: list(
|
||||||
|
ServerInbound.objects.filter(
|
||||||
|
inbound__in=subscription_group.inbounds.all(),
|
||||||
|
active=True
|
||||||
|
).select_related('server', 'inbound').values_list(
|
||||||
|
'server__name', 'inbound__protocol'
|
||||||
|
).distinct()
|
||||||
|
)
|
||||||
|
)()
|
||||||
|
|
||||||
|
if deployed_servers:
|
||||||
|
# Group by server
|
||||||
|
servers_data = {}
|
||||||
|
for server_name, protocol in deployed_servers:
|
||||||
|
if server_name not in servers_data:
|
||||||
|
servers_data[server_name] = []
|
||||||
|
if protocol.upper() not in servers_data[server_name]:
|
||||||
|
servers_data[server_name].append(protocol.upper())
|
||||||
|
|
||||||
|
# Build server info text
|
||||||
|
if servers_data:
|
||||||
|
servers_header = get_localized_message(update.message.from_user, 'servers_in_group')
|
||||||
|
group_servers_info = f"\n{servers_header}\n"
|
||||||
|
for server_name, protocols in servers_data.items():
|
||||||
|
protocols_str = ", ".join(protocols)
|
||||||
|
group_servers_info += f" • {server_name} ({protocols_str})\n"
|
||||||
|
group_servers_info += "\n"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not get server info for group {group_name}: {e}")
|
||||||
|
|
||||||
# Build localized message
|
# Build localized message
|
||||||
group_title = get_localized_message(update.message.from_user, 'group_subscription', group_name=group_name)
|
group_title = get_localized_message(update.message.from_user, 'group_subscription', group_name=group_name)
|
||||||
sub_link_label = get_localized_message(update.message.from_user, 'subscription_link')
|
sub_link_label = get_localized_message(update.message.from_user, 'subscription_link')
|
||||||
portal_label = get_localized_message(update.message.from_user, 'web_portal')
|
portal_label = get_localized_message(update.message.from_user, 'web_portal')
|
||||||
tap_note = get_localized_message(update.message.from_user, 'tap_to_copy')
|
tap_note = get_localized_message(update.message.from_user, 'tap_to_copy')
|
||||||
|
|
||||||
message_text = f"{group_title}\n\n"
|
message_text = f"{group_title}\n"
|
||||||
|
message_text += group_servers_info # Add server info
|
||||||
message_text += f"{sub_link_label}\n"
|
message_text += f"{sub_link_label}\n"
|
||||||
message_text += f"`{subscription_link}`\n\n"
|
message_text += f"`{subscription_link}`\n\n"
|
||||||
message_text += f"{portal_label}\n"
|
|
||||||
message_text += f"{portal_link}\n\n"
|
|
||||||
message_text += tap_note
|
message_text += tap_note
|
||||||
|
|
||||||
# Create back navigation keyboard with only back button
|
# Create back navigation keyboard with only back button
|
||||||
@@ -878,11 +938,6 @@ class TelegramBotManager:
|
|||||||
|
|
||||||
message_text += f"{universal_link_label}\n"
|
message_text += f"{universal_link_label}\n"
|
||||||
message_text += f"`{subscription_link}`\n\n"
|
message_text += f"`{subscription_link}`\n\n"
|
||||||
|
|
||||||
# Add portal link
|
|
||||||
message_text += f"{portal_label}\n"
|
|
||||||
message_text += f"{portal_link}\n\n"
|
|
||||||
|
|
||||||
message_text += all_subs_note
|
message_text += all_subs_note
|
||||||
|
|
||||||
# Create back navigation keyboard with only back button
|
# Create back navigation keyboard with only back button
|
||||||
@@ -927,8 +982,9 @@ class TelegramBotManager:
|
|||||||
|
|
||||||
# Create main keyboard with localized buttons
|
# Create main keyboard with localized buttons
|
||||||
access_btn = get_localized_button(update.message.from_user, 'access')
|
access_btn = get_localized_button(update.message.from_user, 'access')
|
||||||
|
guide_btn = get_localized_button(update.message.from_user, 'guide')
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[KeyboardButton(access_btn)],
|
[KeyboardButton(access_btn), KeyboardButton(guide_btn)],
|
||||||
]
|
]
|
||||||
reply_markup = ReplyKeyboardMarkup(
|
reply_markup = ReplyKeyboardMarkup(
|
||||||
keyboard,
|
keyboard,
|
||||||
@@ -1054,6 +1110,177 @@ class TelegramBotManager:
|
|||||||
logger.error(f"Failed to auto-start bot: {e}")
|
logger.error(f"Failed to auto-start bot: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def _handle_guide_command(self, update: Update):
|
||||||
|
"""Handle guide button - show platform selection"""
|
||||||
|
try:
|
||||||
|
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
# Get localized buttons
|
||||||
|
android_btn = get_localized_button(update.message.from_user, 'android')
|
||||||
|
ios_btn = get_localized_button(update.message.from_user, 'ios')
|
||||||
|
web_portal_btn = get_localized_button(update.message.from_user, 'web_portal')
|
||||||
|
back_btn = get_localized_button(update.message.from_user, 'back')
|
||||||
|
|
||||||
|
# Create platform selection keyboard
|
||||||
|
keyboard = [
|
||||||
|
[KeyboardButton(android_btn), KeyboardButton(ios_btn)],
|
||||||
|
[KeyboardButton(web_portal_btn)],
|
||||||
|
[KeyboardButton(back_btn)]
|
||||||
|
]
|
||||||
|
reply_markup = ReplyKeyboardMarkup(
|
||||||
|
keyboard,
|
||||||
|
resize_keyboard=True,
|
||||||
|
one_time_keyboard=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get localized messages
|
||||||
|
guide_title = get_localized_message(update.message.from_user, 'guide_title')
|
||||||
|
choose_platform = get_localized_message(update.message.from_user, 'guide_choose_platform')
|
||||||
|
|
||||||
|
message_text = f"{guide_title}\n\n{choose_platform}"
|
||||||
|
|
||||||
|
sent_message = await update.message.reply_text(
|
||||||
|
message_text,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
parse_mode='Markdown'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save outgoing message
|
||||||
|
await self._save_outgoing_message(
|
||||||
|
sent_message,
|
||||||
|
update.message.from_user
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Showed guide platform selection to user")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling guide command: {e}")
|
||||||
|
|
||||||
|
async def _handle_android_guide(self, update: Update):
|
||||||
|
"""Handle Android guide selection"""
|
||||||
|
try:
|
||||||
|
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
# Create back navigation keyboard
|
||||||
|
back_btn = get_localized_button(update.message.from_user, 'back')
|
||||||
|
keyboard = [
|
||||||
|
[KeyboardButton(back_btn)]
|
||||||
|
]
|
||||||
|
reply_markup = ReplyKeyboardMarkup(
|
||||||
|
keyboard,
|
||||||
|
resize_keyboard=True,
|
||||||
|
one_time_keyboard=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get Android guide text
|
||||||
|
android_guide = get_localized_message(update.message.from_user, 'android_guide')
|
||||||
|
|
||||||
|
sent_message = await update.message.reply_text(
|
||||||
|
android_guide,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
parse_mode='Markdown',
|
||||||
|
disable_web_page_preview=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save outgoing message
|
||||||
|
await self._save_outgoing_message(
|
||||||
|
sent_message,
|
||||||
|
update.message.from_user
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Sent Android guide to user")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling Android guide: {e}")
|
||||||
|
|
||||||
|
async def _handle_ios_guide(self, update: Update):
|
||||||
|
"""Handle iOS guide selection"""
|
||||||
|
try:
|
||||||
|
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
# Create back navigation keyboard
|
||||||
|
back_btn = get_localized_button(update.message.from_user, 'back')
|
||||||
|
keyboard = [
|
||||||
|
[KeyboardButton(back_btn)]
|
||||||
|
]
|
||||||
|
reply_markup = ReplyKeyboardMarkup(
|
||||||
|
keyboard,
|
||||||
|
resize_keyboard=True,
|
||||||
|
one_time_keyboard=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get iOS guide text
|
||||||
|
ios_guide = get_localized_message(update.message.from_user, 'ios_guide')
|
||||||
|
|
||||||
|
sent_message = await update.message.reply_text(
|
||||||
|
ios_guide,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
parse_mode='Markdown',
|
||||||
|
disable_web_page_preview=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save outgoing message
|
||||||
|
await self._save_outgoing_message(
|
||||||
|
sent_message,
|
||||||
|
update.message.from_user
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Sent iOS guide to user")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling iOS guide: {e}")
|
||||||
|
|
||||||
|
async def _handle_web_portal(self, update: Update, user):
|
||||||
|
"""Handle web portal button - send portal link"""
|
||||||
|
try:
|
||||||
|
from django.conf import settings
|
||||||
|
from telegram import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
# Get the base URL for portal links from settings
|
||||||
|
base_url = getattr(settings, 'EXTERNAL_ADDRESS', 'https://your-server.com')
|
||||||
|
|
||||||
|
# Parse base_url to ensure it has https:// scheme
|
||||||
|
if not base_url.startswith(('http://', 'https://')):
|
||||||
|
base_url = f"https://{base_url}"
|
||||||
|
|
||||||
|
# Generate user portal link
|
||||||
|
portal_link = f"{base_url}/u/{user.hash}"
|
||||||
|
|
||||||
|
# Create back navigation keyboard
|
||||||
|
back_btn = get_localized_button(update.message.from_user, 'back')
|
||||||
|
keyboard = [
|
||||||
|
[KeyboardButton(back_btn)]
|
||||||
|
]
|
||||||
|
reply_markup = ReplyKeyboardMarkup(
|
||||||
|
keyboard,
|
||||||
|
resize_keyboard=True,
|
||||||
|
one_time_keyboard=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get localized messages
|
||||||
|
portal_label = get_localized_message(update.message.from_user, 'web_portal')
|
||||||
|
portal_description = get_localized_message(update.message.from_user, 'web_portal_description')
|
||||||
|
|
||||||
|
message_text = f"{portal_label}\n{portal_link}\n\n{portal_description}"
|
||||||
|
|
||||||
|
sent_message = await update.message.reply_text(
|
||||||
|
message_text,
|
||||||
|
reply_markup=reply_markup,
|
||||||
|
parse_mode='Markdown',
|
||||||
|
disable_web_page_preview=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save outgoing message
|
||||||
|
await self._save_outgoing_message(
|
||||||
|
sent_message,
|
||||||
|
update.message.from_user
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Sent web portal link to user {user.username}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error handling web portal: {e}")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_running(self):
|
def is_running(self):
|
||||||
"""Check if bot is running"""
|
"""Check if bot is running"""
|
||||||
|
@@ -37,8 +37,18 @@ MESSAGES = {
|
|||||||
'video': 'video',
|
'video': 'video',
|
||||||
'content': 'content'
|
'content': 'content'
|
||||||
},
|
},
|
||||||
|
'guide_title': "📖 **VPN Setup Guide**",
|
||||||
|
'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:**",
|
||||||
|
'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**💡 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': {
|
'buttons': {
|
||||||
'access': "🌍 Get access",
|
'access': "🌍 Get access",
|
||||||
|
'guide': "📖 Guide",
|
||||||
|
'android': "🤖 Android",
|
||||||
|
'ios': " iOS",
|
||||||
|
'web_portal': "🌐 Web Portal",
|
||||||
'all_in_one': "🌍 All-in-one",
|
'all_in_one': "🌍 All-in-one",
|
||||||
'back': "⬅️ Back",
|
'back': "⬅️ Back",
|
||||||
'group_prefix': "Group: ",
|
'group_prefix': "Group: ",
|
||||||
@@ -74,8 +84,18 @@ MESSAGES = {
|
|||||||
'video': 'видео',
|
'video': 'видео',
|
||||||
'content': 'контент'
|
'content': 'контент'
|
||||||
},
|
},
|
||||||
|
'guide_title': "📖 **Руководство по настройке VPN**",
|
||||||
|
'guide_choose_platform': "Выберите платформу вашего устройства:",
|
||||||
|
'web_portal_description': "_Веб-портал показывает список ваших доступов на одной удобной странице с некоторой статистикой._",
|
||||||
|
'servers_in_group': "🔒 **Серверы в группе:**",
|
||||||
|
'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**💡 Полезные настройки:**\n• В настройках включите прямой доступ для банковских приложений и местных сайтов для улучшения производительности\n\n**🔄 Если VPN перестал работать:**\nНажмите иконку обновления рядом со списком серверов для обновления подписки.",
|
||||||
'buttons': {
|
'buttons': {
|
||||||
'access': "🌍 Получить VPN",
|
'access': "🌍 Получить VPN",
|
||||||
|
'guide': "📖 Гайд",
|
||||||
|
'android': "🤖 Android",
|
||||||
|
'ios': " iOS",
|
||||||
|
'web_portal': "🌐 Веб-портал",
|
||||||
'all_in_one': "🌍 Все в одном",
|
'all_in_one': "🌍 Все в одном",
|
||||||
'back': "⬅️ Назад",
|
'back': "⬅️ Назад",
|
||||||
'group_prefix': "Группа: ",
|
'group_prefix': "Группа: ",
|
||||||
|
Reference in New Issue
Block a user