редизайн, кнопки назад

This commit is contained in:
Vlad
2024-09-20 03:48:41 +03:00
parent a6d2203e78
commit f8015ced70
11 changed files with 446 additions and 246 deletions
+17 -10
View File
@@ -69,10 +69,17 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in
if not client_data:
print("Не удалось получить данные клиента.")
return False
# Получаем текущий срок действия
current_expiry_time = client_data.get('expiryTime', 0)
# Обновляем данные клиента
client_data['expiryTime'] = new_expiry_time
# Если нет текущего срока, используем новый срок
if current_expiry_time == 0:
current_expiry_time = new_expiry_time
# Определяем новый срок окончания
updated_expiry_time = max(current_expiry_time, new_expiry_time)
# Формируем данные для обновления
payload = {
"id": 1, # Если id динамически изменяется, замените это значение
@@ -81,14 +88,14 @@ def extend_client_key(session, tg_id, client_id, email: str, new_expiry_time: in
{
"id": client_id,
"alterId": 0,
"email": client_data['email'],
"limitIp": 1,
# "totalGB": 2,
"expiryTime": new_expiry_time,
"enable": client_data['enable'],
"email": email.lower(), # Приведение email к нижнему регистру
"limitIp": 2,
"totalGB": 429496729600000,
"expiryTime": updated_expiry_time,
"enable": True,
"tgId": tg_id,
"subId": '',
"flow": 'xtls-rprx-vision'
"subId": "",
"flow": "xtls-rprx-vision"
}
]
})
+7 -1
View File
@@ -89,4 +89,10 @@ async def update_balance(tg_id: int, amount: float):
SET balance = balance + ?
WHERE tg_id = ?
''', (amount, tg_id))
await db.commit()
await db.commit()
async def get_key_count(tg_id: int) -> int:
async with aiosqlite.connect(DATABASE_PATH) as db:
async with db.execute('SELECT COUNT(*) FROM keys k JOIN connections c ON k.client_id = c.client_id WHERE c.tg_id = ?', (tg_id,)) as cursor:
count = await cursor.fetchone()
return count[0] if count else 0
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+104 -37
View File
@@ -2,16 +2,23 @@ from aiogram import types, Router
import aiosqlite
from bot import bot
from datetime import datetime, timedelta
from database import get_active_key_email, get_balance, store_key, update_balance
from client import extend_client_key
from database import get_balance, update_balance
from client import extend_client_key, login_with_credentials
from config import ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_PATH
from config import ADMIN_USERNAME, DATABASE_PATH, ADMIN_PASSWORD
router = Router()
# Обработка запроса на просмотр ключей
@router.callback_query(lambda c: c.data == 'view_keys')
async def process_callback_view_keys(callback_query: types.CallbackQuery):
tg_id = callback_query.from_user.id
# Создаем клавиатуру
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')],
[types.InlineKeyboardButton(text='Продлить ключ', callback_data='renew_key')],
[types.InlineKeyboardButton(text='Назад', callback_data='view_profile')]
])
try:
async with aiosqlite.connect(DATABASE_PATH) as db:
@@ -22,33 +29,30 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
WHERE c.tg_id = ?
''', (tg_id,)) as cursor:
record = await cursor.fetchone()
if record:
key = record[0]
expiry_time = record[1]
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S")
response_message = f"Ваш ключ:\n<pre>{key}</pre>\nДата окончания: <b>{expiry_date}</b>"
# Кнопки для инструкций и продления
instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')
renew_button = types.InlineKeyboardButton(text='Продлить ключ', callback_data='renew_key')
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[instructions_button],
[renew_button]
])
await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard)
key, expiry_time = record
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
current_date = datetime.utcnow()
days_left = (expiry_date - current_date).days
days_left_message = f"Осталось дней: {days_left}" if days_left > 0 else "Ключ истек."
response_message = (f"Ваш ключ:\n<pre>{key}</pre>\n"
f"Дата окончания: <b>{expiry_date.strftime('%Y-%m-%d %H:%M:%S')}</b>\n"
f"{days_left_message}")
else:
response_message = "У вас нет ключей."
await bot.send_message(tg_id, response_message, reply_to_message_id=callback_query.message.message_id)
await delete_previous_message(callback_query)
await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard)
except Exception as e:
response_message = f"Ошибка при получении ключей: {e}"
await bot.send_message(tg_id, response_message, reply_to_message_id=callback_query.message.message_id)
await handle_error(tg_id, callback_query, f"Ошибка при получении ключей: {e}")
await callback_query.answer()
# Обработка запроса на продление ключа
@router.callback_query(lambda c: c.data == 'renew_key')
async def process_callback_renew_key(callback_query: types.CallbackQuery):
tg_id = callback_query.from_user.id
@@ -57,22 +61,71 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
async with aiosqlite.connect(DATABASE_PATH) as db:
async with db.execute('SELECT client_id, email, expiry_time FROM connections WHERE tg_id = ?', (tg_id,)) as cursor:
record = await cursor.fetchone()
if record:
client_id = record[0]
email = record[1] # Получаем email
expiry_time = record[2]
client_id, email, expiry_time = record
current_time = datetime.utcnow().timestamp() * 1000
new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
if expiry_time <= current_time:
await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.")
return
# Создаем клавиатуру для выбора плана продления
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[
[types.InlineKeyboardButton(text='Продлить на 1 месяц (100 руб.)', callback_data='renew_1_month')],
[types.InlineKeyboardButton(text='Продлить на 3 месяца (250 руб.)', callback_data='renew_3_months')],
[types.InlineKeyboardButton(text='Назад', callback_data='view_profile')]
])
balance = await get_balance(tg_id)
response_message = (f"Выберите план продления:\n"
f"Баланс: <b>{balance} руб.</b>\n"
f"Действующий ключ истекает <b>{datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}</b>")
await delete_previous_message(callback_query)
await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard)
except Exception as e:
await callback_query.message.answer(f"Ошибка при выборе плана: {e}")
await callback_query.answer()
# Обработка выбора плана продления
@router.callback_query(lambda c: c.data.startswith('renew_'))
async def process_callback_renew_plan(callback_query: types.CallbackQuery):
tg_id = callback_query.from_user.id
plan = callback_query.data.split('_')[1] # '1' или '3'
days_to_extend = 30 * int(plan) # 30 дней или 90 дней
try:
async with aiosqlite.connect(DATABASE_PATH) as db:
async with db.execute('SELECT client_id, email, expiry_time FROM connections WHERE tg_id = ?', (tg_id,)) as cursor:
record = await cursor.fetchone()
if record:
client_id = record[0]
email = record[1]
expiry_time = record[2]
current_time = datetime.utcnow().timestamp() * 1000
if expiry_time <= current_time:
await callback_query.message.answer("Ваш ключ уже истек и не может быть продлен.")
return
# Рассчитываем новый срок окончания, добавляя дни в зависимости от выбранного плана
new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000)
# Определяем стоимость продления
cost = 100 if plan == '1' else 250
# Проверка баланса
balance = await get_balance(tg_id)
if balance < 100:
await callback_query.message.answer("Недостаточно средств для продления ключа.")
if balance < cost:
replenish_button = types.InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys')
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[replenish_button], [back_button]])
await callback_query.message.answer("Недостаточно средств для продления ключа.", reply_markup=keyboard)
return
# Создаем сессию для API-запросов
@@ -82,16 +135,30 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery):
success = extend_client_key(session, tg_id, client_id, email, new_expiry_time)
if success:
await update_balance(tg_id, -100) # Списание 100 рублей с баланса
await update_balance(tg_id, -cost) # Списание средств с баланса
await db.execute('UPDATE connections SET expiry_time = ? WHERE client_id = ?', (new_expiry_time, client_id))
await db.commit()
await callback_query.message.answer("Ваш ключ был успешно продлен на месяц.")
response_message = f"Ваш ключ был успешно продлен на {days_to_extend // 30} месяц(-)."
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys')
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
await bot.send_message(tg_id, response_message, reply_markup=keyboard)
else:
await callback_query.message.answer("Ошибка при продлении ключа.")
await bot.send_message(tg_id, "Ошибка при продлении ключа.")
else:
await callback_query.message.answer("У вас нет ключей для продления.")
await bot.send_message(tg_id, "У вас нет ключей для продления.")
except Exception as e:
await callback_query.message.answer(f"Ошибка при продлении ключа: {e}")
await bot.send_message(tg_id, f"Ошибка при продлении ключа: {e}")
await callback_query.answer()
await callback_query.answer()
# Удаление предыдущего сообщения
async def delete_previous_message(callback_query: types.CallbackQuery):
if callback_query.message.message_id:
await bot.delete_message(chat_id=callback_query.from_user.id, message_id=callback_query.message.message_id)
# Обработка ошибок
async def handle_error(tg_id, callback_query, message):
await delete_previous_message(callback_query)
await bot.send_message(tg_id, message)
+140 -42
View File
@@ -4,111 +4,209 @@ 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 database import get_balance, update_balance, get_key_count
from bot import bot
from handlers.profile import process_callback_view_profile
router = Router()
# Определение состояний
class ReplenishBalanceState(StatesGroup):
choosing_transfer_method = State()
choosing_amount = 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'):
"""
Отправляет сообщение и удаляет предыдущее, если оно существует.
"""
if state:
try:
state_data = await state.get_data()
previous_message_id = state_data.get(message_key)
if previous_message_id:
await bot.delete_message(chat_id=chat_id, message_id=previous_message_id)
sent_message = await bot.send_message(chat_id=chat_id, text=text, reply_markup=reply_markup)
await state.update_data({message_key: sent_message.message_id})
except Exception as e:
print(f"Ошибка при удалении/отправке сообщения: {e}")
return None
return sent_message
@router.callback_query(lambda c: c.data == 'replenish_balance')
async def process_callback_replenish_balance(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
key_count = await get_key_count(tg_id)
if key_count <= 0:
await send_message_with_deletion(
tg_id,
"У вас нет ключей. Пополнение баланса возможно только при наличии ключа.",
state=state,
message_key='key_error_message_id'
)
create_key_button = InlineKeyboardButton(text='Создать ключ', callback_data='create_key')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[create_key_button]])
await callback_query.message.edit_text(
text="Пожалуйста, создайте ключ для продолжения.",
reply_markup=keyboard
)
return
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_requisites')],
[InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')]
])
await bot.send_message(callback_query.from_user.id, "Выберите метод перевода:", reply_markup=keyboard)
await callback_query.answer() # Уведомляем Telegram, что запрос обработан
await callback_query.message.edit_text(
text="Выберите метод перевода:",
reply_markup=keyboard
)
await callback_query.answer()
@router.callback_query(lambda c: c.data == 'back_to_profile')
async def back_to_profile_handler(callback_query: types.CallbackQuery, state: FSMContext):
await process_callback_view_profile(callback_query, state)
@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 bot.send_message(callback_query.from_user.id, "Неверные данные для выбора метода перевода.")
await send_message_with_deletion(callback_query.from_user.id, "Неверные данные для выбора метода перевода.", state=state, message_key='transfer_method_error_message_id')
return
transfer_method = data[2] # 'requisites'
transfer_method = data[2]
if transfer_method == 'requisites':
message = (
"Банк: Т-Банк\n"
"2200701036597224\n"
"Получатель: Лисицын В.Д.\n"
f"\n"
"После перевода отправьте чек и дождитесь подтверждения."
)
amount_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='100 RUB', callback_data='amount_100')],
[InlineKeyboardButton(text='300 RUB', callback_data='amount_300')],
[InlineKeyboardButton(text='500 RUB', callback_data='amount_500')],
[InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_profile')]
])
# Удаляем старое сообщение о выборе метода
await bot.edit_message_text(
text="Вы выбрали метод перевода. Теперь вы увидите реквизиты.",
chat_id=callback_query.from_user.id,
message_id=callback_query.message.message_id
await callback_query.message.edit_text(
text="Выберите сумму пополнения:",
reply_markup=amount_keyboard
)
# Отправляем реквизиты в новом сообщении
await bot.send_message(callback_query.from_user.id, message)
await state.update_data(transfer_method=transfer_method)
await state.set_state(ReplenishBalanceState.choosing_amount)
else:
await bot.send_message(callback_query.from_user.id, "Неверный метод перевода.")
await send_message_with_deletion(callback_query.from_user.id, "Неверный метод перевода.", state=state, message_key='transfer_method_error_message_id')
@router.callback_query(lambda c: c.data.startswith('amount_'))
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
data = callback_query.data.split('_', 1)
if len(data) != 2:
await send_message_with_deletion(callback_query.from_user.id, "Неверные данные для выбора суммы.", state=state, message_key='amount_error_message_id')
return
await state.update_data(transfer_method=transfer_method)
amount_str = data[1]
amount = int(amount_str)
# Удаляем сообщение с кнопками выбора суммы
state_data = await state.get_data()
amount_selection_message_id = state_data.get('amount_selection_message_id')
if amount_selection_message_id:
try:
await bot.delete_message(chat_id=callback_query.from_user.id, message_id=amount_selection_message_id)
except Exception as e:
print(f"Ошибка при удалении сообщения: {e}")
await state.update_data(amount=amount)
await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation)
transfer_method = state_data.get('transfer_method')
message = (
"Банк: Т-Банк\n"
"2200701036597224\n"
"Получатель: Лисицын В.Д.\n"
"\n"
"После перевода отправьте чек и дождитесь подтверждения."
)
await callback_query.message.edit_text(text=message, reply_markup=None)
admin_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Подтвердить', callback_data=f'admin_confirm_{callback_query.from_user.id}_{transfer_method}')],
[InlineKeyboardButton(text='Отклонить', callback_data=f'admin_decline_{callback_query.from_user.id}_{transfer_method}')]
[InlineKeyboardButton(text='Подтвердить', callback_data=f'admin_confirm_{callback_query.from_user.id}_{transfer_method}_{amount}')],
[InlineKeyboardButton(text='Отклонить', callback_data=f'admin_decline_{callback_query.from_user.id}_{transfer_method}_{amount}')]
])
admin_message = (
f"Пользователь {callback_query.from_user.full_name} запросил пополнение баланса.\n"
f"Метод перевода: {transfer_method}\n"
f"Сумма пополнения: {amount} RUB\n"
"Пожалуйста, подтвердите или отклоните запрос."
)
await bot.send_message(ADMIN_ID, admin_message, reply_markup=admin_keyboard)
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()
@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 bot.send_message(callback_query.from_user.id, "Вы не являетесь администратором.")
await send_message_with_deletion(callback_query.from_user.id, "Вы не являетесь администратором.", state=state, message_key='admin_error_message_id')
return
data = callback_query.data.split('_', 4)
if len(data) < 4:
await bot.send_message(callback_query.from_user.id, "Неверные данные для обработки запроса.")
await send_message_with_deletion(callback_query.from_user.id, "Неверные данные для обработки запроса.", state=state, message_key='admin_error_message_id')
return
action = data[1] # 'confirm' или 'decline'
user_id_str = data[2]
action = data[1]
user_id = int(data[2])
transfer_method = data[3]
amount = int(data[4])
try:
user_id = int(user_id_str) # Преобразование user_id в целое число
if action == 'confirm':
amount = 100 # Пример суммы, на которую нужно пополнить баланс
async with aiosqlite.connect(DATABASE_PATH) as db:
await update_balance(user_id, amount)
await db.commit()
balance = await get_balance(user_id)
await bot.send_message(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}")
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(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.")
# Удаляем сообщение с реквизитами
state_data = await state.get_data()
requisites_message_id = state_data.get('requisites_message_id')
if requisites_message_id:
try:
await bot.delete_message(chat_id=user_id, message_id=requisites_message_id)
except Exception as e:
print(f"Ошибка при удалении сообщения с реквизитами: {e}")
profile_button = InlineKeyboardButton(text='Профиль', callback_data='view_profile')
profile_keyboard = InlineKeyboardMarkup(inline_keyboard=[[profile_button]])
await bot.send_message(user_id, "После пополнения баланса вы можете просмотреть свой профиль.", reply_markup=profile_keyboard)
elif action == 'decline':
await bot.send_message(callback_query.from_user.id, "Пополнение баланса отклонено.")
await send_message_with_deletion(callback_query.from_user.id, "Пополнение баланса отклонено.", state=state, message_key='admin_decline_message_id')
await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.")
except Exception as e:
await bot.send_message(callback_query.from_user.id, f"Ошибка при пополнении баланса: {e}")
print(f"Ошибка при пополнении баланса: {e}") # Отладочное сообщение в консоль
await send_message_with_deletion(callback_query.from_user.id, f"Ошибка при пополнении баланса: {e}", state=state, message_key='admin_error_message_id')
print(f"Ошибка при пополнении баланса: {e}")
await state.clear()
await callback_query.answer()
@router.message(lambda m: m.text and m.text.startswith('Недостаточно средств для продления'))
async def handle_insufficient_funds(message: types.Message):
user_id = message.from_user.id
replenish_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')]
])
await send_message_with_deletion(user_id, "Ваш баланс недостаточен для продления ключа. Пожалуйста, пополните баланс.", reply_markup=replenish_keyboard, state=None, message_key='insufficient_funds_message_id')
+20 -25
View File
@@ -5,7 +5,7 @@ from aiogram.fsm.state import State, StatesGroup
from datetime import datetime
import aiosqlite
from config import DATABASE_PATH
from database import get_balance, has_active_key
from database import get_balance, get_key_count
router = Router()
@@ -15,50 +15,45 @@ class ReplenishBalanceState(StatesGroup):
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(
"SELECT expiry_time FROM connections WHERE tg_id = ? AND expiry_time > ?",
(tg_id, int(datetime.utcnow().timestamp() * 1000))
) as cursor:
record = await cursor.fetchone()
if record:
expiry_date = datetime.utcfromtimestamp(record[0] / 1000).strftime("%Y-%m-%d %H:%M:%S")
# Получаем количество ключей
key_count = await get_key_count(tg_id)
# Получаем баланс
balance = await get_balance(tg_id)
if balance is None:
balance = 0 # По умолчанию 0, если баланс неизвестен
profile_message = (
f"Профиль \n\n"
f"ID: {tg_id}\n"
f"Ключ: {email if expiry_date else 'Нет активного ключа'}\n"
f"Баланс: {balance} RUB\n"
f"<b>Профиль пользователя</b>\n\n"
f"🔹 <b>ID:</b> {tg_id}\n"
f"🔹 <b>Количество ключей:</b> {key_count}\n"
f"🔹 <b>Баланс:</b> {balance} RUB\n"
f"🔹 <b>Дата регистрации:</b> {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')}\n"
)
# Кнопки для действий в профиле
button_create_key = InlineKeyboardButton(text='Создать ключ', callback_data='create_key')
button_view_keys = InlineKeyboardButton(text='Мои ключи', callback_data='view_keys')
button_replenish_balance = InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')
button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu') # Добавляем кнопку "Назад"
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[button_create_key], # Добавляем кнопку "Создать ключ"
[button_view_keys],
[button_replenish_balance]
[button_replenish_balance],
[button_back] # Добавляем кнопку "Назад" в клавиатуру
])
except Exception as e:
profile_message = f"Ошибка при получении данных профиля: {e}"
profile_message = f"❗️ Ошибка при получении данных профиля: {e}"
keyboard = None
# Отправляем сообщение с клавиатурой
await callback_query.message.bot.send_message(
chat_id=callback_query.message.chat.id,
# Редактируем существующее сообщение, а не отправляем новое
await callback_query.message.edit_text(
text=profile_message,
parse_mode='Markdown',
parse_mode='HTML',
reply_markup=keyboard
)
await callback_query.answer()
+73 -12
View File
@@ -4,7 +4,7 @@ from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQu
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from config import API_TOKEN, ADMIN_ID
from config import API_TOKEN, ADMIN_ID, CHANNEL_URL
from bot import dp, bot # Убедитесь, что bot и dp импортируются из правильного места
router = Router()
@@ -22,20 +22,22 @@ async def start_command(message: types.Message):
"Нажмите на соответствующую кнопку ниже, чтобы начать!"
)
button_view_profile = InlineKeyboardButton(text='Мой профиль', callback_data='view_profile')
button_about_vpn = InlineKeyboardButton(text='О VPN', callback_data='about_vpn')
button_feedback = InlineKeyboardButton(text='Обратная связь', callback_data='feedback')
button_view_profile = InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')
button_about_vpn = InlineKeyboardButton(text='🔒 О VPN', callback_data='about_vpn')
button_feedback = InlineKeyboardButton(text='📝 Обратная связь', callback_data='feedback')
button_channel = InlineKeyboardButton(text='📢 Наш канал', url=CHANNEL_URL)
inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[button_view_profile],
[button_about_vpn],
[button_feedback]
[button_feedback],
[button_channel]
])
reply_keyboard = ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="В начало")],
[KeyboardButton(text="Мой профиль")]
[KeyboardButton(text="Меню")],
# [KeyboardButton(text="Мой профиль")]
],
resize_keyboard=True
)
@@ -61,18 +63,72 @@ async def handle_about_vpn(callback_query: CallbackQuery):
"Каждому пользователю предоставляется индивидуальный ключ для подключения. "
"Этот ключ необходим для использования нашего VPN-сервиса."
)
await callback_query.message.answer(
# Кнопка "Назад"
button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu')
# Обновлённая клавиатура с кнопкой "Назад"
inline_keyboard_back = InlineKeyboardMarkup(inline_keyboard=[
[button_back]
])
await callback_query.message.edit_text(
info_message,
parse_mode='Markdown'
parse_mode='Markdown',
reply_markup=inline_keyboard_back # Добавление клавиатуры с кнопкой "Назад"
)
await callback_query.answer()
@router.callback_query(lambda c: c.data == 'back_to_menu')
async def handle_back_to_menu(callback_query: CallbackQuery):
welcome_text = (
"*Добро пожаловать в наш сервис!*\n\n"
"Вы можете воспользоваться следующими функциями:\n\n"
"🔑 *Создать ключ для подключения VPN* - Получите уникальный ключ для доступа к VPN.\n"
"📅 *Узнать дату окончания ключа* - Проверьте, когда истекает срок действия вашего ключа.\n"
"👤 *Просмотреть ваш профиль* - Получите информацию о вашем аккаунте и балансе.\n\n"
"Нажмите на соответствующую кнопку ниже, чтобы начать!"
)
button_view_profile = InlineKeyboardButton(text='👤 Мой профиль', callback_data='view_profile')
button_about_vpn = InlineKeyboardButton(text='🔒 О VPN', callback_data='about_vpn')
button_feedback = InlineKeyboardButton(text='📝 Обратная связь', callback_data='feedback')
button_channel = InlineKeyboardButton(text='📢 Наш канал', url=CHANNEL_URL)
inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[button_view_profile],
[button_about_vpn],
[button_feedback],
[button_channel]
])
# Редактирование сообщения на главное меню
await callback_query.message.edit_text(
welcome_text,
parse_mode='Markdown',
reply_markup=inline_keyboard
)
await callback_query.answer()
@router.callback_query(lambda c: c.data == 'feedback')
async def handle_feedback(callback_query: CallbackQuery, state: FSMContext):
await callback_query.message.answer(
"Напишите нам, если у вас возникли трудности с подключением, есть отзыв или предложение. Ваше сообщение будет отправлено админу.",
parse_mode='Markdown'
feedback_text = "Напишите нам, если у вас возникли трудности с подключением, есть отзыв или предложение. @pocomacho"
# Кнопка "Назад"
button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu')
# Обновлённая клавиатура с кнопкой "Назад"
inline_keyboard_back = InlineKeyboardMarkup(inline_keyboard=[
[button_back]
])
# Редактирование сообщения с кнопкой "Назад"
await callback_query.message.edit_text(
feedback_text,
parse_mode='Markdown',
reply_markup=inline_keyboard_back # Клавиатура с кнопкой "Назад"
)
await state.set_state(FeedbackState.waiting_for_feedback)
await state.update_data(user_id=callback_query.from_user.id)
await callback_query.answer()
@@ -95,3 +151,8 @@ async def receive_feedback(message: types.Message, state: FSMContext):
print(f"Ошибка при отправке обратной связи: {e}") # Логирование ошибок
await state.clear()
# Пример обработки команды старт
@router.message(Command('start'))
async def cmd_start(message: Message):
await start_command(message)
+85 -119
View File
@@ -1,63 +1,52 @@
from aiogram import Bot, Dispatcher, Router, F, types
from aiogram.filters import Command
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from auth import login_with_credentials, link
from client import add_client
from datetime import datetime, timedelta
from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH
from database import add_connection, has_active_key, get_active_key_email, get_balance, store_key, update_balance
from client import extend_client_key
import asyncio
import re
import uuid
import aiosqlite
import re
from aiogram import Bot, Dispatcher, Router, types, F
from aiogram.filters import Command
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from auth import login_with_credentials, link
from client import add_client, extend_client_key
from config import API_TOKEN, ADMIN_PASSWORD, ADMIN_USERNAME, ADMIN_CHAT_ID, DATABASE_PATH
from database import add_connection, has_active_key, get_balance, store_key, update_balance
from bot import dp
from handlers.start import start_command
from bot import bot
from handlers.profile import process_callback_view_profile
import asyncio
from handlers.keys import process_callback_view_keys
from bot import bot
router = Router()
def escape_markdown(text: str) -> str:
"""
Экранирование специальных символов для Markdown.
"""
escape_chars = r'_*[]()~`>#+-=|{}.!'
return re.sub(r'([%s])' % re.escape(escape_chars), r'\\\1', text)
# Удаляем специальные символы из имени ключа
def sanitize_key_name(key_name: str) -> str:
"""
Очищает имя ключа от недопустимых символов и приводит его к нижнему регистру.
"""
# Оставляем только строчные буквы, цифры, символы @._-
return re.sub(r'[^a-z0-9@._-]', '', key_name.lower())
class Form(StatesGroup):
waiting_for_key_name = State()
waiting_for_admin_confirmation = State()
waiting_for_statistics = State()
waiting_for_expiry_date = State()
viewing_profile = State()
# Обработка нажатия кнопки создания ключа
@dp.callback_query(F.data == 'create_key')
async def process_callback_create_key(callback_query: types.CallbackQuery, state: FSMContext):
await bot.send_message(callback_query.from_user.id, "Введите имя вашего профиля VPN:")
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext):
await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:")
await state.set_state(Form.waiting_for_key_name)
await callback_query.answer()
# Обработка текстовых сообщений
@dp.message()
async def handle_text(message: types.Message, state: FSMContext):
async def handle_text(message: Message, state: FSMContext):
current_state = await state.get_state()
print(f"Received message: {message.text}, Current state: {current_state}")
# Обработка команд и переходов
if message.text == "Мой профиль":
# Создайте фейковый объект CallbackQuery для вызова функции
callback_query = types.CallbackQuery(
id="1", # Используйте уникальный идентификатор
id="1",
from_user=message.from_user,
chat_instance='',
data='view_profile',
@@ -66,103 +55,59 @@ async def handle_text(message: types.Message, state: FSMContext):
await process_callback_view_profile(callback_query, state)
return
if message.text == "/start":
await start_command(message)
return
if message.text == "В начало":
if message.text in ["/start", "Меню"]:
await start_command(message)
return
# Если ожидается имя ключа
if current_state == Form.waiting_for_key_name.state:
tg_id = message.from_user.id
key_name = sanitize_key_name(message.text)
tg_name = message.from_user.username
await handle_key_name_input(message, state)
if not key_name:
await bot.send_message(tg_id, "Имя клиента не указано.")
await state.clear()
return
# Обработка ввода имени ключа
async def handle_key_name_input(message: Message, state: FSMContext):
tg_id = message.from_user.id
key_name = sanitize_key_name(message.text)
if await has_active_key(tg_id):
await bot.send_message(tg_id, "У вас уже есть активный ключ. Вы не можете создать новый.")
await state.clear()
return
# Сохраняем информацию о запросе в контексте состояния
await state.update_data(key_name=key_name, tg_id=tg_id)
# Отправляем запрос на подтверждение админу
admin_message = (
f"Новый запрос на создание ключа:\n"
f"Телеграм-ID: {tg_id}\n"
f"Имя ключа: {key_name}\n"
f"Имя пользователя Telegram: @{tg_name}\n"
f"Введите 'yes' для подтверждения или 'no' для отклонения."
)
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Да', callback_data=f'confirm_key_{tg_id}_{key_name}')],
[InlineKeyboardButton(text='Нет', callback_data=f'reject_key_{tg_id}')]
])
await bot.send_message(ADMIN_CHAT_ID, admin_message, reply_markup=keyboard)
await bot.send_message(tg_id, "Ожидайте подтверждения администратора.")
if not key_name:
await message.reply("Имя профиля не указано. Введите имя на английском языке.")
await state.clear()
@dp.callback_query(F.data.startswith('confirm_key_'))
async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMContext):
if callback_query.message.chat.id != int(ADMIN_CHAT_ID):
return
if await has_active_key(tg_id):
await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.")
await state.clear()
return
await state.update_data(key_name=key_name, tg_id=tg_id)
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
data = callback_query.data.split('_', 3)
tg_id = int(data[2])
key_name = data[3]
client_id = str(uuid.uuid4())
email = key_name.lower()
current_time = datetime.utcnow()
expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000)
try:
client_id = str(uuid.uuid4())
email = key_name.lower() # Приведение email к нижнему регистру
limit_ip = 1
total_gb = 0
current_time = datetime.utcnow()
# Определяем время истечения ключа
if await has_active_key(tg_id):
# Если есть активный ключ, устанавливаем срок действия на 30 дней
expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000)
else:
# Первый ключ устанавливаем на 1 день
expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000)
enable = True
flow = "xtls-rprx-vision"
balance = 0
# Проверка данных перед созданием ключа
print(f"Creating key with client_id={client_id}, email={email}, tg_id={tg_id}")
add_client(session, client_id, email, tg_id, limit_ip, total_gb, expiry_time, enable, flow)
# Создание клиента и получение ссылки
add_client(session, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=expiry_time, enable=True, flow="xtls-rprx-vision")
connection_link = link(session, client_id, email)
await add_connection(tg_id, client_id, email, expiry_time, balance)
await store_key(client_id, email, connection_link)
await add_connection(tg_id, client_id, email, expiry_time, 0)
await store_key(client_id, email, connection_link)
# Создаем кнопки
instructions_button = InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[instructions_button]])
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')],
[InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')]
])
# Отправляем сообщение с ключом и кнопкой
key_message = f"Ключ создан:\n<pre>{connection_link}</pre>"
await bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard)
await bot.send_message(callback_query.message.chat.id, "Ключ создан и отправлен клиенту.")
await message.reply(key_message, parse_mode="HTML", reply_markup=keyboard)
except Exception as e:
await bot.send_message(callback_query.message.chat.id, f"Ошибка при создании ключа: {e}")
print(f"Ошибка при создании ключа: {e}") # Отладочное сообщение в консоль
await callback_query.answer()
await message.reply(f"Ошибка при создании ключа: {e}")
print(f"Ошибка при создании ключа: {e}")
await state.clear()
# Обработка кнопки "Инструкции"
@dp.callback_query(F.data == 'instructions')
async def handle_instructions(callback_query: CallbackQuery):
instructions_message = (
@@ -175,12 +120,29 @@ async def handle_instructions(callback_query: CallbackQuery):
"4. Выберите 'Вставить из буфера обмена' для добавления ключа.\n\n"
"Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку."
)
await callback_query.message.answer(
instructions_message,
parse_mode='Markdown'
)
back_button = InlineKeyboardButton(text='Назад', callback_data='back_to_main')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
await callback_query.message.edit_text(instructions_message, parse_mode='Markdown', reply_markup=keyboard)
await callback_query.answer()
# Обработка кнопки "Назад"
@dp.callback_query(F.data == 'back_to_main')
async def handle_back_to_main(callback_query: CallbackQuery):
tg_id = callback_query.from_user.id
new_callback_query = CallbackQuery(
id=callback_query.id,
from_user=callback_query.from_user,
chat_instance=callback_query.chat_instance,
data='view_keys',
message=callback_query.message
)
await process_callback_view_keys(new_callback_query)
await callback_query.answer()
# Фоновая задача для продления ключей
async def renew_expired_keys():
while True:
current_time = datetime.utcnow()
@@ -192,16 +154,20 @@ async def renew_expired_keys():
balance = await get_balance(tg_id)
if balance >= 100:
new_expiry_time = int((current_time + timedelta(days=30)).timestamp() * 1000)
async with aiosqlite.connect(DATABASE_PATH) as db:
await db.execute('UPDATE connections SET expiry_time = ? WHERE tg_id = ? AND client_id = ?', (new_expiry_time, tg_id, client_id))
await db.commit()
await update_balance(tg_id, -100)
await extend_client_key(client_id)
print(f"Ключ для клиента {client_id} продлен на месяц и списано 100 рублей.")
else:
print(f"Недостаточно средств на балансе для клиента {client_id}.")
print(f"Недостаточно средств на балансе для клиента {client_id}. Предложение пополнить баланс.")
replenish_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Пополнить баланс', callback_data='replenish_balance')]
])
await bot.send_message(tg_id, "Ваш баланс недостаточен для продления ключа. Пожалуйста, пополните баланс.", reply_markup=replenish_keyboard)
await asyncio.sleep(3600)
await asyncio.sleep(3600)