diff --git a/handlers/instructions.jpg b/handlers/instructions.jpg new file mode 100644 index 00000000..9c52d9f6 Binary files /dev/null and b/handlers/instructions.jpg differ diff --git a/handlers/instructions.py b/handlers/instructions.py index a0d5f21e..c9c2dc5e 100644 --- a/handlers/instructions.py +++ b/handlers/instructions.py @@ -1,8 +1,12 @@ +import os from aiogram import types from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup - +from aiogram.types import BufferedInputFile async def send_instructions(callback_query: types.CallbackQuery): + # Удаляем предыдущее сообщение + await callback_query.message.delete() + instructions_message = ( "*📋 Инструкции по использованию вашего ключа:*\n\n" "1. **Скачайте приложение для вашего устройства:**\n" @@ -14,8 +18,26 @@ async def send_instructions(callback_query: types.CallbackQuery): "4. **Выберите 'Вставить из буфера обмена' для добавления ключа.**\n\n" "💬 Если у вас возникнут вопросы, не стесняйтесь обращаться в [поддержку](https://t.me/solonet_sup)." ) + + # Путь к изображению + image_path = os.path.join(os.path.dirname(__file__), 'instructions.jpg') + + # Проверка существования файла изображения + if not os.path.isfile(image_path): + await callback_query.message.answer("Файл изображения не найден.") + await callback_query.answer() + return + 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() \ No newline at end of file + # Отправка изображения и сообщения + with open(image_path, 'rb') as image_from_buffer: + await callback_query.message.answer_photo( + BufferedInputFile(image_from_buffer.read(), filename="instructions.jpg"), + caption=instructions_message, + parse_mode='Markdown', + reply_markup=keyboard + ) + + await callback_query.answer() diff --git a/handlers/key_management.py b/handlers/key_management.py index 808b2f78..a2bfed0b 100644 --- a/handlers/key_management.py +++ b/handlers/key_management.py @@ -271,7 +271,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): ) keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='📖 Инструкции по использованию', callback_data='instructions')], + [InlineKeyboardButton(text='📘 Инструкции по использованию', callback_data='instructions')], [InlineKeyboardButton(text='🔙 Перейти в профиль', callback_data='view_profile')] ]) diff --git a/handlers/keys.py b/handlers/keys.py index c61558dc..3e7b6cba 100644 --- a/handlers/keys.py +++ b/handlers/keys.py @@ -318,57 +318,74 @@ async def process_callback_select_server(callback_query: types.CallbackQuery): try: conn = await asyncpg.connect(DATABASE_URL) try: - record = await conn.fetchrow('SELECT email, expiry_time, server_id FROM keys WHERE client_id = $1', client_id) + # Начинаем транзакцию с блокировкой строки по client_id, чтобы предотвратить параллельную миграцию + async with conn.transaction(): + record = await conn.fetchrow( + 'SELECT email, expiry_time, server_id FROM keys WHERE client_id = $1 FOR UPDATE', client_id + ) - if record: - email = record['email'] - expiry_time = record['expiry_time'] - current_server_id = record['server_id'] + if record: + email = record['email'] + expiry_time = record['expiry_time'] + current_server_id = record['server_id'] - # Авторизация на новом сервере - session_new = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) - new_expiry_time = int(datetime.utcnow().timestamp() * 1000) + (expiry_time - datetime.utcnow().timestamp() * 1000) + if current_server_id == server_id: + await callback_query.answer("Клиент уже на этом сервере.") + return - # Добавляем клиента на новый сервер - new_client_data = await add_client(session_new, server_id, client_id, email, tg_id, limit_ip=1, total_gb=0, expiry_time=new_expiry_time, enable=True, flow="xtls-rprx-vision") + # Проверка наличия клиента на новом сервере перед добавлением + session_new = await login_with_credentials(server_id, ADMIN_USERNAME, ADMIN_PASSWORD) + new_client_data = await add_client( + session_new, server_id, client_id, email, tg_id, limit_ip=1, total_gb=0, + expiry_time=int(datetime.utcnow().timestamp() * 1000) + (expiry_time - datetime.utcnow().timestamp() * 1000), + enable=True, flow="xtls-rprx-vision" + ) + + if not new_client_data: + raise Exception("Ошибка при создании клиента на новом сервере.") - if new_client_data: # Генерация нового ключа new_key = await link(session_new, server_id, client_id, email) # Обновляем запись в БД - await conn.execute('UPDATE keys SET server_id = $1, key = $2 WHERE client_id = $3', - server_id, new_key, client_id) + await conn.execute( + 'UPDATE keys SET server_id = $1, key = $2 WHERE client_id = $3', + server_id, new_key, client_id + ) - # Логируем удаление клиента со старого сервера + # Удаление клиента со старого сервера try: session_old = await login_with_credentials(current_server_id, ADMIN_USERNAME, ADMIN_PASSWORD) success_delete = await delete_client(session_old, current_server_id, client_id) - if success_delete: - response_message = (f"Ключ успешно перемещен на новый сервер.\n\n" - f"Удалите старый ключ из вашего приложения и используйте новый для подключения к новому серверу:\n" - f"
{new_key}")
- else:
- response_message = "Ошибка при удалении ключа с текущего сервера. Клиент не удален."
- print(f"Не удалось удалить клиента {client_id} с сервера {current_server_id}. Ответ API: {success_delete}")
+ if not success_delete:
+ raise Exception(f"Ошибка при удалении клиента с сервера {current_server_id}")
+
+ response_message = (
+ f"Ключ успешно перемещен на новый сервер.\n\n"
+ f"Удалите старый ключ из вашего приложения и используйте новый для подключения к новому серверу:\n"
+ f"{new_key}"
+ )
except Exception as e:
- response_message = f"Ошибка при удалении клиента с текущего сервера: {e}"
- print(f"Ошибка при авторизации на старом сервере {current_server_id}: {e}")
+ response_message = f"Ключ перемещен, но возникла ошибка при удалении клиента с текущего сервера: {e}"
+
else:
- response_message = "Ошибка при создании клиента на новом сервере."
- else:
- response_message = "Ключ не найден или уже удален."
+ response_message = "Ключ не найден или уже удален."
back_button = types.InlineKeyboardButton(text='Назад', callback_data='view_keys')
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[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'
+ )
finally:
await conn.close()
except Exception as e:
- await bot.edit_message_text(f"Ошибка при смене локации: {e}", chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode='HTML')
+ await bot.edit_message_text(
+ f"Ошибка при смене локации: {e}", chat_id=tg_id, message_id=callback_query.message.message_id, parse_mode='HTML'
+ )
await callback_query.answer()
diff --git a/handlers/profile.py b/handlers/profile.py
index 880ab930..56fbd69f 100644
--- a/handlers/profile.py
+++ b/handlers/profile.py
@@ -11,7 +11,6 @@ class ReplenishBalanceState(StatesGroup):
waiting_for_admin_confirmation = State()
router = Router()
-
async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext):
tg_id = callback_query.from_user.id
username = callback_query.from_user.full_name
@@ -33,7 +32,6 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta
f"Обязательно подпишитесь на канал здесь\n"
)
- # Если у клиента нет ключей, добавляем сообщение
if key_count == 0:
profile_message += "\nНажмите ➕Устройство снизу чтобы добавить устройство в VPN"
@@ -41,14 +39,14 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta
button_view_keys = InlineKeyboardButton(text='📱 Мои устройства', callback_data='view_keys')
button_replenish_balance = InlineKeyboardButton(text='💳 Пополнить баланс', callback_data='replenish_balance')
button_invite = InlineKeyboardButton(text='👥 Пригласить', callback_data='invite')
+ button_instructions = InlineKeyboardButton(text='📘 Инструкции', callback_data='instructions')
button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu')
keyboard = InlineKeyboardMarkup(inline_keyboard=[
- [button_create_key],
- [button_view_keys],
- [button_replenish_balance],
- [button_invite],
- [button_back]
+ [button_create_key, button_view_keys], # В один ряд "➕ Устройство" и "📱 Мои устройства"
+ [button_replenish_balance], # Отдельная кнопка для пополнения баланса
+ [button_invite, button_instructions], # В один ряд "👥 Пригласить" и "📘 Инструкции"
+ [button_back] # Отдельная кнопка "Назад"
])
except Exception as e:
diff --git a/handlers/start.py b/handlers/start.py
index 16bcfe49..a173d9da 100644
--- a/handlers/start.py
+++ b/handlers/start.py
@@ -18,14 +18,14 @@ async def send_welcome_message(chat_id: int):
welcome_text = (
"🎉 SoloNet — твой доступ в свободный интернет! 🌐✨\n\n"
"Наши преимущества:\n"
- "" + "" + "" "\n🎁 1 день бесплатно\n\n" "Перейдите в профиль для продолжения 👇" )" "🚀 Высокая скорость\n" "🔄 Стабильность\n" "🌍 Смена локаций\n" "💬 Отзывчивая поддержка\n" "📱💻 Для телефонов, компьютеров и планшетов\n" "💰 Реферальная программа: 25% от покупки\n" - "