change_location/CASHBACK/logging/bug_fixes
This commit is contained in:
@@ -18,7 +18,7 @@ bot = Bot(token=API_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTM
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot=bot, storage=storage)
|
||||
|
||||
version = "4.0.0-preAlpha(14-dev)"
|
||||
version = "4.0.0-Alpha(01-dev)"
|
||||
|
||||
register_middleware(dp)
|
||||
|
||||
|
||||
+31
-7
@@ -5,7 +5,7 @@ from typing import Any
|
||||
import asyncpg
|
||||
import pytz
|
||||
|
||||
from config import DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
|
||||
from config import CASHBACK, DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
|
||||
from logger import logger
|
||||
|
||||
|
||||
@@ -492,6 +492,25 @@ async def get_keys_by_server(tg_id: int | None, server_id: str, session: Any):
|
||||
raise
|
||||
|
||||
|
||||
async def get_key_by_server(tg_id: int, client_id: str, session: Any):
|
||||
query = """
|
||||
SELECT
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
created_at,
|
||||
expiry_time,
|
||||
key,
|
||||
server_id,
|
||||
notified,
|
||||
notified_24h
|
||||
FROM keys
|
||||
WHERE tg_id = $1 AND client_id = $2
|
||||
"""
|
||||
record = await session.fetchrow(query, tg_id, client_id)
|
||||
return record
|
||||
|
||||
|
||||
async def get_balance(tg_id: int) -> float:
|
||||
"""
|
||||
Получает баланс пользователя из базы данных.
|
||||
@@ -521,15 +540,15 @@ async def get_balance(tg_id: int) -> float:
|
||||
|
||||
async def update_balance(tg_id: int, amount: float, session: Any = None):
|
||||
"""
|
||||
Обновляет баланс пользователя в базе данных.
|
||||
Обновляет баланс пользователя в базе данных с учетом кэшбека.
|
||||
|
||||
Args:
|
||||
tg_id (int): Telegram ID пользователя
|
||||
amount (float): Сумма для обновления баланса
|
||||
tg_id (int): Telegram ID пользователя.
|
||||
amount (float): Сумма для обновления баланса.
|
||||
session (Any, optional): Сессия базы данных. Если не передана, создается новая.
|
||||
|
||||
Raises:
|
||||
Exception: В случае ошибки при подключении к базе данных или обновлении баланса
|
||||
Exception: В случае ошибки при подключении к базе данных или обновлении баланса.
|
||||
"""
|
||||
conn = None
|
||||
try:
|
||||
@@ -537,16 +556,21 @@ async def update_balance(tg_id: int, amount: float, session: Any = None):
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
session = conn
|
||||
|
||||
extra = amount * (CASHBACK / 100.0) if CASHBACK > 0 else 0
|
||||
total_amount = amount + extra
|
||||
|
||||
await session.execute(
|
||||
"""
|
||||
UPDATE connections
|
||||
SET balance = balance + $1
|
||||
WHERE tg_id = $2
|
||||
""",
|
||||
amount,
|
||||
total_amount,
|
||||
tg_id,
|
||||
)
|
||||
logger.info(f"Баланс пользователя {tg_id} обновлен на сумму {amount}")
|
||||
logger.info(
|
||||
f"Баланс пользователя {tg_id} обновлен на сумму {total_amount} (исходная сумма {amount}, кэшбек {extra})"
|
||||
)
|
||||
|
||||
await handle_referral_on_balance_update(tg_id, amount)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from config import USERNAME_BOT
|
||||
from database import create_coupon, delete_coupon, get_all_coupons
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.coupons_kb import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb
|
||||
@@ -131,7 +132,8 @@ async def handle_coupons_list(callback_query: CallbackQuery, session: Any):
|
||||
f"🏷️ <b>Код:</b> {coupon['code']}\n"
|
||||
f"💰 <b>Сумма:</b> {coupon['amount']} рублей\n"
|
||||
f"🔢 <b>Лимит использования:</b> {coupon['usage_limit']} раз\n"
|
||||
f"✅ <b>Использовано:</b> {coupon['usage_count']} раз\n\n"
|
||||
f"✅ <b>Использовано:</b> {coupon['usage_count']} раз\n"
|
||||
f"🔗 <b>Ссылка:</b> <code>https://t.me/{USERNAME_BOT}?start=coupons_{coupon['code']}</code>\n"
|
||||
)
|
||||
|
||||
await callback_query.message.edit_text(text=coupon_list, reply_markup=kb)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from aiogram import F, Router, types
|
||||
from aiogram import F, Router
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from filters.admin import IsAdminFilter
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
|
||||
from keyboards.admin.sender_kb import AdminSenderCallback, build_sender_kb, build_clusters_kb
|
||||
from keyboards.admin.sender_kb import AdminSenderCallback, build_clusters_kb, build_sender_kb
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
@@ -106,6 +106,13 @@ async def handle_server_name_input(message: Message, state: FSMContext, session:
|
||||
|
||||
server_name = message.text.strip()
|
||||
|
||||
if len(server_name) > 14:
|
||||
await message.answer(
|
||||
text="❌ Имя сервера не должно превышать 10 символов. Попробуйте снова.",
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
)
|
||||
return
|
||||
|
||||
user_data = await state.get_data()
|
||||
cluster_name = user_data.get("cluster_name")
|
||||
|
||||
@@ -234,7 +241,7 @@ async def handle_inbound_id_input(message: Message, state: FSMContext):
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "clusters_manage"), IsAdminFilter())
|
||||
async def handle_clusters_manage(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -249,7 +256,7 @@ async def handle_clusters_manage(
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_availability"), IsAdminFilter())
|
||||
async def handle_servers_availability(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -325,7 +332,7 @@ async def handle_servers_delete(callback_query: CallbackQuery, callback_data: Ad
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_delete_confirm"), IsAdminFilter())
|
||||
async def handle_servers_delete_confirm(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
):
|
||||
server_name = callback_data.data
|
||||
|
||||
@@ -338,7 +345,7 @@ async def handle_servers_delete_confirm(
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "servers_add"), IsAdminFilter())
|
||||
async def handle_servers_add(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, state: FSMContext
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, state: FSMContext
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -360,7 +367,7 @@ async def handle_servers_add(
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "clusters_backup"), IsAdminFilter())
|
||||
async def handle_clusters_backup(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -388,7 +395,7 @@ async def handle_clusters_backup(
|
||||
|
||||
@router.callback_query(AdminServerEditorCallback.filter(F.action == "clusters_sync"), IsAdminFilter())
|
||||
async def handle_clusters_backup(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminServerEditorCallback, session: Any
|
||||
):
|
||||
cluster_name = callback_data.data
|
||||
|
||||
@@ -426,11 +433,10 @@ async def handle_clusters_backup(
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=f"✅ Ключи успешно синхронизированы для кластера {cluster_name}",
|
||||
reply_markup=build_admin_back_kb("servers")
|
||||
reply_markup=build_admin_back_kb("servers"),
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка синхронизации ключей в кластере {cluster_name}: {e}")
|
||||
await callback_query.message.answer(
|
||||
text=f"❌ Произошла ошибка при синхронизации: {e}",
|
||||
reply_markup=build_admin_back_kb("servers")
|
||||
text=f"❌ Произошла ошибка при синхронизации: {e}", reply_markup=build_admin_back_kb("servers")
|
||||
)
|
||||
|
||||
@@ -9,8 +9,16 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
|
||||
from config import TOTAL_GB
|
||||
from database import delete_key, delete_user_data, get_client_id_by_email, get_servers, update_key_expiry, update_trial, \
|
||||
get_balance, update_balance
|
||||
from database import (
|
||||
delete_key,
|
||||
delete_user_data,
|
||||
get_balance,
|
||||
get_client_id_by_email,
|
||||
get_servers,
|
||||
update_balance,
|
||||
update_key_expiry,
|
||||
update_trial,
|
||||
)
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import (
|
||||
delete_key_from_cluster,
|
||||
@@ -132,13 +140,12 @@ async def handle_key_name_input(message: Message, state: FSMContext, session: An
|
||||
IsAdminFilter(),
|
||||
)
|
||||
async def handle_send_message(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
|
||||
await callback_query.message.edit_text(
|
||||
text="✉️ Введите текст сообщения, которое вы хотите отправить пользователю:",
|
||||
reply_markup=build_editor_kb(tg_id)
|
||||
text="✉️ Введите текст сообщения, которое вы хотите отправить пользователю:", reply_markup=build_editor_kb(tg_id)
|
||||
)
|
||||
|
||||
await state.update_data(tg_id=tg_id)
|
||||
@@ -164,7 +171,7 @@ async def handle_message_text_input(message: Message, state: FSMContext):
|
||||
IsAdminFilter(),
|
||||
)
|
||||
async def handle_trial_restore(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
|
||||
@@ -215,7 +222,7 @@ async def handle_balance_change(callback_query: CallbackQuery, callback_data: Ad
|
||||
|
||||
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_balance_add"), IsAdminFilter())
|
||||
async def handle_balance_add(
|
||||
callback_query: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext, session: Any
|
||||
callback_query: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext, session: Any
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
amount = callback_data.data
|
||||
@@ -289,8 +296,10 @@ async def handle_balance_input(message: Message, state: FSMContext, session: Any
|
||||
|
||||
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_key_edit"), IsAdminFilter())
|
||||
async def handle_key_edit(
|
||||
callback_query: CallbackQuery, callback_data: AdminUserEditorCallback | AdminUserKeyEditorCallback,
|
||||
session: Any, update: bool = False
|
||||
callback_query: CallbackQuery,
|
||||
callback_data: AdminUserEditorCallback | AdminUserKeyEditorCallback,
|
||||
session: Any,
|
||||
update: bool = False,
|
||||
):
|
||||
email = callback_data.data
|
||||
key_details = await get_key_details(email, session)
|
||||
@@ -328,7 +337,7 @@ async def handle_change_expiry(callback_query: CallbackQuery, callback_data: Adm
|
||||
|
||||
@router.callback_query(AdminUserKeyEditorCallback.filter(F.action == "add"), IsAdminFilter())
|
||||
async def handle_expiry_add(
|
||||
callback_query: CallbackQuery, callback_data: AdminUserKeyEditorCallback, state: FSMContext, session: Any
|
||||
callback_query: CallbackQuery, callback_data: AdminUserKeyEditorCallback, state: FSMContext, session: Any
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
email = callback_data.data
|
||||
@@ -359,7 +368,7 @@ async def handle_expiry_add(
|
||||
|
||||
@router.callback_query(AdminUserKeyEditorCallback.filter(F.action == "take"), IsAdminFilter())
|
||||
async def handle_expiry_take(
|
||||
callback_query: CallbackQuery, callback_data: AdminUserKeyEditorCallback, state: FSMContext
|
||||
callback_query: CallbackQuery, callback_data: AdminUserKeyEditorCallback, state: FSMContext
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
email = callback_data.data
|
||||
@@ -375,7 +384,7 @@ async def handle_expiry_take(
|
||||
|
||||
@router.callback_query(AdminUserKeyEditorCallback.filter(F.action == "set"), IsAdminFilter())
|
||||
async def handle_expiry_set(
|
||||
callback_query: CallbackQuery, callback_data: AdminUserKeyEditorCallback, state: FSMContext, session: Any
|
||||
callback_query: CallbackQuery, callback_data: AdminUserKeyEditorCallback, state: FSMContext, session: Any
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
email = callback_data.data
|
||||
@@ -480,7 +489,7 @@ async def handle_delete_key(callback_query: CallbackQuery, callback_data: AdminU
|
||||
|
||||
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_delete_key_confirm"), IsAdminFilter())
|
||||
async def handle_delete_key_confirm(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any
|
||||
):
|
||||
email = callback_data.data
|
||||
record = await session.fetchrow("SELECT client_id FROM keys WHERE email = $1", email)
|
||||
@@ -516,7 +525,7 @@ async def handle_delete_user(callback_query: CallbackQuery, callback_data: Admin
|
||||
|
||||
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_delete_user_confirm"), IsAdminFilter())
|
||||
async def handle_delete_user_confirm(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any
|
||||
):
|
||||
tg_id = callback_data.tg_id
|
||||
key_records = await session.fetch("SELECT email, client_id FROM keys WHERE tg_id = $1", tg_id)
|
||||
@@ -548,13 +557,13 @@ async def handle_delete_user_confirm(
|
||||
|
||||
@router.callback_query(AdminUserEditorCallback.filter(F.action == "users_editor"), IsAdminFilter())
|
||||
async def handle_editor(
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext, session: Any
|
||||
callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext, session: Any
|
||||
):
|
||||
await process_user_search(callback_query.message, state, session, callback_data.tg_id, callback_data.edit)
|
||||
|
||||
|
||||
async def process_user_search(
|
||||
message: types.Message, state: FSMContext, session: Any, tg_id: int, edit: bool = False
|
||||
message: types.Message, state: FSMContext, session: Any, tg_id: int, edit: bool = False
|
||||
) -> None:
|
||||
await state.clear()
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
from aiogram import F, Router, types
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, LabeledPrice, Message, PreCheckoutQuery
|
||||
|
||||
+170
-61
@@ -6,12 +6,15 @@ from typing import Any
|
||||
import pytz
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from bot import bot
|
||||
from client import delete_client
|
||||
from config import (
|
||||
ADMIN_PASSWORD,
|
||||
ADMIN_USERNAME,
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DOWNLOAD_ANDROID,
|
||||
@@ -25,6 +28,7 @@ from config import (
|
||||
)
|
||||
from database import (
|
||||
create_temporary_data,
|
||||
delete_key,
|
||||
get_balance,
|
||||
get_key_details,
|
||||
get_trial,
|
||||
@@ -48,22 +52,21 @@ from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
class Form(StatesGroup):
|
||||
waiting_for_server_selection = State()
|
||||
waiting_for_key_name = State()
|
||||
viewing_profile = State()
|
||||
waiting_for_message = State()
|
||||
|
||||
class Form(FSMContext):
|
||||
waiting_for_server_selection = "waiting_for_server_selection"
|
||||
waiting_for_key_name = "waiting_for_key_name"
|
||||
viewing_profile = "viewing_profile"
|
||||
waiting_for_message = "waiting_for_message"
|
||||
|
||||
|
||||
@router.callback_query(F.data == "create_key")
|
||||
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
|
||||
logger.info(f"User {tg_id} confirmed creation of a new key.")
|
||||
|
||||
logger.info(f"Balance for user {tg_id} is sufficient. Proceeding with key creation.")
|
||||
|
||||
await handle_key_creation(tg_id, state, session, callback_query)
|
||||
|
||||
|
||||
@@ -74,37 +77,30 @@ async def handle_key_creation(
|
||||
message_or_query: Message | CallbackQuery,
|
||||
):
|
||||
"""Создание ключа с учётом выбора тарифного плана."""
|
||||
current_time = datetime.utcnow()
|
||||
current_time = datetime.now(moscow_tz)
|
||||
trial_status = await get_trial(tg_id, session)
|
||||
|
||||
if trial_status == 0:
|
||||
expiry_time = current_time + timedelta(days=TRIAL_TIME)
|
||||
logger.info(f"Assigned 1-day trial to user {tg_id}.")
|
||||
|
||||
logger.info(f"Assigned {TRIAL_TIME}-дневный пробный период пользователю {tg_id}.")
|
||||
await session.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id)
|
||||
await create_key(tg_id, expiry_time, state, session, message_or_query)
|
||||
else:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for index, (plan_id, price) in enumerate(RENEWAL_PRICES.items()):
|
||||
discount_text = ""
|
||||
|
||||
if plan_id in DISCOUNTS:
|
||||
discount_percentage = DISCOUNTS[plan_id]
|
||||
discount_text = f" ({discount_percentage}% скидка)"
|
||||
|
||||
if index == len(RENEWAL_PRICES) - 1:
|
||||
discount_text = f" ({discount_percentage}% 🔥)"
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"📅 {plan_id} мес. - {price}₽{discount_text}",
|
||||
callback_data=f"select_plan_{plan_id}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await message_or_query.message.answer(
|
||||
"💳 Выберите тарифный план для создания нового ключа:",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -118,17 +114,13 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
plan_id = callback_query.data.split("_")[-1]
|
||||
plan_price = RENEWAL_PRICES.get(plan_id)
|
||||
|
||||
if plan_price is None:
|
||||
await callback_query.message.answer("🚫 Неверный тарифный план.")
|
||||
return
|
||||
|
||||
duration_days = int(plan_id) * 30
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
if balance < plan_price:
|
||||
required_amount = plan_price - balance
|
||||
|
||||
await create_temporary_data(
|
||||
session,
|
||||
tg_id,
|
||||
@@ -140,7 +132,6 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
"required_amount": required_amount,
|
||||
},
|
||||
)
|
||||
|
||||
if USE_NEW_PAYMENT_FLOW == "YOOKASSA":
|
||||
await process_custom_amount_input(callback_query, session)
|
||||
elif USE_NEW_PAYMENT_FLOW == "ROBOKASSA":
|
||||
@@ -149,14 +140,12 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"💳 Недостаточно средств. Для продолжения необходимо пополнить баланс на {required_amount}₽.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
return
|
||||
|
||||
expiry_time = datetime.utcnow() + timedelta(days=duration_days)
|
||||
expiry_time = datetime.now(moscow_tz) + timedelta(days=duration_days)
|
||||
await create_key(tg_id, expiry_time, None, session, callback_query)
|
||||
await update_balance(tg_id, -plan_price, session)
|
||||
|
||||
@@ -167,18 +156,15 @@ async def create_key(
|
||||
state: FSMContext | None,
|
||||
session: Any,
|
||||
message_or_query: Message | CallbackQuery | None = None,
|
||||
old_key_name: str = None,
|
||||
):
|
||||
"""Создаёт ключ с заданным сроком действия."""
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
expiry_time = expiry_time.astimezone(moscow_tz)
|
||||
|
||||
if USE_COUNTRY_SELECTION:
|
||||
if USE_COUNTRY_SELECTION and message_or_query is not None:
|
||||
logger.info("[Country Selection] USE_COUNTRY_SELECTION включен.")
|
||||
|
||||
logger.info("[Country Selection] Получение наименее загруженного кластера.")
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
logger.info(f"[Country Selection] Наименее загруженный кластер: {least_loaded_cluster}")
|
||||
|
||||
logger.info(f"[Country Selection] Получение списка серверов для кластера {least_loaded_cluster}.")
|
||||
servers = await session.fetch(
|
||||
"SELECT server_name FROM servers WHERE cluster_name = $1",
|
||||
@@ -188,11 +174,14 @@ async def create_key(
|
||||
logger.info(f"[Country Selection] Список серверов: {countries}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
ts = int(expiry_time.timestamp())
|
||||
for country in countries:
|
||||
callback_data = f"select_country|{country}|{expiry_time.isoformat()}"
|
||||
if old_key_name:
|
||||
callback_data = f"select_country|{country}|{ts}|{old_key_name}"
|
||||
else:
|
||||
callback_data = f"select_country|{country}|{ts}"
|
||||
builder.row(InlineKeyboardButton(text=country, callback_data=callback_data))
|
||||
logger.info(f"[Country Selection] Добавлена кнопка для страны: {country} с callback_data: {callback_data}")
|
||||
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="profile"))
|
||||
logger.info("[Country Selection] Добавлена кнопка '⬅️ Назад'.")
|
||||
|
||||
@@ -227,7 +216,6 @@ async def create_key(
|
||||
while True:
|
||||
key_name = generate_random_email()
|
||||
logger.info(f"[Key Generation] Сгенерировано имя ключа: {key_name} для пользователя {tg_id}")
|
||||
|
||||
existing_key = await get_key_details(key_name, session)
|
||||
if not existing_key:
|
||||
break
|
||||
@@ -240,7 +228,6 @@ async def create_key(
|
||||
|
||||
try:
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
|
||||
tasks = [
|
||||
asyncio.create_task(
|
||||
create_key_on_cluster(
|
||||
@@ -252,10 +239,8 @@ async def create_key(
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
logger.info(f"[Key Creation] Ключ создан на кластере {least_loaded_cluster} для пользователя {tg_id}")
|
||||
|
||||
await store_key(
|
||||
tg_id,
|
||||
client_id,
|
||||
@@ -266,10 +251,8 @@ async def create_key(
|
||||
session,
|
||||
)
|
||||
logger.info(f"[Database] Ключ сохранён в базе данных для пользователя {tg_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[Error] Ошибка при создании ключа для пользователя {tg_id}: {e}")
|
||||
|
||||
error_message = "❌ Произошла ошибка при создании подписки. Пожалуйста, попробуйте снова."
|
||||
if isinstance(message_or_query, Message):
|
||||
await message_or_query.answer(error_message)
|
||||
@@ -297,40 +280,126 @@ async def create_key(
|
||||
|
||||
remaining_time = expiry_time - datetime.now(moscow_tz)
|
||||
days = remaining_time.days
|
||||
key_message = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
|
||||
key_message_text = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
|
||||
|
||||
if isinstance(message_or_query, Message):
|
||||
await message_or_query.answer(key_message, reply_markup=builder.as_markup())
|
||||
await message_or_query.answer(key_message_text, reply_markup=builder.as_markup())
|
||||
elif isinstance(message_or_query, CallbackQuery):
|
||||
await message_or_query.message.answer(key_message, reply_markup=builder.as_markup())
|
||||
await message_or_query.message.answer(key_message_text, reply_markup=builder.as_markup())
|
||||
else:
|
||||
await bot.send_message(chat_id=tg_id, text=key_message, reply_markup=builder.as_markup())
|
||||
await bot.send_message(chat_id=tg_id, text=key_message_text, reply_markup=builder.as_markup())
|
||||
|
||||
if state:
|
||||
await state.clear()
|
||||
logger.info(f"[FSM] Состояние пользователя {tg_id} очищено")
|
||||
|
||||
if old_key_name:
|
||||
try:
|
||||
old_record = await get_key_details(old_key_name, session)
|
||||
if old_record is not None:
|
||||
old_client_id = old_record["client_id"]
|
||||
old_email = old_record["email"]
|
||||
server_name = old_record.get("server_id")
|
||||
|
||||
if server_name:
|
||||
server_info = await session.fetchrow(
|
||||
"SELECT api_url, inbound_id, server_name FROM servers WHERE server_name = $1",
|
||||
server_name,
|
||||
)
|
||||
if server_info:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
deletion_success = await delete_client(
|
||||
xui,
|
||||
server_info["inbound_id"],
|
||||
old_email,
|
||||
old_client_id,
|
||||
)
|
||||
if deletion_success:
|
||||
logger.info(f"Клиент с ID {old_client_id} успешно удалён с сервера.")
|
||||
else:
|
||||
logger.warning(f"Не удалось удалить клиента с ID {old_client_id} с сервера.")
|
||||
else:
|
||||
logger.warning(f"Информация о сервере {server_name} не найдена в БД.")
|
||||
else:
|
||||
logger.warning("Имя сервера для старого ключа не указано.")
|
||||
|
||||
await delete_key(old_client_id, session)
|
||||
logger.info(f"Старый ключ {old_key_name} (client_id: {old_client_id}) удалён для пользователя {tg_id}.")
|
||||
else:
|
||||
logger.warning(f"Запись для старого ключа {old_key_name} не найдена.")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении старого ключа {old_key_name} для пользователя {tg_id}: {e}")
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("change_location|"))
|
||||
async def change_location_callback(callback_query: CallbackQuery, session: Any):
|
||||
try:
|
||||
data = callback_query.data.split("|")
|
||||
if len(data) < 2:
|
||||
await callback_query.answer("❌ Некорректные данные", show_alert=True)
|
||||
return
|
||||
|
||||
old_key_name = data[1]
|
||||
record = await get_key_details(old_key_name, session)
|
||||
if not record:
|
||||
await callback_query.answer("❌ Ключ не найден", show_alert=True)
|
||||
return
|
||||
|
||||
expiry_timestamp = record["expiry_time"]
|
||||
ts = int(expiry_timestamp / 1000)
|
||||
expiry_time = datetime.fromtimestamp(ts, tz=moscow_tz)
|
||||
|
||||
servers = await session.fetch("SELECT server_name FROM servers")
|
||||
countries = [row["server_name"] for row in servers]
|
||||
logger.info(f"Доступные страны для смены локации: {countries}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
for country in countries:
|
||||
callback_data = f"select_country|{country}|{ts}|{old_key_name}"
|
||||
builder.row(InlineKeyboardButton(text=country, callback_data=callback_data))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{old_key_name}"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"🌍 Пожалуйста, выберите новую локацию для вашей подписки:", reply_markup=builder.as_markup()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при смене локации для пользователя {callback_query.from_user.id}: {e}")
|
||||
await callback_query.answer("❌ Ошибка смены локации. Попробуйте снова.", show_alert=True)
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("select_country|"))
|
||||
async def handle_country_selection(callback_query: CallbackQuery, session: Any):
|
||||
"""Обработчик выбора страны."""
|
||||
"""
|
||||
Обрабатывает выбор страны.
|
||||
Формат callback data:
|
||||
select_country|{selected_country}|{ts} [|{old_key_name} (опционально)]
|
||||
Если передан old_key_name – значит, происходит смена локации.
|
||||
"""
|
||||
data = callback_query.data.split("|")
|
||||
if len(data) < 3:
|
||||
await callback_query.message.answer("❌ Некорректные данные. Попробуйте снова.")
|
||||
return
|
||||
|
||||
selected_country = data[1]
|
||||
expiry_time_str = data[2]
|
||||
|
||||
tg_id = callback_query.from_user.id
|
||||
|
||||
logger.info(f"Пользователь {tg_id} выбрал страну: {selected_country}")
|
||||
logger.info(f"Получено время истечения: {expiry_time_str}")
|
||||
|
||||
try:
|
||||
expiry_time = datetime.fromisoformat(expiry_time_str)
|
||||
ts = int(data[2])
|
||||
except ValueError:
|
||||
logger.error(f"Ошибка преобразования времени истечения: {expiry_time_str}")
|
||||
await callback_query.message.answer("❌ Некорректное время истечения. Попробуйте снова.")
|
||||
return
|
||||
|
||||
await finalize_key_creation(tg_id, expiry_time, selected_country, None, session, callback_query)
|
||||
expiry_time = datetime.fromtimestamp(ts, tz=moscow_tz)
|
||||
|
||||
old_key_name = data[3] if len(data) > 3 else None
|
||||
|
||||
tg_id = callback_query.from_user.id
|
||||
logger.info(f"Пользователь {tg_id} выбрал страну: {selected_country}")
|
||||
logger.info(f"Получено время истечения (timestamp): {ts}")
|
||||
|
||||
await finalize_key_creation(tg_id, expiry_time, selected_country, None, session, callback_query, old_key_name)
|
||||
|
||||
|
||||
async def finalize_key_creation(
|
||||
@@ -340,15 +409,16 @@ async def finalize_key_creation(
|
||||
state: FSMContext | None,
|
||||
session: Any,
|
||||
callback_query: CallbackQuery,
|
||||
old_key_name: str = None,
|
||||
):
|
||||
"""Финализирует создание ключа с выбранной страной."""
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
"""Финализирует создание ключа с выбранной страной.
|
||||
Если old_key_name передан, после создания нового ключа старый будет удалён.
|
||||
"""
|
||||
expiry_time = expiry_time.astimezone(moscow_tz)
|
||||
|
||||
while True:
|
||||
key_name = generate_random_email()
|
||||
logger.info(f"Generated random key name for user {tg_id}: {key_name}")
|
||||
|
||||
existing_key = await get_key_details(key_name, session)
|
||||
if not existing_key:
|
||||
break
|
||||
@@ -369,7 +439,6 @@ async def finalize_key_creation(
|
||||
raise ValueError(f"Сервер {selected_country} не найден.")
|
||||
|
||||
semaphore = asyncio.Semaphore(2)
|
||||
|
||||
await create_client_on_server(
|
||||
server_info=server_info,
|
||||
tg_id=tg_id,
|
||||
@@ -380,7 +449,6 @@ async def finalize_key_creation(
|
||||
)
|
||||
|
||||
logger.info(f"Key created on server {selected_country} for user {tg_id}.")
|
||||
|
||||
await store_key(
|
||||
tg_id,
|
||||
client_id,
|
||||
@@ -414,9 +482,50 @@ async def finalize_key_creation(
|
||||
|
||||
remaining_time = expiry_time - datetime.now(moscow_tz)
|
||||
days = remaining_time.days
|
||||
key_message = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
|
||||
key_message_text = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
|
||||
|
||||
await callback_query.message.answer(key_message, reply_markup=builder.as_markup())
|
||||
await callback_query.message.answer(key_message_text, reply_markup=builder.as_markup())
|
||||
|
||||
if state:
|
||||
await state.clear()
|
||||
|
||||
if old_key_name:
|
||||
try:
|
||||
old_record = await get_key_details(old_key_name, session)
|
||||
if old_record is not None:
|
||||
old_client_id = old_record["client_id"]
|
||||
old_email = old_record["email"]
|
||||
server_name = old_record.get("server_id")
|
||||
|
||||
if server_name:
|
||||
server_info = await session.fetchrow(
|
||||
"SELECT api_url, inbound_id, server_name FROM servers WHERE server_name = $1",
|
||||
server_name,
|
||||
)
|
||||
if server_info:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
deletion_success = await delete_client(
|
||||
xui,
|
||||
server_info["inbound_id"],
|
||||
old_email,
|
||||
old_client_id,
|
||||
)
|
||||
if deletion_success:
|
||||
logger.info(f"Клиент с ID {old_client_id} успешно удалён с сервера.")
|
||||
else:
|
||||
logger.warning(f"Не удалось удалить клиента с ID {old_client_id} с сервера.")
|
||||
else:
|
||||
logger.warning(f"Информация о сервере {server_name} не найдена в БД.")
|
||||
else:
|
||||
logger.warning("Имя сервера для старого ключа не указано.")
|
||||
|
||||
await delete_key(old_client_id, session)
|
||||
logger.info(f"Старый ключ {old_key_name} (client_id: {old_client_id}) удалён для пользователя {tg_id}.")
|
||||
else:
|
||||
logger.warning(f"Запись для старого ключа {old_key_name} не найдена.")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении старого ключа {old_key_name} для пользователя {tg_id}: {e}")
|
||||
|
||||
+43
-15
@@ -31,9 +31,9 @@ from database import (
|
||||
create_temporary_data,
|
||||
delete_key,
|
||||
get_balance,
|
||||
get_key_by_server,
|
||||
get_key_details,
|
||||
get_keys,
|
||||
get_keys_by_server,
|
||||
get_servers,
|
||||
update_balance,
|
||||
update_key_expiry,
|
||||
@@ -162,6 +162,7 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
key = record["key"]
|
||||
expiry_time = record["expiry_time"]
|
||||
server_name = record["server_id"]
|
||||
country = server_name
|
||||
expiry_date = datetime.utcfromtimestamp(expiry_time / 1000)
|
||||
current_date = datetime.utcnow()
|
||||
time_left = expiry_date - current_date
|
||||
@@ -180,7 +181,10 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
)
|
||||
|
||||
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||
response_message = key_message(key, formatted_expiry_date, days_left_message, server_name)
|
||||
|
||||
response_message = key_message(
|
||||
key, formatted_expiry_date, days_left_message, server_name, country if USE_COUNTRY_SELECTION else None
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
@@ -214,6 +218,12 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
)
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}"))
|
||||
|
||||
if USE_COUNTRY_SELECTION:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🌍 Сменить локацию", callback_data=f"change_location|{key_name}")
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
@@ -386,7 +396,7 @@ async def process_callback_renew_plan(callback_query: CallbackQuery, session: An
|
||||
total_gb = TOTAL_GB * gb_multiplier.get(plan, 1) if TOTAL_GB > 0 else 0
|
||||
|
||||
try:
|
||||
record = await get_keys_by_server(tg_id, client_id, session)
|
||||
record = await get_key_by_server(tg_id, client_id, session)
|
||||
|
||||
if record:
|
||||
email = record["email"]
|
||||
@@ -452,53 +462,71 @@ async def process_callback_renew_plan(callback_query: CallbackQuery, session: An
|
||||
|
||||
|
||||
async def complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_gb, cost, callback_query, plan):
|
||||
logger.info(
|
||||
f"[RENEW] Starting complete_key_renewal with parameters: "
|
||||
f"tg_id={tg_id}, client_id={client_id}, email={email}, "
|
||||
f"new_expiry_time={new_expiry_time}, total_gb={total_gb}, cost={cost}, "
|
||||
f"callback_query={'present' if callback_query else 'None'}, plan={plan}"
|
||||
)
|
||||
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(months=plan)
|
||||
logger.info(f"[RENEW] Constructed response message: {response_message}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
if callback_query:
|
||||
logger.info("[RENEW] Sending response via callback_query.message.answer()")
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
else:
|
||||
logger.info("[RENEW] Sending response via bot.send_message()")
|
||||
await bot.send_message(tg_id, response_message, reply_markup=builder.as_markup())
|
||||
|
||||
logger.info("[RENEW] Connecting to database...")
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
key_info = await get_key_details(email, conn)
|
||||
logger.info("[RENEW] Connected to database.")
|
||||
|
||||
logger.info(f"[RENEW] Retrieving key details for email: {email}")
|
||||
key_info = await get_key_details(email, conn)
|
||||
if not key_info:
|
||||
logger.error(f"[RENEW] Ключ с client_id {client_id} для пользователя {tg_id} не найден.")
|
||||
await conn.close()
|
||||
return
|
||||
logger.info(f"[RENEW] Retrieved key_info: {key_info}")
|
||||
|
||||
server_id = key_info["server_id"]
|
||||
logger.info(f"[RENEW] Using server_id: {server_id}")
|
||||
|
||||
if USE_COUNTRY_SELECTION:
|
||||
logger.info(f"[RENEW] USE_COUNTRY_SELECTION is enabled. Checking cluster info for server_id: {server_id}")
|
||||
cluster_info = await check_server_name_by_cluster(server_id, conn)
|
||||
|
||||
if not cluster_info:
|
||||
logger.error(f"[RENEW] Сервер {server_id} не найден в таблице servers.")
|
||||
await conn.close()
|
||||
return
|
||||
|
||||
cluster_id = cluster_info["cluster_name"]
|
||||
logger.info(f"[RENEW] Retrieved cluster info: {cluster_info}. Using cluster_id: {cluster_id}")
|
||||
else:
|
||||
cluster_id = server_id
|
||||
logger.info(f"[RENEW] USE_COUNTRY_SELECTION is disabled. Using server_id as cluster_id: {cluster_id}")
|
||||
|
||||
logger.info(f"[RENEW] Запуск продления ключа для пользователя {tg_id} на {plan} мес. в кластере {cluster_id}.")
|
||||
|
||||
async def renew_key_on_cluster():
|
||||
await renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
new_expiry_time,
|
||||
total_gb,
|
||||
logger.info(
|
||||
f"[RENEW] Starting renew_key_on_cluster with parameters: "
|
||||
f"cluster_id={cluster_id}, email={email}, client_id={client_id}, "
|
||||
f"new_expiry_time={new_expiry_time}, total_gb={total_gb}"
|
||||
)
|
||||
|
||||
await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, total_gb)
|
||||
logger.info("[RENEW] renew_key_in_cluster completed. Now updating key expiry in DB.")
|
||||
await update_key_expiry(client_id, new_expiry_time, conn)
|
||||
logger.info("[RENEW] Key expiry updated. Now updating balance.")
|
||||
await update_balance(tg_id, -cost, conn)
|
||||
logger.info(f"[RENEW] Ключ {client_id} успешно продлён на {plan} мес. для пользователя {tg_id}.")
|
||||
|
||||
await conn.close()
|
||||
|
||||
logger.info("[RENEW] Initiating key renewal process on cluster.")
|
||||
await renew_key_on_cluster()
|
||||
|
||||
logger.info("[RENEW] Key renewal process completed. Closing database connection.")
|
||||
await conn.close()
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import pytz
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from client import ClientConfig, add_client
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, TRIAL_TIME
|
||||
from database import get_servers, get_trial, store_key, update_trial
|
||||
from handlers.texts import INSTRUCTIONS
|
||||
from handlers.utils import generate_random_email, get_least_loaded_cluster
|
||||
from logger import logger
|
||||
|
||||
|
||||
async def create_trial_key(tg_id: int, session: Any):
|
||||
try:
|
||||
trial_status = await get_trial(tg_id, session)
|
||||
if trial_status == 1:
|
||||
return {"error": "Вы уже использовали пробную версию."}
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при проверке триала: {e}")
|
||||
|
||||
client_id = str(uuid.uuid4())
|
||||
base_email = generate_random_email()
|
||||
public_link = f"{PUBLIC_LINK}{base_email}/{tg_id}"
|
||||
instructions = INSTRUCTIONS
|
||||
result = {"key": public_link, "instructions": instructions, "email": base_email}
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
current_time = datetime.now(moscow_tz)
|
||||
expiry_time = current_time + timedelta(days=TRIAL_TIME)
|
||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||
|
||||
clusters = await get_servers(session)
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
if least_loaded_cluster not in clusters:
|
||||
raise ValueError(f"Кластер {least_loaded_cluster} не найден в базе данных.")
|
||||
|
||||
servers_in_cluster = clusters[least_loaded_cluster]
|
||||
tasks = []
|
||||
|
||||
for server_info in servers_in_cluster:
|
||||
server_name = server_info.get("server_name", "unknown")
|
||||
|
||||
if SUPERNODE:
|
||||
email = f"{base_email}_{server_name.lower()}"
|
||||
else:
|
||||
email = base_email
|
||||
|
||||
tasks.append(
|
||||
add_client(
|
||||
AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
),
|
||||
ClientConfig(
|
||||
client_id=client_id,
|
||||
email=email,
|
||||
tg_id=tg_id,
|
||||
limit_ip=LIMIT_IP,
|
||||
total_gb=TOTAL_GB,
|
||||
expiry_time=expiry_timestamp,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(server_info["inbound_id"]),
|
||||
sub_id=base_email,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await store_key(
|
||||
tg_id,
|
||||
client_id,
|
||||
base_email,
|
||||
expiry_timestamp,
|
||||
public_link,
|
||||
server_id=least_loaded_cluster,
|
||||
session=session,
|
||||
)
|
||||
|
||||
await update_trial(tg_id, 1, session)
|
||||
return result
|
||||
@@ -4,7 +4,8 @@
|
||||
{
|
||||
"distutils": {
|
||||
"extra_compile_args": [
|
||||
"-O2"
|
||||
"-O2",
|
||||
"-static-libgcc"
|
||||
],
|
||||
"name": "handlers.payments.cryprobot_pay",
|
||||
"sources": [
|
||||
|
||||
Binary file not shown.
@@ -4,7 +4,8 @@
|
||||
{
|
||||
"distutils": {
|
||||
"extra_compile_args": [
|
||||
"-O2"
|
||||
"-O2",
|
||||
"-static-libgcc"
|
||||
],
|
||||
"name": "handlers.payments.gift",
|
||||
"sources": [
|
||||
|
||||
Binary file not shown.
@@ -52,7 +52,9 @@ if ROBOKASSA_ENABLE:
|
||||
|
||||
def generate_payment_link(amount, inv_id, description, tg_id):
|
||||
"""Генерация ссылки на оплату."""
|
||||
logger.debug(f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}")
|
||||
logger.debug(
|
||||
f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}"
|
||||
)
|
||||
payment_link = robokassa._payment.link.generate_by_script(
|
||||
out_sum=amount,
|
||||
inv_id=inv_id,
|
||||
@@ -64,7 +66,9 @@ def generate_payment_link(amount, inv_id, description, tg_id):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_robokassa")
|
||||
async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
|
||||
async def process_callback_pay_robokassa(
|
||||
callback_query: types.CallbackQuery, state: FSMContext, session: Any
|
||||
):
|
||||
tg_id = callback_query.message.chat.id
|
||||
logger.info(f"User {tg_id} initiated Robokassa payment.")
|
||||
|
||||
@@ -74,18 +78,18 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i]['callback_data']}",
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i + 1]["text"],
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i + 1]['callback_data']}",
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
|
||||
),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i]['callback_data']}",
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
|
||||
@@ -107,7 +111,9 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("robokassa_amount|"))
|
||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
async def process_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
logger.info(f"Получены данные callback_data: {callback_query.data}")
|
||||
|
||||
data = callback_query.data.split("|")
|
||||
@@ -161,7 +167,9 @@ async def robokassa_webhook(request):
|
||||
shp_id = params.get("shp_id")
|
||||
signature_value = params.get("SignatureValue")
|
||||
|
||||
logger.info(f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}")
|
||||
logger.info(
|
||||
f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}"
|
||||
)
|
||||
|
||||
if not check_payment_signature(params):
|
||||
logger.error("Неверная подпись или данные запроса.")
|
||||
@@ -200,7 +208,9 @@ def check_payment_signature(params):
|
||||
|
||||
logger.info(f"Signature string before hashing: {signature_string}")
|
||||
|
||||
expected_signature = hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
|
||||
expected_signature = (
|
||||
hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
|
||||
)
|
||||
|
||||
logger.info(f"Expected signature: {expected_signature}")
|
||||
logger.info(f"Received signature: {signature_value}")
|
||||
@@ -209,25 +219,27 @@ def check_payment_signature(params):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_robokassa")
|
||||
async def process_custom_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
async def process_custom_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
tg_id = callback_query.message.chat.id
|
||||
logger.info(f"User {tg_id} chose to enter a custom amount.")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay_robokassa"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_robokassa"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"Пожалуйста, введите сумму пополнения.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
await state.set_state(
|
||||
ReplenishBalanceState.waiting_for_payment_confirmation_robokassa
|
||||
)
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
async def handle_custom_amount_input(
|
||||
message: types.Message | types.CallbackQuery, state: FSMContext = None, session: Any = None
|
||||
):
|
||||
async def handle_custom_amount_input(message: types.Message | types.CallbackQuery, state: FSMContext = None, session: Any = None):
|
||||
if isinstance(message, types.CallbackQuery):
|
||||
tg_id = message.message.chat.id
|
||||
else:
|
||||
@@ -237,6 +249,7 @@ async def handle_custom_amount_input(
|
||||
inv_id = 0
|
||||
|
||||
try:
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
user_data = await get_temporary_data(conn, tg_id)
|
||||
await conn.close()
|
||||
@@ -264,13 +277,9 @@ async def handle_custom_amount_input(
|
||||
)
|
||||
|
||||
if state_type == "waiting_for_payment":
|
||||
message_text = (
|
||||
f"Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:"
|
||||
)
|
||||
message_text = f"Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:"
|
||||
elif state_type == "waiting_for_renewal_payment":
|
||||
message_text = (
|
||||
f"Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:"
|
||||
)
|
||||
message_text = f"Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:"
|
||||
else:
|
||||
await message.answer("Некорректное состояние данных. Попробуйте снова.")
|
||||
return
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
{
|
||||
"distutils": {
|
||||
"extra_compile_args": [
|
||||
"-O2"
|
||||
"-O2",
|
||||
"-static-libgcc"
|
||||
],
|
||||
"name": "handlers.payments.stars_pay",
|
||||
"sources": [
|
||||
|
||||
Binary file not shown.
+511
-418
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -4,7 +4,8 @@
|
||||
{
|
||||
"distutils": {
|
||||
"extra_compile_args": [
|
||||
"-O2"
|
||||
"-O2",
|
||||
"-static-libgcc"
|
||||
],
|
||||
"name": "handlers.payments.yookassa_pay",
|
||||
"sources": [
|
||||
|
||||
Binary file not shown.
@@ -4,7 +4,8 @@
|
||||
{
|
||||
"distutils": {
|
||||
"extra_compile_args": [
|
||||
"-O2"
|
||||
"-O2",
|
||||
"-static-libgcc"
|
||||
],
|
||||
"name": "handlers.payments.yoomoney_pay",
|
||||
"sources": [
|
||||
|
||||
Binary file not shown.
+6
-5
@@ -3,12 +3,12 @@ from typing import Any
|
||||
|
||||
import aiofiles
|
||||
import asyncpg
|
||||
from aiogram import F, Router, types
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import DATABASE_URL, NEWS_MESSAGE, RENEWAL_PLANS
|
||||
from config import DATABASE_URL, INSTRUCTIONS_BUTTON, NEWS_MESSAGE, RENEWAL_PLANS
|
||||
from database import get_balance, get_key_count, get_last_payments, get_referral_stats, get_trial
|
||||
from handlers.buttons.profile import (
|
||||
ADD_SUB,
|
||||
@@ -76,9 +76,10 @@ async def process_callback_view_profile(
|
||||
InlineKeyboardButton(text=INVITE, callback_data="invite"),
|
||||
InlineKeyboardButton(text=GIFTS, callback_data="gifts"),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=INSTRUCTIONS, callback_data="instructions"),
|
||||
)
|
||||
if INSTRUCTIONS_BUTTON:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=INSTRUCTIONS, callback_data="instructions"),
|
||||
)
|
||||
if admin:
|
||||
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="start"))
|
||||
|
||||
+96
-69
@@ -13,15 +13,14 @@ from aiogram.types import (
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from bot import bot
|
||||
from config import (
|
||||
CAPTCHA_ENABLE,
|
||||
CHANNEL_EXISTS,
|
||||
CHANNEL_ID,
|
||||
CHANNEL_REQUIRED,
|
||||
CHANNEL_URL,
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DONATIONS_ENABLE,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
SUPPORT_CHAT_URL,
|
||||
)
|
||||
from database import (
|
||||
@@ -31,20 +30,11 @@ from database import (
|
||||
get_coupon_details,
|
||||
get_referral_by_referred_id,
|
||||
get_trial,
|
||||
update_trial,
|
||||
)
|
||||
from handlers.buttons.add_subscribe import (
|
||||
DOWNLOAD_ANDROID_BUTTON,
|
||||
DOWNLOAD_IOS_BUTTON,
|
||||
IMPORT_ANDROID,
|
||||
IMPORT_IOS,
|
||||
PC_BUTTON,
|
||||
TV_BUTTON,
|
||||
update_balance,
|
||||
)
|
||||
from handlers.captcha import generate_captcha
|
||||
from handlers.keys.key_management import create_key
|
||||
from handlers.keys.trial_key import create_trial_key
|
||||
from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn
|
||||
from handlers.texts import WELCOME_TEXT, get_about_vpn
|
||||
from logger import logger
|
||||
|
||||
router = Router()
|
||||
@@ -59,17 +49,42 @@ async def handle_start_callback_query(
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def start_command(message: Message, state: FSMContext, session: Any, admin: bool, captcha: bool = True):
|
||||
"""Обрабатывает команду /start, включает логику рефералов и подарков."""
|
||||
"""Обрабатывает команду /start, включая логику проверки подписки, рефералов и подарков."""
|
||||
logger.info(f"Вызвана функция start_command для пользователя {message.chat.id}")
|
||||
|
||||
await state.clear()
|
||||
|
||||
# Проверка капчи, если включена
|
||||
if CAPTCHA_ENABLE and captcha:
|
||||
captcha = await generate_captcha(message, state)
|
||||
await message.answer(text=captcha["text"], reply_markup=captcha["markup"])
|
||||
captcha_data = await generate_captcha(message, state)
|
||||
await message.answer(text=captcha_data["text"], reply_markup=captcha_data["markup"])
|
||||
return
|
||||
|
||||
if CHANNEL_EXISTS and CHANNEL_REQUIRED:
|
||||
try:
|
||||
member = await bot.get_chat_member(CHANNEL_ID, message.chat.id)
|
||||
if member.status not in ["member", "administrator", "creator"]:
|
||||
await state.update_data(start_text=message.text)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription"))
|
||||
await message.answer(
|
||||
f"Для использования бота, пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка проверки подписки пользователя {message.chat.id}: {e}")
|
||||
await state.update_data(start_text=message.text)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription"))
|
||||
await message.answer(
|
||||
f"Пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}", reply_markup=builder.as_markup()
|
||||
)
|
||||
return
|
||||
|
||||
await process_start_logic(message, state, session, admin)
|
||||
|
||||
|
||||
async def process_start_logic(message: Message, state: FSMContext, session: Any, admin: bool):
|
||||
if message.text:
|
||||
try:
|
||||
connection_exists = await check_connection_exists(message.chat.id)
|
||||
@@ -79,13 +94,49 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
await add_connection(tg_id=message.chat.id, session=session)
|
||||
logger.info(f"Пользователь {message.chat.id} успешно добавлен в базу данных.")
|
||||
|
||||
if "coupons_" in message.text:
|
||||
logger.info(f"Обнаружена ссылка на купон: {message.text}")
|
||||
coupon_code = message.text.split("coupons_")[1].strip()
|
||||
logger.info(f"Пользователь {message.chat.id} ввёл купон: {coupon_code}")
|
||||
|
||||
coupon = await session.fetchrow(
|
||||
"SELECT id, code, amount, usage_limit, usage_count, is_used FROM coupons WHERE code = $1",
|
||||
coupon_code,
|
||||
)
|
||||
|
||||
if coupon is None:
|
||||
logger.warning(f"Купон {coupon_code} не найден.")
|
||||
await message.answer("❌ Купон не найден!")
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
if coupon["is_used"] or coupon["usage_count"] >= coupon["usage_limit"]:
|
||||
logger.info(f"Купон {coupon_code} уже использован или исчерпан.")
|
||||
await message.answer("❌ Этот купон уже использован!")
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
await update_balance(message.chat.id, coupon["amount"])
|
||||
logger.info(f"Начислено {coupon['amount']} единиц для пользователя {message.chat.id}")
|
||||
|
||||
new_usage_count = coupon["usage_count"] + 1
|
||||
is_used = new_usage_count >= coupon["usage_limit"]
|
||||
|
||||
await session.execute(
|
||||
"UPDATE coupons SET usage_count = $1, is_used = $2 WHERE code = $3",
|
||||
new_usage_count,
|
||||
is_used,
|
||||
coupon_code,
|
||||
)
|
||||
|
||||
logger.info(f"Купон {coupon_code} успешно использован, начислено {coupon['amount']} RUB.")
|
||||
await message.answer(f"🎉 Ваш баланс пополнен на {coupon['amount']} RUB по купону!")
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
if "gift_" in message.text:
|
||||
logger.info(f"Обнаружена ссылка на подарок: {message.text}")
|
||||
parts = message.text.split("gift_")[1].split("_")
|
||||
gift_id = parts[0]
|
||||
|
||||
recipient_tg_id = message.chat.id
|
||||
|
||||
gift_info = await get_coupon_details(gift_id, session)
|
||||
|
||||
if gift_info is None:
|
||||
@@ -100,7 +151,13 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
await message.answer("❌ Вы не можете получить подарок от самого себя.")
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
await add_connection(tg_id=recipient_tg_id, session=session)
|
||||
if not connection_exists:
|
||||
await add_referral(recipient_tg_id, gift_info["sender_tg_id"], session)
|
||||
logger.info(
|
||||
f"Пользователь {recipient_tg_id} теперь является рефералом отправителя подарка {gift_info['sender_tg_id']}."
|
||||
)
|
||||
else:
|
||||
logger.info(f"Пользователь {recipient_tg_id} уже зарегистрирован, реферал не добавляется.")
|
||||
|
||||
selected_months = gift_info["selected_months"]
|
||||
expiry_time = gift_info["expiry_time"]
|
||||
@@ -117,7 +174,8 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
f"🎉 Ваш подарок на {selected_months} {'месяц' if selected_months == 1 else 'месяца' if selected_months in [2, 3, 4] else 'месяцев'} активирован!"
|
||||
f"🎉 Ваш подарок на {selected_months} "
|
||||
f"{'месяц' if selected_months == 1 else 'месяца' if selected_months in [2, 3, 4] else 'месяцев'} активирован!"
|
||||
)
|
||||
logger.info(f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}.")
|
||||
return
|
||||
@@ -137,7 +195,6 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
existing_referral = await get_referral_by_referred_id(message.chat.id, session)
|
||||
|
||||
if existing_referral:
|
||||
logger.info(f"Реферал с ID {message.chat.id} уже существует.")
|
||||
return await show_start_menu(message, admin, session)
|
||||
@@ -151,7 +208,7 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
return
|
||||
|
||||
else:
|
||||
logger.info(f"Пользователь {message.chat.id} зашел без реферальной ссылки или подарка.")
|
||||
logger.info(f"Пользователь {message.chat.id} зашел без реферальной ссылки, подарка или купона.")
|
||||
|
||||
await show_start_menu(message, admin, session)
|
||||
|
||||
@@ -162,6 +219,20 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
await show_start_menu(message, admin, session)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "check_subscription")
|
||||
async def check_subscription_callback(callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool):
|
||||
try:
|
||||
member = await bot.get_chat_member(CHANNEL_ID, callback_query.from_user.id)
|
||||
if member.status not in ["member", "administrator", "creator"]:
|
||||
await callback_query.answer("Вы еще не подписаны на канал!", show_alert=True)
|
||||
else:
|
||||
await callback_query.answer("Подписка подтверждена!")
|
||||
await process_start_logic(callback_query.message, state, session, admin)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка проверки подписки (callback) для пользователя {callback_query.from_user.id}: {e}")
|
||||
await callback_query.answer("Ошибка проверки подписки, повторите попытку", show_alert=True)
|
||||
|
||||
|
||||
async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
"""Функция для отображения стандартного меню"""
|
||||
logger.info(f"Показываю главное меню для пользователя {message.chat.id}")
|
||||
@@ -171,7 +242,7 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if trial_status == 0:
|
||||
builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn"))
|
||||
builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="create_key"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
@@ -203,50 +274,6 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "connect_vpn")
|
||||
async def handle_connect_vpn(callback_query: CallbackQuery, session: Any):
|
||||
user_id = callback_query.message.chat.id
|
||||
|
||||
trial_key_info = await create_trial_key(user_id, session)
|
||||
|
||||
if "error" in trial_key_info:
|
||||
await callback_query.message.answer(trial_key_info["error"])
|
||||
else:
|
||||
await update_trial(user_id, 1, session)
|
||||
|
||||
key_message = (
|
||||
f"🔑 <b>Ваш персональный ключ доступа:</b>\n"
|
||||
f"<code>{trial_key_info['key']}</code>\n\n"
|
||||
f"📋 <b>Быстрая инструкция по подключению:</b>\n{INSTRUCTIONS_TRIAL}"
|
||||
)
|
||||
|
||||
email = trial_key_info["email"]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=IMPORT_IOS,
|
||||
url=f"{CONNECT_IOS}{trial_key_info['key']}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=IMPORT_ANDROID,
|
||||
url=f"{CONNECT_ANDROID}{trial_key_info['key']}",
|
||||
),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(key_message, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "about_vpn")
|
||||
async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import secrets
|
||||
import string
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import aiohttp
|
||||
import asyncpg
|
||||
|
||||
@@ -24,11 +24,8 @@ def build_sender_kb() -> InlineKeyboardMarkup:
|
||||
def build_clusters_kb(clusters: list) -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
for cluster in clusters:
|
||||
name = cluster['cluster_name']
|
||||
builder.button(
|
||||
text=f"🌐 {name}",
|
||||
callback_data=AdminSenderCallback(type="cluster", data=name).pack()
|
||||
)
|
||||
name = cluster["cluster_name"]
|
||||
builder.button(text=f"🌐 {name}", callback_data=AdminSenderCallback(type="cluster", data=name).pack())
|
||||
|
||||
builder.row(build_admin_back_btn())
|
||||
builder.adjust(1)
|
||||
|
||||
Reference in New Issue
Block a user