diff --git a/handlers/__pycache__/keys.cpython-310.pyc b/handlers/__pycache__/keys.cpython-310.pyc index 1c9d0b6e..5d50a89d 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 544e555d..e6aecc61 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 b038020a..17b82b22 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 395da8ca..e2dc771c 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 331f0764..b545e2aa 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -25,11 +25,18 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): 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}"
+
+ # Добавляем кнопку с инструкциями
+ instructions_button = types.InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')
+ keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[instructions_button]])
+
+ await bot.send_message(tg_id, response_message, parse_mode="HTML", reply_markup=keyboard)
else:
response_message = "У вас нет ключей."
+ await bot.send_message(tg_id, response_message, reply_to_message_id=callback_query.message.message_id)
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 bot.send_message(tg_id, response_message, parse_mode="HTML", reply_to_message_id=callback_query.message.message_id)
await callback_query.answer()
diff --git a/handlers/pay.py b/handlers/pay.py
index 9c06aeea..bf99bba6 100644
--- a/handlers/pay.py
+++ b/handlers/pay.py
@@ -17,100 +17,98 @@ class ReplenishBalanceState(StatesGroup):
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')]
+ [InlineKeyboardButton(text='По реквизитам', callback_data='transfer_method_requisites')]
])
- await callback_query.message.answer("Выберите метод перевода:", reply_markup=keyboard)
- await callback_query.answer()
+ await bot.send_message(callback_query.from_user.id, "Выберите метод перевода:", reply_markup=keyboard)
+ await callback_query.answer() # Уведомляем Telegram, что запрос обработан
@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("Неверные данные для выбора метода перевода.")
+ await bot.send_message(callback_query.from_user.id, "Неверные данные для выбора метода перевода.")
return
- transfer_method = data[2] # 'requisites' или 'sbp'
+ transfer_method = data[2] # 'requisites'
if transfer_method == 'requisites':
message = (
- "Реквизиты для пополнения:\n"
- f'\n'
- "Банк: Пример Банк\n"
- "Расчетный счет: 12345678901234567890\n"
- "Получатель: ИП Иванов Иван Иванович"
- f'\n'
- "после перевода отправьте чек и ждите подтверждения"
+ "Банк: Т-Банк\n"
+ "2200701036597224\n"
+ "Получатель: Лисицын В.Д.\n"
+ f"\n"
+ "После перевода отправьте чек и дождитесь подтверждения."
)
- elif transfer_method == 'sbp':
- message = (
- "СБП для пополнения:\n"
- f'\n'
- "Номер банка: 1234567890\n"
- "ФИО: Иванов Иван Иванович\n"
- f'\n'
- "после перевода отправьте чек и ждите подтверждения"
+
+ # Удаляем старое сообщение о выборе метода
+ await bot.edit_message_text(
+ text="Вы выбрали метод перевода. Теперь вы увидите реквизиты.",
+ chat_id=callback_query.from_user.id,
+ message_id=callback_query.message.message_id
)
+
+ # Отправляем реквизиты в новом сообщении
+ await bot.send_message(callback_query.from_user.id, message)
+
else:
- await callback_query.answer("Неверный метод перевода.")
+ await bot.send_message(callback_query.from_user.id, "Неверный метод перевода.")
return
- await callback_query.message.answer(message)
+ await state.update_data(transfer_method=transfer_method)
+ await state.set_state(ReplenishBalanceState.waiting_for_admin_confirmation)
+
+ 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}')]
+ ])
+
+ admin_message = (
+ f"Пользователь {callback_query.from_user.full_name} запросил пополнение баланса.\n"
+ f"Метод перевода: {transfer_method}\n"
+ "Пожалуйста, подтвердите или отклоните запрос."
+ )
+
+ await bot.send_message(ADMIN_ID, admin_message, reply_markup=admin_keyboard)
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("Вы не являетесь администратором.")
+ await bot.send_message(callback_query.from_user.id, "Вы не являетесь администратором.")
return
data = callback_query.data.split('_', 4)
if len(data) < 4:
- await callback_query.answer("Неверные данные для обработки запроса.")
+ await bot.send_message(callback_query.from_user.id, "Неверные данные для обработки запроса.")
return
action = data[1] # 'confirm' или 'decline'
- user_id_str = data[3]
+ user_id_str = data[2]
+ transfer_method = 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
+ 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 callback_query.message.answer(f"Баланс пользователя успешно пополнен на {amount} единиц.\nТекущий баланс: {balance}")
-
- # Сообщаем пользователю о пополнении
+ await bot.send_message(callback_query.from_user.id, f"Баланс пользователя успешно пополнен на {amount} RUB.\nТекущий баланс: {balance}")
await bot.send_message(user_id, f"Ваш баланс был успешно пополнен на {amount} RUB.")
elif action == 'decline':
- await callback_query.message.answer("Пополнение баланса отклонено.")
-
- # Сообщаем пользователю об отклонении
+ await bot.send_message(callback_query.from_user.id, "Пополнение баланса отклонено.")
await bot.send_message(user_id, "Ваш запрос на пополнение баланса был отклонен.")
except Exception as e:
- # Отладочное сообщение
- await callback_query.message.answer(f"Ошибка при пополнении баланса: {e}")
+ await bot.send_message(callback_query.from_user.id, f"Ошибка при пополнении баланса: {e}")
print(f"Ошибка при пополнении баланса: {e}") # Отладочное сообщение в консоль
- # Очистка состояния
await state.clear()
await callback_query.answer()
diff --git a/handlers/profile.py b/handlers/profile.py
index 03eefefd..b39c6a8e 100644
--- a/handlers/profile.py
+++ b/handlers/profile.py
@@ -35,24 +35,32 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta
balance = await get_balance(tg_id)
profile_message = (
f"Профиль \n\n"
- f"ID: @{tg_id}\n"
- f"Ключ: {email if email else 'Нет активного ключа'}\n"
+ f"ID: {tg_id}\n"
+ f"Ключ: {email if expiry_date else 'Нет активного ключа'}\n"
f"Баланс: {balance} RUB\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')
- keyboard = InlineKeyboardMarkup(inline_keyboard=[[button_view_keys], [button_replenish_balance]])
+ keyboard = InlineKeyboardMarkup(inline_keyboard=[
+ [button_create_key], # Добавляем кнопку "Создать ключ"
+ [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.message.bot.send_message(
+ chat_id=callback_query.message.chat.id,
+ text=profile_message,
+ parse_mode='Markdown',
+ reply_markup=keyboard
+ )
await callback_query.answer()
@router.callback_query(lambda c: c.data == 'view_profile')
diff --git a/handlers/start.py b/handlers/start.py
index ddac6a2e..b9b9dab0 100644
--- a/handlers/start.py
+++ b/handlers/start.py
@@ -1,31 +1,37 @@
-from aiogram import types, Router
+from aiogram import Bot, Dispatcher, Router, types
from aiogram.filters import Command
-from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup, KeyboardButton
+from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message, ReplyKeyboardMarkup, KeyboardButton
+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 bot import dp, bot # Убедитесь, что bot и dp импортируются из правильного места
router = Router()
-async def start_command(message: types.Message, callback_data: str = None):
+class FeedbackState(StatesGroup):
+ waiting_for_feedback = State()
+
+async def start_command(message: types.Message):
welcome_text = (
- "Добро пожаловать! Вы можете создать ключ для подключения VPN, "
- "просмотреть статистику использования, узнать дату окончания ключа или "
- "просмотреть ваш профиль."
+ "*Добро пожаловать в наш сервис!*\n\n"
+ "Вы можете воспользоваться следующими функциями:\n\n"
+ "🔑 *Создать ключ для подключения VPN* - Получите уникальный ключ для доступа к VPN.\n"
+ "📅 *Узнать дату окончания ключа* - Проверьте, когда истекает срок действия вашего ключа.\n"
+ "👤 *Просмотреть ваш профиль* - Получите информацию о вашем аккаунте и балансе.\n\n"
+ "Нажмите на соответствующую кнопку ниже, чтобы начать!"
)
- # Создаем кнопки для команд
- 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_profile = InlineKeyboardButton(text='Мой профиль', callback_data='view_profile')
+ button_about_vpn = InlineKeyboardButton(text='О VPN', callback_data='about_vpn')
+ button_feedback = InlineKeyboardButton(text='Обратная связь', callback_data='feedback')
- # Создаем клавиатуру с кнопками
inline_keyboard = InlineKeyboardMarkup(inline_keyboard=[
- [button_create_key],
-# [button_view_stats],
-# [button_view_expiry],
- [button_view_profile]
+ [button_view_profile],
+ [button_about_vpn],
+ [button_feedback]
])
- # Создаем кнопку для возврата в начало
reply_keyboard = ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="В начало")],
@@ -34,9 +40,58 @@ async def start_command(message: types.Message, callback_data: str = None):
resize_keyboard=True
)
- # Отправляем сообщение с кнопками
- await message.reply(welcome_text, reply_markup=inline_keyboard)
- await message.answer(
- "",
+ await message.bot.send_message(
+ chat_id=message.chat.id,
+ text=welcome_text,
+ parse_mode='Markdown',
+ reply_markup=inline_keyboard
+ )
+
+ await message.bot.send_message(
+ chat_id=message.chat.id,
+ text="Выберите действие:",
reply_markup=reply_keyboard
- )
\ No newline at end of file
+ )
+
+@router.callback_query(lambda c: c.data == 'about_vpn')
+async def handle_about_vpn(callback_query: CallbackQuery):
+ info_message = (
+ "*О VPN*\n\n"
+ "Мы используем VLESS для обеспечения безопасного и надежного подключения к интернету. "
+ "Каждому пользователю предоставляется индивидуальный ключ для подключения. "
+ "Этот ключ необходим для использования нашего VPN-сервиса."
+ )
+ await callback_query.message.answer(
+ info_message,
+ parse_mode='Markdown'
+ )
+ 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'
+ )
+ await state.set_state(FeedbackState.waiting_for_feedback)
+ await state.update_data(user_id=callback_query.from_user.id)
+ await callback_query.answer()
+
+@router.message(FeedbackState.waiting_for_feedback)
+async def receive_feedback(message: types.Message, state: FSMContext):
+ user_data = await state.get_data()
+ user_id = user_data.get('user_id')
+
+ feedback_message = (
+ f"Обратная связь от пользователя {user_id}:\n\n"
+ f"{message.text}"
+ )
+
+ try:
+ await bot.send_message(ADMIN_ID, feedback_message)
+ await message.answer("Спасибо за ваше сообщение! Мы свяжемся с вами, если это будет необходимо.")
+ except Exception as e:
+ await message.answer("Произошла ошибка при отправке вашего сообщения.")
+ print(f"Ошибка при отправке обратной связи: {e}") # Логирование ошибок
+
+ await state.clear()
diff --git a/key_management.py b/key_management.py
index 5ba71195..98ca6ba7 100644
--- a/key_management.py
+++ b/key_management.py
@@ -1,6 +1,6 @@
from aiogram import Bot, Dispatcher, Router, F, types
from aiogram.filters import Command
-from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message, ReplyKeyboardMarkup, KeyboardButton
+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
@@ -19,7 +19,6 @@ from handlers.profile import process_callback_view_profile
router = Router()
-
def escape_markdown(text: str) -> str:
"""
Экранирование специальных символов для Markdown.
@@ -27,21 +26,26 @@ def escape_markdown(text: str) -> str:
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()
-
+ viewing_profile = State()
@dp.callback_query(F.data == 'create_key')
async def process_callback_create_key(callback_query: types.CallbackQuery, state: FSMContext):
- await callback_query.message.reply("Введите имя вашего профиля VPN:")
+ await bot.send_message(callback_query.from_user.id, "Введите имя вашего профиля VPN:")
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()
@@ -68,16 +72,16 @@ async def handle_text(message: types.Message, state: FSMContext):
if current_state == Form.waiting_for_key_name.state:
tg_id = message.from_user.id
- key_name = message.text
+ key_name = sanitize_key_name(message.text)
tg_name = message.from_user.username
if not key_name:
- await message.reply("Имя клиента не указано.")
+ await bot.send_message(tg_id, "Имя клиента не указано.")
await state.clear()
return
if await has_active_key(tg_id):
- await message.reply("У вас уже есть активный ключ. Вы не можете создать новый.")
+ await bot.send_message(tg_id, "У вас уже есть активный ключ. Вы не можете создать новый.")
await state.clear()
return
@@ -98,8 +102,7 @@ async def handle_text(message: types.Message, state: FSMContext):
])
await bot.send_message(ADMIN_CHAT_ID, admin_message, reply_markup=keyboard)
- await state.set_state(Form.waiting_for_admin_confirmation)
- await message.reply("Ожидайте подтверждения администратора.")
+ await bot.send_message(tg_id, "Ожидайте подтверждения администратора.")
await state.clear()
@dp.callback_query(F.data.startswith('confirm_key_'))
@@ -114,7 +117,7 @@ async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMCon
try:
client_id = str(uuid.uuid4())
- email = key_name
+ email = key_name.lower() # Приведение email к нижнему регистру
limit_ip = 1
total_gb = 0
current_time = datetime.utcnow()
@@ -123,25 +126,46 @@ async def handle_admin_confirmation(callback_query: CallbackQuery, state: FSMCon
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)
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 bot.send_message(tg_id, f"Ключ создан:\n{connection_link}", parse_mode="HTML")
- await callback_query.message.reply("Ключ создан и отправлен клиенту.")
+
+ # Создаем кнопки
+ instructions_button = InlineKeyboardButton(text='Инструкции по использованию', callback_data='instructions')
+ keyboard = InlineKeyboardMarkup(inline_keyboard=[[instructions_button]])
+
+ # Отправляем сообщение с ключом и кнопкой
+ key_message = f"Ключ создан:\n{connection_link}"
+ await bot.send_message(tg_id, key_message, parse_mode="HTML", reply_markup=keyboard)
+ await bot.send_message(callback_query.message.chat.id, "Ключ создан и отправлен клиенту.")
except Exception as e:
- await callback_query.message.reply(f"Ошибка при создании ключа: {e}")
+ await bot.send_message(callback_query.message.chat.id, f"Ошибка при создании ключа: {e}")
+ print(f"Ошибка при создании ключа: {e}") # Отладочное сообщение в консоль
await callback_query.answer()
-@dp.callback_query(F.data.startswith('reject_key_'))
-async def handle_rejection(callback_query: CallbackQuery, state: FSMContext):
- if callback_query.message.chat.id != int(ADMIN_CHAT_ID):
- return
- tg_id = int(callback_query.data.split('_', 3)[2])
- await bot.send_message(tg_id, "Создание ключа отклонено.")
- await callback_query.message.reply("Запрос отклонен.")
+
+@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"
+ "Если у вас возникнут вопросы, не стесняйтесь обращаться в поддержку."
+ )
+ await callback_query.message.answer(
+ instructions_message,
+ parse_mode='Markdown'
+ )
await callback_query.answer()