правки и доработки интерфейса

This commit is contained in:
Vlad
2024-09-28 03:26:28 +03:00
parent 19af987979
commit 7dd00a2f24
8 changed files with 152 additions and 92 deletions
+11
View File
@@ -35,6 +35,14 @@ async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0):
''', tg_id, balance, trial) ''', tg_id, balance, trial)
await conn.close() await conn.close()
async def check_connection_exists(tg_id: int):
conn = await asyncpg.connect(DATABASE_URL)
exists = await conn.fetchval('''
SELECT EXISTS(SELECT 1 FROM connections WHERE tg_id = $1)
''', tg_id)
await conn.close()
return exists
async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str): async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str):
conn = await asyncpg.connect(DATABASE_URL) conn = await asyncpg.connect(DATABASE_URL)
await conn.execute(''' await conn.execute('''
@@ -85,3 +93,6 @@ async def get_key_count(tg_id: int) -> int:
count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE tg_id = $1', tg_id) count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE tg_id = $1', tg_id)
await conn.close() await conn.close()
return count if count is not None else 0 return count if count is not None else 0
async def get_all_users(conn):
return await conn.fetch('SELECT tg_id FROM connections')
Binary file not shown.
Binary file not shown.
Binary file not shown.
+71 -20
View File
@@ -11,6 +11,17 @@ from database import get_balance, update_balance
router = Router() router = Router()
from datetime import datetime, timedelta
import asyncpg
from aiogram import Router, types
from auth import login_with_credentials
from bot import bot
from client import delete_client, extend_client_key
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
from database import get_balance, update_balance
router = Router()
# Обработка запроса на просмотр ключей # Обработка запроса на просмотр ключей
@router.callback_query(lambda c: c.data == 'view_keys') @router.callback_query(lambda c: c.data == 'view_keys')
async def process_callback_view_keys(callback_query: types.CallbackQuery): async def process_callback_view_keys(callback_query: types.CallbackQuery):
@@ -30,26 +41,26 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
key_name = record['email'] key_name = record['email']
client_id = record['client_id'] client_id = record['client_id']
# Заменяем подчеркивание на вертикальную черту в callback_data # Заменяем подчеркивание на вертикальную черту в callback_data
button = types.InlineKeyboardButton(text=key_name, callback_data=f'view_key|{key_name}|{client_id}') button = types.InlineKeyboardButton(text=f"🔑 {key_name}", callback_data=f'view_key|{key_name}|{client_id}')
buttons.append([button]) buttons.append([button])
# Создаем клавиатуру с кнопками # Создаем клавиатуру с кнопками
inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons) inline_keyboard = types.InlineKeyboardMarkup(inline_keyboard=buttons)
response_message = "Выберите ключ для просмотра информации:" response_message = "<b>Выберите ключ для просмотра информации:</b>"
# Редактируем сообщение с клавиатурой # Редактируем сообщение с клавиатурой
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=inline_keyboard) await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=inline_keyboard, parse_mode="HTML")
else: else:
# Если нет ключей, добавляем кнопку "Создать ключ" и "Назад" # Если нет ключей, добавляем кнопку "Создать ключ" и "Назад"
response_message = "У вас нет ключей." response_message = "<b>У вас нет ключей.</b>"
# Кнопка "Создать ключ" # Кнопка "Создать ключ"
create_key_button = types.InlineKeyboardButton(text='Создать ключ', callback_data='create_key') create_key_button = types.InlineKeyboardButton(text=' Создать ключ', callback_data='create_key')
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_profile') # Измените на правильное значение для кнопки "Назад" back_button = types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile') # Измените на правильное значение для кнопки "Назад"
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button], [back_button]]) keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[create_key_button], [back_button]])
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard) await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML")
finally: finally:
await conn.close() await conn.close()
@@ -82,27 +93,27 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
time_left = expiry_date - current_date time_left = expiry_date - current_date
if time_left.total_seconds() <= 0: if time_left.total_seconds() <= 0:
days_left_message = "Ключ истек." days_left_message = "<b>Ключ истек.</b>"
elif time_left.days > 0: elif time_left.days > 0:
days_left_message = f"Осталось дней: {time_left.days}" days_left_message = f"Осталось дней: <b>{time_left.days}</b>"
else: else:
hours_left = time_left.seconds // 3600 hours_left = time_left.seconds // 3600
days_left_message = f"Осталось часов: {hours_left}" days_left_message = f"Осталось часов: <b>{hours_left}</b>"
response_message = (f"Ваш ключ:\n<pre>{key}</pre>\n" response_message = (f"🔑 <b>Ваш ключ:</b>\n<pre>{key}</pre>\n"
f"Дата окончания: <b>{expiry_date.strftime('%Y-%m-%d %H:%M:%S')}</b>\n" f"📅 <b>Дата окончания:</b> {expiry_date.strftime('%Y-%m-%d %H:%M:%S')}\n"
f"{days_left_message}") f"{days_left_message}")
# Кнопки для продления, инструкций и удаления # Кнопки для продления, инструкций и удаления
renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data=f'renew_key|{client_id}') renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data=f'renew_key|{client_id}')
instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions') instructions_button = types.InlineKeyboardButton(text='📘 Инструкции', callback_data='instructions')
delete_button = types.InlineKeyboardButton(text='Удалить ключ', callback_data=f'delete_key|{client_id}') delete_button = types.InlineKeyboardButton(text='Удалить ключ', callback_data=f'delete_key|{client_id}')
back_button = types.InlineKeyboardButton(text='Назад в профиль', callback_data='view_profile') back_button = types.InlineKeyboardButton(text='🔙 Назад в профиль', callback_data='view_profile')
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [delete_button], [back_button]]) keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[renew_button], [instructions_button], [delete_button], [back_button]])
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML") await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML")
else: else:
await bot.edit_message_text("Информация о ключе не найдена.", chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML") await bot.edit_message_text("<b>Информация о ключе не найдена.</b>", chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML")
finally: finally:
await conn.close() await conn.close()
@@ -119,13 +130,53 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта
confirmation_keyboard = types.InlineKeyboardMarkup(inline_keyboard=[ confirmation_keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(text='Да, удалить', callback_data=f'confirm_delete|{client_id}')], [types.InlineKeyboardButton(text='Да, удалить', callback_data=f'confirm_delete|{client_id}')],
[types.InlineKeyboardButton(text='Нет, отменить', callback_data='view_keys')] [types.InlineKeyboardButton(text='Нет, отменить', callback_data='view_keys')]
]) ])
await bot.edit_message_text("Вы уверены, что хотите удалить ключ?", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=confirmation_keyboard) await bot.edit_message_text("<b>Вы уверены, что хотите удалить ключ?</b>", chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=confirmation_keyboard, parse_mode="HTML")
await callback_query.answer() await callback_query.answer()
# Обработка выбора плана продления
@router.callback_query(lambda c: c.data.startswith('renew_key|'))
async def process_callback_renew_key(callback_query: types.CallbackQuery):
tg_id = callback_query.from_user.id
client_id = callback_query.data.split('|')[1] # Используем разделитель вертикальная черта
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
record = await conn.fetchrow('SELECT email, expiry_time FROM keys WHERE client_id = $1', client_id)
if record:
email = record['email']
expiry_time = record['expiry_time']
current_time = datetime.utcnow().timestamp() * 1000 # Получаем текущее время в миллисекундах
# Убираем проверку на истекший ключ
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(text='📅 1 месяц (100 руб.)', callback_data=f'renew_plan|1|{client_id}')],
[types.InlineKeyboardButton(text='📅 3 месяца (250 руб.)', callback_data=f'renew_plan|3|{client_id}')],
[types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile')]
])
balance = await get_balance(tg_id)
response_message = (f"<b>Выберите план продления:</b>\n"
f"💰 <b>Баланс:</b> {balance} руб.\n"
f"📅 <b>Текущая дата истечения ключа:</b> {datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}")
await bot.edit_message_text(response_message, chat_id=tg_id, message_id=callback_query.message.message_id, reply_markup=keyboard, parse_mode="HTML")
finally:
await conn.close()
except Exception as e:
await bot.edit_message_text(f"<b>Ошибка при выборе плана:</b> {e}", chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode="HTML")
await callback_query.answer()
async def handle_error(tg_id, callback_query, message):
await bot.edit_message_text(message, chat_id=tg_id, message_id=callback_query.message.message_id)
# Подтверждение удаления # Подтверждение удаления
@router.callback_query(lambda c: c.data.startswith('renew_key|')) @router.callback_query(lambda c: c.data.startswith('renew_key|'))
+21 -24
View File
@@ -2,12 +2,18 @@ import asyncpg
from datetime import datetime, timedelta from datetime import datetime, timedelta
from aiogram import Bot from aiogram import Bot
from aiogram import Router, types from aiogram import Router, types
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup # Импортируем необходимые классы
from bot import bot from bot import bot
from config import DATABASE_URL, ADMIN_ID
from aiogram.filters import Command from aiogram.filters import Command
from database import get_all_users
from aiogram.fsm.state import StatesGroup, State
from aiogram.fsm.context import FSMContext
router = Router() router = Router()
from config import DATABASE_URL class NotificationStates(StatesGroup):
waiting_for_notification_text = State()
async def notify_expiring_keys(bot: Bot): async def notify_expiring_keys(bot: Bot):
try: try:
@@ -25,8 +31,14 @@ async def notify_expiring_keys(bot: Bot):
email = record['email'] email = record['email']
expiry_time = record['expiry_time'] expiry_time = record['expiry_time']
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S') expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')
# Создаем клавиатуру с кнопкой "Пополнить баланс"
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')]
])
message = f"Ваш ключ <b>{email}</b> истечет <b>{expiry_date}</b>. Пожалуйста, продлите его." message = f"Ваш ключ <b>{email}</b> истечет <b>{expiry_date}</b>. Пожалуйста, продлите его."
await bot.send_message(chat_id=tg_id, text=message, parse_mode='HTML') await bot.send_message(chat_id=tg_id, text=message, parse_mode='HTML', reply_markup=keyboard)
# Получаем все истекшие ключи # Получаем все истекшие ключи
expired_records = await conn.fetch(''' expired_records = await conn.fetch('''
@@ -37,31 +49,16 @@ async def notify_expiring_keys(bot: Bot):
for record in expired_records: for record in expired_records:
tg_id = record['tg_id'] tg_id = record['tg_id']
email = record['email'] email = record['email']
# Создаем клавиатуру с кнопкой "Пополнить баланс"
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')]
])
message = f"Ваш ключ <b>{email}</b> уже истек. Пожалуйста, продлите его." message = f"Ваш ключ <b>{email}</b> уже истек. Пожалуйста, продлите его."
await bot.send_message(chat_id=tg_id, text=message, parse_mode='HTML') await bot.send_message(chat_id=tg_id, text=message, parse_mode='HTML', reply_markup=keyboard)
finally: finally:
await conn.close() await conn.close()
except Exception as e: except Exception as e:
print(f"Ошибка при отправке уведомлений: {e}") print(f"Ошибка при отправке уведомлений: {e}")
@router.message(Command(commands=['notify']))
async def notify_command(message: types.Message):
# Запрашиваем у администратора ID пользователя и текст уведомления
await message.answer("Введите ID пользователя и текст уведомления в формате:\n<code>/notify user_id текст</code>", parse_mode="HTML")
# Обработка команды уведомления
@router.message()
async def process_notification(message: types.Message):
if message.text.startswith("/notify"):
try:
# Парсим команду
command, user_id, *text = message.text.split()
text = ' '.join(text)
# Отправляем сообщение пользователю
await bot.send_message(chat_id=user_id, text=text)
await message.answer(f"Уведомление успешно отправлено пользователю {user_id}.")
except Exception as e:
await message.answer(f"Ошибка при отправке уведомления: {e}")
+15 -27
View File
@@ -6,7 +6,7 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from bot import bot from bot import bot
from config import ADMIN_ID, DATABASE_URL from config import ADMIN_ID, DATABASE_URL
from database import get_balance, get_key_count, update_balance from database import get_balance, get_key_count, update_balance, add_connection, check_connection_exists
from handlers.profile import process_callback_view_profile from handlers.profile import process_callback_view_profile
router = Router() router = Router()
@@ -17,9 +17,6 @@ class ReplenishBalanceState(StatesGroup):
waiting_for_admin_confirmation = State() waiting_for_admin_confirmation = State()
async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key='last_message_id'): async def send_message_with_deletion(chat_id, text, reply_markup=None, state=None, message_key='last_message_id'):
"""
Отправляет новое сообщение и удаляет предыдущее сообщение (если оно существует), сохраненное в состоянии FSM.
"""
if state: if state:
try: try:
state_data = await state.get_data() state_data = await state.get_data()
@@ -42,10 +39,20 @@ async def send_message_with_deletion(chat_id, text, reply_markup=None, state=Non
async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext): async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id tg_id = callback_query.from_user.id
# Проверяем, есть ли у пользователя ключи
key_count = await get_key_count(tg_id)
# Если ключей нет, проверяем, существует ли запись с таким tg_id
if key_count == 0:
exists = await check_connection_exists(tg_id)
# Если записи нет, создаем нового клиента в базе данных
if not exists:
await add_connection(tg_id, balance=0.0, trial=0)
await state.set_state(ReplenishBalanceState.choosing_transfer_method) await state.set_state(ReplenishBalanceState.choosing_transfer_method)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')], [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')],
[InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] # Кнопка "Назад" [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')]
]) ])
await callback_query.message.edit_text( await callback_query.message.edit_text(
@@ -54,13 +61,10 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery
) )
await callback_query.answer() await callback_query.answer()
@router.callback_query(lambda c: c.data == 'back_to_profile') @router.callback_query(lambda c: c.data == 'back_to_profile')
async def back_to_profile_handler(callback_query: types.CallbackQuery, state: FSMContext): async def back_to_profile_handler(callback_query: types.CallbackQuery, state: FSMContext):
await process_callback_view_profile(callback_query, state) await process_callback_view_profile(callback_query, state)
@router.callback_query(lambda c: c.data.startswith('transfer_method_')) @router.callback_query(lambda c: c.data.startswith('transfer_method_'))
async def process_transfer_method_selection(callback_query: types.CallbackQuery, state: FSMContext): async def process_transfer_method_selection(callback_query: types.CallbackQuery, state: FSMContext):
data = callback_query.data.split('_', 2) data = callback_query.data.split('_', 2)
@@ -76,7 +80,7 @@ async def process_transfer_method_selection(callback_query: types.CallbackQuery,
[InlineKeyboardButton(text='100 RUB', callback_data='amount_100')], [InlineKeyboardButton(text='100 RUB', callback_data='amount_100')],
[InlineKeyboardButton(text='300 RUB', callback_data='amount_300')], [InlineKeyboardButton(text='300 RUB', callback_data='amount_300')],
[InlineKeyboardButton(text='500 RUB', callback_data='amount_500')], [InlineKeyboardButton(text='500 RUB', callback_data='amount_500')],
[InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')] # Кнопка "Назад" [InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')]
]) ])
await callback_query.message.edit_text( await callback_query.message.edit_text(
@@ -90,7 +94,6 @@ async def process_transfer_method_selection(callback_query: types.CallbackQuery,
await send_message_with_deletion(callback_query.from_user.id, "Неверный метод перевода.", state=state, message_key='transfer_method_error_message_id') await send_message_with_deletion(callback_query.from_user.id, "Неверный метод перевода.", state=state, message_key='transfer_method_error_message_id')
return return
@router.callback_query(lambda c: c.data.startswith('amount_')) @router.callback_query(lambda c: c.data.startswith('amount_'))
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext): async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
data = callback_query.data.split('_', 1) data = callback_query.data.split('_', 1)
@@ -102,18 +105,15 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
amount_str = data[1] amount_str = data[1]
amount = int(amount_str) amount = int(amount_str)
# Получаем ID сообщения с кнопками выбора суммы
state_data = await state.get_data() state_data = await state.get_data()
amount_selection_message_id = state_data.get('amount_selection_message_id') amount_selection_message_id = state_data.get('amount_selection_message_id')
# Удаляем сообщение с кнопками выбора суммы
if amount_selection_message_id: if amount_selection_message_id:
try: try:
await bot.delete_message(chat_id=callback_query.from_user.id, message_id=amount_selection_message_id) await bot.delete_message(chat_id=callback_query.from_user.id, message_id=amount_selection_message_id)
except Exception as e: except Exception as e:
print(f"Ошибка при удалении сообщения: {e}") print(f"Ошибка при удалении сообщения: {e}")
# Обновляем состояние и сохраняем выбранную сумму
await state.update_data(amount=amount) await state.update_data(amount=amount)
await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation) await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation)
@@ -126,10 +126,9 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
"После перевода отправьте чек и дождитесь подтверждения." "После перевода отправьте чек и дождитесь подтверждения."
) )
# Редактируем текущее сообщение
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=message, text=message,
reply_markup=None # Или добавьте клавиатуру, если нужно reply_markup=None
) )
admin_keyboard = InlineKeyboardMarkup(inline_keyboard=[ admin_keyboard = InlineKeyboardMarkup(inline_keyboard=[
@@ -147,8 +146,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
await send_message_with_deletion(ADMIN_ID, admin_message, reply_markup=admin_keyboard, state=state, message_key='admin_request_message_id') await send_message_with_deletion(ADMIN_ID, admin_message, reply_markup=admin_keyboard, state=state, message_key='admin_request_message_id')
await callback_query.answer() await callback_query.answer()
@router.callback_query(lambda c: c.data.startswith('admin_')) @router.callback_query(lambda c: c.data.startswith('admin_'))
async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext): async def process_admin_confirmation(callback_query: types.CallbackQuery, state: FSMContext):
if callback_query.from_user.id != ADMIN_ID: if callback_query.from_user.id != ADMIN_ID:
@@ -169,35 +166,26 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state:
try: try:
user_id = int(user_id_str) user_id = int(user_id_str)
# Получаем данные из состояния для удаления сообщения с реквизитами
state_data = await state.get_data() state_data = await state.get_data()
requisites_message_id = state_data.get('requisites_message_id') requisites_message_id = state_data.get('requisites_message_id')
if action == 'confirm': if action == 'confirm':
# Подключаемся к базе данных PostgreSQL
conn = await asyncpg.connect(DATABASE_URL) conn = await asyncpg.connect(DATABASE_URL)
try: try:
# Обновляем баланс пользователя
await update_balance(user_id, amount) await update_balance(user_id, amount)
# Получаем обновленный баланс
balance = await get_balance(user_id) balance = await get_balance(user_id)
# Создаем клавиатуру с кнопкой "Профиль"
profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile') profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile')
profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]]) profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]])
# Отправляем уведомление с кнопкой "Профиль" администратору
await send_message_with_deletion(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}", state=state, message_key='admin_confirm_message_id') await send_message_with_deletion(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}", state=state, message_key='admin_confirm_message_id')
# Отправляем уведомление пользователю с кнопкой "Профиль"
await bot.send_message( await bot.send_message(
user_id, user_id,
f"Ваш баланс был успешно пополнен на {amount} RUB.", f"Ваш баланс был успешно пополнен на {amount} RUB.",
reply_markup=profile_keyboard reply_markup=profile_keyboard
) )
# Удаляем сообщение с реквизитами
requisites_message_id = state_data.get('requisites_message_id') requisites_message_id = state_data.get('requisites_message_id')
if requisites_message_id: if requisites_message_id:
try: try:
@@ -218,4 +206,4 @@ async def process_admin_confirmation(callback_query: types.CallbackQuery, state:
finally: finally:
await state.clear() await state.clear()
await callback_query.answer() await callback_query.answer()
+34 -21
View File
@@ -9,7 +9,6 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.state import State, StatesGroup
from aiogram.types import (CallbackQuery, InlineKeyboardButton, from aiogram.types import (CallbackQuery, InlineKeyboardButton,
InlineKeyboardMarkup, Message) InlineKeyboardMarkup, Message)
from pytz import timezone
from auth import link, login_with_credentials from auth import link, login_with_credentials
from bot import bot, dp from bot import bot, dp
@@ -20,6 +19,7 @@ from database import (add_connection, get_balance, has_active_key, store_key,
update_balance) update_balance)
from handlers.profile import process_callback_view_profile from handlers.profile import process_callback_view_profile
from handlers.start import start_command from handlers.start import start_command
from handlers.notifications import send_notification
router = Router() router = Router()
@@ -46,16 +46,22 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC
if trial_status == 1: if trial_status == 1:
await callback_query.message.edit_text( await callback_query.message.edit_text(
"У вас уже был пробный ключ. Новый стоит 100 рублей и сразу на месяц. \n\n" "<b>⚠️ У вас уже был пробный ключ.</b>\n\n"
"Хотите продолжить?", "Новый ключ будет выдан на <b>один месяц</b> и стоит <b>100 рублей</b>.\n\n"
"<i>Хотите продолжить?</i>",
parse_mode="HTML", # Добавляем параметр parse_mode
reply_markup=InlineKeyboardMarkup(inline_keyboard=[ reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')], [InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')],
[InlineKeyboardButton(text='Назад', callback_data='cancel_create_key')] [InlineKeyboardButton(text='↩️ Назад', callback_data='cancel_create_key')]
]) ])
) )
await state.update_data(creating_new_key=True) await state.update_data(creating_new_key=True)
else: else:
await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:") await callback_query.message.edit_text(
"<b>🎉 Вам будет выдан пробный ключ на 24 часа!</b>\n\n"
"<i>Пожалуйста, введите название для вашего пробного ключа:</i>",
parse_mode="HTML"
)
await state.set_state(Form.waiting_for_key_name) await state.set_state(Form.waiting_for_key_name)
await callback_query.answer() await callback_query.answer()
@@ -77,7 +83,7 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
await state.clear() await state.clear()
return return
await callback_query.message.edit_text("🔑 Пожалуйста, выберите имя для вашего нового ключа:") await callback_query.message.edit_text("🔑 Пожалуйста, введите имя нового ключа:")
await state.set_state(Form.waiting_for_key_name) await state.set_state(Form.waiting_for_key_name)
await state.update_data(creating_new_key=True) await state.update_data(creating_new_key=True)
@@ -113,7 +119,6 @@ async def handle_text(message: Message, state: FSMContext):
async def handle_key_name_input(message: Message, state: FSMContext): async def handle_key_name_input(message: Message, state: FSMContext):
tg_id = message.from_user.id tg_id = message.from_user.id
key_name = sanitize_key_name(message.text) key_name = sanitize_key_name(message.text)
if not key_name: if not key_name:
@@ -140,7 +145,7 @@ async def handle_key_name_input(message: Message, state: FSMContext):
if trial_status == 0: if trial_status == 0:
# Создаем пробный ключ на 1 день # Создаем пробный ключ на 1 день
expiry_time = int((current_time + timedelta(days=1, hours=3)).timestamp() * 1000) expiry_time = current_time + timedelta(days=1, hours=3)
else: else:
# Проверяем баланс перед созданием нового ключа # Проверяем баланс перед созданием нового ключа
balance = await get_balance(tg_id) balance = await get_balance(tg_id)
@@ -152,26 +157,25 @@ async def handle_key_name_input(message: Message, state: FSMContext):
return return
await update_balance(tg_id, -100) await update_balance(tg_id, -100)
expiry_time = int((current_time + timedelta(days=30, hours=3)).timestamp() * 1000) expiry_time = current_time + timedelta(days=30, hours=3)
expiry_timestamp = int(expiry_time.timestamp() * 1000)
try: try:
# Попробуем добавить клиента # Попробуем добавить клиента
response = add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_time, enable=True, flow="xtls-rprx-vision") response = add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_timestamp, enable=True, flow="xtls-rprx-vision")
# Проверяем статус ответа от сервера
if not response.get("success", True): if not response.get("success", True):
error_msg = response.get("msg", "Неизвестная ошибка.") error_msg = response.get("msg", "Неизвестная ошибка.")
if "Duplicate email" in error_msg: if "Duplicate email" in error_msg:
await message.bot.send_message(tg_id, "❌ Этот email уже используется. Пожалуйста, выберите другое имя для ключа.") await message.bot.send_message(tg_id, "❌ Этот email уже используется. Пожалуйста, выберите другое имя для ключа.")
await state.set_state(Form.waiting_for_key_name) # Возвращаем пользователя к вводу имени ключа await state.set_state(Form.waiting_for_key_name)
return return
else: else:
raise Exception(error_msg) raise Exception(error_msg)
# Если добавление клиента прошло успешно, получаем ссылку
connection_link = link(session, client_id, email) connection_link = link(session, client_id, email)
# Проверка существующей записи
conn = await asyncpg.connect(DATABASE_URL) conn = await asyncpg.connect(DATABASE_URL)
try: try:
existing_connection = await conn.fetchrow('SELECT * FROM connections WHERE tg_id = $1', tg_id) existing_connection = await conn.fetchrow('SELECT * FROM connections WHERE tg_id = $1', tg_id)
@@ -180,11 +184,21 @@ async def handle_key_name_input(message: Message, state: FSMContext):
await conn.execute('UPDATE connections SET trial = 1 WHERE tg_id = $1', tg_id) await conn.execute('UPDATE connections SET trial = 1 WHERE tg_id = $1', tg_id)
else: else:
await add_connection(tg_id, 0, 1) await add_connection(tg_id, 0, 1)
finally: finally:
await conn.close() await conn.close()
await store_key(tg_id, client_id, email, expiry_time, connection_link) await store_key(tg_id, client_id, email, expiry_timestamp, connection_link)
# Рассчитываем оставшееся время до окончания действия ключа
remaining_time = expiry_time - current_time
days = remaining_time.days
hours, remainder = divmod(remaining_time.seconds, 3600)
minutes, _ = divmod(remainder, 60)
# Формируем сообщение с информацией о ключе
remaining_time_message = (
f"Оставшееся время ключа: {days} день"
)
keyboard = InlineKeyboardMarkup(inline_keyboard=[ keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='📖 Инструкции по использованию', callback_data='instructions')], [InlineKeyboardButton(text='📖 Инструкции по использованию', callback_data='instructions')],
@@ -193,8 +207,10 @@ async def handle_key_name_input(message: Message, state: FSMContext):
key_message = ( key_message = (
"✅ Ключ успешно создан:\n" "✅ Ключ успешно создан:\n"
f"<pre>{connection_link}</pre>" f"<pre>{connection_link}</pre>\n\n"
f"{remaining_time_message}"
) )
await message.bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard) await message.bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard)
except Exception as e: except Exception as e:
@@ -202,9 +218,6 @@ async def handle_key_name_input(message: Message, state: FSMContext):
await state.clear() await state.clear()
@dp.callback_query(F.data == 'instructions') @dp.callback_query(F.data == 'instructions')
async def handle_instructions(callback_query: CallbackQuery): async def handle_instructions(callback_query: CallbackQuery):
instructions_message = ( instructions_message = (