diff --git a/awg/bot_manager.py b/awg/bot_manager.py index 30f00be..ab318e2 100644 --- a/awg/bot_manager.py +++ b/awg/bot_manager.py @@ -8,24 +8,19 @@ import re import json import sys import uuid -import pytz -import zipfile -import shutil -from aiogram import Bot, Dispatcher, types -from aiogram.filters import Command +from aiogram import Bot, types +from aiogram.dispatcher import Dispatcher +from aiogram.dispatcher.middlewares import BaseMiddleware +from aiogram.utils import executor from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from aiogram import BaseMiddleware from datetime import datetime, timedelta from apscheduler.schedulers.asyncio import AsyncIOScheduler -from apscheduler.triggers.interval import IntervalTrigger -from yoomoney import Client, Quickpay +import pytz +import shutil logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -# Базовый путь -BASE_DIR = "/root/amnezia-bot/awg" - # Загрузка конфигурации setting = db.get_config() bot_token = setting.get('bot_token') @@ -34,8 +29,6 @@ moderator_ids = setting.get('moderator_ids', []) wg_config_file = setting.get('wg_config_file') docker_container = setting.get('docker_container') endpoint = setting.get('endpoint') -yoomoney_token = setting.get('yoomoney_token') -yoomoney_wallet = setting.get('yoomoney_wallet') pricing = setting.get('pricing', { '1_month': 1000.0, '3_months': 2500.0, @@ -43,57 +36,27 @@ pricing = setting.get('pricing', { '12_months': 8000.0 }) -# Проверка обязательных настроек -required_settings = ['bot_token', 'admin_ids', 'wg_config_file', 'docker_container', 'endpoint'] -missing_settings = [key for key in required_settings if not setting.get(key)] -if missing_settings: - logger.error(f"Отсутствуют обязательные настройки: {', '.join(missing_settings)}") +if not all([bot_token, admin_ids, wg_config_file, docker_container, endpoint]): + logger.error("Некоторые обязательные настройки отсутствуют.") sys.exit(1) -# Проверка настроек YooMoney -if not all([yoomoney_token, yoomoney_wallet]): - logger.warning("Настройки YooMoney отсутствуют. Установите их через бот.") - -# Преобразование admin_ids и moderator_ids -try: - admins = [int(admin_id) for admin_id in admin_ids.split(',')] if isinstance(admin_ids, str) else [int(admin_id) for admin_id in admin_ids] - moderators = [int(mod_id) for mod_id in moderator_ids.split(',')] if isinstance(moderator_ids, str) else [int(mod_id) for mod_id in moderator_ids] -except ValueError as e: - logger.error(f"Ошибка преобразования admin_ids или moderator_ids: {str(e)}") - sys.exit(1) - -bot = Bot(token=bot_token) -dp = Dispatcher() +admins = [int(admin_id) for admin_id in admin_ids] +moderators = [int(mod_id) for mod_id in moderator_ids] +bot = Bot(bot_token) WG_CONFIG_FILE = wg_config_file DOCKER_CONTAINER = docker_container ENDPOINT = endpoint -yoomoney_client = Client(yoomoney_token) if yoomoney_token else None PRICING = pricing class AdminMessageDeletionMiddleware(BaseMiddleware): - async def __call__(self, handler, event, data): - if isinstance(event, types.Message) and event.from_user.id in admins and event.text.startswith('/'): - asyncio.create_task(delete_message_after_delay(event.chat.id, event.message_id)) - return await handler(event, data) + async def on_process_message(self, message: types.Message, data: dict): + if message.from_user.id in admins and message.text.startswith('/'): + asyncio.create_task(delete_message_after_delay(message.chat.id, message.message_id)) +dp = Dispatcher(bot) scheduler = AsyncIOScheduler(timezone=pytz.utc) scheduler.start() -dp.message.middleware(AdminMessageDeletionMiddleware()) - -# Вспомогательная функция для обновления сообщений -async def update_user_message(user_id, chat_id, text, reply_markup, old_message_id=None, state=None): - if old_message_id: - try: - await bot.delete_message(chat_id, old_message_id) - except: - pass - sent_message = await bot.send_message(chat_id, text, reply_markup=reply_markup, parse_mode="Markdown") - user_main_messages[user_id] = { - 'chat_id': sent_message.chat.id, - 'message_id': sent_message.message_id, - 'state': state - } - return sent_message +dp.middleware.setup(AdminMessageDeletionMiddleware()) # Главное меню def get_main_menu_markup(user_id): @@ -122,22 +85,16 @@ def get_main_menu_markup(user_id): ) else: markup.add( - InlineKeyboardButton("💳 Купить ключ", callback_data="buy_key"), - InlineKeyboardButton("🎟️ Получить ключ по промокоду", callback_data="use_promocode") + InlineKeyboardButton("🎟️ Получить ключ по промокоду", callback_data="use_promocode"), + InlineKeyboardButton("🏠 Домой", callback_data="home") ) return markup # Меню покупки ключа def get_buy_key_menu(user_id): markup = InlineKeyboardMarkup(row_width=2) - markup.add( - InlineKeyboardButton("📅 Выбрать период", callback_data="select_subscription_period"), - InlineKeyboardButton("🎟️ Ввести промокод", callback_data="enter_promocode_for_buy") - ) + markup.add(InlineKeyboardButton("🎟️ Ввести промокод", callback_data="use_promocode")) markup.add(InlineKeyboardButton("🏠 Домой", callback_data="home")) - discount = user_main_messages.get(user_id, {}).get('promocode_discount', 0) - if discount > 0: - markup.add(InlineKeyboardButton(f"Сбросить скидку ({discount}%)", callback_data="reset_promocode")) return markup # Меню настроек @@ -149,22 +106,9 @@ def get_settings_menu(): ) markup.add( InlineKeyboardButton("👤 Добавить админа", callback_data="add_admin"), - InlineKeyboardButton("💸 Настройки YooMoney", callback_data="yoomoney_settings") + InlineKeyboardButton("💰 Настройки цен", callback_data="pricing_settings") ) - markup.add( - InlineKeyboardButton("💰 Настройки цен", callback_data="pricing_settings"), - InlineKeyboardButton("⬅️ Назад", callback_data="home") - ) - return markup - -# Меню настроек YooMoney -def get_yoomoney_settings_menu(): - markup = InlineKeyboardMarkup(row_width=2) - markup.add( - InlineKeyboardButton("🔑 Установить токен", callback_data="set_yoomoney_token"), - InlineKeyboardButton("💼 Установить кошелёк", callback_data="set_yoomoney_wallet") - ) - markup.add(InlineKeyboardButton("⬅️ Назад", callback_data="settings")) + markup.add(InlineKeyboardButton("⬅️ Назад", callback_data="home")) return markup # Меню настроек цен @@ -209,55 +153,54 @@ async def delete_message_after_delay(chat_id: int, message_id: int, delay: int = pass async def generate_vpn_key(conf_path: str) -> str: - try: - process = await asyncio.create_subprocess_exec( - 'python3.11', os.path.join(BASE_DIR, 'awg-decode.py'), '--encode', conf_path, - stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE - ) - stdout, stderr = await process.communicate() - if process.returncode == 0 and stdout.decode().startswith('vpn://'): - return stdout.decode().strip() - else: - logger.error(f"Ошибка генерации vpn://: {stderr.decode()}") - return "" - except Exception as e: - logger.error(f"Ошибка в generate_vpn_key: {str(e)}") + process = await asyncio.create_subprocess_exec( + 'python3.11', '/root/amnezia-bot/awg/awg-decode.py', '--encode', conf_path, + stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + if process.returncode == 0 and stdout.decode().startswith('vpn://'): + return stdout.decode().strip() + else: + logger.error(f"Ошибка генерации vpn://: {stderr.decode()}") return "" async def issue_vpn_key(user_id: int, period: str) -> bool: - try: - username = f"user_{user_id}_{uuid.uuid4().hex[:8]}" - success = db.root_add(username, ipv6=False) - if success: - months = {'1_month': 1, '3_months': 3, '6_months': 6, '12_months': 12}.get(period, 1) - expiration = datetime.now(pytz.utc) + timedelta(days=30 * months) - db.set_user_expiration(username, expiration, "Неограниченно") - db.set_user_telegram_id(username, user_id) - conf_path = os.path.join(BASE_DIR, 'users', username, f'{username}.conf') - if os.path.exists(conf_path): - vpn_key = await generate_vpn_key(conf_path) - caption = f"Ваш VPN ключ ({period.replace('_', ' ')}):\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) - return True - return False - except Exception as e: - logger.error(f"Ошибка в issue_vpn_key для user_id {user_id}: {str(e)}") - return False + username = f"user_{user_id}_{uuid.uuid4().hex[:8]}" + success = db.root_add(username, ipv6=False) + if success: + months = {'1_month': 1, '3_months': 3, '6_months': 6, '12_months': 12}.get(period, 1) + expiration = datetime.now(pytz.utc) + timedelta(days=30 * months) + db.set_user_expiration(username, expiration, "Неограниченно") + db.set_user_telegram_id(username, user_id) + 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"Ваш VPN ключ ({period.replace('_', ' ')}):\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) + return True + return False -@dp.message(Command(commands=['start', 'help'])) +@dp.message_handler(commands=['start', 'help']) async def start_command_handler(message: types.Message): user_id = message.from_user.id - await update_user_message( - user_id=user_id, - chat_id=message.chat.id, - text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id), - old_message_id=user_main_messages.get(user_id, {}).get('message_id') - ) + if user_id in user_main_messages: + try: + await bot.delete_message( + chat_id=user_main_messages[user_id]['chat_id'], + message_id=user_main_messages[user_id]['message_id'] + ) + except: + pass + 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, + 'state': None + } -@dp.message(Command(commands=['add_admin'])) +@dp.message_handler(commands=['add_admin']) async def add_admin_command(message: types.Message): if message.from_user.id not in admins: await message.answer("У вас нет прав.") @@ -269,319 +212,267 @@ async def add_admin_command(message: types.Message): admins.append(new_admin_id) await message.answer(f"Админ {new_admin_id} добавлен.") await bot.send_message(new_admin_id, "Вы назначены администратором!") - else: - await message.answer(f"Пользователь {new_admin_id} уже админ.") except: await message.answer("Формат: /add_admin ") - await update_user_message( - user_id=message.from_user.id, - chat_id=message.chat.id, - text="Выберите действие:", - reply_markup=get_main_menu_markup(message.from_user.id), - old_message_id=message.message_id - ) -@dp.message() +@dp.message_handler() async def handle_messages(message: types.Message): global PRICING user_id = message.from_user.id user_state = user_main_messages.get(user_id, {}).get('state') - try: - if user_state == 'waiting_for_user_name': - user_name = message.text.strip() - if not re.match(r'^[a-zA-Z0-9_-]+$', user_name): - await message.reply("Имя может содержать только буквы, цифры, - и _.") - return - success = db.root_add(user_name, ipv6=False) - if success: - conf_path = os.path.join(BASE_DIR, 'users', user_name, f'{user_name}.conf') - if os.path.exists(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```" - 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) - else: - await message.reply("Ошибка добавления пользователя.") - elif user_state == 'waiting_for_admin_id' and user_id in admins: - try: - new_admin_id = int(message.text.strip()) - if new_admin_id not in admins: - db.add_admin(new_admin_id) - admins.append(new_admin_id) - await message.reply(f"Админ {new_admin_id} добавлен.") - await bot.send_message(new_admin_id, "Вы назначены администратором!") - else: - await message.reply(f"Пользователь {new_admin_id} уже админ.") - except: - await message.reply("Введите корректный Telegram ID.") - elif user_state == 'waiting_for_promocode': - promocode = message.text.strip() - promocode_data = db.apply_promocode(promocode) - if promocode_data: - subscription_period = promocode_data.get('subscription_period') - if subscription_period: - success = await issue_vpn_key(user_id, subscription_period) - if success: - await message.reply(f"Промокод активирован! VPN ключ на {subscription_period.replace('_', ' ')} выдан.") - else: - await message.reply("Ошибка при выдаче ключа. Обратитесь к администратору.") - else: - await message.reply("Промокод не предоставляет ключ.") - else: - await message.reply("Неверный или истёкший промокод.") - elif user_state == 'waiting_for_promocode_for_buy': - promocode = message.text.strip() - promocode_data = db.apply_promocode(promocode) - if promocode_data: - discount = promocode_data.get('discount', 0) - user_main_messages[user_id]['promocode_discount'] = discount - await message.reply(f"Промокод активирован! Скидка: {discount}%") - else: - await message.reply("Неверный или истёкший промокод.") - await update_user_message( - user_id=user_id, - chat_id=message.chat.id, - text="Меню покупки ключа:", - reply_markup=get_buy_key_menu(user_id), - state=None - ) + if user_state == 'waiting_for_user_name': + user_name = message.text.strip() + if not re.match(r'^[a-zA-Z0-9_-]+$', user_name): + await message.reply("Имя может содержать только буквы, цифры, - и _.") return - elif user_state == 'waiting_for_new_promocode' and user_id in admins: - try: - parts = message.text.strip().split() - if len(parts) != 5: - raise ValueError("Неверный формат") - code, discount, days_valid, max_uses, subscription_period = parts - discount = float(discount) - days_valid = int(days_valid) - max_uses = int(max_uses) if max_uses.lower() != 'none' else None - if subscription_period not in ['none', '1_month', '3_months', '6_months', '12_months']: - raise ValueError("Неверный период подписки") - subscription_period = None if subscription_period.lower() == 'none' else subscription_period - 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, subscription_period): - await message.reply( - f"Промокод {code} добавлен: скидка {discount}%, действует {days_valid} дней, " - f"макс. использований: {max_uses or 'неограничено'}, период подписки: {subscription_period or 'нет'}" - ) + success = db.root_add(user_name, ipv6=False) + if success: + conf_path = os.path.join('users', user_name, f'{user_name}.conf') + if os.path.exists(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```" + 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) + 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, + 'state': None + } + elif user_state == 'waiting_for_admin_id' and user_id in admins: + try: + new_admin_id = int(message.text.strip()) + if new_admin_id not in admins: + db.add_admin(new_admin_id) + admins.append(new_admin_id) + await message.reply(f"Админ {new_admin_id} добавлен.") + await bot.send_message(new_admin_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, + 'state': None + } + except: + await message.reply("Введите корректный Telegram ID.") + elif user_state == 'waiting_for_promocode': + promocode = message.text.strip() + promocode_data = db.apply_promocode(promocode) + if promocode_data: + subscription_period = promocode_data.get('subscription_period') + if subscription_period: + success = await issue_vpn_key(user_id, subscription_period) + if success: + await message.reply(f"Промокод активирован! VPN ключ на {subscription_period.replace('_', ' ')} выдан.") else: - await message.reply("Промокод уже существует.") - except: + await message.reply("Ошибка при выдаче ключа. Обратитесь к администратору.") + else: + await message.reply("Промокод не предоставляет ключ.") + else: + await message.reply("Неверный или истёкший промокод.") + 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, + 'state': None + } + elif user_state == 'waiting_for_new_promocode' and user_id in admins: + try: + parts = message.text.strip().split() + if len(parts) != 5: + raise ValueError("Неверный формат") + code, discount, days_valid, max_uses, subscription_period = parts + discount = float(discount) + days_valid = int(days_valid) + max_uses = int(max_uses) if max_uses.lower() != 'none' else None + if subscription_period not in ['none', '1_month', '3_months', '6_months', '12_months']: + raise ValueError("Неверный период подписки") + subscription_period = None if subscription_period.lower() == 'none' else subscription_period + 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, subscription_period): await message.reply( - "Формат: <код> <скидка%> <дней_действия> <макс_использований|none> <период_подписки|none>\n" - "Пример: PROMO1 10 30 none 1_month" + f"Промокод {code} добавлен: скидка {discount}%, действует {days_valid} дней, " + f"макс. использований: {max_uses or 'неограничено'}, период подписки: {subscription_period or 'нет'}" ) - elif user_state == 'waiting_for_yoomoney_token' and user_id in admins: - token = message.text.strip() - db.set_yoomoney_config(token=token) - global yoomoney_client, yoomoney_token - yoomoney_token = token - yoomoney_client = Client(yoomoney_token) - await message.reply("Токен YooMoney успешно обновлён.") - await update_user_message( - user_id=user_id, - chat_id=message.chat.id, - text="Настройки YooMoney:", - reply_markup=get_yoomoney_settings_menu(), - state=None + else: + await message.reply("Промокод уже существует.") + except: + await message.reply( + "Формат: <код> <скидка%> <дней_действия> <макс_использований|none> <период_подписки|none>\n" + "Пример: PROMO1 10 30 none 1_month" ) + 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, + 'state': None + } + elif user_state.startswith('waiting_for_price_') and user_id in admins: + period = user_state.split('waiting_for_price_')[1] + try: + price = float(message.text.strip()) + if price <= 0: + raise ValueError("Цена должна быть положительной.") + db.set_pricing(period, price) + PRICING[period] = price + await message.reply(f"Цена для {period.replace('_', ' ')} обновлена: ₽{price:.2f}") + except: + await message.reply("Введите корректное число (например, 1000.00).") return - elif user_state == 'waiting_for_yoomoney_wallet' and user_id in admins: - wallet = message.text.strip() - if not re.match(r'^\d{15,18}$', wallet): - await message.reply("Введите корректный номер кошелька YooMoney (15-18 цифр).") + sent_message = await message.answer("Настройки цен:", reply_markup=get_pricing_settings_menu()) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } + elif user_state.startswith('waiting_for_custom_date_') and user_id in admins: + username = user_state.split('waiting_for_custom_date_')[1] + try: + expiration = datetime.strptime(message.text.strip(), '%d-%m-%Y').replace(tzinfo=pytz.utc) + if expiration < datetime.now(pytz.utc): + await message.reply("Дата должна быть в будущем.") return - db.set_yoomoney_config(wallet=wallet) - global yoomoney_wallet - yoomoney_wallet = wallet - await message.reply("Номер кошелька YooMoney успешно обновлён.") - await update_user_message( - user_id=user_id, - chat_id=message.chat.id, - text="Настройки YooMoney:", - reply_markup=get_yoomoney_settings_menu(), - state=None - ) + db.set_user_expiration(username, expiration, "Неограниченно") + await message.reply(f"Подписка для {username} продлена до {expiration.strftime('%d-%m-%Y')}.") + except: + await message.reply("Введите дату в формате ДД-ММ-ГГГГ (например, 31-12-2025).") return - elif user_state.startswith('waiting_for_price_') and user_id in admins: - period = user_state.split('waiting_for_price_')[1] - try: - price = float(message.text.strip()) - if price <= 0: - raise ValueError("Цена должна быть положительной.") - db.set_pricing(period, price) - PRICING[period] = price - await message.reply(f"Цена для {period.replace('_', ' ')} обновлена: ₽{price:.2f}") - except: - await message.reply("Введите корректное число (например, 1000.00).") - return - await update_user_message( - user_id=user_id, - chat_id=message.chat.id, - text="Настройки цен:", - reply_markup=get_pricing_settings_menu(), - state=None - ) - return - elif user_state.startswith('waiting_for_custom_date_') and user_id in admins: - username = user_state.split('waiting_for_custom_date_')[1] - try: - expiration = datetime.strptime(message.text.strip(), '%d-%m-%Y').replace(tzinfo=pytz.utc) - if expiration < datetime.now(pytz.utc): - await message.reply("Дата должна быть в будущем.") - return - db.set_user_expiration(username, expiration, "Неограниченно") - await message.reply(f"Подписка для {username} продлена до {expiration.strftime('%d-%m-%Y')}.") - except: - await message.reply("Введите дату в формате ДД-ММ-ГГГГ (например, 31-12-2025).") - return - await update_user_message( - user_id=user_id, - chat_id=message.chat.id, - text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id), - state=None - ) - except Exception as e: - logger.error(f"Ошибка в handle_messages для user_id {user_id}: {str(e)}") - await message.reply("Произошла ошибка. Попробуйте снова.") - await update_user_message( - user_id=user_id, - chat_id=message.chat.id, - text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id), - state=None - ) + 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, + 'state': None + } -@dp.callback_query(lambda c: c.data == "settings") +@dp.callback_query_handler(lambda c: c.data == "settings") async def settings_menu_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 update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Настройки:", - reply_markup=get_settings_menu(), - old_message_id=callback_query.message.message_id + reply_markup=get_settings_menu() ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "yoomoney_settings") -async def yoomoney_settings_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 update_user_message( - user_id=user_id, - chat_id=callback_query.message.chat.id, - text="Настройки YooMoney:", - reply_markup=get_yoomoney_settings_menu(), - old_message_id=callback_query.message.message_id - ) - await callback_query.answer() - -@dp.callback_query(lambda c: c.data == "set_yoomoney_token") -async def set_yoomoney_token_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 update_user_message( - user_id=user_id, - chat_id=callback_query.message.chat.id, - text="Введите токен YooMoney:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="yoomoney_settings")), - old_message_id=callback_query.message.message_id, - state='waiting_for_yoomoney_token' - ) - await callback_query.answer() - -@dp.callback_query(lambda c: c.data == "set_yoomoney_wallet") -async def set_yoomoney_wallet_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 update_user_message( - user_id=user_id, - chat_id=callback_query.message.chat.id, - text="Введите номер кошелька YooMoney (15-18 цифр):", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="yoomoney_settings")), - old_message_id=callback_query.message.message_id, - state='waiting_for_yoomoney_wallet' - ) - await callback_query.answer() - -@dp.callback_query(lambda c: c.data == "pricing_settings") +@dp.callback_query_handler(lambda c: c.data == "pricing_settings") async def pricing_settings_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 update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Настройки цен:", - reply_markup=get_pricing_settings_menu(), - old_message_id=callback_query.message.message_id + reply_markup=get_pricing_settings_menu() ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data.startswith('set_price_')) +@dp.callback_query_handler(lambda c: c.data.startswith('set_price_')) async def set_price_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 period = callback_query.data.split('set_price_')[1] - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=f"Введите новую цену для {period.replace('_', ' ')} в рублях (например, 1000.00):", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="pricing_settings")), - old_message_id=callback_query.message.message_id, - state=f'waiting_for_price_{period}' + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="pricing_settings")) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': f'waiting_for_price_{period}' + } await callback_query.answer() -@dp.callback_query(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): 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 - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Введите имя пользователя:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")), - old_message_id=callback_query.message.message_id, - state='waiting_for_user_name' + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': 'waiting_for_user_name' + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "add_admin") +@dp.callback_query_handler(lambda c: c.data == "add_admin") async def prompt_for_admin_id(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 update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Введите Telegram ID нового админа:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home")), - old_message_id=callback_query.message.message_id, - state='waiting_for_admin_id' + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home")) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': 'waiting_for_admin_id' + } await callback_query.answer() -@dp.callback_query(lambda c: c.data.startswith('client_')) +@dp.callback_query_handler(lambda c: c.data.startswith('client_')) async def client_selected_callback(callback_query: types.CallbackQuery): user_id = callback_query.from_user.id if user_id not in admins and user_id not in moderators: @@ -622,30 +513,44 @@ async def client_selected_callback(callback_query: types.CallbackQuery): InlineKeyboardButton("🏠 Домой", callback_data="home") ) - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=text, - reply_markup=keyboard, - old_message_id=callback_query.message.message_id + parse_mode="Markdown", + reply_markup=keyboard ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() except Exception as e: logger.error(f"Ошибка в client_selected_callback: {str(e)}") - await update_user_message( - user_id=user_id, + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=f"Ошибка при загрузке профиля: {str(e)}", reply_markup=InlineKeyboardMarkup().add( InlineKeyboardButton("⬅️ Назад", callback_data="list_users"), InlineKeyboardButton("🏠 Домой", callback_data="home") - ), - old_message_id=callback_query.message.message_id + ) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer("Ошибка на сервере.", show_alert=True) -@dp.callback_query(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): user_id = callback_query.from_user.id if user_id not in admins and user_id not in moderators: @@ -655,15 +560,25 @@ async def list_users_callback(callback_query: types.CallbackQuery): try: clients = db.get_client_list() if not clients: - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Список клиентов пуст.", reply_markup=InlineKeyboardMarkup().add( InlineKeyboardButton("🏠 Домой", callback_data="home") - ), - old_message_id=callback_query.message.message_id + ) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() return @@ -678,29 +593,42 @@ async def list_users_callback(callback_query: types.CallbackQuery): keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home")) - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Выберите пользователя:", - reply_markup=keyboard, - old_message_id=callback_query.message.message_id + reply_markup=keyboard ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() except Exception as e: logger.error(f"Ошибка в list_users_callback: {str(e)}") - await update_user_message( - user_id=user_id, + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=f"Ошибка: {str(e)}", reply_markup=InlineKeyboardMarkup().add( InlineKeyboardButton("🏠 Домой", callback_data="home") - ), - old_message_id=callback_query.message.message_id + ) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer("Ошибка на сервере.", show_alert=True) -@dp.callback_query(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): user_id = callback_query.from_user.id if user_id not in admins: @@ -710,16 +638,26 @@ async def list_admins_callback(callback_query: types.CallbackQuery): for admin_id in admins: keyboard.insert(InlineKeyboardButton(f"🗑️ Удалить {admin_id}", callback_data=f"remove_admin_{admin_id}")) keyboard.add(InlineKeyboardButton("⬅️ Назад", callback_data="settings")) - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=f"Администраторы:\n" + "\n".join(f"- {admin_id}" for admin_id in admins), - reply_markup=keyboard, - old_message_id=callback_query.message.message_id + reply_markup=keyboard ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data.startswith('remove_admin_')) +@dp.callback_query_handler(lambda c: c.data.startswith('remove_admin_')) async def remove_admin_callback(callback_query: types.CallbackQuery): user_id = callback_query.from_user.id if user_id not in admins: @@ -734,7 +672,7 @@ async def remove_admin_callback(callback_query: types.CallbackQuery): await bot.send_message(admin_id, "Вы удалены из администраторов.") await list_admins_callback(callback_query) -@dp.callback_query(lambda c: c.data.startswith('delete_user_')) +@dp.callback_query_handler(lambda c: c.data.startswith('delete_user_')) async def client_delete_callback(callback_query: types.CallbackQuery): user_id = callback_query.from_user.id if user_id not in admins and user_id not in moderators: @@ -743,7 +681,7 @@ async def client_delete_callback(callback_query: types.CallbackQuery): username = callback_query.data.split('delete_user_')[1] try: if db.deactive_user_db(username): - shutil.rmtree(os.path.join(BASE_DIR, 'users', username), ignore_errors=True) + shutil.rmtree(os.path.join('users', username), ignore_errors=True) db.remove_user_expiration(username) db.set_user_telegram_id(username, None) logger.info(f"Пользователь {username} успешно удалён.") @@ -754,32 +692,53 @@ async def client_delete_callback(callback_query: types.CallbackQuery): except Exception as e: logger.error(f"Ошибка при удалении пользователя {username}: {str(e)}") text = f"Ошибка при удалении **{username}**: {str(e)}" - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=text, - reply_markup=get_main_menu_markup(user_id), - old_message_id=callback_query.message.message_id + parse_mode="Markdown", + 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, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data.startswith('renew_user_')) +@dp.callback_query_handler(lambda c: c.data.startswith('renew_user_')) async def renew_user_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 username = callback_query.data.split('renew_user_')[1] - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Выберите период продления или укажите дату:", - reply_markup=get_renewal_period_keyboard(username), - old_message_id=callback_query.message.message_id + reply_markup=get_renewal_period_keyboard(username) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data.startswith('renew_period_')) +@dp.callback_query_handler(lambda c: c.data.startswith('renew_period_')) async def renew_period_callback(callback_query: types.CallbackQuery): user_id = callback_query.from_user.id if user_id not in admins: @@ -790,53 +749,87 @@ async def renew_period_callback(callback_query: types.CallbackQuery): username = parts[0] period = parts[1] if len(parts) > 1 else 'custom_date' if period == 'custom_date': - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Введите дату продления в формате ДД-ММ-ГГГГ (например, 31-12-2025):", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home")), - old_message_id=callback_query.message.message_id, - state=f'waiting_for_custom_date_{username}' + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home")) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': f'waiting_for_custom_date_{username}' + } else: months = {'1_month': 1, '3_months': 3, '6_months': 6, '12_months': 12}[period] expiration = datetime.now(pytz.utc) + timedelta(days=30 * months) db.set_user_expiration(username, expiration, "Неограниченно") text = f"Подписка для {username} продлена до {expiration.strftime('%Y-%m-%d %H:%M UTC')}." logger.info(f"Подписка для {username} продлена на {period} до {expiration}.") - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=text, - reply_markup=get_main_menu_markup(user_id), - old_message_id=callback_query.message.message_id + parse_mode="Markdown", + 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, + 'state': None + } await callback_query.answer() except Exception as e: text = f"Ошибка при продлении: {str(e)}" logger.error(f"Ошибка при продлении подписки для {username}: {str(e)}") - await update_user_message( - user_id=user_id, + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=text, - reply_markup=get_main_menu_markup(user_id), - old_message_id=callback_query.message.message_id + parse_mode="Markdown", + 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, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "home") +@dp.callback_query_handler(lambda c: c.data == "home") async def return_home(callback_query: types.CallbackQuery): user_id = callback_query.from_user.id - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id), - old_message_id=callback_query.message.message_id + 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, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "get_config") +@dp.callback_query_handler(lambda c: c.data == "get_config") async def list_users_for_config(callback_query: types.CallbackQuery): user_id = callback_query.from_user.id if user_id not in admins and user_id not in moderators: @@ -851,23 +844,33 @@ async def list_users_for_config(callback_query: types.CallbackQuery): for client in clients: keyboard.insert(InlineKeyboardButton(client[0], callback_data=f"send_config_{client[0]}")) keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home")) - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Выберите пользователя:", - reply_markup=keyboard, - old_message_id=callback_query.message.message_id + reply_markup=keyboard ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data.startswith('send_config_')) +@dp.callback_query_handler(lambda c: c.data.startswith('send_config_')) async def send_user_config(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 username = callback_query.data.split('send_config_')[1] - conf_path = os.path.join(BASE_DIR, 'users', username, f'{username}.conf') + 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}:\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```" @@ -876,175 +879,95 @@ async def send_user_config(callback_query: types.CallbackQuery): await bot.pin_chat_message(user_id, config_message.message_id, disable_notification=True) else: await bot.send_message(user_id, f"Конфигурация для **{username}** не найдена.", parse_mode="Markdown") - await update_user_message( - user_id=user_id, + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id), - old_message_id=callback_query.message.message_id + 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, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "create_backup") +@dp.callback_query_handler(lambda c: c.data == "create_backup") async def create_backup_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 - try: - backup_filename = f"backup_{datetime.now().strftime('%Y-%m-%d')}.zip" - with zipfile.ZipFile(backup_filename, 'w') as zipf: - for file in ['awg-decode.py', 'newclient.sh', 'removeclient.sh']: - file_path = os.path.join(BASE_DIR, file) - if os.path.exists(file_path): - zipf.write(file_path, file) - for root, _, files in os.walk(os.path.join(BASE_DIR, 'files')): - for file in files: - file_path = os.path.join(root, file) - zipf.write(file_path, os.path.relpath(file_path, BASE_DIR)) - for root, _, files in os.walk(os.path.join(BASE_DIR, 'users')): - for file in files: - file_path = os.path.join(root, file) - zipf.write(file_path, os.path.relpath(file_path, BASE_DIR)) - with open(backup_filename, 'rb') as f: - await bot.send_document(user_id, f, caption=backup_filename) - os.remove(backup_filename) - except Exception as e: - logger.error(f"Ошибка при создании бэкапа: {str(e)}") - await bot.send_message(user_id, f"Ошибка при создании бэкапа: {str(e)}") - await update_user_message( - user_id=user_id, + backup_filename = f"backup_{datetime.now().strftime('%Y-%m-%d')}.zip" + with zipfile.ZipFile(backup_filename, 'w') as zipf: + for file in ['awg-decode.py', 'newclient.sh', 'removeclient.sh']: + if os.path.exists(file): + zipf.write(file) + for root, _, files in os.walk('files'): + for file in files: + zipf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), os.getcwd())) + for root, _, files in os.walk('users'): + for file in files: + zipf.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), os.getcwd())) + with open(backup_filename, 'rb') as f: + await bot.send_document(user_id, f, caption=backup_filename) + os.remove(backup_filename) + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id), - old_message_id=callback_query.message.message_id + 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, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "buy_key") +@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 - await update_user_message( - user_id=user_id, - chat_id=callback_query.message.chat.id, - text="Меню покупки ключа:", - reply_markup=get_buy_key_menu(user_id), - old_message_id=callback_query.message.message_id - ) - await callback_query.answer() - -@dp.callback_query(lambda c: c.data == "select_subscription_period") -async def select_period_callback(callback_query: types.CallbackQuery): - user_id = callback_query.from_user.id - keyboard = InlineKeyboardMarkup(row_width=2) - discount = user_main_messages.get(user_id, {}).get('promocode_discount', 0) - for period, price in PRICING.items(): - final_price = price * (1 - discount / 100) - keyboard.add(InlineKeyboardButton( - f"{period.replace('_', ' ')} - ₽{final_price:.2f}{' (-' + str(discount) + '%)' if discount > 0 else ''}", - callback_data=f"confirm_period_{period}" - )) - keyboard.add(InlineKeyboardButton("⬅️ Назад", callback_data="buy_key")) - keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home")) - await update_user_message( - user_id=user_id, - chat_id=callback_query.message.chat.id, - text="Выберите период подписки:", - reply_markup=keyboard, - old_message_id=callback_query.message.message_id - ) - await callback_query.answer() - -@dp.callback_query(lambda c: c.data.startswith('confirm_period_')) -async def confirm_period_callback(callback_query: types.CallbackQuery): - user_id = callback_query.from_user.id - period = callback_query.data.split('confirm_period_')[1] - price = PRICING[period] - discount = user_main_messages.get(user_id, {}).get('promocode_discount', 0) - final_price = price * (1 - discount / 100) - - if not yoomoney_client or not yoomoney_wallet: - await callback_query.answer("Платежи недоступны. Настройки YooMoney не установлены.", show_alert=True) - return - try: - payment_id = str(uuid.uuid4()) - db.add_payment(user_id, payment_id, final_price, 'pending') - - quickpay = Quickpay( - receiver=yoomoney_wallet, - quickpay_form="shop", - targets=f"VPN Subscription {period}", - paymentType="SB", - sum=final_price, - label=payment_id - ) - payment_url = quickpay.redirected_url - - keyboard = InlineKeyboardMarkup().add( - InlineKeyboardButton("💳 Оплатить", url=payment_url), - InlineKeyboardButton("⬅️ Назад", callback_data="select_subscription_period"), - InlineKeyboardButton("🏠 Домой", callback_data="home") - ) - await update_user_message( - user_id=user_id, + await bot.delete_message( chat_id=callback_query.message.chat.id, - text=f"Подписка на {period.replace('_', ' ')}: ₽{final_price:.2f} (скидка {discount}%)\nОплатите по ссылке:", - reply_markup=keyboard, - old_message_id=callback_query.message.message_id, - state=None, - pending_payment={ - 'payment_id': payment_id, - 'period': period, - 'username': None - } + message_id=callback_query.message.message_id ) - await callback_query.answer() - except Exception as e: - logger.error(f"Ошибка при создании платежа для user_id {user_id}: {str(e)}") - await callback_query.answer("Ошибка при создании платежа.", show_alert=True) - -@dp.callback_query(lambda c: c.data == "enter_promocode_for_buy") -async def enter_promocode_for_buy_callback(callback_query: types.CallbackQuery): - user_id = callback_query.from_user.id - await update_user_message( - user_id=user_id, + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, - text="Введите промокод для скидки:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="buy_key")), - old_message_id=callback_query.message.message_id, - state='waiting_for_promocode_for_buy' + text="Меню получения ключа:", + reply_markup=get_buy_key_menu(user_id) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "reset_promocode") -async def reset_promocode_callback(callback_query: types.CallbackQuery): - user_id = callback_query.from_user.id - if 'promocode_discount' in user_main_messages.get(user_id, {}): - del user_main_messages[user_id]['promocode_discount'] - await update_user_message( - user_id=user_id, - chat_id=callback_query.message.chat.id, - text="Скидка сброшена. Меню покупки ключа:", - reply_markup=get_buy_key_menu(user_id), - old_message_id=callback_query.message.message_id - ) - await callback_query.answer() - -@dp.callback_query(lambda c: c.data == "use_promocode") +@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 update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Введите промокод для получения ключа:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")), - old_message_id=callback_query.message.message_id, - state='waiting_for_promocode' + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': 'waiting_for_promocode' + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "manage_promocodes") +@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: @@ -1060,32 +983,51 @@ async def manage_promocodes_callback(callback_query: types.CallbackQuery): InlineKeyboardButton("🗑️ Удалить промокод", callback_data="delete_promocode"), InlineKeyboardButton("🏠 Домой", callback_data="home") ) - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text=text, - reply_markup=keyboard, - old_message_id=callback_query.message.message_id + reply_markup=keyboard ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "add_promocode") +@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 update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Введите промокод в формате: <код> <скидка%> <дней_действия> <макс_использований|none> <период_подписки|none>\nПример: PROMO1 10 30 none 1_month", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")), - old_message_id=callback_query.message.message_id, - state='waiting_for_new_promocode' + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")) ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': 'waiting_for_new_promocode' + } await callback_query.answer() -@dp.callback_query(lambda c: c.data == "delete_promocode") +@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: @@ -1096,16 +1038,26 @@ async def delete_promocode_callback(callback_query: types.CallbackQuery): for code in promocodes: keyboard.insert(InlineKeyboardButton(f"🗑️ {code}", callback_data=f"remove_promocode_{code}")) keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home")) - await update_user_message( - user_id=user_id, + try: + await bot.delete_message( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id + ) + except: + pass + sent_message = await bot.send_message( chat_id=callback_query.message.chat.id, text="Выберите промокод для удаления:", - reply_markup=keyboard, - old_message_id=callback_query.message.message_id + reply_markup=keyboard ) + user_main_messages[user_id] = { + 'chat_id': sent_message.chat.id, + 'message_id': sent_message.message_id, + 'state': None + } await callback_query.answer() -@dp.callback_query(lambda c: c.data.startswith('remove_promocode_')) +@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: @@ -1118,34 +1070,5 @@ async def remove_promocode_callback(callback_query: types.CallbackQuery): await callback_query.answer(f"Промокод {code} не найден.", show_alert=True) await manage_promocodes_callback(callback_query) -async def check_pending_payments(): - try: - payments = db.get_pending_payments() - for payment in payments: - user_id, payment_id, amount, period = payment - try: - history = yoomoney_client.operation_history(label=payment_id) - for operation in history.operations: - if operation.status == "success" and operation.amount == amount: - db.update_payment_status(payment_id, 'completed') - success = await issue_vpn_key(user_id, period) - if success: - await bot.send_message(user_id, f"Оплата подтверждена! VPN ключ на {period.replace('_', ' ')} выдан.") - else: - await bot.send_message(user_id, "Ошибка при выдаче ключа. Обратитесь к администратору.") - break - except Exception as e: - logger.error(f"Ошибка при проверке платежа {payment_id}: {str(e)}") - except Exception as e: - logger.error(f"Ошибка в check_pending_payments: {str(e)}") - -scheduler.add_job(check_pending_payments, IntervalTrigger(minutes=5)) - -async def main(): - try: - await dp.start_polling(bot) - finally: - await bot.session.close() - if __name__ == '__main__': - asyncio.run(main()) + executor.start_polling(dp, skip_updates=True)