diff --git a/bot.py b/bot.py index 1d5f2ec8..cdc9dc8b 100644 --- a/bot.py +++ b/bot.py @@ -20,6 +20,7 @@ dp.include_router(stats.router) dp.include_router(expiry.router) dp.include_router(balance.router) dp.include_router(key_management_router) +dp.include_router(pay.router) diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 9876cae9..1c9d0b6e 100644 Binary files a/handlers/__pycache__/keys.cpython-310.pyc and b/handlers/__pycache__/keys.cpython-310.pyc differ diff --git a/handlers/__pycache__/pay.cpython-310.pyc b/handlers/__pycache__/pay.cpython-310.pyc index f9a2f72c..544e555d 100644 Binary files a/handlers/__pycache__/pay.cpython-310.pyc and b/handlers/__pycache__/pay.cpython-310.pyc differ diff --git a/handlers/__pycache__/profile.cpython-310.pyc b/handlers/__pycache__/profile.cpython-310.pyc index 44a606fa..b038020a 100644 Binary files a/handlers/__pycache__/profile.cpython-310.pyc and b/handlers/__pycache__/profile.cpython-310.pyc differ diff --git a/handlers/__pycache__/start.cpython-310.pyc b/handlers/__pycache__/start.cpython-310.pyc index 963cd457..395da8ca 100644 Binary files a/handlers/__pycache__/start.cpython-310.pyc and b/handlers/__pycache__/start.cpython-310.pyc differ diff --git a/handlers/keys.py b/handlers/keys.py index 043b4fc7..331f0764 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -2,6 +2,8 @@ from aiogram import types, Router import aiosqlite from config import DATABASE_PATH from bot import bot +from datetime import datetime + router = Router() @router.callback_query(lambda c: c.data == 'view_keys') @@ -11,7 +13,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): try: async with aiosqlite.connect(DATABASE_PATH) as db: async with db.execute(''' - SELECT k.key + SELECT k.key, c.expiry_time FROM keys k JOIN connections c ON k.client_id = c.client_id WHERE c.tg_id = ? @@ -20,7 +22,9 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): if record: key = record[0] - response_message = f"Ваш ключ:\n
{key}
" + expiry_time = record[1] + expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S") + response_message = f"Ваш ключ:\n
{key}
\nДата окончания: {expiry_date}" else: response_message = "У вас нет ключей." diff --git a/handlers/pay.py b/handlers/pay.py index e69de29b..9c06aeea 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -0,0 +1,116 @@ +from aiogram import types, Router +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +import aiosqlite +from config import DATABASE_PATH, ADMIN_ID +from database import get_balance, update_balance +from bot import bot + +router = Router() + +class ReplenishBalanceState(StatesGroup): + choosing_transfer_method = State() + waiting_for_admin_confirmation = State() + +@router.callback_query(lambda c: c.data == 'replenish_balance') +async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext): + await state.set_state(ReplenishBalanceState.choosing_transfer_method) + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')], + [InlineKeyboardButton(text='По СБП', callback_data='transfer_method_sbp')] + ]) + await callback_query.message.answer("Выберите метод перевода:", reply_markup=keyboard) + await callback_query.answer() + +@router.callback_query(lambda c: c.data.startswith('transfer_method_')) +async def process_transfer_method_selection(callback_query: types.CallbackQuery, state: FSMContext): + data = callback_query.data.split('_', 2) + + if len(data) != 3: + await callback_query.answer("Неверные данные для выбора метода перевода.") + return + + transfer_method = data[2] # 'requisites' или 'sbp' + + if transfer_method == 'requisites': + message = ( + "Реквизиты для пополнения:\n" + f'\n' + "Банк: Пример Банк\n" + "Расчетный счет: 12345678901234567890\n" + "Получатель: ИП Иванов Иван Иванович" + f'\n' + "после перевода отправьте чек и ждите подтверждения" + ) + elif transfer_method == 'sbp': + message = ( + "СБП для пополнения:\n" + f'\n' + "Номер банка: 1234567890\n" + "ФИО: Иванов Иван Иванович\n" + f'\n' + "после перевода отправьте чек и ждите подтверждения" + ) + else: + await callback_query.answer("Неверный метод перевода.") + return + + await callback_query.message.answer(message) + await callback_query.answer() + +@router.callback_query(lambda c: c.data.startswith('admin_')) +async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): + if callback_query.from_user.id != ADMIN_ID: + await callback_query.answer("Вы не являетесь администратором.") + return + + data = callback_query.data.split('_', 4) + + if len(data) < 4: + await callback_query.answer("Неверные данные для обработки запроса.") + return + + action = data[1] # 'confirm' или 'decline' + user_id_str = data[3] + + try: + user_id = int(user_id_str) # Преобразование user_id в целое число + + if action == 'confirm': + + amount_str = data[4].split('_')[0] + + # Убедимся, что amount_str действительно является числом + if amount_str.isdigit(): + amount = int(amount_str) # Преобразование amount в целое число + else: + await callback_query.answer("Неверная сумма для пополнения. Проверьте формат данных.") + print(f"Ошибка преобразования суммы: amount_str='{amount_str}'") # Отладочное сообщение в консоль + return + + async with aiosqlite.connect(DATABASE_PATH) as db: + # Обновляем баланс пользователя + await update_balance(user_id, amount) + await db.commit() + + balance = await get_balance(user_id) + await callback_query.message.answer(f"Баланс пользователя успешно пополнен на {amount} единиц.\nТекущий баланс: {balance}") + + # Сообщаем пользователю о пополнении + await bot.send_message(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.") + + elif action == 'decline': + await callback_query.message.answer("Пополнение баланса отклонено.") + + # Сообщаем пользователю об отклонении + await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.") + + except Exception as e: + # Отладочное сообщение + await callback_query.message.answer(f"Ошибка при пополнении баланса: {e}") + print(f"Ошибка при пополнении баланса: {e}") # Отладочное сообщение в консоль + + # Очистка состояния + await state.clear() + await callback_query.answer() diff --git a/handlers/profile.py b/handlers/profile.py index d51e4d96..03eefefd 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -1,12 +1,11 @@ -from aiogram import Bot, Dispatcher, Router, types -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery +from aiogram import types, Router +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from datetime import datetime import aiosqlite -from config import DATABASE_PATH, ADMIN_ID -from database import get_balance, update_balance, has_active_key -from bot import bot +from config import DATABASE_PATH +from database import get_balance, has_active_key router = Router() @@ -14,14 +13,15 @@ class ReplenishBalanceState(StatesGroup): choosing_transfer_method = State() waiting_for_admin_confirmation = State() -@router.callback_query(lambda c: c.data == 'view_profile') async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id username = callback_query.from_user.username try: - # Получаем активный ключ + # Проверяем наличие активного ключа email = await has_active_key(tg_id) + expiry_date = "Нет активного ключа" + if email: async with aiosqlite.connect(DATABASE_PATH) as db: async with db.execute( @@ -29,136 +29,32 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta (tg_id, int(datetime.utcnow().timestamp() * 1000)) ) as cursor: record = await cursor.fetchone() - expiry_date = "Неизвестно" if record is None else datetime.utcfromtimestamp(record[0] / 1000).strftime("%Y-%m-%d %H:%M:%S") - - balance = await get_balance(tg_id) - profile_message = ( - f"Профиль клиента:\n" - f"Никнейм: @{username}\n" - f"Email: {email}\n" - f"Дата окончания ключа: {expiry_date}\n" - f"Баланс: {balance}\n" - ) - - button_view_keys = InlineKeyboardButton(text='Мои ключи', callback_data='view_keys') - button_replenish_balance = InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance') - keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_view_keys], [button_replenish_balance]]) - - profile_message += "Вы можете просмотреть ваши ключи или пополнить баланс ниже:" - else: - profile_message = "У вас нет активных ключей." - keyboard = None + if record: + expiry_date = datetime.utcfromtimestamp(record[0] / 1000).strftime("%Y-%m-%d %H:%M:%S") + + balance = await get_balance(tg_id) + profile_message = ( + f"Профиль \n\n" + f"ID: @{tg_id}\n" + f"Ключ: {email if email else 'Нет активного ключа'}\n" + f"Баланс: {balance} RUB\n" + ) + + # Кнопки для действий в профиле + button_view_keys = InlineKeyboardButton(text='Мои ключи', callback_data='view_keys') + button_replenish_balance = InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance') + keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_view_keys], [button_replenish_balance]]) + + profile_message += "\nВы можете просмотреть ваши ключи или пополнить баланс ниже:" except Exception as e: profile_message = f"Ошибка при получении данных профиля: {e}" keyboard = None - # Отправляем сообщение с клавиатурой внизу + # Отправляем сообщение с клавиатурой await callback_query.message.reply(profile_message, reply_markup=keyboard) await callback_query.answer() - -@router.callback_query(lambda c: c.data == 'replenish_balance') -async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext): - await state.set_state(ReplenishBalanceState.choosing_transfer_method) - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')], - [InlineKeyboardButton(text='По СБП', callback_data='transfer_method_sbp')] - ]) - await callback_query.message.answer("Выберите метод перевода:", reply_markup=keyboard) - await callback_query.answer() - -@router.callback_query(lambda c: c.data.startswith('transfer_method_')) -async def process_transfer_method_selection(callback_query: types.CallbackQuery, state: FSMContext): - data = callback_query.data.split('_', 2) - - if len(data) != 3: - await callback_query.answer("Неверные данные для выбора метода перевода.") - return - - transfer_method = data[2] # 'requisites' или 'sbp' - amount = 100 # Можно заменить на 300, если нужно - user_id = callback_query.from_user.id - - # Сохраняем данные в состояние - await state.update_data(transfer_method=transfer_method, amount=amount, user_id=user_id) - - # Выводим состояние для отладки - state_data = await state.get_data() - print("Сохраненные данные состояния:", state_data) - - # Отправляем запрос на подтверждение администратору - admin_message = ( - f"Запрос на пополнение баланса:\n" - f"Метод перевода: {transfer_method.replace('requisites', 'По реквизитам').replace('sbp', 'По СБП')}\n" - f"Сумма: {amount} единиц\n" - f"Пользователь: {user_id}\n\n" - f"Подтвердите или отклоните пополнение." - ) - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Подтвердить', callback_data=f'admin_confirm_replenish_{user_id}_{amount}_{transfer_method}')], - [InlineKeyboardButton(text='Отклонить', callback_data=f'admin_decline_replenish_{user_id}')] - ]) - - # Отправляем сообщение администратору - await callback_query.message.answer("Запрос на пополнение отправлен администратору.") - await bot.send_message(ADMIN_ID, admin_message, reply_markup=keyboard) - - await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation) - await callback_query.answer() - -@router.callback_query(lambda c: c.data.startswith('admin_')) -async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): - if callback_query.from_user.id != ADMIN_ID: - await callback_query.answer("Вы не являетесь администратором.") - return - - data = callback_query.data.split('_', 4) - - if len(data) < 4: - await callback_query.answer("Неверные данные для обработки запроса.") - return - - action = data[1] # 'confirm' или 'decline' - user_id_str = data[3] - - try: - user_id = int(user_id_str) # Преобразование user_id в целое число - - if action == 'confirm': - - amount_str = data[4].split('_')[0] - - # Убедимся, что amount_str действительно является числом - if amount_str.isdigit(): - amount = int(amount_str) # Преобразование amount в целое число - else: - await callback_query.answer("Неверная сумма для пополнения. Проверьте формат данных.") - print(f"Ошибка преобразования суммы: amount_str='{amount_str}'") # Отладочное сообщение в консоль - return - - async with aiosqlite.connect(DATABASE_PATH) as db: - # Обновляем баланс пользователя - await update_balance(user_id, amount) - await db.commit() - - balance = await get_balance(user_id) - await callback_query.message.answer(f"Баланс пользователя успешно пополнен на {amount} единиц.\nТекущий баланс: {balance}") - - # Сообщаем пользователю о пополнении - await bot.send_message(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.") - - elif action == 'decline': - await callback_query.message.answer("Пополнение баланса отклонено.") - - # Сообщаем пользователю об отклонении - await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.") - - except Exception as e: - # Отладочное сообщение - await callback_query.message.answer(f"Ошибка при пополнении баланса: {e}") - print(f"Ошибка при пополнении баланса: {e}") # Отладочное сообщение в консоль - - # Очистка состояния - await state.clear() - await callback_query.answer() +@router.callback_query(lambda c: c.data == 'view_profile') +async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext): + await process_callback_view_profile(callback_query, state) diff --git a/handlers/start.py b/handlers/start.py index f2b3c05a..ddac6a2e 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -4,30 +4,39 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeybo router = Router() -@router.message(Command("start")) -async def start_command(message: types.Message): - welcome_text = "Добро пожаловать! Вы можете создать ключ для подключения VPN, просмотреть статистику использования, узнать дату окончания ключа или просмотреть ваш профиль." +async def start_command(message: types.Message, callback_data: str = None): + welcome_text = ( + "Добро пожаловать! Вы можете создать ключ для подключения VPN, " + "просмотреть статистику использования, узнать дату окончания ключа или " + "просмотреть ваш профиль." + ) + # Создаем кнопки для команд button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key') - button_view_stats = InlineKeyboardButton(text='Посмотреть статистику', callback_data='view_stats') - button_view_expiry = InlineKeyboardButton(text='Дата окончания ключа', callback_data='view_expiry') +# button_view_stats = InlineKeyboardButton(text='Посмотреть статистику', callback_data='view_stats') +# button_view_expiry = InlineKeyboardButton(text='Дата окончания ключа', callback_data='view_expiry') button_view_profile = InlineKeyboardButton(text='Мой профиль', callback_data='view_profile') - - # Основное меню - keyboard = InlineKeyboardMarkup(inline_keyboard=[ + + # Создаем клавиатуру с кнопками + inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[ [button_create_key], - [button_view_stats], - [button_view_expiry], +# [button_view_stats], +# [button_view_expiry], [button_view_profile] ]) - - # Отправка приветственного сообщения - await message.reply(welcome_text, reply_markup=keyboard) - # Кнопка "В начало" для возвращения - main_menu_keyboard = ReplyKeyboardMarkup( - keyboard=[[KeyboardButton(text="В начало")]], + # Создаем кнопку для возврата в начало + reply_keyboard = ReplyKeyboardMarkup( + keyboard=[ + [KeyboardButton(text="В начало")], + [KeyboardButton(text="Мой профиль")] + ], resize_keyboard=True ) - - await message.answer("Чтобы вернуться в начало, нажмите кнопку ниже:", reply_markup=main_menu_keyboard) + + # Отправляем сообщение с кнопками + await message.reply(welcome_text, reply_markup=inline_keyboard) + await message.answer( + "", + reply_markup=reply_keyboard + ) \ No newline at end of file diff --git a/key_management.py b/key_management.py index 3709931b..5ba71195 100644 --- a/key_management.py +++ b/key_management.py @@ -15,9 +15,11 @@ import re from bot import dp from handlers.start import start_command from bot import bot +from handlers.profile import process_callback_view_profile router = Router() + def escape_markdown(text: str) -> str: """ Экранирование специальных символов для Markdown. @@ -39,10 +41,23 @@ async def process_callback_create_key(callback_query: types.CallbackQuery, state await state.set_state(Form.waiting_for_key_name) await callback_query.answer() + @dp.message() async def handle_text(message: types.Message, state: FSMContext): current_state = await state.get_state() + if message.text == "Мой профиль": + # Создайте фейковый объект CallbackQuery для вызова функции + callback_query = types.CallbackQuery( + id="1", # Используйте уникальный идентификатор + from_user=message.from_user, + chat_instance='', + data='view_profile', + message=message + ) + await process_callback_view_profile(callback_query, state) + return + if message.text == "/start": await start_command(message) return