Этот коммит содержится в:
stevefoxru
2025-03-10 14:54:53 +03:00
коммит произвёл GitHub
родитель 588291eec9
Коммит cac5c130d8
+133 -24
Просмотреть файл
@@ -18,7 +18,7 @@ from aiogram.dispatcher import Dispatcher
from aiogram.dispatcher.middlewares import BaseMiddleware from aiogram.dispatcher.middlewares import BaseMiddleware
from aiogram.utils import executor from aiogram.utils import executor
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from datetime import datetime from datetime import datetime, timedelta
from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger from apscheduler.triggers.interval import IntervalTrigger
@@ -58,15 +58,21 @@ dp.middleware.setup(AdminMessageDeletionMiddleware())
# Главное меню с новым порядком и эмодзи # Главное меню с новым порядком и эмодзи
def get_main_menu_markup(user_id): def get_main_menu_markup(user_id):
markup = InlineKeyboardMarkup(row_width=2) markup = InlineKeyboardMarkup(row_width=2)
markup.add(InlineKeyboardButton("➕ Добавить пользователя", callback_data="add_user")) # Последовательное добавление кнопок с помощью add
markup.insert(InlineKeyboardButton("📋 Список клиентов", callback_data="list_users")) markup.add(
markup.insert(InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config")) InlineKeyboardButton("➕ Добавить пользователя", callback_data="add_user"),
if user_id in admins: InlineKeyboardButton("📋 Список клиентов", callback_data="list_users")
markup.insert(InlineKeyboardButton("👥 Список админов", callback_data="list_admins")) )
markup.insert(InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin")) markup.add(
InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"),
InlineKeyboardButton("️ Инструкция", callback_data="instructions")
)
if user_id in admins: 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="create_backup"))
markup.add(InlineKeyboardButton("️ Инструкция", callback_data="instructions"))
return markup return markup
user_main_messages = {} user_main_messages = {}
@@ -116,6 +122,10 @@ async def delete_message_after_delay(chat_id: int, message_id: int, delay: int =
pass pass
def parse_relative_time(relative_str: str) -> datetime: 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', '') relative_str = relative_str.lower().replace(' ago', '')
delta = 0 delta = 0
for part in relative_str.split(', '): for part in relative_str.split(', '):
@@ -132,14 +142,20 @@ def parse_relative_time(relative_str: str) -> datetime:
elif 'month' in unit: elif 'month' in unit:
delta += num * 2592000 delta += num * 2592000
return datetime.now(pytz.UTC) - timedelta(seconds=delta) 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']) @dp.message_handler(commands=['start', 'help'])
async def help_command_handler(message: types.Message): async def help_command_handler(message: types.Message):
user_id = message.from_user.id user_id = message.from_user.id
if user_id in admins or user_id in moderators: if user_id in admins or user_id in moderators:
sent_message = await message.answer("Выберите действие:", reply_markup=get_main_menu_markup(user_id)) 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: else:
await message.answer("У вас нет доступа к этому боту.") 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: if user_id not in admins and user_id not in moderators:
await callback_query.answer("Нет прав.", show_alert=True) await callback_query.answer("Нет прав.", show_alert=True)
return return
try:
logger.info(f"Callback data: {callback_query.data}")
username = callback_query.data.split('client_')[1] username = callback_query.data.split('client_')[1]
logger.info(f"Выбран клиент: {username}")
clients = db.get_client_list() clients = db.get_client_list()
logger.info(f"Список клиентов: {clients}")
client_info = next((c for c in clients if c[0] == username), None) client_info = next((c for c in clients if c[0] == username), None)
if not client_info: if not client_info:
await callback_query.answer("Пользователь не найден.", show_alert=True) await callback_query.answer("Пользователь не найден.", show_alert=True)
return return
logger.info(f"Информация о клиенте: {client_info}")
# Базовые значения
status = "🔴 Офлайн" status = "🔴 Офлайн"
incoming_traffic = "↓—" incoming_traffic = "↓—"
outgoing_traffic = "↑—" outgoing_traffic = "↑—"
ipv4_address = re.search(r'(\d{1,3}\.){3}\d{1,3}/\d+', client_info[2]) or "" ipv4_address = ""
active_clients = db.get_active_list() if isinstance(client_info, (tuple, list)) and len(client_info) > 2 and client_info[2] is not None:
active_info = next((ac for ac in active_clients if ac[0] == username), 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]) 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 "❌ Офлайн" 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)}" incoming_traffic = f"{humanize.naturalsize(incoming_bytes)}"
outgoing_traffic = f"{humanize.naturalsize(outgoing_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 = ( text = (
f"📧 *Имя:* {username}\n" f"📧 *Имя:* {username}\n"
f"🌐 *IPv4:* {ipv4_address}\n" f"🌐 *IPv4:* {ipv4_address}\n"
@@ -271,6 +323,8 @@ async def client_selected_callback(callback_query: types.CallbackQuery):
f"🔼 *Исходящий:* {incoming_traffic}\n" f"🔼 *Исходящий:* {incoming_traffic}\n"
f"🔽 *Входящий:* {outgoing_traffic}" f"🔽 *Входящий:* {outgoing_traffic}"
) )
# Создаём клавиатуру
keyboard = InlineKeyboardMarkup(row_width=2).add( keyboard = InlineKeyboardMarkup(row_width=2).add(
InlineKeyboardButton("️ IP info", callback_data=f"ip_info_{username}"), InlineKeyboardButton("️ IP info", callback_data=f"ip_info_{username}"),
InlineKeyboardButton("🔗 Подключения", callback_data=f"connections_{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="list_users"),
InlineKeyboardButton("🏠 Домой", callback_data="home") InlineKeyboardButton("🏠 Домой", callback_data="home")
) )
logger.info(f"Редактирование сообщения для {username}")
await bot.edit_message_text( await bot.edit_message_text(
chat_id=callback_query.message.chat.id, chat_id=callback_query.message.chat.id,
message_id=callback_query.message.message_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() 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") @dp.callback_query_handler(lambda c: c.data == "list_users")
async def list_users_callback(callback_query: types.CallbackQuery): async def list_users_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id user_id = callback_query.from_user.id
if user_id not in admins and user_id not in moderators: if user_id not in admins and user_id not in moderators:
await callback_query.answer("Нет прав.", show_alert=True) await callback_query.answer("Нет прав.", show_alert=True)
return return
try:
logger.info("Запуск list_users_callback")
clients = db.get_client_list() clients = db.get_client_list()
logger.info(f"Клиенты: {clients}")
if not 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 return
keyboard = InlineKeyboardMarkup(row_width=2) keyboard = InlineKeyboardMarkup(row_width=2)
active_clients = {client[0]: client[1] for client in db.get_active_list()} active_clients = {client[0]: client[1] for client in db.get_active_list()}
logger.info(f"Активные клиенты: {active_clients}")
now = datetime.now(pytz.UTC) now = datetime.now(pytz.UTC)
for client in clients: for client in clients:
username = client[0] username = client[0]
last_handshake = active_clients.get(username) 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 "" logger.info(f"Клиент: {username}, last_handshake: {last_handshake}")
keyboard.insert(InlineKeyboardButton(f"{status} {username}", callback_data=f"client_{username}")) # Упрощённая логика статуса для теста
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")) keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
await bot.edit_message_text( await bot.edit_message_text(
chat_id=callback_query.message.chat.id, chat_id=callback_query.message.chat.id,
message_id=callback_query.message.message_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() 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") @dp.callback_query_handler(lambda c: c.data == "list_admins")
async def list_admins_callback(callback_query: types.CallbackQuery): async def list_admins_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id user_id = callback_query.from_user.id
@@ -537,12 +637,9 @@ async def mobile_instructions(callback_query: types.CallbackQuery):
return return
instruction_text = ( instruction_text = (
"📱 *Инструкция для мобильных устройств:*\n\n" "📱 *Инструкция для мобильных устройств:*\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" " - [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" " - Или через [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" "2. Откройте приложение и выберите 'Добавить конфигурацию'.\n"
"3. Скопируйте VPN ключ из сообщения с файлом .conf.\n" "3. Скопируйте VPN ключ из сообщения с файлом .conf.\n"
"4. Вставьте ключ в приложение и нажмите 'Подключить'.\n" "4. Вставьте ключ в приложение и нажмите 'Подключить'.\n"
@@ -590,14 +687,26 @@ async def pc_instructions(callback_query: types.CallbackQuery):
await callback_query.answer() await callback_query.answer()
def parse_transfer(transfer_str): 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] 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} 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(): for unit, multiplier in size_map.items():
if unit in incoming: 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: 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 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: async def generate_vpn_key(conf_path: str) -> str:
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(