location-selection/major_update_notifications/gifts and more fixes
This commit is contained in:
+6
-6
@@ -792,18 +792,18 @@ async def get_referral_stats(referrer_tg_id: int):
|
||||
WHERE rl.level < {MAX_REFERRAL_LEVELS}
|
||||
)
|
||||
SELECT
|
||||
SUM(p.amount * CASE
|
||||
{" ".join([f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]}" for level in REFERRAL_BONUS_PERCENTAGES])}
|
||||
ELSE 0
|
||||
END) AS total_bonus
|
||||
COALESCE(SUM(p.amount * (
|
||||
CASE
|
||||
{" ".join([f"WHEN rl.level = {level} THEN {REFERRAL_BONUS_PERCENTAGES[level]}" for level in REFERRAL_BONUS_PERCENTAGES])}
|
||||
ELSE 0
|
||||
END)), 0) AS total_bonus
|
||||
FROM referral_levels rl
|
||||
JOIN payments p ON rl.referred_tg_id = p.tg_id
|
||||
WHERE p.status = 'success'
|
||||
WHERE p.status = 'success' AND rl.level <= {MAX_REFERRAL_LEVELS}
|
||||
""",
|
||||
referrer_tg_id,
|
||||
)
|
||||
|
||||
total_referral_bonus = total_referral_bonus or 0
|
||||
logger.debug(
|
||||
f"Получена общая сумма бонусов от рефералов: {total_referral_bonus}"
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ async def handle_admin_callback_query(callback_query: CallbackQuery, state: FSMC
|
||||
async def handle_admin_message(message: types.Message, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
BOT_VERSION = "4.0.0-preAlpha(14)"
|
||||
BOT_VERSION = "4.0.0-Alpha(21)"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
|
||||
@@ -9,7 +9,7 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from py3xui import AsyncApi
|
||||
|
||||
from backup import create_backup_and_send_to_admins
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, SUPERNODE
|
||||
from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
|
||||
from database import check_unique_server_name, get_servers_from_db
|
||||
from filters.admin import IsAdminFilter
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
|
||||
@@ -3,3 +3,4 @@ MY_GIFTS = "🎁 Мои подарки"
|
||||
PROFILE = "👤 Личный кабинет"
|
||||
BACK = "🔙 Назад"
|
||||
GIFTS_ABOUT = "<b>Дарите подарки и следите, чтобы они дошли до адресата! 🎄</b>"
|
||||
SHARE_GIFT = "🎁 Поделиться подарком"
|
||||
|
||||
@@ -20,6 +20,7 @@ from config import (
|
||||
RENEWAL_PRICES,
|
||||
SUPPORT_CHAT_URL,
|
||||
TRIAL_TIME,
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
from database import (
|
||||
@@ -37,7 +38,7 @@ from handlers.buttons.add_subscribe import (
|
||||
PC_BUTTON,
|
||||
TV_BUTTON,
|
||||
)
|
||||
from handlers.keys.key_utils import create_key_on_cluster
|
||||
from handlers.keys.key_utils import create_client_on_server, create_key_on_cluster
|
||||
from handlers.payments.robokassa_pay import handle_custom_amount_input
|
||||
from handlers.payments.yookassa_pay import process_custom_amount_input
|
||||
from handlers.texts import DISCOUNTS, key_message_success
|
||||
@@ -166,9 +167,9 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
)
|
||||
return
|
||||
|
||||
await update_balance(tg_id, -plan_price)
|
||||
expiry_time = datetime.utcnow() + timedelta(days=duration_days)
|
||||
await create_key(tg_id, expiry_time, None, session, callback_query)
|
||||
await update_balance(tg_id, -plan_price)
|
||||
|
||||
|
||||
async def create_key(
|
||||
@@ -182,9 +183,65 @@ async def create_key(
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
expiry_time = expiry_time.astimezone(moscow_tz)
|
||||
|
||||
if USE_COUNTRY_SELECTION:
|
||||
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",
|
||||
least_loaded_cluster,
|
||||
)
|
||||
countries = [server["server_name"] for server in servers]
|
||||
logger.info(f"[Country Selection] Список серверов: {countries}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
for country in countries:
|
||||
callback_data = f"select_country|{country}|{expiry_time.isoformat()}"
|
||||
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] Добавлена кнопка '🔙 Назад'.")
|
||||
|
||||
if isinstance(message_or_query, Message):
|
||||
logger.info("[Country Selection] Сообщение пользователя - тип Message.")
|
||||
await message_or_query.answer(
|
||||
"🌍 Пожалуйста, выберите страну для вашего ключа:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
logger.info("[Country Selection] Сообщение отправлено с выбором страны.")
|
||||
elif isinstance(message_or_query, CallbackQuery):
|
||||
logger.info("[Country Selection] Сообщение пользователя - тип CallbackQuery.")
|
||||
await message_or_query.message.answer(
|
||||
"🌍 Пожалуйста, выберите страну для вашего ключа:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
logger.info("[Country Selection] Сообщение отправлено с выбором страны.")
|
||||
elif tg_id is not None:
|
||||
logger.info("[Country Selection] Использование tg_id для отправки сообщения.")
|
||||
await bot.send_message(
|
||||
chat_id=tg_id,
|
||||
text="🌍 Пожалуйста, выберите страну для вашего ключа:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
logger.info(f"[Country Selection] Сообщение отправлено напрямую в чат {tg_id}.")
|
||||
else:
|
||||
logger.error("[Country Selection] Невозможно определить идентификатор чата. Сообщение не отправлено.")
|
||||
|
||||
logger.info("[Country Selection] Возврат из функции.")
|
||||
return
|
||||
|
||||
while True:
|
||||
key_name = generate_random_email()
|
||||
logger.info(f"Generated random key name for user {tg_id}: {key_name}")
|
||||
logger.info(f"[Key Generation] Сгенерировано имя ключа: {key_name} для пользователя {tg_id}")
|
||||
|
||||
existing_key = await session.fetchrow(
|
||||
"SELECT * FROM keys WHERE email = $1 AND tg_id = $2",
|
||||
@@ -193,13 +250,11 @@ async def create_key(
|
||||
)
|
||||
if not existing_key:
|
||||
break
|
||||
logger.warning(
|
||||
f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one."
|
||||
)
|
||||
logger.warning(f"[Key Generation] Имя ключа {key_name} уже существует. Генерация нового.")
|
||||
|
||||
client_id = str(uuid.uuid4())
|
||||
email = key_name.lower()
|
||||
expiry_timestamp = int(expiry_time.astimezone(moscow_tz).timestamp() * 1000)
|
||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
|
||||
|
||||
try:
|
||||
@@ -218,7 +273,7 @@ async def create_key(
|
||||
]
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
logger.info(f"Key created on cluster {least_loaded_cluster} for user {tg_id}.")
|
||||
logger.info(f"[Key Creation] Ключ создан на кластере {least_loaded_cluster} для пользователя {tg_id}")
|
||||
|
||||
await store_key(
|
||||
tg_id,
|
||||
@@ -229,9 +284,10 @@ async def create_key(
|
||||
least_loaded_cluster,
|
||||
session,
|
||||
)
|
||||
logger.info(f"[Database] Ключ сохранён в базе данных для пользователя {tg_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error while creating the key for user {tg_id} on cluster: {e}")
|
||||
logger.error(f"[Error] Ошибка при создании ключа для пользователя {tg_id}: {e}")
|
||||
|
||||
error_message = "❌ Произошла ошибка при создании подписки. Пожалуйста, попробуйте снова."
|
||||
if isinstance(message_or_query, Message):
|
||||
@@ -271,3 +327,121 @@ async def create_key(
|
||||
|
||||
if state:
|
||||
await state.clear()
|
||||
logger.info(f"[FSM] Состояние пользователя {tg_id} очищено")
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("select_country|"))
|
||||
async def handle_country_selection(callback_query: CallbackQuery, session: Any):
|
||||
"""Обработчик выбора страны."""
|
||||
data = callback_query.data.split("|")
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
async def finalize_key_creation(
|
||||
tg_id: int,
|
||||
expiry_time: datetime,
|
||||
selected_country: str,
|
||||
state: FSMContext | None,
|
||||
session: Any,
|
||||
callback_query: CallbackQuery,
|
||||
):
|
||||
"""Финализирует создание ключа с выбранной страной."""
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
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 session.fetchrow(
|
||||
"SELECT * FROM keys WHERE email = $1 AND tg_id = $2",
|
||||
key_name,
|
||||
tg_id,
|
||||
)
|
||||
if not existing_key:
|
||||
break
|
||||
logger.warning(
|
||||
f"Key name '{key_name}' already exists for user {tg_id}. Generating a new one."
|
||||
)
|
||||
|
||||
client_id = str(uuid.uuid4())
|
||||
email = key_name.lower()
|
||||
expiry_timestamp = int(expiry_time.timestamp() * 1000)
|
||||
public_link = f"{PUBLIC_LINK}{email}/{tg_id}"
|
||||
|
||||
try:
|
||||
server_info = await session.fetchrow(
|
||||
"SELECT api_url, inbound_id, server_name FROM servers WHERE server_name = $1",
|
||||
selected_country,
|
||||
)
|
||||
|
||||
if not server_info:
|
||||
raise ValueError(f"Сервер {selected_country} не найден.")
|
||||
|
||||
semaphore = asyncio.Semaphore(2)
|
||||
|
||||
await create_client_on_server(
|
||||
server_info=server_info,
|
||||
tg_id=tg_id,
|
||||
client_id=client_id,
|
||||
email=email,
|
||||
expiry_timestamp=expiry_timestamp,
|
||||
semaphore=semaphore,
|
||||
)
|
||||
|
||||
logger.info(f"Key created on server {selected_country} for user {tg_id}.")
|
||||
|
||||
await store_key(
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
public_link,
|
||||
selected_country,
|
||||
session,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error while creating the key for user {tg_id}: {e}")
|
||||
await callback_query.message.answer("❌ Произошла ошибка при создании подписки. Пожалуйста, попробуйте снова.")
|
||||
return
|
||||
|
||||
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}{public_link}"),
|
||||
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{public_link}"),
|
||||
)
|
||||
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"))
|
||||
|
||||
remaining_time = expiry_time - datetime.now(moscow_tz)
|
||||
days = remaining_time.days
|
||||
key_message = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅")
|
||||
|
||||
await callback_query.message.answer(key_message, reply_markup=builder.as_markup())
|
||||
|
||||
if state:
|
||||
await state.clear()
|
||||
|
||||
+74
-45
@@ -8,9 +8,9 @@ from database import get_servers_from_db
|
||||
from logger import logger
|
||||
|
||||
|
||||
async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_timestamp):
|
||||
async def create_key_on_cluster(cluster_id: str, tg_id: int, client_id: str, email: str, expiry_timestamp: int):
|
||||
"""
|
||||
Создает ключ на всех серверах указанного кластера с одинаковым sub_id и уникальным email при активном SUPERNODE.
|
||||
Создает ключ на всех серверах указанного кластера.
|
||||
"""
|
||||
try:
|
||||
servers = await get_servers_from_db()
|
||||
@@ -21,58 +21,87 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
|
||||
|
||||
semaphore = asyncio.Semaphore(2)
|
||||
|
||||
async def create_client_on_server(server_info):
|
||||
async with semaphore:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
server_name = server_info.get("server_name", "unknown")
|
||||
|
||||
if not inbound_id:
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск."
|
||||
)
|
||||
return
|
||||
|
||||
if SUPERNODE:
|
||||
unique_email = f"{email}_{server_name.lower()}"
|
||||
sub_id = email
|
||||
else:
|
||||
unique_email = email
|
||||
sub_id = unique_email
|
||||
|
||||
await add_client(
|
||||
xui,
|
||||
client_id,
|
||||
unique_email,
|
||||
tg_id,
|
||||
limit_ip=LIMIT_IP,
|
||||
total_gb=TOTAL_GB,
|
||||
expiry_time=expiry_timestamp,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(inbound_id),
|
||||
sub_id=sub_id
|
||||
)
|
||||
|
||||
if SUPERNODE:
|
||||
await asyncio.sleep(0.7)
|
||||
|
||||
if SUPERNODE:
|
||||
for server_info in cluster:
|
||||
await create_client_on_server(server_info)
|
||||
await create_client_on_server(
|
||||
server_info,
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
semaphore,
|
||||
)
|
||||
else:
|
||||
await asyncio.gather(*(create_client_on_server(server) for server in cluster))
|
||||
await asyncio.gather(
|
||||
*(
|
||||
create_client_on_server(
|
||||
server,
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
semaphore,
|
||||
)
|
||||
for server in cluster
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при создании ключа: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
async def create_client_on_server(
|
||||
server_info: dict,
|
||||
tg_id: int,
|
||||
client_id: str,
|
||||
email: str,
|
||||
expiry_timestamp: int,
|
||||
semaphore: asyncio.Semaphore,
|
||||
):
|
||||
"""
|
||||
Создает клиента на указанном сервере.
|
||||
"""
|
||||
async with semaphore:
|
||||
xui = AsyncApi(
|
||||
server_info["api_url"],
|
||||
username=ADMIN_USERNAME,
|
||||
password=ADMIN_PASSWORD,
|
||||
)
|
||||
|
||||
inbound_id = server_info.get("inbound_id")
|
||||
server_name = server_info.get("server_name", "unknown")
|
||||
|
||||
if not inbound_id:
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск."
|
||||
)
|
||||
return
|
||||
|
||||
if SUPERNODE:
|
||||
unique_email = f"{email}_{server_name.lower()}"
|
||||
sub_id = email
|
||||
else:
|
||||
unique_email = email
|
||||
sub_id = unique_email
|
||||
|
||||
await add_client(
|
||||
xui,
|
||||
client_id,
|
||||
unique_email,
|
||||
tg_id,
|
||||
limit_ip=LIMIT_IP,
|
||||
total_gb=TOTAL_GB,
|
||||
expiry_time=expiry_timestamp,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(inbound_id),
|
||||
sub_id=sub_id,
|
||||
)
|
||||
|
||||
if SUPERNODE:
|
||||
await asyncio.sleep(0.7)
|
||||
|
||||
|
||||
|
||||
async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, total_gb):
|
||||
|
||||
+96
-53
@@ -4,6 +4,8 @@ import os
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.types import BufferedInputFile, InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
@@ -12,6 +14,7 @@ from bot import bot
|
||||
from config import (
|
||||
CONNECT_ANDROID,
|
||||
CONNECT_IOS,
|
||||
DATABASE_URL,
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
ENABLE_DELETE_KEY_BUTTON,
|
||||
@@ -19,6 +22,7 @@ from config import (
|
||||
PUBLIC_LINK,
|
||||
RENEWAL_PLANS,
|
||||
TOTAL_GB,
|
||||
USE_COUNTRY_SELECTION,
|
||||
USE_NEW_PAYMENT_FLOW,
|
||||
)
|
||||
from database import (
|
||||
@@ -78,7 +82,9 @@ async def process_callback_or_message_view_keys(
|
||||
try:
|
||||
records = await session.fetch(
|
||||
"""
|
||||
SELECT email, client_id FROM keys WHERE tg_id = $1
|
||||
SELECT email, client_id, expiry_time
|
||||
FROM keys
|
||||
WHERE tg_id = $1
|
||||
""",
|
||||
chat_id,
|
||||
)
|
||||
@@ -96,30 +102,50 @@ async def process_callback_or_message_view_keys(
|
||||
|
||||
def build_keys_response(records):
|
||||
"""
|
||||
Формирует сообщение и клавиатуру для устройств.
|
||||
Формирует сообщение и клавиатуру для устройств с указанием срока действия подписки.
|
||||
"""
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
if records:
|
||||
response_message = "<b>🔑 Список ваших подписок:</b>\n\n"
|
||||
for record in records:
|
||||
key_name = record["email"]
|
||||
expiry_time = record.get("expiry_time")
|
||||
|
||||
if expiry_time:
|
||||
expiry_date_full = datetime.fromtimestamp(expiry_time / 1000, tz=moscow_tz)
|
||||
formatted_date_full = expiry_date_full.strftime(
|
||||
"до %d %B %Y года, %H:%M"
|
||||
).lower()
|
||||
|
||||
formatted_date_short = expiry_date_full.strftime("до %d %B").lower()
|
||||
else:
|
||||
formatted_date_full = "без срока действия"
|
||||
formatted_date_short = "без срока действия"
|
||||
|
||||
button_text = f"{key_name} ({formatted_date_short})"
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"🔑 {key_name}", callback_data=f"view_key|{key_name}"
|
||||
text=button_text, callback_data=f"view_key|{key_name}"
|
||||
)
|
||||
)
|
||||
|
||||
response_message += f"• <b>{key_name}</b> ({formatted_date_full})\n"
|
||||
|
||||
else:
|
||||
response_message = (
|
||||
"<b>🔑 У вас пока нет подписок.</b>\n\n"
|
||||
"Вы можете создать новую подписку для подключения устройств."
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="➕ Добавить подписку", callback_data="create_key")
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
inline_keyboard = builder.as_markup()
|
||||
response_message = (
|
||||
"<b>🔑 Список ваших подписок</b>\n\n"
|
||||
"<i>👇 Выберите подписку для управления или добавьте новую для подключения дополнительного устройства:</i>"
|
||||
)
|
||||
return inline_keyboard, response_message
|
||||
|
||||
|
||||
@@ -219,7 +245,6 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
|
||||
),
|
||||
)
|
||||
|
||||
# ✅ Добавлена проверка флага ENABLE_DELETE_KEY_BUTTON
|
||||
if ENABLE_DELETE_KEY_BUTTON:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
@@ -383,38 +408,27 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery, sessio
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)',
|
||||
callback_data=f"renew_plan|1|{client_id}",
|
||||
for plan_id, plan_details in RENEWAL_PLANS.items():
|
||||
months = plan_details["months"]
|
||||
price = plan_details["price"]
|
||||
discount = DISCOUNTS.get(plan_id, 0)
|
||||
button_text = (
|
||||
f'📅 {months} месяц{"а" if months > 1 else ""} ({price} руб.)'
|
||||
+ (f' {discount}% скидка' if discount > 0 else "")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=button_text,
|
||||
callback_data=f"renew_plan|{months}|{client_id}",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.) {DISCOUNTS["3"]}% скидка',
|
||||
callback_data=f"renew_plan|3|{client_id}",
|
||||
text="🔙 Назад", callback_data="view_keys"
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.) {DISCOUNTS["6"]}% скидка',
|
||||
callback_data=f"renew_plan|6|{client_id}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 12 месяцев ({RENEWAL_PLANS["12"]["price"]} руб.) ({DISCOUNTS["12"]}% 🔥)',
|
||||
callback_data=f"renew_plan|12|{client_id}",
|
||||
)
|
||||
)
|
||||
back_button = InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data="view_keys"
|
||||
)
|
||||
builder.row(back_button)
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
response_message = PLAN_SELECTION_MSG.format(
|
||||
@@ -579,29 +593,58 @@ async def complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_g
|
||||
else:
|
||||
await bot.send_message(tg_id, response_message, reply_markup=builder.as_markup())
|
||||
|
||||
servers = await get_servers_from_db()
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
key_info = await conn.fetchrow(
|
||||
"""
|
||||
SELECT server_id
|
||||
FROM keys
|
||||
WHERE tg_id = $1 AND client_id = $2
|
||||
""",
|
||||
tg_id,
|
||||
client_id,
|
||||
)
|
||||
|
||||
logger.info(f"[RENEW] Запуск продления ключа для пользователя {tg_id} на {plan} мес. на всех серверах.")
|
||||
if not key_info:
|
||||
logger.error(f"[RENEW] Ключ с client_id {client_id} для пользователя {tg_id} не найден.")
|
||||
await conn.close()
|
||||
return
|
||||
|
||||
async def renew_key_on_servers():
|
||||
tasks = []
|
||||
for cluster_id in servers:
|
||||
task = asyncio.create_task(
|
||||
renew_key_in_cluster(
|
||||
cluster_id,
|
||||
email,
|
||||
client_id,
|
||||
new_expiry_time,
|
||||
total_gb,
|
||||
)
|
||||
)
|
||||
tasks.append(task)
|
||||
server_id = key_info["server_id"]
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
if USE_COUNTRY_SELECTION:
|
||||
cluster_info = await conn.fetchrow(
|
||||
"""
|
||||
SELECT cluster_name
|
||||
FROM servers
|
||||
WHERE server_name = $1
|
||||
""",
|
||||
server_id,
|
||||
)
|
||||
|
||||
if not cluster_info:
|
||||
logger.error(f"[RENEW] Сервер {server_id} не найден в таблице servers.")
|
||||
await conn.close()
|
||||
return
|
||||
|
||||
cluster_id = cluster_info["cluster_name"]
|
||||
else:
|
||||
cluster_id = server_id
|
||||
|
||||
await conn.close()
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
await update_balance(tg_id, -cost)
|
||||
await update_key_expiry(client_id, new_expiry_time)
|
||||
await update_balance(tg_id, -cost)
|
||||
logger.info(f"[RENEW] Ключ {client_id} успешно продлён на {plan} мес. для пользователя {tg_id}.")
|
||||
|
||||
await renew_key_on_servers()
|
||||
|
||||
await renew_key_on_cluster()
|
||||
|
||||
+98
-13
@@ -1,10 +1,12 @@
|
||||
import asyncio
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
|
||||
import asyncpg
|
||||
import pytz
|
||||
from aiogram import Bot, Router, types
|
||||
from aiogram.exceptions import TelegramForbiddenError
|
||||
from aiogram.types import BufferedInputFile
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from py3xui import AsyncApi
|
||||
|
||||
@@ -15,10 +17,10 @@ from config import (
|
||||
AUTO_RENEW_KEYS,
|
||||
DATABASE_URL,
|
||||
DEV_MODE,
|
||||
EXPIRED_KEYS_CHECK_INTERVAL,
|
||||
RENEWAL_PLANS,
|
||||
TOTAL_GB,
|
||||
TRIAL_TIME,
|
||||
EXPIRED_KEYS_CHECK_INTERVAL
|
||||
)
|
||||
from database import (
|
||||
add_blocked_user,
|
||||
@@ -184,11 +186,22 @@ async def process_10h_record(record, bot, conn):
|
||||
|
||||
await conn.execute("UPDATE keys SET notified = TRUE WHERE client_id = $1", record["client_id"])
|
||||
|
||||
image_path = os.path.join("img", "notify_10h.jpg")
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]]
|
||||
)
|
||||
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="notify_10h.jpg"),
|
||||
caption=KEY_RENEWED.format(email=email),
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED.format(email=email), reply_markup=keyboard)
|
||||
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
|
||||
except Exception as e:
|
||||
@@ -197,6 +210,7 @@ async def process_10h_record(record, bot, conn):
|
||||
await send_renewal_notification(bot, tg_id, email, message, conn, record["client_id"], "notified")
|
||||
|
||||
|
||||
|
||||
async def notify_24h_keys(
|
||||
bot: Bot,
|
||||
conn: asyncpg.Connection,
|
||||
@@ -222,7 +236,6 @@ async def notify_24h_keys(
|
||||
logger.info("Обработка всех уведомлений за 24 часа завершена.")
|
||||
|
||||
|
||||
|
||||
async def process_24h_record(record, bot, conn):
|
||||
tg_id = record["tg_id"]
|
||||
email = record["email"]
|
||||
@@ -259,11 +272,22 @@ async def process_24h_record(record, bot, conn):
|
||||
|
||||
await conn.execute("UPDATE keys SET notified_24h = TRUE WHERE client_id = $1", record["client_id"])
|
||||
|
||||
image_path = os.path.join("img", "notify_24h.jpg")
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]]
|
||||
)
|
||||
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="notify_24h.jpg"),
|
||||
caption=KEY_RENEWED.format(email=email),
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED.format(email=email), reply_markup=keyboard)
|
||||
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
|
||||
except Exception as e:
|
||||
@@ -279,7 +303,19 @@ async def send_renewal_notification(bot, tg_id, email, message, conn, client_id,
|
||||
keyboard.row(types.InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
keyboard.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await bot.send_message(tg_id, message, reply_markup=keyboard.as_markup())
|
||||
image_path = os.path.join("img", "notify_24h.jpg")
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="notify_24h.jpg"),
|
||||
caption=message,
|
||||
reply_markup=keyboard.as_markup(),
|
||||
)
|
||||
else:
|
||||
await bot.send_message(tg_id, message, reply_markup=keyboard.as_markup())
|
||||
|
||||
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
|
||||
|
||||
await conn.execute(f"UPDATE keys SET {flag} = TRUE WHERE client_id = $1", client_id)
|
||||
@@ -293,7 +329,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
|
||||
inactive_trial_users = await conn.fetch(
|
||||
"""
|
||||
SELECT tg_id, username FROM users
|
||||
SELECT tg_id, username, first_name, last_name FROM users
|
||||
WHERE tg_id IN (
|
||||
SELECT tg_id FROM connections
|
||||
WHERE trial = 0
|
||||
@@ -306,7 +342,16 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
|
||||
for user in inactive_trial_users:
|
||||
tg_id = user["tg_id"]
|
||||
username = user.get("username", "Пользователь")
|
||||
|
||||
username = user["username"]
|
||||
first_name = user["first_name"]
|
||||
last_name = user["last_name"]
|
||||
display_name = (
|
||||
username
|
||||
or first_name
|
||||
or last_name
|
||||
or "Пользователь"
|
||||
)
|
||||
|
||||
try:
|
||||
can_notify = await check_notification_time(
|
||||
@@ -329,7 +374,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
keyboard = builder.as_markup()
|
||||
|
||||
message = (
|
||||
f"👋 Привет, {username}!\n\n"
|
||||
f"👋 Привет, {display_name}!\n\n"
|
||||
f"🎉 У тебя есть бесплатный пробный период на {TRIAL_TIME} дней!\n"
|
||||
"🕒 Не упусти возможность попробовать наш VPN прямо сейчас.\n\n"
|
||||
"💡 Нажми на кнопку ниже, чтобы активировать пробный доступ."
|
||||
@@ -365,7 +410,7 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
|
||||
expiring_keys = await conn.fetch(
|
||||
"""
|
||||
SELECT tg_id, client_id, expiry_time, email FROM keys
|
||||
SELECT tg_id, client_id, expiry_time, email, server_id FROM keys
|
||||
WHERE expiry_time <= $1 AND expiry_time > $2
|
||||
""",
|
||||
threshold_time,
|
||||
@@ -380,6 +425,26 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке подписки {record['client_id']}: {e}")
|
||||
|
||||
expired_keys = await conn.fetch(
|
||||
"""
|
||||
SELECT tg_id, client_id, email, server_id FROM keys
|
||||
WHERE expiry_time <= $1
|
||||
""",
|
||||
current_time,
|
||||
)
|
||||
|
||||
logger.info(f"Найдено {len(expired_keys)} истёкших ключей.")
|
||||
|
||||
for record in expired_keys:
|
||||
try:
|
||||
await delete_key_from_cluster(record["server_id"], record["email"], record["email"])
|
||||
await conn.execute(
|
||||
"DELETE FROM keys WHERE client_id = $1", record["client_id"]
|
||||
)
|
||||
logger.info(f"Удалён истёкший ключ {record['client_id']} пользователя {record['tg_id']}.")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при удалении истёкшего ключа {record['client_id']}: {e}")
|
||||
|
||||
|
||||
async def process_key(record, bot, conn):
|
||||
tg_id = record["tg_id"]
|
||||
@@ -409,6 +474,8 @@ async def process_key(record, bot, conn):
|
||||
]
|
||||
)
|
||||
|
||||
image_path = os.path.join("img", "notify_expired.jpg")
|
||||
|
||||
try:
|
||||
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
|
||||
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
|
||||
@@ -433,15 +500,33 @@ async def process_key(record, bot, conn):
|
||||
logger.info(f"Флаги notified сброшены для клиента {client_id}.")
|
||||
|
||||
try:
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="notify_expired.jpg"),
|
||||
caption = KEY_RENEWED.format(email=email),
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось отправить уведомление о продлении клиенту {tg_id}: {e}")
|
||||
|
||||
else:
|
||||
message_expired = "Ваша подписка истекла. Пополните баланс для продления."
|
||||
message_expired = f"Ваша подписка {email} истекла. Пополните баланс для продления."
|
||||
try:
|
||||
await bot.send_message(tg_id, text=message_expired, reply_markup=keyboard)
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await bot.send_photo(
|
||||
tg_id,
|
||||
photo=BufferedInputFile(image_file.read(), filename="notify_expired.jpg"),
|
||||
caption=message_expired,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
else:
|
||||
await bot.send_message(tg_id, text=message_expired, reply_markup=keyboard)
|
||||
logger.info(f"Уведомление об истечении подписки отправлено пользователю {tg_id}.")
|
||||
except Exception as e:
|
||||
logger.error(f"Не удалось отправить уведомление об истечении клиенту {tg_id}: {e}")
|
||||
|
||||
+2286
-1396
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -92,12 +92,6 @@ async def process_callback_pay_robokassa(
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 Ввести свою сумму",
|
||||
callback_data="enter_custom_amount_robokassa",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
|
||||
|
||||
key_count = await get_key_count(tg_id)
|
||||
|
||||
+86
-86
@@ -4202,7 +4202,7 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
*
|
||||
* balance = await get_balance(user_id) # <<<<<<<<<<<<<<
|
||||
* if balance >= plan_price:
|
||||
* await update_balance(user_id, -plan_price)
|
||||
* expiry_time = datetime.utcnow() + timedelta(days=duration_days)
|
||||
*/
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_get_balance); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 58, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
@@ -4270,8 +4270,8 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
*
|
||||
* balance = await get_balance(user_id)
|
||||
* if balance >= plan_price: # <<<<<<<<<<<<<<
|
||||
* await update_balance(user_id, -plan_price)
|
||||
*
|
||||
* expiry_time = datetime.utcnow() + timedelta(days=duration_days)
|
||||
* await create_key(user_id, expiry_time, None, conn, None)
|
||||
*/
|
||||
__pyx_t_5 = PyObject_RichCompare(__pyx_cur_scope->__pyx_v_balance, __pyx_cur_scope->__pyx_v_plan_price, Py_GE); __Pyx_XGOTREF(__pyx_t_5); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 59, __pyx_L13_error)
|
||||
__pyx_t_11 = __Pyx_PyObject_IsTrue(__pyx_t_5); if (unlikely((__pyx_t_11 < 0))) __PYX_ERR(0, 59, __pyx_L13_error)
|
||||
@@ -4281,39 +4281,86 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
/* "handlers/payments/utils.py":60
|
||||
* balance = await get_balance(user_id)
|
||||
* if balance >= plan_price:
|
||||
* await update_balance(user_id, -plan_price) # <<<<<<<<<<<<<<
|
||||
* expiry_time = datetime.utcnow() + timedelta(days=duration_days) # <<<<<<<<<<<<<<
|
||||
* await create_key(user_id, expiry_time, None, conn, None)
|
||||
*
|
||||
* expiry_time = datetime.utcnow() + timedelta(days=duration_days)
|
||||
*/
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_update_balance); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_datetime); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
__pyx_t_8 = PyNumber_Negative(__pyx_cur_scope->__pyx_v_plan_price); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_utcnow); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_8);
|
||||
__pyx_t_9 = NULL;
|
||||
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
|
||||
__pyx_t_4 = NULL;
|
||||
__pyx_t_7 = 0;
|
||||
#if CYTHON_UNPACK_METHODS
|
||||
if (unlikely(PyMethod_Check(__pyx_t_4))) {
|
||||
__pyx_t_9 = PyMethod_GET_SELF(__pyx_t_4);
|
||||
if (likely(__pyx_t_9)) {
|
||||
PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_4);
|
||||
__Pyx_INCREF(__pyx_t_9);
|
||||
if (unlikely(PyMethod_Check(__pyx_t_8))) {
|
||||
__pyx_t_4 = PyMethod_GET_SELF(__pyx_t_8);
|
||||
if (likely(__pyx_t_4)) {
|
||||
PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_8);
|
||||
__Pyx_INCREF(__pyx_t_4);
|
||||
__Pyx_INCREF(function);
|
||||
__Pyx_DECREF_SET(__pyx_t_4, function);
|
||||
__Pyx_DECREF_SET(__pyx_t_8, function);
|
||||
__pyx_t_7 = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
{
|
||||
PyObject *__pyx_callargs[3] = {__pyx_t_9, __pyx_cur_scope->__pyx_v_user_id, __pyx_t_8};
|
||||
__pyx_t_5 = __Pyx_PyObject_FastCall(__pyx_t_4, __pyx_callargs+1-__pyx_t_7, 2+__pyx_t_7);
|
||||
__Pyx_XDECREF(__pyx_t_9); __pyx_t_9 = 0;
|
||||
__Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
|
||||
PyObject *__pyx_callargs[2] = {__pyx_t_4, NULL};
|
||||
__pyx_t_5 = __Pyx_PyObject_FastCall(__pyx_t_8, __pyx_callargs+1-__pyx_t_7, 0+__pyx_t_7);
|
||||
__Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0;
|
||||
if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_5);
|
||||
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
|
||||
__Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
|
||||
}
|
||||
__pyx_r = __Pyx_Coroutine_Yield_From(__pyx_generator, __pyx_t_5);
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_8, __pyx_n_s_timedelta); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_8);
|
||||
__pyx_t_4 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
if (PyDict_SetItem(__pyx_t_4, __pyx_n_s_days, __pyx_cur_scope->__pyx_v_duration_days) < 0) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_8, __pyx_empty_tuple, __pyx_t_4); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_9);
|
||||
__Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
|
||||
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
|
||||
__pyx_t_4 = PyNumber_Add(__pyx_t_5, __pyx_t_9); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
__Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
|
||||
__Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
|
||||
__Pyx_GIVEREF(__pyx_t_4);
|
||||
__pyx_cur_scope->__pyx_v_expiry_time = __pyx_t_4;
|
||||
__pyx_t_4 = 0;
|
||||
|
||||
/* "handlers/payments/utils.py":61
|
||||
* if balance >= plan_price:
|
||||
* expiry_time = datetime.utcnow() + timedelta(days=duration_days)
|
||||
* await create_key(user_id, expiry_time, None, conn, None) # <<<<<<<<<<<<<<
|
||||
*
|
||||
* await update_balance(user_id, -plan_price)
|
||||
*/
|
||||
__Pyx_INCREF(__pyx_cur_scope->__pyx_v_create_key);
|
||||
__pyx_t_9 = __pyx_cur_scope->__pyx_v_create_key; __pyx_t_5 = NULL;
|
||||
__pyx_t_7 = 0;
|
||||
#if CYTHON_UNPACK_METHODS
|
||||
if (unlikely(PyMethod_Check(__pyx_t_9))) {
|
||||
__pyx_t_5 = PyMethod_GET_SELF(__pyx_t_9);
|
||||
if (likely(__pyx_t_5)) {
|
||||
PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_9);
|
||||
__Pyx_INCREF(__pyx_t_5);
|
||||
__Pyx_INCREF(function);
|
||||
__Pyx_DECREF_SET(__pyx_t_9, function);
|
||||
__pyx_t_7 = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
{
|
||||
PyObject *__pyx_callargs[6] = {__pyx_t_5, __pyx_cur_scope->__pyx_v_user_id, __pyx_cur_scope->__pyx_v_expiry_time, Py_None, __pyx_cur_scope->__pyx_v_conn, Py_None};
|
||||
__pyx_t_4 = __Pyx_PyObject_FastCall(__pyx_t_9, __pyx_callargs+1-__pyx_t_7, 5+__pyx_t_7);
|
||||
__Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
|
||||
if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 61, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
__Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
|
||||
}
|
||||
__pyx_r = __Pyx_Coroutine_Yield_From(__pyx_generator, __pyx_t_4);
|
||||
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
|
||||
__Pyx_XGOTREF(__pyx_r);
|
||||
if (likely(__pyx_r)) {
|
||||
__Pyx_XGIVEREF(__pyx_t_1);
|
||||
@@ -4338,82 +4385,34 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
__pyx_t_3 = __pyx_cur_scope->__pyx_t_2;
|
||||
__pyx_cur_scope->__pyx_t_2 = 0;
|
||||
__Pyx_XGOTREF(__pyx_t_3);
|
||||
if (unlikely(!__pyx_sent_value)) __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
if (unlikely(!__pyx_sent_value)) __PYX_ERR(0, 61, __pyx_L13_error)
|
||||
} else {
|
||||
PyObject* exc_type = __Pyx_PyErr_CurrentExceptionType();
|
||||
if (exc_type) {
|
||||
if (likely(exc_type == PyExc_StopIteration || (exc_type != PyExc_GeneratorExit && __Pyx_PyErr_GivenExceptionMatches(exc_type, PyExc_StopIteration)))) PyErr_Clear();
|
||||
else __PYX_ERR(0, 60, __pyx_L13_error)
|
||||
else __PYX_ERR(0, 61, __pyx_L13_error)
|
||||
}
|
||||
}
|
||||
|
||||
/* "handlers/payments/utils.py":62
|
||||
* await update_balance(user_id, -plan_price)
|
||||
*
|
||||
* expiry_time = datetime.utcnow() + timedelta(days=duration_days) # <<<<<<<<<<<<<<
|
||||
* await create_key(user_id, expiry_time, None, conn, None)
|
||||
*
|
||||
*/
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_4, __pyx_n_s_datetime); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 62, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
__pyx_t_8 = __Pyx_PyObject_GetAttrStr(__pyx_t_4, __pyx_n_s_utcnow); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 62, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_8);
|
||||
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
|
||||
__pyx_t_4 = NULL;
|
||||
__pyx_t_7 = 0;
|
||||
#if CYTHON_UNPACK_METHODS
|
||||
if (unlikely(PyMethod_Check(__pyx_t_8))) {
|
||||
__pyx_t_4 = PyMethod_GET_SELF(__pyx_t_8);
|
||||
if (likely(__pyx_t_4)) {
|
||||
PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_8);
|
||||
__Pyx_INCREF(__pyx_t_4);
|
||||
__Pyx_INCREF(function);
|
||||
__Pyx_DECREF_SET(__pyx_t_8, function);
|
||||
__pyx_t_7 = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
{
|
||||
PyObject *__pyx_callargs[2] = {__pyx_t_4, NULL};
|
||||
__pyx_t_5 = __Pyx_PyObject_FastCall(__pyx_t_8, __pyx_callargs+1-__pyx_t_7, 0+__pyx_t_7);
|
||||
__Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0;
|
||||
if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 62, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_5);
|
||||
__Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
|
||||
}
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_8, __pyx_n_s_timedelta); if (unlikely(!__pyx_t_8)) __PYX_ERR(0, 62, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_8);
|
||||
__pyx_t_4 = __Pyx_PyDict_NewPresized(1); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 62, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
if (PyDict_SetItem(__pyx_t_4, __pyx_n_s_days, __pyx_cur_scope->__pyx_v_duration_days) < 0) __PYX_ERR(0, 62, __pyx_L13_error)
|
||||
__pyx_t_9 = __Pyx_PyObject_Call(__pyx_t_8, __pyx_empty_tuple, __pyx_t_4); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 62, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_9);
|
||||
__Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0;
|
||||
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
|
||||
__pyx_t_4 = PyNumber_Add(__pyx_t_5, __pyx_t_9); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 62, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
__Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
|
||||
__Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
|
||||
__Pyx_GIVEREF(__pyx_t_4);
|
||||
__pyx_cur_scope->__pyx_v_expiry_time = __pyx_t_4;
|
||||
__pyx_t_4 = 0;
|
||||
|
||||
/* "handlers/payments/utils.py":63
|
||||
* await create_key(user_id, expiry_time, None, conn, None)
|
||||
*
|
||||
* expiry_time = datetime.utcnow() + timedelta(days=duration_days)
|
||||
* await create_key(user_id, expiry_time, None, conn, None) # <<<<<<<<<<<<<<
|
||||
* await update_balance(user_id, -plan_price) # <<<<<<<<<<<<<<
|
||||
*
|
||||
* await clear_temporary_data(conn, user_id)
|
||||
*/
|
||||
__Pyx_INCREF(__pyx_cur_scope->__pyx_v_create_key);
|
||||
__pyx_t_9 = __pyx_cur_scope->__pyx_v_create_key; __pyx_t_5 = NULL;
|
||||
__Pyx_GetModuleGlobalName(__pyx_t_9, __pyx_n_s_update_balance); if (unlikely(!__pyx_t_9)) __PYX_ERR(0, 63, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_9);
|
||||
__pyx_t_5 = PyNumber_Negative(__pyx_cur_scope->__pyx_v_plan_price); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 63, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_5);
|
||||
__pyx_t_8 = NULL;
|
||||
__pyx_t_7 = 0;
|
||||
#if CYTHON_UNPACK_METHODS
|
||||
if (unlikely(PyMethod_Check(__pyx_t_9))) {
|
||||
__pyx_t_5 = PyMethod_GET_SELF(__pyx_t_9);
|
||||
if (likely(__pyx_t_5)) {
|
||||
__pyx_t_8 = PyMethod_GET_SELF(__pyx_t_9);
|
||||
if (likely(__pyx_t_8)) {
|
||||
PyObject* function = PyMethod_GET_FUNCTION(__pyx_t_9);
|
||||
__Pyx_INCREF(__pyx_t_5);
|
||||
__Pyx_INCREF(__pyx_t_8);
|
||||
__Pyx_INCREF(function);
|
||||
__Pyx_DECREF_SET(__pyx_t_9, function);
|
||||
__pyx_t_7 = 1;
|
||||
@@ -4421,9 +4420,10 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
}
|
||||
#endif
|
||||
{
|
||||
PyObject *__pyx_callargs[6] = {__pyx_t_5, __pyx_cur_scope->__pyx_v_user_id, __pyx_cur_scope->__pyx_v_expiry_time, Py_None, __pyx_cur_scope->__pyx_v_conn, Py_None};
|
||||
__pyx_t_4 = __Pyx_PyObject_FastCall(__pyx_t_9, __pyx_callargs+1-__pyx_t_7, 5+__pyx_t_7);
|
||||
__Pyx_XDECREF(__pyx_t_5); __pyx_t_5 = 0;
|
||||
PyObject *__pyx_callargs[3] = {__pyx_t_8, __pyx_cur_scope->__pyx_v_user_id, __pyx_t_5};
|
||||
__pyx_t_4 = __Pyx_PyObject_FastCall(__pyx_t_9, __pyx_callargs+1-__pyx_t_7, 2+__pyx_t_7);
|
||||
__Pyx_XDECREF(__pyx_t_8); __pyx_t_8 = 0;
|
||||
__Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
|
||||
if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 63, __pyx_L13_error)
|
||||
__Pyx_GOTREF(__pyx_t_4);
|
||||
__Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0;
|
||||
@@ -4464,7 +4464,7 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
}
|
||||
|
||||
/* "handlers/payments/utils.py":65
|
||||
* await create_key(user_id, expiry_time, None, conn, None)
|
||||
* await update_balance(user_id, -plan_price)
|
||||
*
|
||||
* await clear_temporary_data(conn, user_id) # <<<<<<<<<<<<<<
|
||||
* return
|
||||
@@ -4544,8 +4544,8 @@ static PyObject *__pyx_gb_8handlers_8payments_5utils_2generator(__pyx_CoroutineO
|
||||
*
|
||||
* balance = await get_balance(user_id)
|
||||
* if balance >= plan_price: # <<<<<<<<<<<<<<
|
||||
* await update_balance(user_id, -plan_price)
|
||||
*
|
||||
* expiry_time = datetime.utcnow() + timedelta(days=duration_days)
|
||||
* await create_key(user_id, expiry_time, None, conn, None)
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
+1381
-1397
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -100,6 +100,15 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
)
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
await session.execute(
|
||||
"""
|
||||
INSERT INTO connections (tg_id, balance, trial)
|
||||
VALUES ($1, 0, 1)
|
||||
ON CONFLICT (tg_id) DO UPDATE SET trial = 1
|
||||
""",
|
||||
recipient_tg_id,
|
||||
)
|
||||
|
||||
selected_months = gift_info["selected_months"]
|
||||
expiry_time = gift_info["expiry_time"]
|
||||
expiry_time_naive = expiry_time.replace(tzinfo=None)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Reference in New Issue
Block a user