Update bot_manager.py
Этот коммит содержится в:
+133
-24
@@ -18,7 +18,7 @@ from aiogram.dispatcher import Dispatcher
|
||||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||
from aiogram.utils import executor
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
|
||||
@@ -58,15 +58,21 @@ dp.middleware.setup(AdminMessageDeletionMiddleware())
|
||||
# Главное меню с новым порядком и эмодзи
|
||||
def get_main_menu_markup(user_id):
|
||||
markup = InlineKeyboardMarkup(row_width=2)
|
||||
markup.add(InlineKeyboardButton("➕ Добавить пользователя", callback_data="add_user"))
|
||||
markup.insert(InlineKeyboardButton("📋 Список клиентов", callback_data="list_users"))
|
||||
markup.insert(InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"))
|
||||
if user_id in admins:
|
||||
markup.insert(InlineKeyboardButton("👥 Список админов", callback_data="list_admins"))
|
||||
markup.insert(InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin"))
|
||||
# Последовательное добавление кнопок с помощью add
|
||||
markup.add(
|
||||
InlineKeyboardButton("➕ Добавить пользователя", callback_data="add_user"),
|
||||
InlineKeyboardButton("📋 Список клиентов", callback_data="list_users")
|
||||
)
|
||||
markup.add(
|
||||
InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"),
|
||||
InlineKeyboardButton("ℹ️ Инструкция", callback_data="instructions")
|
||||
)
|
||||
if user_id in admins:
|
||||
markup.add(
|
||||
InlineKeyboardButton("👥 Список админов", callback_data="list_admins"),
|
||||
InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin")
|
||||
)
|
||||
markup.add(InlineKeyboardButton("💾 Создать бекап", callback_data="create_backup"))
|
||||
markup.add(InlineKeyboardButton("ℹ️ Инструкция", callback_data="instructions"))
|
||||
return markup
|
||||
|
||||
user_main_messages = {}
|
||||
@@ -116,6 +122,10 @@ async def delete_message_after_delay(chat_id: int, message_id: int, delay: int =
|
||||
pass
|
||||
|
||||
def parse_relative_time(relative_str: str) -> datetime:
|
||||
if not isinstance(relative_str, str) or not relative_str.strip():
|
||||
logger.error(f"Некорректный relative_str: {relative_str}")
|
||||
return datetime.now(pytz.UTC) # Значение по умолчанию
|
||||
try:
|
||||
relative_str = relative_str.lower().replace(' ago', '')
|
||||
delta = 0
|
||||
for part in relative_str.split(', '):
|
||||
@@ -132,14 +142,20 @@ def parse_relative_time(relative_str: str) -> datetime:
|
||||
elif 'month' in unit:
|
||||
delta += num * 2592000
|
||||
return datetime.now(pytz.UTC) - timedelta(seconds=delta)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в parse_relative_time: {str(e)}")
|
||||
return datetime.now(pytz.UTC) # Значение по умолчанию
|
||||
|
||||
@dp.message_handler(commands=['start', 'help'])
|
||||
async def help_command_handler(message: types.Message):
|
||||
user_id = message.from_user.id
|
||||
if user_id in admins or user_id in moderators:
|
||||
sent_message = await message.answer("Выберите действие:", reply_markup=get_main_menu_markup(user_id))
|
||||
user_main_messages[user_id] = {'chat_id': sent_message.chat.id, 'message_id': sent_message.message_id}
|
||||
# Не закрепляем меню
|
||||
user_main_messages[user_id] = {
|
||||
'chat_id': sent_message.chat.id,
|
||||
'message_id': sent_message.message_id,
|
||||
'state': None # Инициализируем state явно
|
||||
}
|
||||
else:
|
||||
await message.answer("У вас нет доступа к этому боту.")
|
||||
|
||||
@@ -243,27 +259,63 @@ async def client_selected_callback(callback_query: types.CallbackQuery):
|
||||
if user_id not in admins and user_id not in moderators:
|
||||
await callback_query.answer("Нет прав.", show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info(f"Callback data: {callback_query.data}")
|
||||
username = callback_query.data.split('client_')[1]
|
||||
logger.info(f"Выбран клиент: {username}")
|
||||
|
||||
clients = db.get_client_list()
|
||||
logger.info(f"Список клиентов: {clients}")
|
||||
client_info = next((c for c in clients if c[0] == username), None)
|
||||
if not client_info:
|
||||
await callback_query.answer("Пользователь не найден.", show_alert=True)
|
||||
return
|
||||
logger.info(f"Информация о клиенте: {client_info}")
|
||||
|
||||
# Базовые значения
|
||||
status = "🔴 Офлайн"
|
||||
incoming_traffic = "↓—"
|
||||
outgoing_traffic = "↑—"
|
||||
ipv4_address = re.search(r'(\d{1,3}\.){3}\d{1,3}/\d+', client_info[2]) or "—"
|
||||
active_clients = db.get_active_list()
|
||||
active_info = next((ac for ac in active_clients if ac[0] == username), None)
|
||||
ipv4_address = "—"
|
||||
if isinstance(client_info, (tuple, list)) and len(client_info) > 2 and client_info[2] is not None:
|
||||
logger.info(f"client_info[2]: {client_info[2]}")
|
||||
ip_match = re.search(r'(\d{1,3}\.){3}\d{1,3}/\d+', str(client_info[2]))
|
||||
ipv4_address = ip_match.group(0) if ip_match else "—"
|
||||
else:
|
||||
logger.info(f"client_info некорректно: {client_info}")
|
||||
|
||||
if active_info and active_info[1].lower() not in ['never', 'нет данных', '-']:
|
||||
# Проверяем активность
|
||||
active_clients = db.get_active_list()
|
||||
logger.info(f"Список активных клиентов: {active_clients}")
|
||||
active_info = next((ac for ac in active_clients if ac[0] == username), None)
|
||||
logger.info(f"Активная информация: {active_info}")
|
||||
|
||||
if (active_info is not None and isinstance(active_info, (tuple, list)) and
|
||||
len(active_info) > 2 and active_info[1] is not None and
|
||||
active_info[1].lower() not in ['never', 'нет данных', '-']):
|
||||
logger.info(f"active_info[1]: {active_info[1]}")
|
||||
try:
|
||||
last_handshake = parse_relative_time(active_info[1])
|
||||
if last_handshake is not None:
|
||||
logger.info(f"last_handshake: {last_handshake}")
|
||||
status = "🟢 Онлайн" if (datetime.now(pytz.UTC) - last_handshake).total_seconds() <= 60 else "❌ Офлайн"
|
||||
incoming_bytes, outgoing_bytes = parse_transfer(active_info[2])
|
||||
if len(active_info) > 2 and active_info[2] is not None:
|
||||
logger.info(f"active_info[2]: {active_info[2]}")
|
||||
try:
|
||||
transfer_result = parse_transfer(active_info[2])
|
||||
if transfer_result is not None:
|
||||
incoming_bytes, outgoing_bytes = transfer_result
|
||||
incoming_traffic = f"↓{humanize.naturalsize(incoming_bytes)}"
|
||||
outgoing_traffic = f"↑{humanize.naturalsize(outgoing_bytes)}"
|
||||
else:
|
||||
logger.info("parse_transfer вернул None")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в parse_transfer: {str(e)}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в parse_relative_time: {str(e)}")
|
||||
|
||||
# Формируем текст профиля
|
||||
text = (
|
||||
f"📧 *Имя:* {username}\n"
|
||||
f"🌐 *IPv4:* {ipv4_address}\n"
|
||||
@@ -271,6 +323,8 @@ async def client_selected_callback(callback_query: types.CallbackQuery):
|
||||
f"🔼 *Исходящий:* {incoming_traffic}\n"
|
||||
f"🔽 *Входящий:* {outgoing_traffic}"
|
||||
)
|
||||
|
||||
# Создаём клавиатуру
|
||||
keyboard = InlineKeyboardMarkup(row_width=2).add(
|
||||
InlineKeyboardButton("ℹ️ IP info", callback_data=f"ip_info_{username}"),
|
||||
InlineKeyboardButton("🔗 Подключения", callback_data=f"connections_{username}"),
|
||||
@@ -278,6 +332,8 @@ async def client_selected_callback(callback_query: types.CallbackQuery):
|
||||
InlineKeyboardButton("⬅️ Назад", callback_data="list_users"),
|
||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
||||
)
|
||||
|
||||
logger.info(f"Редактирование сообщения для {username}")
|
||||
await bot.edit_message_text(
|
||||
chat_id=callback_query.message.chat.id,
|
||||
message_id=callback_query.message.message_id,
|
||||
@@ -287,26 +343,58 @@ async def client_selected_callback(callback_query: types.CallbackQuery):
|
||||
)
|
||||
await callback_query.answer()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в client_selected_callback: {str(e)}")
|
||||
await bot.edit_message_text(
|
||||
chat_id=callback_query.message.chat.id,
|
||||
message_id=callback_query.message.message_id,
|
||||
text=f"Ошибка при загрузке профиля: {str(e)}",
|
||||
reply_markup=InlineKeyboardMarkup().add(
|
||||
InlineKeyboardButton("⬅️ Назад", callback_data="list_users"),
|
||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
||||
)
|
||||
)
|
||||
await callback_query.answer("Ошибка на сервере.", show_alert=True)
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == "list_users")
|
||||
async def list_users_callback(callback_query: types.CallbackQuery):
|
||||
user_id = callback_query.from_user.id
|
||||
if user_id not in admins and user_id not in moderators:
|
||||
await callback_query.answer("Нет прав.", show_alert=True)
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Запуск list_users_callback")
|
||||
clients = db.get_client_list()
|
||||
logger.info(f"Клиенты: {clients}")
|
||||
if not clients:
|
||||
await callback_query.answer("Список пуст.", show_alert=True)
|
||||
await bot.edit_message_text(
|
||||
chat_id=callback_query.message.chat.id,
|
||||
message_id=callback_query.message.message_id,
|
||||
text="Список клиентов пуст.",
|
||||
reply_markup=InlineKeyboardMarkup().add(
|
||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
||||
)
|
||||
)
|
||||
await callback_query.answer()
|
||||
return
|
||||
|
||||
keyboard = InlineKeyboardMarkup(row_width=2)
|
||||
active_clients = {client[0]: client[1] for client in db.get_active_list()}
|
||||
logger.info(f"Активные клиенты: {active_clients}")
|
||||
now = datetime.now(pytz.UTC)
|
||||
|
||||
for client in clients:
|
||||
username = client[0]
|
||||
last_handshake = active_clients.get(username)
|
||||
status = "❌" if not last_handshake or last_handshake.lower() in ['never', 'нет данных', '-'] else "🟢" if (now - parse_relative_time(last_handshake)).days <= 5 else "❌"
|
||||
keyboard.insert(InlineKeyboardButton(f"{status} {username}", callback_data=f"client_{username}"))
|
||||
logger.info(f"Клиент: {username}, last_handshake: {last_handshake}")
|
||||
# Упрощённая логика статуса для теста
|
||||
status = "❌" if not last_handshake or last_handshake.lower() in ['never', 'нет данных', '-'] else "🟢"
|
||||
button_text = f"{status} {username}"
|
||||
keyboard.insert(InlineKeyboardButton(button_text, callback_data=f"client_{username}"))
|
||||
|
||||
keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
|
||||
|
||||
await bot.edit_message_text(
|
||||
chat_id=callback_query.message.chat.id,
|
||||
message_id=callback_query.message.message_id,
|
||||
@@ -315,6 +403,18 @@ async def list_users_callback(callback_query: types.CallbackQuery):
|
||||
)
|
||||
await callback_query.answer()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в list_users_callback: {str(e)}")
|
||||
await bot.edit_message_text(
|
||||
chat_id=callback_query.message.chat.id,
|
||||
message_id=callback_query.message.message_id,
|
||||
text=f"Ошибка: {str(e)}",
|
||||
reply_markup=InlineKeyboardMarkup().add(
|
||||
InlineKeyboardButton("🏠 Домой", callback_data="home")
|
||||
)
|
||||
)
|
||||
await callback_query.answer("Ошибка на сервере.", show_alert=True)
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == "list_admins")
|
||||
async def list_admins_callback(callback_query: types.CallbackQuery):
|
||||
user_id = callback_query.from_user.id
|
||||
@@ -537,12 +637,9 @@ async def mobile_instructions(callback_query: types.CallbackQuery):
|
||||
return
|
||||
instruction_text = (
|
||||
"📱 *Инструкция для мобильных устройств:*\n\n"
|
||||
"1.1. Скачайте приложение AmneziaVPN (Android}:\n"
|
||||
"1. Скачайте приложение AmneziaVPN:\n"
|
||||
" - [Google Play](https://play.google.com/store/apps/details?id=org.amnezia.vpn&hl=ru)\n"
|
||||
" - Или через [GitHub](https://github.com/amnezia-vpn/amnezia-client)\n"
|
||||
"1.2. Скачайте приложение AmneziaWG (iOS):\n"
|
||||
" - [App Store](https://apps.apple.com/ru/app/amneziawg/id6478942365)\n"
|
||||
" - Или если у вас регион не Россия то [AmneziaVPN](https://apps.apple.com/kz/app/amneziavpn/id1600529900)"
|
||||
"2. Откройте приложение и выберите 'Добавить конфигурацию'.\n"
|
||||
"3. Скопируйте VPN ключ из сообщения с файлом .conf.\n"
|
||||
"4. Вставьте ключ в приложение и нажмите 'Подключить'.\n"
|
||||
@@ -590,14 +687,26 @@ async def pc_instructions(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
|
||||
def parse_transfer(transfer_str):
|
||||
if not isinstance(transfer_str, str) or not transfer_str.strip():
|
||||
logger.error(f"Некорректный transfer_str: {transfer_str}")
|
||||
return 0, 0 # Значения по умолчанию
|
||||
try:
|
||||
incoming, outgoing = re.split(r'[/,]', transfer_str)[:2]
|
||||
size_map = {'B': 1, 'KB': 10**3, 'KiB': 1024, 'MB': 10**6, 'MiB': 1024**2, 'GB': 10**9, 'GiB': 1024**3}
|
||||
incoming_bytes = outgoing_bytes = 0
|
||||
for unit, multiplier in size_map.items():
|
||||
if unit in incoming:
|
||||
incoming_bytes = float(re.match(r'([\d.]+)', incoming)[0]) * multiplier
|
||||
match = re.match(r'([\d.]+)', incoming)
|
||||
if match:
|
||||
incoming_bytes = float(match.group(0)) * multiplier
|
||||
if unit in outgoing:
|
||||
outgoing_bytes = float(re.match(r'([\d.]+)', outgoing)[0]) * multiplier
|
||||
match = re.match(r'([\d.]+)', outgoing)
|
||||
if match:
|
||||
outgoing_bytes = float(match.group(0)) * multiplier
|
||||
return incoming_bytes, outgoing_bytes
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка в parse_transfer: {str(e)}")
|
||||
return 0, 0 # Значения по умолчанию
|
||||
|
||||
async def generate_vpn_key(conf_path: str) -> str:
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
|
||||
Ссылка в новой задаче
Block a user