rename sub and reorganization menu

This commit is contained in:
Vladless
2025-03-27 02:01:39 +03:00
parent 2133ca422a
commit 5bbe946d64
5 changed files with 130 additions and 114 deletions
+17 -2
View File
@@ -42,8 +42,23 @@ CREATE TABLE IF NOT EXISTS keys
PRIMARY KEY (tg_id, client_id)
);
ALTER TABLE keys
ADD COLUMN is_frozen BOOLEAN DEFAULT FALSE;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'keys' AND column_name = 'is_frozen'
) THEN
ALTER TABLE keys ADD COLUMN is_frozen BOOLEAN DEFAULT FALSE;
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'keys' AND column_name = 'alias'
) THEN
ALTER TABLE keys ADD COLUMN alias TEXT;
END IF;
END$$;
CREATE TABLE IF NOT EXISTS referrals
(
+1 -3
View File
@@ -62,12 +62,10 @@ async def init_db(file_path: str = "assets/schema.sql"):
with open(file_path) as file:
sql_content = file.read()
statements = [stmt.strip() for stmt in sql_content.split(";") if stmt.strip()]
conn = await asyncpg.connect(DATABASE_URL)
try:
for statement in statements:
await conn.execute(statement)
await conn.execute(sql_content)
except Exception as e:
logger.error(f"Error while executing SQL statement: {e}")
finally:
+56 -6
View File
@@ -75,12 +75,18 @@ from handlers.texts import (
)
from handlers.utils import edit_or_send_message, handle_error
from logger import logger
from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext
locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8")
router = Router()
class RenameKeyState(StatesGroup):
waiting_for_new_alias = State()
@router.callback_query(F.data == "view_keys")
@router.message(F.text == "/subs")
async def process_callback_or_message_view_keys(callback_query_or_message: Message | CallbackQuery, session: Any):
@@ -112,21 +118,24 @@ def build_keys_response(records):
if records:
response_message = "<b>🔑 Список ваших подписок:</b>\n\n<blockquote>"
for record in records:
key_name = record["email"]
alias = record.get("alias")
email = record["email"]
client_id = record["client_id"]
expiry_time = record.get("expiry_time")
key_display = alias.strip() if alias else email
if expiry_time:
expiry_date_full = datetime.fromtimestamp(expiry_time / 1000, tz=moscow_tz)
formatted_date_full = expiry_date_full.strftime("до %d.%m.%y, %H:%M")
formatted_date_short = expiry_date_full.strftime("до %d.%m.%y")
else:
formatted_date_full = "без срока действия"
formatted_date_short = "без срока действия"
button_text = f"🔑{key_name} ({formatted_date_short})"
builder.row(InlineKeyboardButton(text=button_text, callback_data=f"view_key|{key_name}"))
key_button = InlineKeyboardButton(text=f"🔑 {key_display}", callback_data=f"view_key|{email}")
rename_button = InlineKeyboardButton(text="✏️", callback_data=f"rename_key|{client_id}")
builder.row(key_button, rename_button)
response_message += f"• <b>{key_name}</b> ({formatted_date_full})\n"
response_message += f"• <b>{key_display}</b> ({formatted_date_full})\n"
response_message += "</blockquote>\n"
else:
@@ -139,6 +148,47 @@ def build_keys_response(records):
return inline_keyboard, response_message
@router.callback_query(F.data.startswith("rename_key|"))
async def handle_rename_key(callback: CallbackQuery, state: FSMContext):
client_id = callback.data.split("|")[1]
await state.set_state(RenameKeyState.waiting_for_new_alias)
await state.update_data(client_id=client_id, target_message=callback.message)
await edit_or_send_message(
target_message=callback.message,
text="✏️ Введите новое имя подписки (до 10 символов):",
reply_markup=None
)
@router.message(F.text, RenameKeyState.waiting_for_new_alias)
async def handle_new_alias_input(message: Message, state: FSMContext, session: Any):
alias = message.text.strip()
if len(alias) > 10:
await message.answer("❌ Имя слишком длинное. Введите до 10 символов.")
return
data = await state.get_data()
client_id = data.get("client_id")
try:
await session.execute(
"UPDATE keys SET alias = $1 WHERE tg_id = $2 AND client_id = $3",
alias,
message.chat.id,
client_id,
)
except Exception as e:
await message.answer("❌ Не удалось переименовать подписку.")
logger.error(f"Ошибка при обновлении alias: {e}")
finally:
await state.clear()
await process_callback_or_message_view_keys(message, session)
@router.callback_query(F.data.startswith("view_key|"))
async def process_callback_view_key(callback_query: CallbackQuery, session: Any):
tg_id = callback_query.message.chat.id
+1 -1
View File
@@ -98,7 +98,7 @@ async def process_callback_view_profile(
InlineKeyboardButton(text="🔧 Администратор", callback_data=AdminPanelCallback(action="admin").pack())
)
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="start"))
builder.row(InlineKeyboardButton(text="💬 О сервисе", callback_data="about_vpn"))
await edit_or_send_message(
target_message=target_message,
+55 -102
View File
@@ -47,6 +47,7 @@ from handlers.texts import (
from logger import logger
from .admin.panel.keyboard import AdminPanelCallback
from .utils import edit_or_send_message
from handlers.profile import process_callback_view_profile
router = Router()
@@ -112,191 +113,135 @@ async def process_start_logic(
text = text_to_process if text_to_process is not None else message.text
if text:
try:
connection_exists = await check_connection_exists(message.chat.id)
logger.info(f"Проверка существования подключения: {connection_exists}")
if not connection_exists:
await add_connection(tg_id=message.chat.id, session=session)
logger.info(f"Пользователь {message.chat.id} успешно добавлен в базу данных.")
if "coupons_" in text:
logger.info(f"Обнаружена ссылка на купон: {text}")
coupon_code = 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} не найден.")
if not coupon:
await message.answer("❌ Купон не найден!")
return await show_start_menu(message, admin, session)
usage_exists = await session.fetchval(
"SELECT 1 FROM coupon_usages WHERE coupon_id = $1 AND user_id = $2",
coupon["id"],
message.chat.id,
coupon["id"], message.chat.id,
)
if usage_exists:
logger.info(f"Пользователь {message.chat.id} уже активировал купон {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["usage_count"] + 1,
coupon["usage_count"] + 1 >= coupon["usage_limit"],
coupon_code,
)
await session.execute(
"INSERT INTO coupon_usages (coupon_id, user_id, used_at) VALUES ($1, $2, NOW())",
coupon["id"],
message.chat.id,
)
logger.info(
f"Купон {coupon_code} успешно использован пользователем {message.chat.id}, начислено {coupon['amount']} RUB."
coupon["id"], message.chat.id,
)
await message.answer(COUPON_SUCCESS_MSG.format(amount=coupon["amount"]))
return await show_start_menu(message, admin, session)
if "gift_" in text:
logger.info(f"Обнаружена ссылка на подарок: {text}")
parts = text.split("gift_")[1].split("_")
if len(parts) < 2:
logger.error("Неверный формат ссылки на подарок: недостаточно частей после 'gift_'")
await message.answer("❌ Неверный формат ссылки на подарок.")
return await show_start_menu(message, admin, session)
gift_id = parts[0]
recipient_tg_id = message.chat.id
gift_info = await session.fetchrow(
"""
SELECT sender_tg_id, selected_months, expiry_time, is_used, recipient_tg_id
FROM gifts WHERE gift_id = $1
""",
"SELECT sender_tg_id, selected_months, expiry_time, is_used, recipient_tg_id FROM gifts WHERE gift_id = $1",
gift_id,
)
if gift_info is None:
logger.warning(f"Подарок с ID {gift_id} уже был использован или не существует.")
if not gift_info:
await message.answer(GIFT_ALREADY_USED_OR_NOT_EXISTS_MSG)
return await show_start_menu(message, admin, session)
if gift_info["is_used"]:
logger.warning(f"Подарок с ID {gift_id} уже был активирован ранее.")
await message.answer("Этот подарок уже был использован.")
return await show_start_menu(message, admin, session)
if gift_info["sender_tg_id"] == recipient_tg_id:
logger.warning(f"Пользователь {recipient_tg_id} попытался активировать свой же подарок.")
if gift_info["sender_tg_id"] == message.chat.id:
await message.answer("❌ Вы не можете получить подарок от самого себя.")
return await show_start_menu(message, admin, session)
if gift_info["recipient_tg_id"] is not None:
logger.warning(
f"Подарок {gift_id} уже привязан к другому пользователю ({gift_info['recipient_tg_id']})."
)
if gift_info["recipient_tg_id"]:
await message.answer("❌ Этот подарок уже был активирован другим пользователем.")
return await show_start_menu(message, admin, 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']}."
)
await add_referral(message.chat.id, gift_info["sender_tg_id"], session)
await session.execute(
"""
UPDATE connections SET trial = 1 WHERE tg_id = $1
""",
recipient_tg_id,
"UPDATE connections SET trial = 1 WHERE tg_id = $1", message.chat.id
)
selected_months = gift_info["selected_months"]
expiry_time = gift_info["expiry_time"].replace(tzinfo=None)
logger.info(f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}.")
await create_key(recipient_tg_id, expiry_time, state, session, message)
logger.info(f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев.")
await create_key(
message.chat.id,
gift_info["expiry_time"].replace(tzinfo=None),
state,
session,
message,
)
await session.execute(
"""
UPDATE gifts SET is_used = TRUE, recipient_tg_id = $1
WHERE gift_id = $2
""",
recipient_tg_id,
"UPDATE gifts SET is_used = TRUE, recipient_tg_id = $1 WHERE gift_id = $2",
message.chat.id,
gift_id,
)
await message.answer(
f"🎉 Ваш подарок на {selected_months} "
f"{'месяц' if selected_months == 1 else 'месяца' if selected_months in [2, 3, 4] else 'месяцев'} активирован!"
f"🎉 Ваш подарок на {gift_info['selected_months']} "
f"{'месяц' if gift_info['selected_months'] == 1 else 'месяца' if gift_info['selected_months'] in [2, 3, 4] else 'месяцев'} активирован!"
)
logger.info(f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}.")
return
elif "referral_" in text:
if "referral_" in text:
try:
referrer_tg_id = int(text.split("referral_")[1])
if connection_exists:
logger.info(f"Пользователь {message.chat.id} уже зарегистрирован и не может стать рефералом.")
connection_exists_now = await check_connection_exists(message.chat.id)
if connection_exists_now:
await message.answer("❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку.")
return await show_start_menu(message, admin, session)
if referrer_tg_id == message.chat.id:
logger.warning(f"Пользователь {message.chat.id} попытался стать рефералом самого себя.")
await message.answer("❌ Вы не можете быть рефералом самого себя.")
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)
await add_referral(message.chat.id, referrer_tg_id, session)
logger.info(f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}")
await message.answer(REFERRAL_SUCCESS_MSG.format(referrer_tg_id=referrer_tg_id))
try:
await bot.send_message(
referrer_tg_id, NEW_REFERRAL_NOTIFICATION.format(referred_id=message.chat.id)
)
logger.info(
f"Уведомление отправлено пользователю {referrer_tg_id} о новом реферале {message.chat.id}"
referrer_tg_id,
NEW_REFERRAL_NOTIFICATION.format(referred_id=message.chat.id),
)
except Exception as e:
logger.error(f"Не удалось отправить уведомление пригласившему ({referrer_tg_id}): {e}")
return await show_start_menu(message, admin, session)
except (ValueError, IndexError):
pass
except (ValueError, IndexError) as e:
logger.error(f"Ошибка при обработке реферальной ссылки: {e}")
return
logger.info("Пользователь зашел без реферальной ссылки, подарка или купона.")
else:
logger.info(f"Пользователь {message.chat.id} зашел без реферальной ссылки, подарка или купона.")
except Exception as e:
logger.error(f"Ошибка при обработке текста {message.text} {e}", exc_info=True)
await message.answer("❌ Произошла ошибка. Попробуйте позже.")
return
await show_start_menu(message, admin, session)
except (ValueError, IndexError) as e:
logger.error(f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}")
await message.answer("❌ Произошла ошибка. Пожалуйста, попробуйте снова.")
final_exists = await check_connection_exists(message.chat.id)
if final_exists:
return await process_callback_view_profile(message, state, admin)
else:
await show_start_menu(message, admin, session)
await add_connection(tg_id=message.chat.id, session=session)
return await show_start_menu(message, admin, session)
@router.callback_query(F.data == "check_subscription")
@@ -340,11 +285,11 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
trial_status = await get_trial(message.chat.id, session)
logger.info(f"Trial status для {message.chat.id}: {trial_status}")
if trial_status == 0:
builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="create_key"))
builder.row(InlineKeyboardButton(text="🎁 Пробная подписка", callback_data="create_key"))
else:
logger.warning(f"Сессия базы данных отсутствует, пропускаем проверку триала для {message.chat.id}")
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
# builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
if CHANNEL_EXISTS:
builder.row(
@@ -373,11 +318,19 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
async def handle_about_vpn(callback_query: CallbackQuery):
builder = InlineKeyboardBuilder()
if DONATIONS_ENABLE:
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
builder.row(InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL))
builder.row(
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
)
support_btn = InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL)
if CHANNEL_EXISTS:
builder.row(InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL))
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="start"))
channel_btn = InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL)
builder.row(support_btn, channel_btn)
else:
builder.row(support_btn)
builder.row(
InlineKeyboardButton(text="← Назад", callback_data="start")
)
text = get_about_vpn("3.2.3-minor")
await edit_or_send_message(