Этот коммит содержится в:
stevefoxru
2025-03-10 14:54:53 +03:00
коммит произвёл GitHub
родитель 588291eec9
Коммит cac5c130d8
+208 -99
Просмотреть файл
@@ -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,30 +122,40 @@ 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:
relative_str = relative_str.lower().replace(' ago', '') if not isinstance(relative_str, str) or not relative_str.strip():
delta = 0 logger.error(f"Некорректный relative_str: {relative_str}")
for part in relative_str.split(', '): return datetime.now(pytz.UTC) # Значение по умолчанию
num, unit = part.split() try:
num = int(num) relative_str = relative_str.lower().replace(' ago', '')
if 'minute' in unit: delta = 0
delta += num * 60 for part in relative_str.split(', '):
elif 'hour' in unit: num, unit = part.split()
delta += num * 3600 num = int(num)
elif 'day' in unit: if 'minute' in unit:
delta += num * 86400 delta += num * 60
elif 'week' in unit: elif 'hour' in unit:
delta += num * 604800 delta += num * 3600
elif 'month' in unit: elif 'day' in unit:
delta += num * 2592000 delta += num * 86400
return datetime.now(pytz.UTC) - timedelta(seconds=delta) elif 'week' in unit:
delta += num * 604800
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']) @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,49 +259,102 @@ 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
username = callback_query.data.split('client_')[1]
clients = db.get_client_list()
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
status = "🔴 Офлайн" try:
incoming_traffic = "↓—" logger.info(f"Callback data: {callback_query.data}")
outgoing_traffic = "↑—" username = callback_query.data.split('client_')[1]
ipv4_address = re.search(r'(\d{1,3}\.){3}\d{1,3}/\d+', client_info[2]) or "" logger.info(f"Выбран клиент: {username}")
active_clients = db.get_active_list()
active_info = next((ac for ac in active_clients if ac[0] == username), None) 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 = ""
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}")
# Проверяем активность
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 "❌ Офлайн"
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"
f"🌐 *Статус:* {status}\n"
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}"),
InlineKeyboardButton("🗑️ Удалить", callback_data=f"delete_user_{username}"),
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,
text=text,
parse_mode="Markdown",
reply_markup=keyboard
)
await callback_query.answer()
if active_info and active_info[1].lower() not in ['never', 'нет данных', '-']: except Exception as e:
last_handshake = parse_relative_time(active_info[1]) logger.error(f"Ошибка в client_selected_callback: {str(e)}")
status = "🟢 Онлайн" if (datetime.now(pytz.UTC) - last_handshake).total_seconds() <= 60 else "❌ Офлайн" await bot.edit_message_text(
incoming_bytes, outgoing_bytes = parse_transfer(active_info[2]) chat_id=callback_query.message.chat.id,
incoming_traffic = f"{humanize.naturalsize(incoming_bytes)}" message_id=callback_query.message.message_id,
outgoing_traffic = f"{humanize.naturalsize(outgoing_bytes)}" text=f"Ошибка при загрузке профиля: {str(e)}",
reply_markup=InlineKeyboardMarkup().add(
text = ( InlineKeyboardButton("⬅️ Назад", callback_data="list_users"),
f"📧 *Имя:* {username}\n" InlineKeyboardButton("🏠 Домой", callback_data="home")
f"🌐 *IPv4:* {ipv4_address}\n" )
f"🌐 *Статус:* {status}\n" )
f"🔼 *Исходящий:* {incoming_traffic}\n" await callback_query.answer("Ошибка на сервере.", show_alert=True)
f"🔽 *Входящий:* {outgoing_traffic}"
)
keyboard = InlineKeyboardMarkup(row_width=2).add(
InlineKeyboardButton("️ IP info", callback_data=f"ip_info_{username}"),
InlineKeyboardButton("🔗 Подключения", callback_data=f"connections_{username}"),
InlineKeyboardButton("🗑️ Удалить", callback_data=f"delete_user_{username}"),
InlineKeyboardButton("⬅️ Назад", callback_data="list_users"),
InlineKeyboardButton("🏠 Домой", callback_data="home")
)
await bot.edit_message_text(
chat_id=callback_query.message.chat.id,
message_id=callback_query.message.message_id,
text=text,
parse_mode="Markdown",
reply_markup=keyboard
)
await callback_query.answer()
@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):
@@ -293,27 +362,58 @@ async def list_users_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
clients = db.get_client_list()
if not clients:
await callback_query.answer("Список пуст.", show_alert=True)
return
keyboard = InlineKeyboardMarkup(row_width=2) try:
active_clients = {client[0]: client[1] for client in db.get_active_list()} logger.info("Запуск list_users_callback")
now = datetime.now(pytz.UTC) clients = db.get_client_list()
for client in clients: logger.info(f"Клиенты: {clients}")
username = client[0] if not clients:
last_handshake = active_clients.get(username) await bot.edit_message_text(
status = "" if not last_handshake or last_handshake.lower() in ['never', 'нет данных', '-'] else "🟢" if (now - parse_relative_time(last_handshake)).days <= 5 else "" chat_id=callback_query.message.chat.id,
keyboard.insert(InlineKeyboardButton(f"{status} {username}", callback_data=f"client_{username}")) message_id=callback_query.message.message_id,
keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home")) text="Список клиентов пуст.",
await bot.edit_message_text( reply_markup=InlineKeyboardMarkup().add(
chat_id=callback_query.message.chat.id, InlineKeyboardButton("🏠 Домой", callback_data="home")
message_id=callback_query.message.message_id, )
text="Выберите пользователя:", )
reply_markup=keyboard await callback_query.answer()
) return
await callback_query.answer()
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)
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,
text="Выберите пользователя:",
reply_markup=keyboard
)
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):
@@ -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):
incoming, outgoing = re.split(r'[/,]', transfer_str)[:2] if not isinstance(transfer_str, str) or not transfer_str.strip():
size_map = {'B': 1, 'KB': 10**3, 'KiB': 1024, 'MB': 10**6, 'MiB': 1024**2, 'GB': 10**9, 'GiB': 1024**3} logger.error(f"Некорректный transfer_str: {transfer_str}")
for unit, multiplier in size_map.items(): return 0, 0 # Значения по умолчанию
if unit in incoming: try:
incoming_bytes = float(re.match(r'([\d.]+)', incoming)[0]) * multiplier incoming, outgoing = re.split(r'[/,]', transfer_str)[:2]
if unit in outgoing: size_map = {'B': 1, 'KB': 10**3, 'KiB': 1024, 'MB': 10**6, 'MiB': 1024**2, 'GB': 10**9, 'GiB': 1024**3}
outgoing_bytes = float(re.match(r'([\d.]+)', outgoing)[0]) * multiplier incoming_bytes = outgoing_bytes = 0
return incoming_bytes, outgoing_bytes for unit, multiplier in size_map.items():
if unit in incoming:
match = re.match(r'([\d.]+)', incoming)
if match:
incoming_bytes = float(match.group(0)) * multiplier
if unit in outgoing:
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: async def generate_vpn_key(conf_path: str) -> str:
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(