Этот коммит содержится в:
stevefoxru
2025-04-22 17:04:17 +03:00
коммит произвёл GitHub
родитель b40860fe01
Коммит c9f0d0cb6d
+260 -64
Просмотреть файл
@@ -13,6 +13,7 @@ import zipfile
import ipaddress import ipaddress
import humanize import humanize
import shutil import shutil
import uuid
from aiogram import Bot, types from aiogram import Bot, types
from aiogram.dispatcher import Dispatcher from aiogram.dispatcher import Dispatcher
from aiogram.dispatcher.middlewares import BaseMiddleware from aiogram.dispatcher.middlewares import BaseMiddleware
@@ -45,6 +46,13 @@ WG_CONFIG_FILE = wg_config_file
DOCKER_CONTAINER = docker_container DOCKER_CONTAINER = docker_container
ENDPOINT = endpoint ENDPOINT = endpoint
# Цены за периоды (в условных единицах)
PRICING = {
'1_month': 10.0,
'3_months': 25.0,
'6_months': 45.0
}
class AdminMessageDeletionMiddleware(BaseMiddleware): class AdminMessageDeletionMiddleware(BaseMiddleware):
async def on_process_message(self, message: types.Message, data: dict): async def on_process_message(self, message: types.Message, data: dict):
if message.from_user.id in admins and message.text.startswith('/'): if message.from_user.id in admins and message.text.startswith('/'):
@@ -55,10 +63,9 @@ scheduler = AsyncIOScheduler(timezone=pytz.UTC)
scheduler.start() scheduler.start()
dp.middleware.setup(AdminMessageDeletionMiddleware()) 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)
# Последовательное добавление кнопок с помощью add
markup.add( markup.add(
InlineKeyboardButton("➕ Добавить пользователя", callback_data="add_user"), InlineKeyboardButton("➕ Добавить пользователя", callback_data="add_user"),
InlineKeyboardButton("📋 Список клиентов", callback_data="list_users") InlineKeyboardButton("📋 Список клиентов", callback_data="list_users")
@@ -67,12 +74,19 @@ def get_main_menu_markup(user_id):
InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"), InlineKeyboardButton("🔑 Получить конфиг", callback_data="get_config"),
InlineKeyboardButton("️ Инструкция", callback_data="instructions") InlineKeyboardButton("️ Инструкция", callback_data="instructions")
) )
markup.add(
InlineKeyboardButton("💳 Купить ключ", callback_data="buy_key"),
InlineKeyboardButton("🎟️ Использовать промокод", callback_data="use_promocode")
)
if user_id in admins: if user_id in admins:
markup.add( markup.add(
InlineKeyboardButton("👥 Список админов", callback_data="list_admins"), InlineKeyboardButton("👥 Список админов", callback_data="list_admins"),
InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin") InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin")
) )
markup.add(InlineKeyboardButton("💾 Создать бекап", callback_data="create_backup")) markup.add(
InlineKeyboardButton("💾 Создать бекап", callback_data="create_backup"),
InlineKeyboardButton("🎟️ Управление промокодами", callback_data="manage_promocodes")
)
return markup return markup
user_main_messages = {} user_main_messages = {}
@@ -124,7 +138,7 @@ async def delete_message_after_delay(chat_id: int, message_id: int, delay: int =
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(): if not isinstance(relative_str, str) or not relative_str.strip():
logger.error(f"Некорректный relative_str: {relative_str}") logger.error(f"Некорректный relative_str: {relative_str}")
return datetime.now(pytz.UTC) # Значение по умолчанию return datetime.now(pytz.UTC)
try: try:
relative_str = relative_str.lower().replace(' ago', '') relative_str = relative_str.lower().replace(' ago', '')
delta = 0 delta = 0
@@ -144,7 +158,7 @@ def parse_relative_time(relative_str: str) -> datetime:
return datetime.now(pytz.UTC) - timedelta(seconds=delta) return datetime.now(pytz.UTC) - timedelta(seconds=delta)
except Exception as e: except Exception as e:
logger.error(f"Ошибка в parse_relative_time: {str(e)}") logger.error(f"Ошибка в parse_relative_time: {str(e)}")
return datetime.now(pytz.UTC) # Значение по умолчанию 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):
@@ -154,10 +168,18 @@ async def help_command_handler(message: types.Message):
user_main_messages[user_id] = { user_main_messages[user_id] = {
'chat_id': sent_message.chat.id, 'chat_id': sent_message.chat.id,
'message_id': sent_message.message_id, 'message_id': sent_message.message_id,
'state': None # Инициализируем state явно 'state': None
} }
else: else:
await message.answer("У вас нет доступа к этому боту.") sent_message = await message.answer("Добро пожаловать! Купите ключ для доступа к VPN.", reply_markup=InlineKeyboardMarkup().add(
InlineKeyboardButton("💳 Купить ключ", callback_data="buy_key"),
InlineKeyboardButton("🎟️ Использовать промокод", callback_data="use_promocode")
))
user_main_messages[user_id] = {
'chat_id': sent_message.chat.id,
'message_id': sent_message.message_id,
'state': None
}
@dp.message_handler(commands=['add_admin']) @dp.message_handler(commands=['add_admin'])
async def add_admin_command(message: types.Message): async def add_admin_command(message: types.Message):
@@ -177,11 +199,8 @@ async def add_admin_command(message: types.Message):
@dp.message_handler() @dp.message_handler()
async def handle_messages(message: types.Message): async def handle_messages(message: types.Message):
user_id = message.from_user.id user_id = message.from_user.id
if user_id not in admins and user_id not in moderators:
await message.answer("У вас нет доступа.")
return
user_state = user_main_messages.get(user_id, {}).get('state') user_state = user_main_messages.get(user_id, {}).get('state')
if user_state == 'waiting_for_user_name': if user_state == 'waiting_for_user_name':
user_name = message.text.strip() user_name = message.text.strip()
if not re.match(r'^[a-zA-Z0-9_-]+$', user_name): if not re.match(r'^[a-zA-Z0-9_-]+$', user_name):
@@ -194,10 +213,8 @@ async def handle_messages(message: types.Message):
vpn_key = await generate_vpn_key(conf_path) vpn_key = await generate_vpn_key(conf_path)
caption = f"Конфигурация для {user_name}:\nAmneziaVPN:\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```\n{vpn_key}\n```" caption = f"Конфигурация для {user_name}:\nAmneziaVPN:\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```\n{vpn_key}\n```"
with open(conf_path, 'rb') as config: with open(conf_path, 'rb') as config:
# Отправляем конфиг отдельным сообщением и закрепляем его
config_message = await bot.send_document(user_id, config, caption=caption, parse_mode="Markdown") config_message = await bot.send_document(user_id, config, caption=caption, parse_mode="Markdown")
await bot.pin_chat_message(user_id, config_message.message_id, disable_notification=True) await bot.pin_chat_message(user_id, config_message.message_id, disable_notification=True)
# Обновляем меню внизу, не закрепляя его
await bot.edit_message_text( await bot.edit_message_text(
chat_id=user_main_messages[user_id]['chat_id'], chat_id=user_main_messages[user_id]['chat_id'],
message_id=user_main_messages[user_id]['message_id'], message_id=user_main_messages[user_id]['message_id'],
@@ -222,6 +239,41 @@ async def handle_messages(message: types.Message):
user_main_messages[user_id]['state'] = None user_main_messages[user_id]['state'] = None
except: except:
await message.reply("Введите корректный Telegram ID.") await message.reply("Введите корректный Telegram ID.")
elif user_state == 'waiting_for_promocode':
promocode = message.text.strip()
discount = db.apply_promocode(promocode)
if discount:
user_main_messages[user_id]['promocode_discount'] = discount
await message.reply(f"Промокод активирован! Скидка: {discount}%")
else:
await message.reply("Неверный или истёкший промокод.")
await bot.edit_message_text(
chat_id=user_main_messages[user_id]['chat_id'],
message_id=user_main_messages[user_id]['message_id'],
text="Выберите действие:",
reply_markup=get_main_menu_markup(user_id)
)
user_main_messages[user_id]['state'] = None
elif user_state == 'waiting_for_new_promocode' and user_id in admins:
try:
code, discount, days_valid, max_uses = message.text.strip().split()
discount = float(discount)
days_valid = int(days_valid)
max_uses = int(max_uses) if max_uses.lower() != 'none' else None
expires_at = datetime.now(pytz.UTC) + timedelta(days=days_valid) if days_valid > 0 else None
if db.add_promocode(code, discount, expires_at, max_uses):
await message.reply(f"Промокод {code} добавлен: скидка {discount}%, действует {days_valid} дней, макс. использований: {max_uses or 'неограничено'}")
else:
await message.reply("Промокод уже существует.")
except:
await message.reply("Формат: <код> <скидка%> <дней_действия> <макс_использований|none>")
await bot.edit_message_text(
chat_id=user_main_messages[user_id]['chat_id'],
message_id=user_main_messages[user_id]['message_id'],
text="Выберите действие:",
reply_markup=get_main_menu_markup(user_id)
)
user_main_messages[user_id]['state'] = None
@dp.callback_query_handler(lambda c: c.data == "add_user") @dp.callback_query_handler(lambda c: c.data == "add_user")
async def prompt_for_user_name(callback_query: types.CallbackQuery): async def prompt_for_user_name(callback_query: types.CallbackQuery):
@@ -261,70 +313,51 @@ async def client_selected_callback(callback_query: types.CallbackQuery):
return return
try: 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 = "" ipv4_address = ""
expiration = db.get_user_expiration(username)
expiration_text = expiration.strftime("%Y-%m-%d %H:%M UTC") if expiration else "Не установлен"
if isinstance(client_info, (tuple, list)) and len(client_info) > 2 and client_info[2] is not None: 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])) 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 "" ipv4_address = ip_match.group(0) if ip_match else ""
else:
logger.info(f"client_info некорректно: {client_info}")
# Проверяем активность
active_clients = db.get_active_list() 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) 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 if active_info and isinstance(active_info, (tuple, list)) and len(active_info) > 2:
len(active_info) > 2 and active_info[1] is not None and if active_info[1] and active_info[1].lower() not in ['never', 'нет данных', '-']:
active_info[1].lower() not in ['never', 'нет данных', '-']): try:
logger.info(f"active_info[1]: {active_info[1]}") last_handshake = parse_relative_time(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 "❌ Офлайн" status = "🟢 Онлайн" if (datetime.now(pytz.UTC) - last_handshake).total_seconds() <= 60 else "❌ Офлайн"
if len(active_info) > 2 and active_info[2] is not None: except:
logger.info(f"active_info[2]: {active_info[2]}") pass
try: if active_info[2]:
transfer_result = parse_transfer(active_info[2]) try:
if transfer_result is not None: incoming_bytes, outgoing_bytes = parse_transfer(active_info[2])
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)}" except:
else: pass
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"
f"🌐 *Статус:* {status}\n" f"🌐 *Статус:* {status}\n"
f"🔼 *Исходящий:* {incoming_traffic}\n" f"🔼 *Исходящий:* {incoming_traffic}\n"
f"🔽 *Входящий:* {outgoing_traffic}" f"🔽 *Входящий:* {outgoing_traffic}\n"
f"⏰ *Срок действия:* {expiration_text}"
) )
# Создаём клавиатуру
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}"),
@@ -333,7 +366,6 @@ async def client_selected_callback(callback_query: types.CallbackQuery):
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,
@@ -364,9 +396,7 @@ async def list_users_callback(callback_query: types.CallbackQuery):
return return
try: 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 bot.edit_message_text( await bot.edit_message_text(
chat_id=callback_query.message.chat.id, chat_id=callback_query.message.chat.id,
@@ -381,14 +411,9 @@ async def list_users_callback(callback_query: types.CallbackQuery):
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)
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)
logger.info(f"Клиент: {username}, last_handshake: {last_handshake}")
# Упрощённая логика статуса для теста
status = "" if not last_handshake or last_handshake.lower() in ['never', 'нет данных', '-'] else "🟢" status = "" if not last_handshake or last_handshake.lower() in ['never', 'нет данных', '-'] else "🟢"
button_text = f"{status} {username}" button_text = f"{status} {username}"
keyboard.insert(InlineKeyboardButton(button_text, callback_data=f"client_{username}")) keyboard.insert(InlineKeyboardButton(button_text, callback_data=f"client_{username}"))
@@ -519,6 +544,7 @@ async def client_delete_callback(callback_query: types.CallbackQuery):
username = callback_query.data.split('delete_user_')[1] username = callback_query.data.split('delete_user_')[1]
if db.deactive_user_db(username): if db.deactive_user_db(username):
shutil.rmtree(os.path.join('users', username), ignore_errors=True) shutil.rmtree(os.path.join('users', username), ignore_errors=True)
db.remove_user_expiration(username)
text = f"Пользователь **{username}** удален." text = f"Пользователь **{username}** удален."
else: else:
text = f"Не удалось удалить **{username}**." text = f"Не удалось удалить **{username}**."
@@ -534,9 +560,6 @@ async def client_delete_callback(callback_query: types.CallbackQuery):
@dp.callback_query_handler(lambda c: c.data == "home") @dp.callback_query_handler(lambda c: c.data == "home")
async def return_home(callback_query: types.CallbackQuery): async def return_home(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:
await callback_query.answer("Нет прав.", show_alert=True)
return
user_main_messages[user_id]['state'] = None user_main_messages[user_id]['state'] = None
await bot.edit_message_text( await bot.edit_message_text(
chat_id=callback_query.message.chat.id, chat_id=callback_query.message.chat.id,
@@ -581,7 +604,6 @@ async def send_user_config(callback_query: types.CallbackQuery):
vpn_key = await generate_vpn_key(conf_path) vpn_key = await generate_vpn_key(conf_path)
caption = f"Конфигурация для {username}:\nAmneziaVPN:\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```\n{vpn_key}\n```" caption = f"Конфигурация для {username}:\nAmneziaVPN:\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```\n{vpn_key}\n```"
with open(conf_path, 'rb') as config: with open(conf_path, 'rb') as config:
# Отправляем конфиг отдельным сообщением и закрепляем его
config_message = await bot.send_document(user_id, config, caption=caption, parse_mode="Markdown") config_message = await bot.send_document(user_id, config, caption=caption, parse_mode="Markdown")
await bot.pin_chat_message(user_id, config_message.message_id, disable_notification=True) await bot.pin_chat_message(user_id, config_message.message_id, disable_notification=True)
else: else:
@@ -686,10 +708,182 @@ async def pc_instructions(callback_query: types.CallbackQuery):
) )
await callback_query.answer() await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "buy_key")
async def buy_key_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
keyboard = InlineKeyboardMarkup(row_width=2).add(
InlineKeyboardButton("1 месяц - $10", callback_data="select_period_1_month"),
InlineKeyboardButton("3 месяца - $25", callback_data="select_period_3_months"),
InlineKeyboardButton("6 месяцев - $45", callback_data="select_period_6_months"),
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()
@dp.callback_query_handler(lambda c: c.data.startswith('select_period_'))
async def select_period_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
period = callback_query.data.split('select_period_')[1]
price = PRICING[period]
discount = user_main_messages.get(user_id, {}).get('promocode_discount', 0)
final_price = price * (1 - discount / 100)
# Заглушка для создания платежа
payment_id = str(uuid.uuid4())
db.add_payment(user_id, payment_id, final_price, 'pending')
# Предполагается, что здесь будет интеграция с платежной системой
payment_url = f"https://example.com/pay/{payment_id}" # Замените на реальный URL платежной системы
keyboard = InlineKeyboardMarkup().add(
InlineKeyboardButton("💳 Оплатить", url=payment_url),
InlineKeyboardButton("⬅️ Назад", callback_data="buy_key"),
InlineKeyboardButton("🏠 Домой", callback_data="home")
)
await bot.edit_message_text(
chat_id=callback_query.message.chat.id,
message_id=callback_query.message.message_id,
text=f"Подписка на {period.replace('_', ' ')}: ${final_price:.2f} (скидка {discount}%)\nОплатите по ссылке:",
reply_markup=keyboard
)
user_main_messages[user_id]['pending_payment'] = {
'payment_id': payment_id,
'period': period,
'username': None
}
await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "use_promocode")
async def use_promocode_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
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"))
)
user_main_messages[user_id]['state'] = 'waiting_for_promocode'
await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "manage_promocodes")
async def manage_promocodes_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
if user_id not in admins:
await callback_query.answer("Нет прав.", show_alert=True)
return
promocodes = db.get_promocodes()
text = "Промокоды:\n" + "\n".join(
f"{code}: {info['discount']}% (использовано {info['uses']}/{info['max_uses'] or ''}, до {info['expires_at'] or 'неограничено'})"
for code, info in promocodes.items()
) if promocodes else "Промокоды отсутствуют."
keyboard = InlineKeyboardMarkup(row_width=2).add(
InlineKeyboardButton(" Добавить промокод", callback_data="add_promocode"),
InlineKeyboardButton("🗑️ Удалить промокод", callback_data="delete_promocode"),
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,
reply_markup=keyboard
)
await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "add_promocode")
async def add_promocode_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
if user_id not in admins:
await callback_query.answer("Нет прав.", show_alert=True)
return
await bot.edit_message_text(
chat_id=callback_query.message.chat.id,
message_id=callback_query.message.message_id,
text="Введите промокод в формате: <код> <скидка%> <дней_действия> <макс_использований|none>",
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home"))
)
user_main_messages[user_id]['state'] = 'waiting_for_new_promocode'
await callback_query.answer()
@dp.callback_query_handler(lambda c: c.data == "delete_promocode")
async def delete_promocode_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
if user_id not in admins:
await callback_query.answer("Нет прав.", show_alert=True)
return
promocodes = db.get_promocodes()
keyboard = InlineKeyboardMarkup(row_width=2)
for code in promocodes:
keyboard.insert(InlineKeyboardButton(f"🗑️ {code}", callback_data=f"remove_promocode_{code}"))
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()
@dp.callback_query_handler(lambda c: c.data.startswith('remove_promocode_'))
async def remove_promocode_callback(callback_query: types.CallbackQuery):
user_id = callback_query.from_user.id
if user_id not in admins:
await callback_query.answer("Нет прав.", show_alert=True)
return
code = callback_query.data.split('remove_promocode_')[1]
if db.remove_promocode(code):
await callback_query.answer(f"Промокод {code} удалён.", show_alert=True)
else:
await callback_query.answer(f"Промокод {code} не найден.", show_alert=True)
await manage_promocodes_callback(callback_query)
async def check_expired_keys():
now = datetime.now(pytz.UTC)
for user, expiration, _ in db.get_users_with_expiration():
if expiration and datetime.fromisoformat(expiration).replace(tzinfo=pytz.UTC) < now:
db.deactive_user_db(user)
shutil.rmtree(os.path.join('users', user), ignore_errors=True)
db.remove_user_expiration(user)
logger.info(f"Пользователь {user} деактивирован из-за истечения срока действия.")
async def check_payment_status():
# Заглушка для проверки статуса платежа
# В реальной системе здесь будет запрос к API платежной системы
payments = db.get_all_payments()
for payment in payments:
if payment['status'] == 'pending':
# Предположим, что платеж подтвержден (заглушка)
db.update_payment_status(payment['payment_id'], 'completed')
user_id = payment['user_id']
if user_id in user_main_messages and 'pending_payment' in user_main_messages[user_id]:
pending = user_main_messages[user_id]['pending_payment']
period = pending['period']
months = {'1_month': 1, '3_months': 3, '6_months': 6}[period]
expiration = datetime.now(pytz.UTC) + timedelta(days=30 * months)
# Генерация уникального имени пользователя
username = f"user_{user_id}_{uuid.uuid4().hex[:8]}"
success = db.root_add(username, ipv6=False)
if success:
db.set_user_expiration(username, expiration, "Неограниченно")
conf_path = os.path.join('users', username, f'{username}.conf')
if os.path.exists(conf_path):
vpn_key = await generate_vpn_key(conf_path)
caption = f"Ваш ключ для {username} (действует до {expiration.strftime('%Y-%m-%d')}):\nAmneziaVPN:\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```\n{vpn_key}\n```"
with open(conf_path, 'rb') as config:
config_message = await bot.send_document(user_id, config, caption=caption, parse_mode="Markdown")
await bot.pin_chat_message(user_id, config_message.message_id, disable_notification=True)
user_main_messages[user_id].pop('pending_payment', None)
user_main_messages[user_id].pop('promocode_discount', None)
def parse_transfer(transfer_str): def parse_transfer(transfer_str):
if not isinstance(transfer_str, str) or not transfer_str.strip(): if not isinstance(transfer_str, str) or not transfer_str.strip():
logger.error(f"Некорректный transfer_str: {transfer_str}") logger.error(f"Некорректный transfer_str: {transfer_str}")
return 0, 0 # Значения по умолчанию return 0, 0
try: 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}
@@ -706,7 +900,7 @@ def parse_transfer(transfer_str):
return incoming_bytes, outgoing_bytes return incoming_bytes, outgoing_bytes
except Exception as e: except Exception as e:
logger.error(f"Ошибка в parse_transfer: {str(e)}") logger.error(f"Ошибка в parse_transfer: {str(e)}")
return 0, 0 # Значения по умолчанию 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(
@@ -736,6 +930,8 @@ async def on_startup(dp):
logger.error("Список админов пуст.") logger.error("Список админов пуст.")
sys.exit(1) sys.exit(1)
scheduler.add_job(db.ensure_peer_names, IntervalTrigger(minutes=1)) scheduler.add_job(db.ensure_peer_names, IntervalTrigger(minutes=1))
scheduler.add_job(check_expired_keys, IntervalTrigger(minutes=5))
scheduler.add_job(check_payment_status, IntervalTrigger(minutes=1))
async def on_shutdown(dp): async def on_shutdown(dp):
scheduler.shutdown() scheduler.shutdown()