Merge pull request #36 from Vladless/new1

основной текст в отдельном файле
This commit is contained in:
Vladislav Lisitsyn
2024-10-22 01:55:10 +03:00
committed by GitHub
14 changed files with 126 additions and 121 deletions
+1
View File
@@ -1,4 +1,5 @@
import json
import aiohttp
from config import SERVERS
+6 -3
View File
@@ -1,10 +1,13 @@
import subprocess
import os
import logging
import os
import subprocess
from datetime import datetime
from aiogram.types import BufferedInputFile
from bot import bot
from config import ADMIN_ID, DB_USER, DB_NAME, DB_PASSWORD
from config import ADMIN_ID, DB_NAME, DB_PASSWORD, DB_USER
async def backup_database():
+2 -1
View File
@@ -8,7 +8,8 @@ storage = MemoryStorage()
dp = Dispatcher(bot=bot, storage=storage)
router = Router()
from handlers import key_management, keys, notifications, pay, profile, start, backup_handler
from handlers import (backup_handler, key_management, keys, notifications, pay,
profile, start)
dp.include_router(start.router)
dp.include_router(profile.router)
+2 -1
View File
@@ -1,7 +1,8 @@
import json
import aiohttp
from config import SERVERS
from config import SERVERS
async def add_client(session, server_id: str, client_id: str, email: str, tg_id: str, limit_ip: int, total_gb: int, expiry_time: int, enable: bool, flow: str):
+1 -7
View File
@@ -126,7 +126,6 @@ async def update_balance(tg_id: int, amount: float):
WHERE tg_id = $2
''', amount, tg_id)
# Проверка и начисление реферального бонуса
await handle_referral_on_balance_update(tg_id, amount)
await conn.close()
@@ -157,7 +156,6 @@ async def add_referral(referred_tg_id: int, referrer_tg_id: int):
async def handle_referral_on_balance_update(tg_id: int, amount: float):
conn = await asyncpg.connect(DATABASE_URL)
# Проверяем, есть ли реферал в таблице
referral = await conn.fetchrow('''
SELECT referrer_tg_id FROM referrals WHERE referred_tg_id = $1
''', tg_id)
@@ -165,17 +163,13 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float):
if referral:
referrer_tg_id = referral['referrer_tg_id']
# Начисляем бонус (25% от платежа) пригласившему
bonus = amount * 0.25 # 25% от платежа
bonus = amount * 0.25
# Проверяем, чтобы бонус не был отрицательным
if bonus < 0:
bonus = 0
# Обновляем баланс пригласившего
await update_balance(referrer_tg_id, bonus)
# Обновляем статус бонуса для пригласившего
await conn.execute('''
UPDATE referrals SET reward_issued = TRUE
WHERE referrer_tg_id = $1 AND referred_tg_id = $2
+1 -1
View File
@@ -9,6 +9,6 @@ router = Router()
@router.message(Command('backup'))
async def backup_command(message: Message):
await message.answer("Запускаю бэкап базы данных...")
await backup_database() # Запуск функции бэкапа
await backup_database()
await message.answer("Бэкап завершен и отправлен админу.")
+7 -15
View File
@@ -1,28 +1,21 @@
import os
from aiogram import types
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.types import BufferedInputFile
from aiogram.types import (BufferedInputFile, InlineKeyboardButton,
InlineKeyboardMarkup)
from handlers.texts import INSTRUCTIONS
async def send_instructions(callback_query: types.CallbackQuery):
# Удаляем предыдущее сообщение
await callback_query.message.delete()
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"
" - **Для Windows:** [Hiddify Next](https://github.com/hiddify/hiddify-next/releases/latest/download/Hiddify-Windows-Setup-x64.Msix)\n\n"
"2. **Скопируйте предоставленный ключ**, который вы получили ранее.\n"
"3. **Откройте приложение и нажмите на плюсик сверху справа.**\n"
"4. **Выберите 'Вставить из буфера обмена' для добавления ключа.**\n\n"
"💬 Если у вас возникнут вопросы, не стесняйтесь обращаться в [поддержку](https://t.me/solonet_sup)."
INSTRUCTIONS
)
# Путь к изображению
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()
@@ -31,7 +24,6 @@ async def send_instructions(callback_query: types.CallbackQuery):
back_button = InlineKeyboardButton(text='🔙 Назад', callback_data='back_to_main')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
# Отправка изображения и сообщения
with open(image_path, 'rb') as image_from_buffer:
await callback_query.message.answer_photo(
BufferedInputFile(image_from_buffer.read(), filename="instructions.jpg"),
+16 -24
View File
@@ -1,25 +1,27 @@
import re
import uuid
from datetime import datetime, timedelta
from aiogram.filters import Command
import asyncpg
from aiogram import F, Router, types
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import (CallbackQuery, InlineKeyboardButton,
InlineKeyboardMarkup, Message)
from auth import link, login_with_credentials
from bot import dp, bot
from bot import bot, dp
from client import add_client
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS, ADMIN_ID
from config import (ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL,
SERVERS)
from database import add_connection, get_balance, store_key, update_balance
from handlers.backup_handler import backup_command
from handlers.instructions import send_instructions
from handlers.pay import ReplenishBalanceState, process_custom_amount_input
from handlers.profile import process_callback_view_profile
from handlers.start import start_command
from handlers.pay import ReplenishBalanceState, process_custom_amount_input
from handlers.backup_handler import backup_command
from handlers.texts import KEY, KEY_TRIAL, NULL_BALANCE
router = Router()
@@ -41,16 +43,14 @@ async def process_callback_create_key(callback_query: CallbackQuery, state: FSMC
try:
for server_id, server in SERVERS.items():
count = await conn.fetchval('SELECT COUNT(*) FROM keys WHERE server_id = $1', server_id)
# Используем 60 как максимальное количество ключей
percent_full = (count / 60) * 100 if count <= 60 else 100
server_name = f"{server['name']} ({percent_full:.1f}%)"
server_buttons.append([InlineKeyboardButton(text=server_name, callback_data=f'select_server|{server_id}')])
finally:
await conn.close()
# Добавляем кнопку "Назад"
button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='view_profile')
server_buttons.append([button_back]) # Кнопка "Назад" внизу списка серверов
server_buttons.append([button_back])
await callback_query.message.edit_text(
"<b>⚙️ Выберите сервер для создания ключа:</b>",
@@ -78,9 +78,7 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext):
if trial_status == 1:
await callback_query.message.edit_text(
"<b>⚠️ У вас уже был пробный ключ.</b>\n\n"
"Новый ключ будет выдан на <b>один месяц</b> и стоит <b>100 рублей</b>.\n\n"
"<i>Хотите продолжить?</i>",
KEY,
parse_mode="HTML",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='✅ Да, создать новый ключ', callback_data='confirm_create_new_key')],
@@ -90,8 +88,7 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext):
await state.update_data(creating_new_key=True)
else:
await callback_query.message.edit_text(
"<b>🎉 Вам будет выдан пробный ключ на 24 часа!</b>\n\n"
"<i>Пожалуйста, введите название для вашего пробного ключа:</i>",
KEY_TRIAL,
parse_mode="HTML"
)
await state.set_state(Form.waiting_for_key_name)
@@ -109,8 +106,7 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex
replenish_button = InlineKeyboardButton(text='Перейти в профиль', callback_data='view_profile')
keyboard = InlineKeyboardMarkup(inline_keyboard=[[replenish_button]])
await callback_query.message.edit_text(
"❗️ Недостаточно средств на балансе для создания нового ключа. "
"Пожалуйста, пополните баланс.",
NULL_BALANCE,
reply_markup=keyboard
)
await state.clear()
@@ -140,7 +136,6 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
# Получаем всех пользователей с не использованным пробным ключом
records = await conn.fetch('''
SELECT tg_id FROM connections WHERE trial = 0
''')
@@ -153,10 +148,8 @@ async def handle_send_trial_command(message: types.Message, state: FSMContext):
"Пожалуйста, воспользуйтесь им, чтобы протестировать наш сервис."
)
try:
# Отправляем сообщение каждому пользователю
await bot.send_message(chat_id=tg_id, text=trial_message)
except Exception as e:
# Если бот был заблокирован пользователем, просто пропускаем его
if "Forbidden: bot was blocked by the user" in str(e):
print(f"Бот заблокирован пользователем с tg_id: {tg_id}")
else:
@@ -179,11 +172,11 @@ async def send_message_to_all_clients(message: types.Message, state: FSMContext)
return
await message.answer("Введите текст сообщения, который вы хотите отправить всем клиентам:")
await state.set_state(Form.waiting_for_message) # Устанавливаем состояние ожидания сообщения
await state.set_state(Form.waiting_for_message)
@router.message(Form.waiting_for_message)
async def process_message_to_all(message: types.Message, state: FSMContext):
text_message = message.text # Получаем текст сообщения
text_message = message.text
try:
conn = await asyncpg.connect(DATABASE_URL)
@@ -203,14 +196,14 @@ async def process_message_to_all(message: types.Message, state: FSMContext):
finally:
await conn.close()
await state.clear() # Очистка состояния после отправки сообщения
await state.clear()
@router.message()
async def handle_text(message: types.Message, state: FSMContext):
current_state = await state.get_state()
if message.text in ["/send_to_all"]:
await send_message_to_all_clients(message, state) # Передаем состояние
await send_message_to_all_clients(message, state)
return
if message.text == "Мой профиль":
@@ -225,7 +218,6 @@ async def handle_text(message: types.Message, state: FSMContext):
return
if current_state == ReplenishBalanceState.entering_custom_amount.state:
# Здесь вызовите ваш обработчик ввода суммы
await process_custom_amount_input(message, state)
return
@@ -235,7 +227,7 @@ async def handle_text(message: types.Message, state: FSMContext):
if message.text == "/backup":
await backup_command(message)
elif current_state is None: # Если состояние не задано, обрабатываем обычные сообщения
elif current_state is None:
await start_command(message)
+3 -11
View File
@@ -1,3 +1,4 @@
import locale
from datetime import datetime, timedelta
import asyncpg
@@ -8,7 +9,7 @@ from bot import bot
from client import add_client, delete_client, extend_client_key
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SERVERS
from database import get_balance, update_balance
import locale
from handlers.texts import NO_KEYS
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8')
@@ -33,7 +34,6 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
button = types.InlineKeyboardButton(text=f"🔑 {key_name}", callback_data=f'view_key|{key_name}|{client_id}')
buttons.append([button])
# Добавляем кнопку "Назад" в конце списка
back_button = types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile')
buttons.append([back_button])
@@ -46,9 +46,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery):
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:
response_message = (
"<b>У вас нет подключённых устройств</b> 😕\n\n"
"➕ <b>Создайте первое устройство, чтобы подключиться к VPN</b>.\n"
"Ваши устройства будут отображаться здесь."
NO_KEYS
)
create_key_button = types.InlineKeyboardButton(text=' Создать ключ', callback_data='create_key')
back_button = types.InlineKeyboardButton(text='🔙 Назад', callback_data='view_profile')
@@ -99,7 +97,6 @@ async def process_callback_view_key(callback_query: types.CallbackQuery):
hours_left = time_left.seconds // 3600
days_left_message = f"Осталось часов: <b>{hours_left}</b>"
# Форматируем дату окончания в удобочитаемом виде
formatted_expiry_date = expiry_date.strftime('%d %B %Y года')
response_message = (f"🔑 <b>Ваш ключ:</b>\n<pre>{key}</pre>\n"
@@ -318,7 +315,6 @@ async def process_callback_select_server(callback_query: types.CallbackQuery):
try:
conn = await asyncpg.connect(DATABASE_URL)
try:
# Начинаем транзакцию с блокировкой строки по 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
@@ -333,7 +329,6 @@ async def process_callback_select_server(callback_query: types.CallbackQuery):
await callback_query.answer("Клиент уже на этом сервере.")
return
# Проверка наличия клиента на новом сервере перед добавлением
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,
@@ -344,16 +339,13 @@ async def process_callback_select_server(callback_query: types.CallbackQuery):
if not new_client_data:
raise Exception("Ошибка при создании клиента на новом сервере.")
# Генерация нового ключа
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
)
# Удаление клиента со старого сервера
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)
+5 -14
View File
@@ -1,14 +1,11 @@
from datetime import datetime, timedelta
import asyncpg
from aiogram import Bot, Router, types
from aiogram.filters import Command
from aiogram import Bot, Router
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from auth import login_with_credentials
from bot import bot
from client import delete_client, extend_client_key
from config import ADMIN_ID, ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
from database import get_balance, update_balance
from config import DATABASE_URL
from database import get_balance
router = Router()
@@ -23,7 +20,6 @@ async def notify_expiring_keys(bot: Bot):
threshold_time_10h = (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000
threshold_time_24h = (datetime.utcnow() + timedelta(days=1)).timestamp() * 1000
# Уведомления за 10 часов
records = await conn.fetch('''
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified = FALSE
@@ -35,17 +31,14 @@ async def notify_expiring_keys(bot: Bot):
expiry_time = record['expiry_time']
server_id = record['server_id']
# Логика формирования и отправки сообщения для уведомлений за 10 часов
message = f"🔔 Уведомление: Ваш ключ для сервера {server_id} истекает через 10 часов.\n" \
f"Email: {email}\n" \
f"Дата истечения: {datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}"
await bot.send_message(tg_id, message)
# Обновление статуса уведомления
await conn.execute('UPDATE keys SET notified = TRUE WHERE client_id = $1', record['client_id'])
# Уведомления за 24 часа
records_24h = await conn.fetch('''
SELECT tg_id, email, expiry_time, client_id, server_id FROM keys
WHERE expiry_time <= $1 AND expiry_time > $2 AND notified_24h = FALSE
@@ -63,7 +56,6 @@ async def notify_expiring_keys(bot: Bot):
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')
balance = await get_balance(tg_id)
# Логика формирования и отправки сообщения для уведомлений за 24 часа
message_24h = f"⏳ Уведомление: Ваш ключ для сервера {server_id} истекает через 24 часа.\n" \
f"Email: {email}\n" \
f"Осталось времени: {hours_left} часов\n" \
@@ -72,7 +64,6 @@ async def notify_expiring_keys(bot: Bot):
await bot.send_message(tg_id, message_24h)
# Обновление статуса уведомления за 24 часа
await conn.execute('UPDATE keys SET notified_24h = TRUE WHERE client_id = $1', record['client_id'])
finally:
+4 -8
View File
@@ -6,7 +6,7 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiohttp import web
from yookassa import Configuration, Payment
from yookassa import Configuration, Payment
from bot import bot
from config import YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
@@ -58,7 +58,6 @@ async def process_callback_replenish_balance(callback_query: types.CallbackQuery
if not exists:
await add_connection(tg_id, balance=0.0, trial=0)
# Клавиатура с фиксированными суммами и новой кнопкой "Ввести сумму"
amount_keyboard = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='100 RUB', callback_data='amount_100'), InlineKeyboardButton(text='300 RUB', callback_data='amount_300')],
[InlineKeyboardButton(text='600 RUB', callback_data='amount_600'), InlineKeyboardButton(text='1000 RUB', callback_data='amount_1000')],
@@ -99,7 +98,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
customer_name = callback_query.from_user.full_name
customer_id = callback_query.from_user.id
# Используем tg_id для email
customer_email = f"{customer_id}@solo.net"
payment = Payment.create({
@@ -116,7 +114,7 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
"receipt": {
"customer": {
"full_name": customer_name,
"email": customer_email, # Используем email в формате "tg_id@solo.net"
"email": customer_email,
"phone": "79000000000"
},
"items": [
@@ -201,7 +199,6 @@ async def process_enter_custom_amount(callback_query: types.CallbackQuery, state
@router.message(State(ReplenishBalanceState.entering_custom_amount))
async def process_custom_amount_input(message: types.Message, state: FSMContext):
# Проверяем, является ли введенное значение числом
if message.text.isdigit():
amount = int(message.text)
if amount <= 0:
@@ -211,7 +208,6 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
await state.update_data(amount=amount)
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation)
# Создание платежа
try:
payment = Payment.create({
"amount": {
@@ -227,8 +223,8 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
"receipt": {
"customer": {
"full_name": message.from_user.full_name,
"email": f"{message.from_user.id}@solo.net", # Используем tg_id как email
"phone": "79000000000" # Здесь можно использовать номер телефона клиента, если он доступен
"email": f"{message.from_user.id}@solo.net",
"phone": "79000000000"
},
"items": [
{
+8 -7
View File
@@ -1,10 +1,11 @@
from aiogram import Router, types
from aiogram.fsm.context import FSMContext
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from bot import bot
from database import get_balance, get_key_count, get_referral_stats
from bot import bot
from database import get_balance, get_key_count, get_referral_stats, get_keys
class ReplenishBalanceState(StatesGroup):
choosing_transfer_method = State()
@@ -43,10 +44,10 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta
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_instructions], # В один ряд "👥 Пригласить" и "📘 Инструкции"
[button_back] # Отдельная кнопка "Назад"
[button_create_key, button_view_keys],
[button_replenish_balance],
[button_invite, button_instructions],
[button_back]
])
except Exception as e:
+6 -29
View File
@@ -1,13 +1,15 @@
import os
from aiogram import Router
from aiogram.filters import Command
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import (BufferedInputFile, CallbackQuery,
InlineKeyboardButton, InlineKeyboardMarkup, Message)
from texts import ABOUT_VPN, WELCOME_TEXT
from bot import bot
from database import add_referral, check_connection_exists, add_connection
from config import CHANNEL_URL, SUPPORT_CHAT_URL, BOT_VERSION # Импорт BOT_VERSION
from config import CHANNEL_URL, SUPPORT_CHAT_URL
from database import add_connection, add_referral, check_connection_exists
router = Router()
@@ -15,20 +17,7 @@ class FeedbackState(StatesGroup):
waiting_for_feedback = State()
async def send_welcome_message(chat_id: int):
welcome_text = (
"<b>🎉 SoloNet — твой доступ в свободный интернет! 🌐✨</b>\n\n"
"<b>Наши преимущества:</b>\n"
"<blockquote>"
"🚀 <b>Высокая скорость</b>\n"
"🔄 <b>Стабильность</b>\n"
"🌍 <b>Смена локаций</b>\n"
"💬 <b>Отзывчивая поддержка</b>\n"
"📱💻 <b>Для телефонов, компьютеров и планшетов</b>\n"
"💰 <b>Реферальная программа: 25% от покупки</b>\n"
"</blockquote>"
"\n\n🎁 <b>1 день бесплатно!</b>\n\n" # Добавление версии бота
"<i>Перейдите в профиль для продолжения 👇</i>"
)
welcome_text = WELCOME_TEXT
image_path = os.path.join(os.path.dirname(__file__), 'pic.jpg')
@@ -71,19 +60,7 @@ async def start_command(message: Message):
@router.callback_query(lambda c: c.data == 'about_vpn')
async def handle_about_vpn(callback_query: CallbackQuery):
await callback_query.message.delete()
info_message = (
"*О VPN*\n\n"
"<b>🚀 Высокоскоростные серверы</b>\n"
"Мы используем высокоскоростные серверы в различных локациях для обеспечения стабильного и быстрого соединения.\n\n"
"<b>🔐 Безопасность данных</b>\n"
"Для защиты ваших данных мы применяем новейшие протоколы шифрования, которые гарантируют вашу конфиденциальность.\n\n"
"<b>🔑 Ваш ключ — ваша безопасность!</b>\n"
"Не передавайте своё шифрование сторонним лицам, чтобы избежать рисков.\n\n"
f"<i>Версия бота: {BOT_VERSION}</i>" # Добавление версии бота в информацию о VPN
)
info_message = ABOUT_VPN
button_back = InlineKeyboardButton(text='⬅️ Назад', callback_data='back_to_menu')
inline_keyboard_back = InlineKeyboardMarkup(inline_keyboard=[[button_back]])
+64
View File
@@ -0,0 +1,64 @@
from config import BOT_VERSION
WELCOME_TEXT = (
"<b>🎉 SoloNet — твой доступ в свободный интернет! 🌐✨</b>\n\n"
"<b>Наши преимущества:</b>\n"
"<blockquote>"
"🚀 <b>Высокая скорость</b>\n"
"🔄 <b>Стабильность</b>\n"
"🌍 <b>Смена локаций</b>\n"
"💬 <b>Отзывчивая поддержка</b>\n"
"📱💻 <b>Для телефонов, компьютеров и планшетов</b>\n"
"💰 <b>Реферальная программа: 25% от покупки</b>\n"
"</blockquote>"
"\n\n🎁 <b>1 день бесплатно!</b>\n\n"
"<i>Перейдите в профиль для продолжения 👇</i>"
)
ABOUT_VPN = (
"*О VPN*\n\n"
"<b>🚀 Высокоскоростные серверы</b>\n"
"Мы используем высокоскоростные серверы в различных локациях для обеспечения стабильного и быстрого соединения.\n\n"
"<b>🔐 Безопасность данных</b>\n"
"Для защиты ваших данных мы применяем новейшие протоколы шифрования, которые гарантируют вашу конфиденциальность.\n\n"
"<b>🔑 Ваш ключ — ваша безопасность!</b>\n"
"Не передавайте своё шифрование сторонним лицам, чтобы избежать рисков.\n\n"
f"<i>Версия бота: {BOT_VERSION}</i>"
)
KEY = (
"<b>⚠️ У вас уже был пробный ключ.</b>\n\n"
"Новый ключ будет выдан на <b>один месяц</b> и стоит <b>100 рублей</b>.\n\n"
"<i>Хотите продолжить?</i>"
)
KEY_TRIAL = (
"<b>🎉 Вам будет выдан пробный ключ на 24 часа!</b>\n\n"
"<i>Пожалуйста, введите название для вашего пробного ключа:</i>"
)
NULL_BALANCE = (
"❗️ Недостаточно средств на балансе для создания нового ключа. "
"Пожалуйста, пополните баланс."
)
NO_KEYS = (
"<b>У вас нет подключённых устройств</b> 😕\n\n"
"➕ <b>Создайте первое устройство, чтобы подключиться к VPN</b>.\n"
"Ваши устройства будут отображаться здесь."
)
INSTRUCTIONS = (
"*📋 Инструкции по использованию вашего ключа:*\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"
" - **Для Windows:** [Hiddify Next](https://github.com/hiddify/hiddify-next/releases/latest/download/Hiddify-Windows-Setup-x64.Msix)\n\n"
"2. **Скопируйте предоставленный ключ**, который вы получили ранее.\n"
"3. **Откройте приложение и нажмите на плюсик сверху справа.**\n"
"4. **Выберите 'Вставить из буфера обмена' для добавления ключа.**\n\n"
"💬 Если у вас возникнут вопросы, не стесняйтесь обращаться в [поддержку](https://t.me/solonet_sup)."
)