Files
Solo_bot/key_management.py
T
2024-09-20 05:48:09 +03:00

249 lines
12 KiB
Python

from datetime import datetime, timedelta
import asyncio
import re
import uuid
import aiosqlite
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 handlers.profile import process_callback_view_profile
from handlers.keys import process_callback_view_keys
from bot import bot
router = Router()
# Удаляем специальные символы из имени ключа
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_expiry_date = State()
viewing_profile = State()
# Обработка нажатия кнопки создания ключа
@dp.callback_query(F.data == 'create_key')
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
if await has_active_key(tg_id):
await callback_query.message.edit_text(
"У вас уже есть активный ключ. Вы можете создать новый ключ за дополнительную плату в размере 100 рублей. "
"Хотите продолжить?",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')],
[InlineKeyboardButton(text='Назад', callback_data='cancel_create_key')] # Изменили текст на "Назад"
])
)
await state.update_data(creating_new_key=True) # Сохраняем состояние создания нового ключа
else:
await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:")
await state.set_state(Form.waiting_for_key_name)
await callback_query.answer()
@dp.callback_query(F.data == 'cancel_create_key')
async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext):
await process_callback_view_profile(callback_query, state) # Возвращаем в профиль
await callback_query.answer()
@dp.callback_query(F.data == 'confirm_create_new_key')
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext):
await callback_query.message.edit_text("Пожалуйста, выберите имя для вашего нового ключа:")
await state.set_state(Form.waiting_for_key_name)
await state.update_data(creating_new_key=True) # Убедитесь, что это состояние сохранено
await callback_query.answer()
@dp.callback_query(F.data == 'cancel_create_key')
async def cancel_create_key(callback_query: CallbackQuery):
await callback_query.message.edit_text("Создание нового ключа отменено.")
await callback_query.answer()
# Обработка текстовых сообщений
@dp.message()
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 == "Мой профиль":
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 in ["/start", "Меню"]:
await start_command(message)
return
# Если ожидается имя ключа
if current_state == Form.waiting_for_key_name.state:
await handle_key_name_input(message, state)
async def handle_key_name_input(message: Message, state: FSMContext):
tg_id = message.from_user.id
key_name = sanitize_key_name(message.text)
if not key_name:
await message.reply("Имя ключа не указано. Введите имя на английском языке.")
await state.clear()
return
# Получаем данные состояния
data = await state.get_data()
creating_new_key = data.get('creating_new_key', False)
session = login_with_credentials(ADMIN_USERNAME, ADMIN_PASSWORD)
client_id = str(uuid.uuid4())
email = key_name.lower()
current_time = datetime.utcnow()
expiry_time = int((current_time + timedelta(days=1)).timestamp() * 1000)
if creating_new_key:
balance = await get_balance(tg_id)
if balance < 100:
replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
await message.reply("Недостаточно средств на балансе для создания нового ключа.", reply_markup=keyboard)
await state.clear()
return
await update_balance(tg_id, -100) # Списание 100 рублей за новый ключ
try:
# Создание клиента и получение ссылки
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, 0)
await store_key(tg_id, client_id, email, connection_link)
keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')],
[InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')]
])
key_message = f"Ключ создан:\n<pre>{connection_link}</pre>"
await message.reply(key_message, parse_mode="HTML", reply_markup=keyboard)
except Exception as e:
await message.reply(f"Ошибка при создании ключа: {e}")
print(f"Ошибка при создании ключа: {e}")
await state.clear()
# Обработка нажатия кнопки создания ключа
@dp.callback_query(F.data == 'create_key')
async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
if await has_active_key(tg_id):
await callback_query.message.edit_text(
"У вас уже есть активный ключ. Вы можете создать новый ключ за дополнительную плату в размере 100 рублей. "
"Хотите продолжить?",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Да, создать новый ключ', callback_data='confirm_create_new_key')],
[InlineKeyboardButton(text='Нет, оставить текущий', callback_data='cancel_create_key')]
])
)
await state.update_data(creating_new_key=True) # Сохраняем состояние создания нового ключа
else:
await callback_query.message.edit_text("Вам будет выдан пробный ключ. Пожалуйста, выберите имя для вашего ключа:")
await state.set_state(Form.waiting_for_key_name)
await callback_query.answer()
@dp.callback_query(F.data == 'confirm_create_new_key')
async def confirm_create_new_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.callback_query(F.data == 'cancel_create_key')
async def cancel_create_key(callback_query: CallbackQuery):
await callback_query.message.edit_text("Создание нового ключа отменено.")
await callback_query.answer()
# Обработка кнопки "Инструкции"
@dp.callback_query(F.data == 'instructions')
async def handle_instructions(callback_query: CallbackQuery):
instructions_message = (
"*Инструкции по использованию вашего ключа:*\n\n"
"1. Скачайте приложение для вашего устройства:\n"
" - Для Android: [V2Ray](https://play.google.com/store/apps/details?id=com.v2ray.ang&hl=ru&pli=1)\n"
" - Для iPhone: [Streisand](https://apps.apple.com/ru/app/streisand/id6450534064)\n\n"
"2. Скопируйте предоставленный ключ, который вы получили ранее.\n"
"3. Откройте приложение и нажмите на плюсик сверху справа.\n"
"4. Выберите 'Вставить из буфера обмена' для добавления ключа.\n\n"
"Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку."
)
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()
async with aiosqlite.connect(DATABASE_PATH) as db:
async with db.execute('SELECT tg_id, client_id FROM connections WHERE expiry_time <= ?', (int(current_time.timestamp() * 1000),)) as cursor:
expired_keys = await cursor.fetchall()
for tg_id, client_id in 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}. Предложение пополнить баланс.")
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)