From 87431cc6ab9dec3e89a60ead5e759680fcfae4f9 Mon Sep 17 00:00:00 2001 From: stevefoxru <53931856+stevefoxru@users.noreply.github.com> Date: Mon, 28 Apr 2025 14:26:20 +0300 Subject: [PATCH] Update bot_manager.py --- awg/bot_manager.py | 1143 ++++++++++++++++++-------------------------- 1 file changed, 460 insertions(+), 683 deletions(-) diff --git a/awg/bot_manager.py b/awg/bot_manager.py index d9f1757..1939929 100644 --- a/awg/bot_manager.py +++ b/awg/bot_manager.py @@ -8,6 +8,9 @@ import re import json import sys import uuid +import pytz +import zipfile +import shutil from aiogram import Bot, types from aiogram.dispatcher import Dispatcher from aiogram.dispatcher.middlewares import BaseMiddleware @@ -15,11 +18,15 @@ from aiogram.utils import executor from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton from datetime import datetime, timedelta from apscheduler.schedulers.asyncio import AsyncIOScheduler +from apscheduler.triggers.interval import IntervalTrigger from yoomoney import Client, Quickpay 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') @@ -37,15 +44,25 @@ pricing = setting.get('pricing', { '12_months': 8000.0 }) -if not all([bot_token, admin_ids, wg_config_file, docker_container, endpoint]): - logger.error("Некоторые обязательные настройки отсутствуют.") +# Проверка обязательных настроек +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)}") sys.exit(1) +# Проверка настроек YooMoney if not all([yoomoney_token, yoomoney_wallet]): logger.warning("Настройки YooMoney отсутствуют. Установите их через бот.") -admins = [int(admin_id) for admin_id in admin_ids] -moderators = [int(mod_id) for mod_id in moderator_ids] +# Преобразование 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(bot_token) WG_CONFIG_FILE = wg_config_file DOCKER_CONTAINER = docker_container @@ -63,6 +80,21 @@ scheduler = AsyncIOScheduler(timezone=pytz.utc) scheduler.start() dp.middleware.setup(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 + # Главное меню def get_main_menu_markup(user_id): markup = InlineKeyboardMarkup(row_width=2) @@ -177,52 +209,53 @@ async def delete_message_after_delay(chat_id: int, message_id: int, delay: int = pass async def generate_vpn_key(conf_path: str) -> str: - 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()}") + 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)}") return "" async def issue_vpn_key(user_id: int, period: str) -> bool: - 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 + 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 @dp.message_handler(commands=['start', 'help']) async def start_command_handler(message: types.Message): user_id = message.from_user.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 - } + 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') + ) @dp.message_handler(commands=['add_admin']) async def add_admin_command(message: types.Message): @@ -236,8 +269,17 @@ 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_handler() async def handle_messages(message: types.Message): @@ -245,173 +287,173 @@ async def handle_messages(message: types.Message): user_id = message.from_user.id user_state = user_main_messages.get(user_id, {}).get('state') - 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('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("Ошибка при выдаче ключа. Обратитесь к администратору.") - 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_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("Неверный или истёкший промокод.") - sent_message = await message.answer("Выберите действие:", 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 - } - 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 'нет'}" - ) - 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 == '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 успешно обновлён.") - sent_message = await message.answer("Настройки YooMoney:", reply_markup=get_yoomoney_settings_menu()) - user_main_messages[user_id] = { - 'chat_id': sent_message.chat.id, - 'message_id': sent_message.message_id, - 'state': None - } - 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 цифр).") - return - db.set_yoomoney_config(wallet=wallet) - global yoomoney_wallet - yoomoney_wallet = wallet - await message.reply("Номер кошелька YooMoney успешно обновлён.") - sent_message = await message.answer("Настройки YooMoney:", reply_markup=get_yoomoney_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_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 - 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("Дата должна быть в будущем.") + 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 - db.set_user_expiration(username, expiration, "Неограниченно") - await message.reply(f"Подписка для {username} продлена до {expiration.strftime('%d-%m-%Y')}.") - except: - await message.reply("Введите дату в формате ДД-ММ-ГГГГ (например, 31-12-2025).") + 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 + ) return - 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( + f"Промокод {code} добавлен: скидка {discount}%, действует {days_valid} дней, " + f"макс. использований: {max_uses or 'неограничено'}, период подписки: {subscription_period or 'нет'}" + ) + else: + await message.reply("Промокод уже существует.") + except: + await message.reply( + "Формат: <код> <скидка%> <дней_действия> <макс_использований|none> <период_подписки|none>\n" + "Пример: PROMO1 10 30 none 1_month" + ) + 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 + ) + 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 цифр).") + 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 + ) + 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 + ) @dp.callback_query_handler(lambda c: c.data == "settings") async def settings_menu_callback(callback_query: types.CallbackQuery): @@ -419,23 +461,13 @@ async def settings_menu_callback(callback_query: types.CallbackQuery): if user_id not in admins: await callback_query.answer("Нет прав.", show_alert=True) return - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Настройки:", - reply_markup=get_settings_menu() + reply_markup=get_settings_menu(), + 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() @dp.callback_query_handler(lambda c: c.data == "yoomoney_settings") @@ -444,23 +476,13 @@ async def yoomoney_settings_callback(callback_query: types.CallbackQuery): if user_id not in admins: await callback_query.answer("Нет прав.", show_alert=True) return - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Настройки YooMoney:", - reply_markup=get_yoomoney_settings_menu() + reply_markup=get_yoomoney_settings_menu(), + 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() @dp.callback_query_handler(lambda c: c.data == "set_yoomoney_token") @@ -469,23 +491,14 @@ async def set_yoomoney_token_callback(callback_query: types.CallbackQuery): if user_id not in admins: await callback_query.answer("Нет прав.", show_alert=True) return - 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( + 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")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="yoomoney_settings")), + old_message_id=callback_query.message.message_id, + state='waiting_for_yoomoney_token' ) - user_main_messages[user_id] = { - 'chat_id': sent_message.chat.id, - 'message_id': sent_message.message_id, - 'state': 'waiting_for_yoomoney_token' - } await callback_query.answer() @dp.callback_query_handler(lambda c: c.data == "set_yoomoney_wallet") @@ -494,23 +507,14 @@ async def set_yoomoney_wallet_callback(callback_query: types.CallbackQuery): if user_id not in admins: await callback_query.answer("Нет прав.", show_alert=True) return - 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( + 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")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="yoomoney_settings")), + old_message_id=callback_query.message.message_id, + state='waiting_for_yoomoney_wallet' ) - user_main_messages[user_id] = { - 'chat_id': sent_message.chat.id, - 'message_id': sent_message.message_id, - 'state': 'waiting_for_yoomoney_wallet' - } await callback_query.answer() @dp.callback_query_handler(lambda c: c.data == "pricing_settings") @@ -519,23 +523,13 @@ async def pricing_settings_callback(callback_query: types.CallbackQuery): if user_id not in admins: await callback_query.answer("Нет прав.", show_alert=True) return - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Настройки цен:", - reply_markup=get_pricing_settings_menu() + reply_markup=get_pricing_settings_menu(), + 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() @dp.callback_query_handler(lambda c: c.data.startswith('set_price_')) @@ -545,23 +539,14 @@ async def set_price_callback(callback_query: types.CallbackQuery): await callback_query.answer("Нет прав.", show_alert=True) return period = callback_query.data.split('set_price_')[1] - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text=f"Введите новую цену для {period.replace('_', ' ')} в рублях (например, 1000.00):", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="pricing_settings")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="pricing_settings")), + old_message_id=callback_query.message.message_id, + state=f'waiting_for_price_{period}' ) - 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_handler(lambda c: c.data == "add_user") @@ -570,23 +555,14 @@ async def prompt_for_user_name(callback_query: types.CallbackQuery): if user_id not in admins and user_id not in moderators: await callback_query.answer("Нет прав.", show_alert=True) return - try: - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Введите имя пользователя:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")), + old_message_id=callback_query.message.message_id, + state='waiting_for_user_name' ) - 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_handler(lambda c: c.data == "add_admin") @@ -595,23 +571,14 @@ async def prompt_for_admin_id(callback_query: types.CallbackQuery): if user_id not in admins: await callback_query.answer("Нет прав.", show_alert=True) return - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Введите Telegram ID нового админа:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home")), + old_message_id=callback_query.message.message_id, + state='waiting_for_admin_id' ) - 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_handler(lambda c: c.data.startswith('client_')) @@ -655,41 +622,27 @@ async def client_selected_callback(callback_query: types.CallbackQuery): InlineKeyboardButton("🏠 Домой", callback_data="home") ) - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text=text, - parse_mode="Markdown", - reply_markup=keyboard + reply_markup=keyboard, + 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() except Exception as e: logger.error(f"Ошибка в client_selected_callback: {str(e)}") - sent_message = await bot.send_message( + await update_user_message( + user_id=user_id, 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_handler(lambda c: c.data == "list_users") @@ -702,25 +655,15 @@ async def list_users_callback(callback_query: types.CallbackQuery): try: clients = db.get_client_list() if not clients: - 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( + await update_user_message( + user_id=user_id, 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 @@ -735,39 +678,26 @@ async def list_users_callback(callback_query: types.CallbackQuery): keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home")) - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Выберите пользователя:", - reply_markup=keyboard + reply_markup=keyboard, + 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() except Exception as e: logger.error(f"Ошибка в list_users_callback: {str(e)}") - sent_message = await bot.send_message( + await update_user_message( + user_id=user_id, 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_handler(lambda c: c.data == "list_admins") @@ -780,23 +710,13 @@ 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")) - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text=f"Администраторы:\n" + "\n".join(f"- {admin_id}" for admin_id in admins), - reply_markup=keyboard + reply_markup=keyboard, + 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() @dp.callback_query_handler(lambda c: c.data.startswith('remove_admin_')) @@ -823,7 +743,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('users', username), ignore_errors=True) + shutil.rmtree(os.path.join(BASE_DIR, 'users', username), ignore_errors=True) db.remove_user_expiration(username) db.set_user_telegram_id(username, None) logger.info(f"Пользователь {username} успешно удалён.") @@ -834,24 +754,13 @@ async def client_delete_callback(callback_query: types.CallbackQuery): except Exception as e: logger.error(f"Ошибка при удалении пользователя {username}: {str(e)}") text = f"Ошибка при удалении **{username}**: {str(e)}" - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text=text, - parse_mode="Markdown", - reply_markup=get_main_menu_markup(user_id) + reply_markup=get_main_menu_markup(user_id), + 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() @dp.callback_query_handler(lambda c: c.data.startswith('renew_user_')) @@ -861,23 +770,13 @@ async def renew_user_callback(callback_query: types.CallbackQuery): await callback_query.answer("Нет прав.", show_alert=True) return username = callback_query.data.split('renew_user_')[1] - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Выберите период продления или укажите дату:", - reply_markup=get_renewal_period_keyboard(username) + reply_markup=get_renewal_period_keyboard(username), + 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() @dp.callback_query_handler(lambda c: c.data.startswith('renew_period_')) @@ -891,84 +790,50 @@ 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': - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Введите дату продления в формате ДД-ММ-ГГГГ (например, 31-12-2025):", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("Отмена", callback_data="home")), + old_message_id=callback_query.message.message_id, + state=f'waiting_for_custom_date_{username}' ) - 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}.") - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text=text, - parse_mode="Markdown", - reply_markup=get_main_menu_markup(user_id) + reply_markup=get_main_menu_markup(user_id), + 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() except Exception as e: text = f"Ошибка при продлении: {str(e)}" logger.error(f"Ошибка при продлении подписки для {username}: {str(e)}") - sent_message = await bot.send_message( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text=text, - parse_mode="Markdown", - reply_markup=get_main_menu_markup(user_id) + reply_markup=get_main_menu_markup(user_id), + 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() @dp.callback_query_handler(lambda c: c.data == "home") async def return_home(callback_query: types.CallbackQuery): user_id = callback_query.from_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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id) + reply_markup=get_main_menu_markup(user_id), + 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() @dp.callback_query_handler(lambda c: c.data == "get_config") @@ -986,23 +851,13 @@ 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")) - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Выберите пользователя:", - reply_markup=keyboard + reply_markup=keyboard, + 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() @dp.callback_query_handler(lambda c: c.data.startswith('send_config_')) @@ -1012,7 +867,7 @@ async def send_user_config(callback_query: types.CallbackQuery): await callback_query.answer("Нет прав.", show_alert=True) return username = callback_query.data.split('send_config_')[1] - conf_path = os.path.join('users', username, f'{username}.conf') + 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"Конфигурация для {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```" @@ -1021,16 +876,13 @@ 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") - sent_message = await bot.send_message( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id) + reply_markup=get_main_menu_markup(user_id), + 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() @dp.callback_query_handler(lambda c: c.data == "create_backup") @@ -1039,52 +891,46 @@ async def create_backup_callback(callback_query: types.CallbackQuery): if user_id not in admins: await callback_query.answer("Нет прав.", show_alert=True) return - 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( + 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, chat_id=callback_query.message.chat.id, text="Выберите действие:", - reply_markup=get_main_menu_markup(user_id) + reply_markup=get_main_menu_markup(user_id), + 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() @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 - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Меню покупки ключа:", - reply_markup=get_buy_key_menu(user_id) + reply_markup=get_buy_key_menu(user_id), + 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() @dp.callback_query_handler(lambda c: c.data == "select_subscription_period") @@ -1100,23 +946,13 @@ async def select_period_callback(callback_query: types.CallbackQuery): )) keyboard.add(InlineKeyboardButton("⬅️ Назад", callback_data="buy_key")) keyboard.add(InlineKeyboardButton("🏠 Домой", callback_data="home")) - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Выберите период подписки:", - reply_markup=keyboard + reply_markup=keyboard, + 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() @dp.callback_query_handler(lambda c: c.data.startswith('confirm_period_')) @@ -1128,71 +964,57 @@ async def confirm_period_callback(callback_query: types.CallbackQuery): final_price = price * (1 - discount / 100) if not yoomoney_client or not yoomoney_wallet: - await callback_query.answer("Платежи недоступны. Обратитесь к администратору.", show_alert=True) + await callback_query.answer("Платежи недоступны. Настройки YooMoney не установлены.", show_alert=True) return - 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") - ) try: - await bot.delete_message( - chat_id=callback_query.message.chat.id, - message_id=callback_query.message.message_id + 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 ) - except: - pass - sent_message = await bot.send_message( - chat_id=callback_query.message.chat.id, - text=f"Подписка на {period.replace('_', ' ')}: ₽{final_price:.2f} (скидка {discount}%)\nОплатите по ссылке:", - reply_markup=keyboard - ) - user_main_messages[user_id] = { - 'chat_id': sent_message.chat.id, - 'message_id': sent_message.message_id, - 'state': None, - 'pending_payment': { - 'payment_id': payment_id, - 'period': period, - 'username': None - } - } - await callback_query.answer() + 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, + 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 + } + ) + 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_handler(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 - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Введите промокод для скидки:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="buy_key")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("⬅️ Назад", callback_data="buy_key")), + old_message_id=callback_query.message.message_id, + state='waiting_for_promocode_for_buy' ) - user_main_messages[user_id] = { - 'chat_id': sent_message.chat.id, - 'message_id': sent_message.message_id, - 'state': 'waiting_for_promocode_for_buy' - } await callback_query.answer() @dp.callback_query_handler(lambda c: c.data == "reset_promocode") @@ -1200,45 +1022,26 @@ 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'] - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Скидка сброшена. Меню покупки ключа:", - reply_markup=get_buy_key_menu(user_id) + reply_markup=get_buy_key_menu(user_id), + 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() @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 - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Введите промокод для получения ключа:", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")), + old_message_id=callback_query.message.message_id, + state='waiting_for_promocode' ) - 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_handler(lambda c: c.data == "manage_promocodes") @@ -1257,23 +1060,13 @@ async def manage_promocodes_callback(callback_query: types.CallbackQuery): InlineKeyboardButton("🗑️ Удалить промокод", callback_data="delete_promocode"), InlineKeyboardButton("🏠 Домой", callback_data="home") ) - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text=text, - reply_markup=keyboard + reply_markup=keyboard, + 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() @dp.callback_query_handler(lambda c: c.data == "add_promocode") @@ -1282,23 +1075,14 @@ async def add_promocode_callback(callback_query: types.CallbackQuery): if user_id not in admins: await callback_query.answer("Нет прав.", show_alert=True) return - 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( + await update_user_message( + user_id=user_id, 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")) + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton("🏠 Домой", callback_data="home")), + old_message_id=callback_query.message.message_id, + state='waiting_for_new_promocode' ) - 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_handler(lambda c: c.data == "delete_promocode") @@ -1312,23 +1096,13 @@ 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")) - 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( + await update_user_message( + user_id=user_id, chat_id=callback_query.message.chat.id, text="Выберите промокод для удаления:", - reply_markup=keyboard + reply_markup=keyboard, + 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() @dp.callback_query_handler(lambda c: c.data.startswith('remove_promocode_')) @@ -1345,22 +1119,25 @@ async def remove_promocode_callback(callback_query: types.CallbackQuery): await manage_promocodes_callback(callback_query) async def check_pending_payments(): - 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)}") + 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))