Major bug fixes

This commit is contained in:
Vladless
2025-02-06 02:03:12 +03:00
parent eb824162d2
commit 3de1635efa
21 changed files with 2602 additions and 10605 deletions
+2 -1
View File
@@ -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("Отправляем стартовое меню.")
+11 -15
View File
@@ -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}",
+3 -8
View File
@@ -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)
+9 -16
View File
@@ -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
View File
@@ -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
View File
@@ -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
+3 -1961
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+221 -812
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -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
View File
@@ -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)
+1 -1
View File
File diff suppressed because one or more lines are too long