Major 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-Alpha(01-dev)"
|
||||
version = "4.0.0-Alpha(06-dev)"
|
||||
|
||||
register_middleware(dp)
|
||||
|
||||
@@ -41,6 +41,7 @@ async def errors_handler(
|
||||
if (
|
||||
"query is too old and response timeout expired or query ID is invalid" in error_message
|
||||
or "message can't be deleted for everyone" in error_message
|
||||
or "message to delete not found" in error_message
|
||||
):
|
||||
logger.warning("Отправляем стартовое меню.")
|
||||
|
||||
|
||||
@@ -414,22 +414,18 @@ async def handle_clusters_backup(
|
||||
)
|
||||
return
|
||||
|
||||
servers = await get_servers(session)
|
||||
cluster_servers = servers.get(cluster_name, [])
|
||||
|
||||
for key in keys_to_sync:
|
||||
for _server in cluster_servers:
|
||||
try:
|
||||
await create_key_on_cluster(
|
||||
cluster_name,
|
||||
key["tg_id"],
|
||||
key["client_id"],
|
||||
key["email"],
|
||||
key["expiry_time"],
|
||||
)
|
||||
await asyncio.sleep(0.6)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при добавлении ключа {key['client_id']} в кластер {cluster_name}: {e}")
|
||||
try:
|
||||
await create_key_on_cluster(
|
||||
cluster_name,
|
||||
key["tg_id"],
|
||||
key["client_id"],
|
||||
key["email"],
|
||||
key["expiry_time"],
|
||||
)
|
||||
await asyncio.sleep(0.6)
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при добавлении ключа {key['client_id']} в кластер {cluster_name}: {e}")
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=f"✅ Ключи успешно синхронизированы для кластера {cluster_name}",
|
||||
|
||||
@@ -146,7 +146,7 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
)
|
||||
return
|
||||
expiry_time = datetime.now(moscow_tz) + timedelta(days=duration_days)
|
||||
await create_key(tg_id, expiry_time, None, session, callback_query)
|
||||
await create_key(tg_id, expiry_time, None, session, callback_query, None, plan_id)
|
||||
await update_balance(tg_id, -plan_price, session)
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ async def create_key(
|
||||
session: Any,
|
||||
message_or_query: Message | CallbackQuery | None = None,
|
||||
old_key_name: str = None,
|
||||
plan: int = None,
|
||||
):
|
||||
"""Создаёт ключ с заданным сроком действия."""
|
||||
|
||||
@@ -230,13 +231,7 @@ async def create_key(
|
||||
least_loaded_cluster = await get_least_loaded_cluster()
|
||||
tasks = [
|
||||
asyncio.create_task(
|
||||
create_key_on_cluster(
|
||||
least_loaded_cluster,
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
)
|
||||
create_key_on_cluster(least_loaded_cluster, tg_id, client_id, email, expiry_timestamp, plan)
|
||||
)
|
||||
]
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
@@ -10,7 +10,9 @@ from handlers.utils import get_least_loaded_cluster
|
||||
from logger import logger
|
||||
|
||||
|
||||
async def create_key_on_cluster(cluster_id: str, tg_id: int, client_id: str, email: str, expiry_timestamp: int):
|
||||
async def create_key_on_cluster(
|
||||
cluster_id: str, tg_id: int, client_id: str, email: str, expiry_timestamp: int, plan: int = None
|
||||
):
|
||||
"""
|
||||
Создает ключ на всех серверах указанного кластера.
|
||||
"""
|
||||
@@ -26,24 +28,12 @@ async def create_key_on_cluster(cluster_id: str, tg_id: int, client_id: str, ema
|
||||
if SUPERNODE:
|
||||
for server_info in cluster:
|
||||
await create_client_on_server(
|
||||
server_info,
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
semaphore,
|
||||
server_info, tg_id, client_id, email, expiry_timestamp, semaphore, plan=plan
|
||||
)
|
||||
else:
|
||||
await asyncio.gather(
|
||||
*(
|
||||
create_client_on_server(
|
||||
server,
|
||||
tg_id,
|
||||
client_id,
|
||||
email,
|
||||
expiry_timestamp,
|
||||
semaphore,
|
||||
)
|
||||
create_client_on_server(server, tg_id, client_id, email, expiry_timestamp, semaphore, plan=plan)
|
||||
for server in cluster
|
||||
)
|
||||
)
|
||||
@@ -60,6 +50,7 @@ async def create_client_on_server(
|
||||
email: str,
|
||||
expiry_timestamp: int,
|
||||
semaphore: asyncio.Semaphore,
|
||||
plan: int = None,
|
||||
):
|
||||
"""
|
||||
Создает клиента на указанном сервере.
|
||||
@@ -85,6 +76,8 @@ async def create_client_on_server(
|
||||
unique_email = email
|
||||
sub_id = unique_email
|
||||
|
||||
total_gb_value = int(TOTAL_GB) if plan is None else int(plan) * int(TOTAL_GB)
|
||||
|
||||
await add_client(
|
||||
xui,
|
||||
ClientConfig(
|
||||
@@ -92,7 +85,7 @@ async def create_client_on_server(
|
||||
email=unique_email,
|
||||
tg_id=tg_id,
|
||||
limit_ip=LIMIT_IP,
|
||||
total_gb=TOTAL_GB,
|
||||
total_gb=total_gb_value,
|
||||
expiry_time=expiry_timestamp,
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
|
||||
+24
-24
@@ -97,29 +97,29 @@ def build_keys_response(records):
|
||||
Формирует сообщение и клавиатуру для устройств с указанием срока действия подписки.
|
||||
"""
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||||
|
||||
if records:
|
||||
response_message = "<b>🔑 Список ваших подписок:</b>\n\n"
|
||||
response_message = "<b>🔑 Список ваших подписок:</b>\n\n<blockquote>"
|
||||
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()
|
||||
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})"
|
||||
button_text = f"🔑{key_name} ({formatted_date_short})"
|
||||
builder.row(InlineKeyboardButton(text=button_text, callback_data=f"view_key|{key_name}"))
|
||||
|
||||
response_message += f"• <b>{key_name}</b> ({formatted_date_full})\n"
|
||||
|
||||
response_message += "</blockquote>\n"
|
||||
|
||||
else:
|
||||
response_message = (
|
||||
"<b>🔑 У вас пока нет подписок.</b>\n\nВы можете создать новую подписку для подключения устройств."
|
||||
@@ -463,70 +463,70 @@ 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"[RENEW] Начинаю процесс продления ключа с параметрами: "
|
||||
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}"
|
||||
f"callback_query={'есть' if callback_query else 'отсутствует'}, plan={plan}"
|
||||
)
|
||||
|
||||
response_message = SUCCESS_RENEWAL_MSG.format(months=plan)
|
||||
logger.info(f"[RENEW] Constructed response message: {response_message}")
|
||||
logger.info(f"[RENEW] Сформировано сообщение: {response_message}")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
if callback_query:
|
||||
logger.info("[RENEW] Sending response via callback_query.message.answer()")
|
||||
logger.info("[RENEW] Отправка ответа через 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()")
|
||||
logger.info("[RENEW] Отправка ответа через bot.send_message()")
|
||||
await bot.send_message(tg_id, response_message, reply_markup=builder.as_markup())
|
||||
|
||||
logger.info("[RENEW] Connecting to database...")
|
||||
logger.info("[RENEW] Подключение к базе данных...")
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
logger.info("[RENEW] Connected to database.")
|
||||
logger.info("[RENEW] Подключение к базе данных установлено.")
|
||||
|
||||
logger.info(f"[RENEW] Retrieving key details for email: {email}")
|
||||
logger.info(f"[RENEW] Получение данных о ключе для 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}")
|
||||
logger.info(f"[RENEW] Данные о ключе получены: {key_info}")
|
||||
|
||||
server_id = key_info["server_id"]
|
||||
logger.info(f"[RENEW] Using server_id: {server_id}")
|
||||
logger.info(f"[RENEW] Используется 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}")
|
||||
logger.info(f"[RENEW] USE_COUNTRY_SELECTION включён. Проверяю информацию о сервере {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}")
|
||||
logger.info(f"[RENEW] Информация о сервере получена: {cluster_info}. Использую 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] USE_COUNTRY_SELECTION выключен. Использую server_id в качестве cluster_id: {cluster_id}")
|
||||
|
||||
logger.info(f"[RENEW] Запуск продления ключа для пользователя {tg_id} на {plan} мес. в кластере {cluster_id}.")
|
||||
|
||||
async def renew_key_on_cluster():
|
||||
logger.info(
|
||||
f"[RENEW] Starting renew_key_on_cluster with parameters: "
|
||||
f"[RENEW] Запуск renew_key_on_cluster с параметрами: "
|
||||
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.")
|
||||
logger.info("[RENEW] Продление ключа на сервере завершено. Обновляю срок действия в базе данных.")
|
||||
await update_key_expiry(client_id, new_expiry_time, conn)
|
||||
logger.info("[RENEW] Key expiry updated. Now updating balance.")
|
||||
logger.info("[RENEW] Срок действия ключа обновлён. Обновляю баланс пользователя.")
|
||||
await update_balance(tg_id, -cost, conn)
|
||||
logger.info(f"[RENEW] Ключ {client_id} успешно продлён на {plan} мес. для пользователя {tg_id}.")
|
||||
|
||||
logger.info("[RENEW] Initiating key renewal process on cluster.")
|
||||
logger.info("[RENEW] Инициализация процесса продления ключа в кластере.")
|
||||
await renew_key_on_cluster()
|
||||
|
||||
logger.info("[RENEW] Key renewal process completed. Closing database connection.")
|
||||
logger.info("[RENEW] Процесс продления ключа завершён. Закрываю соединение с базой данных.")
|
||||
await conn.close()
|
||||
|
||||
+4
-2
@@ -61,9 +61,11 @@ async def handle_pay(callback_query: CallbackQuery):
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"💸 <b>Выберите удобный способ пополнения баланса:</b>\n\n"
|
||||
"💸 <b>Выберите удобный способ пополнения баланса:</b>\n"
|
||||
"<blockquote>"
|
||||
"• Быстро и безопасно\n"
|
||||
"• Поддержка разных платежных систем\n"
|
||||
"• Моментальное зачисление средств 🚀",
|
||||
"• Моментальное зачисление средств 🚀\n"
|
||||
"</blockquote>",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+3
-1961
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+3
-1434
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+221
-812
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+2295
-3463
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+4
-2
@@ -55,9 +55,11 @@ async def process_callback_view_profile(
|
||||
profile_message = profile_message_send(username, chat_id, int(balance), key_count)
|
||||
|
||||
if key_count == 0:
|
||||
profile_message += "\n<pre>🔧 <i>Нажмите кнопку ➕ Подписка, чтобы настроить VPN-подключение</i></pre>"
|
||||
profile_message += (
|
||||
"\n<blockquote>🔧 <i>Нажмите кнопку ➕ Подписка, чтобы настроить VPN-подключение</i></blockquote>"
|
||||
)
|
||||
else:
|
||||
profile_message += f"\n<pre> <i>{NEWS_MESSAGE}</i></pre>"
|
||||
profile_message += f"\n<blockquote> <i>{NEWS_MESSAGE}</i></blockquote>"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
|
||||
+16
-23
@@ -54,8 +54,8 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
|
||||
try:
|
||||
await state.clear()
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось очистить состояние для пользователя {message.chat.id}: {e}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if CAPTCHA_ENABLE and captcha:
|
||||
captcha_data = await generate_captcha(message, state)
|
||||
@@ -67,6 +67,7 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
member = await bot.get_chat_member(CHANNEL_ID, message.chat.id)
|
||||
if member.status not in ["member", "administrator", "creator"]:
|
||||
builder = InlineKeyboardBuilder()
|
||||
await state.update_data(original_text=message.text)
|
||||
builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription"))
|
||||
await message.answer(
|
||||
f"Для использования бота, пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}",
|
||||
@@ -86,8 +87,11 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
await process_start_logic(message, state, session, admin)
|
||||
|
||||
|
||||
async def process_start_logic(message: Message, state: FSMContext, session: Any, admin: bool):
|
||||
if message.text:
|
||||
async def process_start_logic(
|
||||
message: Message, state: FSMContext, session: Any, admin: bool, text_to_process: str = None
|
||||
):
|
||||
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}")
|
||||
@@ -96,16 +100,15 @@ async def process_start_logic(message: Message, state: FSMContext, session: Any,
|
||||
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()
|
||||
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} не найден.")
|
||||
await message.answer("❌ Купон не найден!")
|
||||
@@ -116,7 +119,6 @@ async def process_start_logic(message: Message, state: FSMContext, session: Any,
|
||||
coupon["id"],
|
||||
message.chat.id,
|
||||
)
|
||||
|
||||
if usage_exists:
|
||||
logger.info(f"Пользователь {message.chat.id} уже активировал купон {coupon_code}.")
|
||||
await message.answer("❌ Вы уже использовали этот купон!")
|
||||
@@ -152,7 +154,7 @@ async def process_start_logic(message: Message, state: FSMContext, session: Any,
|
||||
await message.answer(f"🎉 Ваш баланс пополнен на {coupon['amount']} RUB по купону!")
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
if "gift_" in message.text:
|
||||
if "gift_" in text:
|
||||
logger.info(f"Обнаружена ссылка на подарок: {message.text}")
|
||||
parts = message.text.split("gift_")[1].split("_")
|
||||
gift_id = parts[0]
|
||||
@@ -219,7 +221,7 @@ async def process_start_logic(message: Message, state: FSMContext, session: Any,
|
||||
logger.info(f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}.")
|
||||
return
|
||||
|
||||
elif "referral_" in message.text:
|
||||
elif "referral_" in text:
|
||||
try:
|
||||
referrer_tg_id = int(message.text.split("referral_")[1])
|
||||
|
||||
@@ -262,16 +264,11 @@ async def process_start_logic(message: Message, state: FSMContext, session: Any,
|
||||
async def check_subscription_callback(callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool):
|
||||
user_id = callback_query.from_user.id
|
||||
logger.info(f"[CALLBACK] Получен callback 'check_subscription' от пользователя {user_id}")
|
||||
|
||||
try:
|
||||
logger.info(f"[CALLBACK] Запрос информации о подписке для пользователя {user_id} на канал {CHANNEL_ID}")
|
||||
member = await bot.get_chat_member(CHANNEL_ID, user_id)
|
||||
logger.info(f"[CALLBACK] Статус подписки пользователя {user_id}: {member.status}")
|
||||
|
||||
if member.status not in ["member", "administrator", "creator"]:
|
||||
logger.info(
|
||||
f"[CALLBACK] Пользователь {user_id} НЕ подписан на канал {CHANNEL_ID}. Текущий статус: {member.status}"
|
||||
)
|
||||
await callback_query.answer("Вы еще не подписаны на канал!", show_alert=True)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="✅ Я подписался", callback_data="check_subscription"))
|
||||
@@ -279,16 +276,12 @@ async def check_subscription_callback(callback_query: CallbackQuery, state: FSMC
|
||||
f"Для использования бота, пожалуйста, подпишитесь на наш канал: {CHANNEL_URL}",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
logger.info(f"[CALLBACK] Обновлено сообщение с приглашением подписаться для пользователя {user_id}")
|
||||
else:
|
||||
logger.info(f"[CALLBACK] Пользователь {user_id} подписан на канал {CHANNEL_ID} - подписка подтверждена")
|
||||
await callback_query.answer("Подписка подтверждена!")
|
||||
logger.info(
|
||||
f"[CALLBACK] Перед вызовом process_start_logic для пользователя {user_id}. Текущее сообщение: {callback_query.message.text}"
|
||||
)
|
||||
await process_start_logic(callback_query.message, state, session, admin)
|
||||
data = await state.get_data()
|
||||
original_text = data.get("original_text", callback_query.message.text)
|
||||
await process_start_logic(callback_query.message, state, session, admin, text_to_process=original_text)
|
||||
logger.info(f"[CALLBACK] Завершен вызов process_start_logic для пользователя {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[CALLBACK] Ошибка проверки подписки для пользователя {user_id}: {e}", exc_info=True)
|
||||
await callback_query.answer("Ошибка проверки подписки, повторите попытку", show_alert=True)
|
||||
|
||||
Reference in New Issue
Block a user