Refactor and clean up code formatting across multiple files
- Consolidated and simplified code formatting by removing unnecessary line breaks and improving readability in various functions. - Updated the handling of backup file sending in backup.py for better clarity. - Streamlined error handling and logging messages in several handlers to enhance consistency. - Adjusted the Makefile to exclude specific files during Ruff checks and formatting. - Made minor adjustments to function signatures and parameter handling for improved clarity and consistency. This commit enhances code maintainability and readability without altering functionality.
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
name: Code Formatting
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install ruff
|
||||
|
||||
- name: Run Ruff formatter with pyproject.toml
|
||||
run: ruff format . --config pyproject.toml --exclude main.py,handlers/payments
|
||||
|
||||
- name: Check for changes
|
||||
id: check_changes
|
||||
run: |
|
||||
git diff --exit-code || echo "changes_detected=true" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit and push changes
|
||||
if: steps.check_changes.outputs.changes_detected == 'true'
|
||||
run: |
|
||||
git config --global user.name 'GitHub Actions'
|
||||
git config --global user.email 'actions@github.com'
|
||||
git add .
|
||||
git commit -m "Auto-format code with Ruff using pyproject.toml"
|
||||
git push
|
||||
@@ -1,6 +1,7 @@
|
||||
formatting:
|
||||
@echo "Running Ruff..." && ruff check . --fix
|
||||
@echo "Running Ruff format..." && ruff format .
|
||||
@echo "Running Ruff format..." && ruff format . --config pyproject.toml --exclude main.py,handlers/payments
|
||||
|
||||
@echo "Running Ruff..." && ruff check . --config pyproject.toml --exclude main.py,handlers/payments --fix
|
||||
|
||||
lint:
|
||||
@echo "Running Ruff checks..." && ruff check .
|
||||
@echo "Running Ruff checks..." && ruff check . --config pyproject.toml --exclude main.py,handlers/payments
|
||||
@@ -59,9 +59,7 @@ def _create_database_backup():
|
||||
async def _send_backup_to_admin(bot, backup_file_path):
|
||||
try:
|
||||
with open(backup_file_path, "rb") as backup_file:
|
||||
backup_input_file = BufferedInputFile(
|
||||
backup_file.read(), filename=os.path.basename(backup_file_path)
|
||||
)
|
||||
backup_input_file = BufferedInputFile(backup_file.read(), filename=os.path.basename(backup_file_path))
|
||||
admin_ids: int | list[int] = ADMIN_ID
|
||||
if isinstance(admin_ids, list):
|
||||
for id in admin_ids:
|
||||
|
||||
@@ -44,7 +44,7 @@ async def errors_handler(
|
||||
filename=f"error_{event.update.update_id}.txt",
|
||||
),
|
||||
caption=f"{hbold(type(event.exception).__name__)}: {str(event.exception)[:1021]}...",
|
||||
)
|
||||
)
|
||||
except TelegramBadRequest as exception:
|
||||
logger.warning(f"Failed to send error details: {exception}")
|
||||
except Exception as exception:
|
||||
|
||||
@@ -15,7 +15,7 @@ async def add_client(
|
||||
enable: bool,
|
||||
flow: str,
|
||||
inbound_id: int,
|
||||
sub_id
|
||||
sub_id,
|
||||
):
|
||||
"""
|
||||
Adds a client to the server via 3x-ui.
|
||||
@@ -45,9 +45,7 @@ async def add_client(
|
||||
error_message = str(e)
|
||||
|
||||
if "Duplicate email" in error_message:
|
||||
logger.warning(
|
||||
f"Дублированный email: {email}. Пропуск. Сообщение: {error_message}"
|
||||
)
|
||||
logger.warning(f"Дублированный email: {email}. Пропуск. Сообщение: {error_message}")
|
||||
return {"status": "duplicate", "email": email}
|
||||
|
||||
logger.error(f"Ошибка при добавлении клиента {email}: {error_message}")
|
||||
@@ -55,7 +53,7 @@ async def add_client(
|
||||
|
||||
|
||||
async def extend_client_key(
|
||||
xui, inbound_id, email: str, new_expiry_time: int, client_id: str, total_gb: int, sub_id = str
|
||||
xui, inbound_id, email: str, new_expiry_time: int, client_id: str, total_gb: int, sub_id=str
|
||||
):
|
||||
"""
|
||||
Функция для обновления срока действия ключа клиента по email.
|
||||
@@ -72,9 +70,7 @@ async def extend_client_key(
|
||||
logger.warning(f"Ошибка: клиент {email} не имеет действительного ID.")
|
||||
return
|
||||
|
||||
logger.info(
|
||||
f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}"
|
||||
)
|
||||
logger.info(f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}")
|
||||
|
||||
client.id = client_id
|
||||
client.expiry_time = new_expiry_time
|
||||
@@ -87,9 +83,7 @@ async def extend_client_key(
|
||||
|
||||
await xui.client.update(client.id, client)
|
||||
await xui.client.reset_stats(inbound_id, email)
|
||||
logger.info(
|
||||
f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}."
|
||||
)
|
||||
logger.info(f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обновлении клиента с email {email}: {e}")
|
||||
|
||||
+40
-121
@@ -26,9 +26,7 @@ async def save_temporary_data(session, tg_id: int, state: str, data: dict):
|
||||
|
||||
async def get_temporary_data(session, tg_id: int) -> dict | None:
|
||||
"""Извлекает временные данные пользователя."""
|
||||
result = await session.fetchrow(
|
||||
"SELECT state, data FROM temporary_data WHERE tg_id = $1", tg_id
|
||||
)
|
||||
result = await session.fetchrow("SELECT state, data FROM temporary_data WHERE tg_id = $1", tg_id)
|
||||
if result:
|
||||
return {"state": result["state"], "data": json.loads(result["data"])}
|
||||
return None
|
||||
@@ -71,18 +69,14 @@ async def check_unique_server_name(server_name: str) -> bool:
|
||||
"""
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
|
||||
result = await conn.fetchrow(
|
||||
"SELECT 1 FROM servers WHERE server_name = $1 LIMIT 1", server_name
|
||||
)
|
||||
result = await conn.fetchrow("SELECT 1 FROM servers WHERE server_name = $1 LIMIT 1", server_name)
|
||||
|
||||
await conn.close()
|
||||
|
||||
return result is None
|
||||
|
||||
|
||||
async def create_coupon(
|
||||
coupon_code: str, amount: float, usage_limit: int, session: Any
|
||||
):
|
||||
async def create_coupon(coupon_code: str, amount: float, usage_limit: int, session: Any):
|
||||
"""
|
||||
Создает новый купон в базе данных.
|
||||
|
||||
@@ -142,7 +136,8 @@ async def get_all_coupons(session: Any, page: int = 1, per_page: int = 10):
|
||||
ORDER BY id
|
||||
LIMIT $1 OFFSET $2
|
||||
""",
|
||||
per_page, offset
|
||||
per_page,
|
||||
offset,
|
||||
)
|
||||
|
||||
total_count = await session.fetchval("SELECT COUNT(*) FROM coupons")
|
||||
@@ -150,12 +145,7 @@ async def get_all_coupons(session: Any, page: int = 1, per_page: int = 10):
|
||||
|
||||
logger.info(f"Успешно получено {len(coupons)} купонов из базы данных (страница {page})")
|
||||
|
||||
return {
|
||||
"coupons": coupons,
|
||||
"total": total_count,
|
||||
"pages": total_pages,
|
||||
"current_page": page
|
||||
}
|
||||
return {"coupons": coupons, "total": total_count, "pages": total_pages, "current_page": page}
|
||||
except Exception as e:
|
||||
logger.error(f"Критическая ошибка при получении списка купонов: {e}")
|
||||
logger.exception("Трассировка стека ошибки получения купонов")
|
||||
@@ -230,9 +220,7 @@ async def restore_trial(tg_id: int, session: Any):
|
||||
logger.info(f"Триальный период успешно восстановлен для пользователя {tg_id}")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при восстановлении триального периода для пользователя {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при восстановлении триального периода для пользователя {tg_id}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -264,9 +252,7 @@ async def use_trial(tg_id: int, session: Any):
|
||||
return False
|
||||
|
||||
|
||||
async def add_connection(
|
||||
tg_id: int, balance: float = 0.0, trial: int = 0, session: Any = None
|
||||
):
|
||||
async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0, session: Any = None):
|
||||
"""
|
||||
Добавляет новое подключение для пользователя в базу данных.
|
||||
|
||||
@@ -293,9 +279,7 @@ async def add_connection(
|
||||
f"Успешно добавлено новое подключение для пользователя {tg_id} с балансом {balance} и статусом триала {trial}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Не удалось добавить подключение для пользователя {tg_id}. Причина: {e}"
|
||||
)
|
||||
logger.error(f"Не удалось добавить подключение для пользователя {tg_id}. Причина: {e}")
|
||||
raise
|
||||
|
||||
|
||||
@@ -369,9 +353,7 @@ async def store_key(
|
||||
key,
|
||||
server_id,
|
||||
)
|
||||
logger.info(
|
||||
f"Ключ успешно сохранен для пользователя {tg_id} на сервере {server_id}"
|
||||
)
|
||||
logger.info(f"Ключ успешно сохранен для пользователя {tg_id} на сервере {server_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при сохранении ключа для пользователя {tg_id}: {e}")
|
||||
raise
|
||||
@@ -437,14 +419,10 @@ async def get_keys_by_server(tg_id: int, server_id: str):
|
||||
tg_id,
|
||||
server_id,
|
||||
)
|
||||
logger.info(
|
||||
f"Успешно получено {len(records)} ключей для пользователя {tg_id} на сервере {server_id}"
|
||||
)
|
||||
logger.info(f"Успешно получено {len(records)} ключей для пользователя {tg_id} на сервере {server_id}")
|
||||
return records
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при получении ключей для пользователя {tg_id} на сервере {server_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при получении ключей для пользователя {tg_id} на сервере {server_id}: {e}")
|
||||
raise
|
||||
finally:
|
||||
if conn:
|
||||
@@ -468,14 +446,10 @@ async def has_active_key(tg_id: int) -> bool:
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
count = await conn.fetchval("SELECT COUNT(*) FROM keys WHERE tg_id = $1", tg_id)
|
||||
logger.info(
|
||||
f"Проверка наличия ключей для пользователя {tg_id}. Найдено ключей: {count}"
|
||||
)
|
||||
logger.info(f"Проверка наличия ключей для пользователя {tg_id}. Найдено ключей: {count}")
|
||||
return count > 0
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при проверке наличия ключей для пользователя {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при проверке наличия ключей для пользователя {tg_id}: {e}")
|
||||
raise
|
||||
finally:
|
||||
if conn:
|
||||
@@ -498,9 +472,7 @@ async def get_balance(tg_id: int) -> float:
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
balance = await conn.fetchval(
|
||||
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
balance = await conn.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
logger.info(f"Получен баланс для пользователя {tg_id}: {balance}")
|
||||
return balance if balance is not None else 0.0
|
||||
except Exception as e:
|
||||
@@ -558,15 +530,11 @@ async def get_trial(tg_id: int, session: Any) -> int:
|
||||
int: Статус триала (0 - не использован, 1 - использован)
|
||||
"""
|
||||
try:
|
||||
trial = await session.fetchval(
|
||||
"SELECT trial FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
trial = await session.fetchval("SELECT trial FROM connections WHERE tg_id = $1", tg_id)
|
||||
logger.info(f"Получен статус триала для пользователя {tg_id}: {trial}")
|
||||
return trial if trial is not None else 0
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при получении статуса триала для пользователя {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при получении статуса триала для пользователя {tg_id}: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -590,9 +558,7 @@ async def get_key_count(tg_id: int) -> int:
|
||||
logger.info(f"Получено количество ключей для пользователя {tg_id}: {count}")
|
||||
return count if count is not None else 0
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при получении количества ключей для пользователя {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при получении количества ключей для пользователя {tg_id}: {e}")
|
||||
return 0
|
||||
finally:
|
||||
if conn:
|
||||
@@ -623,11 +589,8 @@ async def get_all_users(conn):
|
||||
|
||||
async def add_referral(referred_tg_id: int, referrer_tg_id: int, session: Any):
|
||||
try:
|
||||
|
||||
if referred_tg_id == referrer_tg_id:
|
||||
logger.warning(
|
||||
f"Пользователь {referred_tg_id} попытался использовать свою собственную реферальную ссылку."
|
||||
)
|
||||
logger.warning(f"Пользователь {referred_tg_id} попытался использовать свою собственную реферальную ссылку.")
|
||||
return
|
||||
|
||||
await session.execute(
|
||||
@@ -638,9 +601,7 @@ async def add_referral(referred_tg_id: int, referrer_tg_id: int, session: Any):
|
||||
referred_tg_id,
|
||||
referrer_tg_id,
|
||||
)
|
||||
logger.info(
|
||||
f"Добавлена реферальная связь: приглашенный {referred_tg_id}, пригласивший {referrer_tg_id}"
|
||||
)
|
||||
logger.info(f"Добавлена реферальная связь: приглашенный {referred_tg_id}, пригласивший {referrer_tg_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при добавлении реферала: {e}")
|
||||
raise
|
||||
@@ -673,9 +634,7 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float):
|
||||
|
||||
for level in range(1, MAX_REFERRAL_LEVELS + 1):
|
||||
if current_tg_id in visited_tg_ids:
|
||||
logger.warning(
|
||||
f"Обнаружен цикл в реферальной цепочке для пользователя {current_tg_id}. Прекращение."
|
||||
)
|
||||
logger.warning(f"Обнаружен цикл в реферальной цепочке для пользователя {current_tg_id}. Прекращение.")
|
||||
break
|
||||
|
||||
visited_tg_ids.add(current_tg_id)
|
||||
@@ -714,15 +673,11 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float):
|
||||
bonus = round(amount * bonus_percent, 2)
|
||||
|
||||
if bonus > 0:
|
||||
logger.info(
|
||||
f"Начисление бонуса {bonus} рублей рефереру {referrer_tg_id} на уровне {level}."
|
||||
)
|
||||
logger.info(f"Начисление бонуса {bonus} рублей рефереру {referrer_tg_id} на уровне {level}.")
|
||||
await update_balance(referrer_tg_id, bonus)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при обработке многоуровневой реферальной системы для {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при обработке многоуровневой реферальной системы для {tg_id}: {e}")
|
||||
finally:
|
||||
if conn:
|
||||
await conn.close()
|
||||
@@ -821,9 +776,7 @@ async def get_referral_stats(referrer_tg_id: int):
|
||||
)
|
||||
|
||||
total_referral_bonus = total_referral_bonus or 0
|
||||
logger.debug(
|
||||
f"Получена общая сумма бонусов от рефералов: {total_referral_bonus}"
|
||||
)
|
||||
logger.debug(f"Получена общая сумма бонусов от рефералов: {total_referral_bonus}")
|
||||
|
||||
return {
|
||||
"total_referrals": total_referrals,
|
||||
@@ -833,9 +786,7 @@ async def get_referral_stats(referrer_tg_id: int):
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при получении статистики рефералов для пользователя {referrer_tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при получении статистики рефералов для пользователя {referrer_tg_id}: {e}")
|
||||
raise
|
||||
finally:
|
||||
if conn:
|
||||
@@ -857,9 +808,7 @@ async def update_key_expiry(client_id: str, new_expiry_time: int):
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
logger.info(
|
||||
f"Установлено подключение к базе данных для обновления времени истечения ключа клиента {client_id}"
|
||||
)
|
||||
logger.info(f"Установлено подключение к базе данных для обновления времени истечения ключа клиента {client_id}")
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -873,9 +822,7 @@ async def update_key_expiry(client_id: str, new_expiry_time: int):
|
||||
logger.info(f"Успешно обновлено время истечения ключа для клиента {client_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при обновлении времени истечения ключа для клиента {client_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при обновлении времени истечения ключа для клиента {client_id}: {e}")
|
||||
raise
|
||||
finally:
|
||||
if conn:
|
||||
@@ -896,9 +843,7 @@ async def delete_key(client_id: str):
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
logger.info(
|
||||
f"Установлено подключение к базе данных для удаления ключа клиента {client_id}"
|
||||
)
|
||||
logger.info(f"Установлено подключение к базе данных для удаления ключа клиента {client_id}")
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -932,9 +877,7 @@ async def add_balance_to_client(client_id: str, amount: float):
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
logger.info(
|
||||
f"Установлено подключение к базе данных для пополнения баланса клиента {client_id}"
|
||||
)
|
||||
logger.info(f"Установлено подключение к базе данных для пополнения баланса клиента {client_id}")
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -972,9 +915,7 @@ async def get_client_id_by_email(email: str):
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
logger.info(
|
||||
f"Установлено подключение к базе данных для поиска client_id по email: {email}"
|
||||
)
|
||||
logger.info(f"Установлено подключение к базе данных для поиска client_id по email: {email}")
|
||||
|
||||
client_id = await conn.fetchval(
|
||||
"""
|
||||
@@ -1015,13 +956,9 @@ async def get_tg_id_by_client_id(client_id: str):
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
logger.info(
|
||||
f"Установлено подключение к базе данных для поиска Telegram ID по client_id: {client_id}"
|
||||
)
|
||||
logger.info(f"Установлено подключение к базе данных для поиска Telegram ID по client_id: {client_id}")
|
||||
|
||||
result = await conn.fetchrow(
|
||||
"SELECT tg_id FROM keys WHERE client_id = $1", client_id
|
||||
)
|
||||
result = await conn.fetchrow("SELECT tg_id FROM keys WHERE client_id = $1", client_id)
|
||||
|
||||
if result:
|
||||
logger.info(f"Найден Telegram ID для client_id: {client_id}")
|
||||
@@ -1064,9 +1001,7 @@ async def upsert_user(
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
logger.info(
|
||||
f"Установлено подключение к базе данных для обновления пользователя {tg_id}"
|
||||
)
|
||||
logger.info(f"Установлено подключение к базе данных для обновления пользователя {tg_id}")
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -1113,9 +1048,7 @@ async def add_payment(tg_id: int, amount: float, payment_system: str):
|
||||
conn = None
|
||||
try:
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
logger.info(
|
||||
f"Установлено подключение к базе данных для добавления платежа пользователя {tg_id}"
|
||||
)
|
||||
logger.info(f"Установлено подключение к базе данных для добавления платежа пользователя {tg_id}")
|
||||
|
||||
await conn.execute(
|
||||
"""
|
||||
@@ -1126,9 +1059,7 @@ async def add_payment(tg_id: int, amount: float, payment_system: str):
|
||||
amount,
|
||||
payment_system,
|
||||
)
|
||||
logger.info(
|
||||
f"Успешно добавлен платеж для пользователя {tg_id} на сумму {amount}"
|
||||
)
|
||||
logger.info(f"Успешно добавлен платеж для пользователя {tg_id} на сумму {amount}")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при добавлении платежа для пользователя {tg_id}: {e}")
|
||||
raise
|
||||
@@ -1161,19 +1092,13 @@ async def add_notification(tg_id: int, notification_type: str, session: Any):
|
||||
tg_id,
|
||||
notification_type,
|
||||
)
|
||||
logger.info(
|
||||
f"Успешно добавлено уведомление типа {notification_type} для пользователя {tg_id}"
|
||||
)
|
||||
logger.info(f"Успешно добавлено уведомление типа {notification_type} для пользователя {tg_id}")
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при добавлении notification для пользователя {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при добавлении notification для пользователя {tg_id}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
async def check_notification_time(
|
||||
tg_id: int, notification_type: str, hours: int = 12, session: Any = None
|
||||
) -> bool:
|
||||
async def check_notification_time(tg_id: int, notification_type: str, hours: int = 12, session: Any = None) -> bool:
|
||||
"""
|
||||
Проверяет, прошло ли указанное количество часов с момента последнего уведомления.
|
||||
|
||||
@@ -1218,9 +1143,7 @@ async def check_notification_time(
|
||||
return can_notify
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при проверке времени уведомления для пользователя {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при проверке времени уведомления для пользователя {tg_id}: {e}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
@@ -1259,11 +1182,8 @@ async def get_servers_from_db():
|
||||
|
||||
|
||||
async def delete_user_data(session: Any, tg_id: int):
|
||||
|
||||
try:
|
||||
await session.execute(
|
||||
"DELETE FROM gifts WHERE sender_tg_id = $1 OR recipient_tg_id = $1", tg_id
|
||||
)
|
||||
await session.execute("DELETE FROM gifts WHERE sender_tg_id = $1 OR recipient_tg_id = $1", tg_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"У Вас версия без подарков для {tg_id}: {e}")
|
||||
await session.execute("DELETE FROM payments WHERE tg_id = $1", tg_id)
|
||||
@@ -1322,7 +1242,6 @@ async def store_gift_link(
|
||||
logger.error(f"Не удалось добавить подарок с ID {gift_id} в базу данных.")
|
||||
return False
|
||||
except Exception as e:
|
||||
|
||||
logger.error(f"Ошибка при сохранении подарка с ID {gift_id} в базе данных: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -10,6 +10,5 @@ class IsAdminFilter(BaseFilter):
|
||||
admin_ids: int | list[int] = ADMIN_ID
|
||||
if isinstance(admin_ids, list):
|
||||
return message.from_user.id in admin_ids
|
||||
return message.from_user.id == admin_ids
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@@ -3,6 +3,7 @@ __all__ = ("router",)
|
||||
from aiogram import Router
|
||||
|
||||
from .admin import router as admin_router
|
||||
from .captcha import router as captcha_router
|
||||
from .coupons import router as coupons_router
|
||||
from .donate import router as donate_router
|
||||
from .instructions import router as instructions_router
|
||||
@@ -13,7 +14,6 @@ from .payments import router as payments_router
|
||||
from .profile import router as profile_router
|
||||
from .start import router as start_router
|
||||
from .user import router as user_router
|
||||
from .captcha import router as captcha_router
|
||||
|
||||
router = Router(name="handlers_main_router")
|
||||
|
||||
|
||||
@@ -19,25 +19,19 @@ router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "coupons_editor", IsAdminFilter())
|
||||
async def show_coupon_management_menu(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def show_coupon_management_menu(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await state.clear()
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="➕ Создать купон", callback_data="create_coupon")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="➕ Создать купон", callback_data="create_coupon"))
|
||||
builder.row(InlineKeyboardButton(text="🎟️ Купоны", callback_data="coupons"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
await callback_query.message.answer(
|
||||
"🛠 Меню управления купонами:", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("🛠 Меню управления купонами:", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("coupons"), IsAdminFilter())
|
||||
async def show_coupon_list(callback_query: types.CallbackQuery, session: Any):
|
||||
try:
|
||||
page = int(callback_query.data.split(':')[1]) if ':' in callback_query.data else 1
|
||||
page = int(callback_query.data.split(":")[1]) if ":" in callback_query.data else 1
|
||||
per_page = 10
|
||||
result = await get_all_coupons(session, page, per_page)
|
||||
coupons = result["coupons"]
|
||||
@@ -63,15 +57,16 @@ async def show_coupon_list(callback_query: types.CallbackQuery, session: Any):
|
||||
f"🔢 <b>Лимит использования:</b> {coupon['usage_limit']} раз\n"
|
||||
f"✅ <b>Использовано:</b> {coupon['usage_count']} раз\n\n"
|
||||
)
|
||||
builder.row(InlineKeyboardButton(
|
||||
text=f"❌ Удалить {coupon['code']}",
|
||||
callback_data=f"delete_coupon_{coupon['code']}"
|
||||
))
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"❌ Удалить {coupon['code']}", callback_data=f"delete_coupon_{coupon['code']}"
|
||||
)
|
||||
)
|
||||
|
||||
if current_page > 1:
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Предыдущая", callback_data=f"coupons:{current_page-1}"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Предыдущая", callback_data=f"coupons:{current_page - 1}"))
|
||||
if current_page < total_pages:
|
||||
builder.row(InlineKeyboardButton(text="➡️ Следующая", callback_data=f"coupons:{current_page+1}"))
|
||||
builder.row(InlineKeyboardButton(text="➡️ Следующая", callback_data=f"coupons:{current_page + 1}"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
await callback_query.message.answer(coupon_list, reply_markup=builder.as_markup())
|
||||
@@ -154,9 +149,7 @@ async def process_coupon_data(message: types.Message, state: FSMContext, session
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
|
||||
|
||||
await message.answer(result_message, reply_markup=builder.as_markup())
|
||||
await state.clear()
|
||||
|
||||
+47
-161
@@ -40,48 +40,23 @@ async def handle_admin_message(message: types.Message, state: FSMContext):
|
||||
BOT_VERSION = "4.0.0-preAlpha(14)"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📊 Статистика пользователей", callback_data="user_stats"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="👥 Управление пользователями", callback_data="user_editor"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🖥️ Управление серверами", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🎟️ Управление купонами", callback_data="coupons_editor"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🤖 Управление Ботом", callback_data="bot_management")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📊 Статистика пользователей", callback_data="user_stats"))
|
||||
builder.row(InlineKeyboardButton(text="👥 Управление пользователями", callback_data="user_editor"))
|
||||
builder.row(InlineKeyboardButton(text="🖥️ Управление серверами", callback_data="servers_editor"))
|
||||
builder.row(InlineKeyboardButton(text="🎟️ Управление купонами", callback_data="coupons_editor"))
|
||||
builder.row(InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to"))
|
||||
builder.row(InlineKeyboardButton(text="🤖 Управление Ботом", callback_data="bot_management"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
await message.answer(
|
||||
f"🤖 Панель администратора\n\nВерсия бота: <b>{BOT_VERSION}</b>",
|
||||
reply_markup=builder.as_markup()
|
||||
f"🤖 Панель администратора\n\nВерсия бота: <b>{BOT_VERSION}</b>", reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "bot_management")
|
||||
async def handle_bot_management(callback_query: types.CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups"))
|
||||
builder.row(InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot"))
|
||||
builder.row(InlineKeyboardButton(text="🚫 Баны", callback_data="ban_user"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="admin"))
|
||||
await callback_query.message.answer(
|
||||
@@ -106,13 +81,9 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any):
|
||||
total_payments_month = await session.fetchval(
|
||||
"SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('month', CURRENT_DATE)"
|
||||
)
|
||||
total_payments_all_time = await session.fetchval(
|
||||
"SELECT COALESCE(SUM(amount), 0) FROM payments"
|
||||
)
|
||||
total_payments_all_time = await session.fetchval("SELECT COALESCE(SUM(amount), 0) FROM payments")
|
||||
|
||||
registrations_today = await session.fetchval(
|
||||
"SELECT COUNT(*) FROM users WHERE created_at >= CURRENT_DATE"
|
||||
)
|
||||
registrations_today = await session.fetchval("SELECT COUNT(*) FROM users WHERE created_at >= CURRENT_DATE")
|
||||
registrations_week = await session.fetchval(
|
||||
"SELECT COUNT(*) FROM users WHERE created_at >= date_trunc('week', CURRENT_DATE)"
|
||||
)
|
||||
@@ -120,9 +91,7 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any):
|
||||
"SELECT COUNT(*) FROM users WHERE created_at >= date_trunc('month', CURRENT_DATE)"
|
||||
)
|
||||
|
||||
users_updated_today = await session.fetchval(
|
||||
"SELECT COUNT(*) FROM users WHERE updated_at >= CURRENT_DATE"
|
||||
)
|
||||
users_updated_today = await session.fetchval("SELECT COUNT(*) FROM users WHERE updated_at >= CURRENT_DATE")
|
||||
|
||||
active_keys = await session.fetchval(
|
||||
"SELECT COUNT(*) FROM keys WHERE expiry_time > $1",
|
||||
@@ -153,27 +122,17 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any):
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔄 Обновить", callback_data="user_stats")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔄 Обновить", callback_data="user_stats"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📥 Выгрузить пользователей в CSV",
|
||||
callback_data="export_users_csv",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📥 Выгрузить оплаты в CSV", callback_data="export_payments_csv"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📥 Выгрузить оплаты в CSV", callback_data="export_payments_csv"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
stats_message, reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(stats_message, reply_markup=builder.as_markup())
|
||||
except Exception as e:
|
||||
logger.error(f"Error in user_stats_menu: {e}")
|
||||
|
||||
@@ -200,14 +159,10 @@ async def export_users_csv(callback_query: CallbackQuery, session: Any):
|
||||
)
|
||||
|
||||
if not users:
|
||||
await callback_query.message.answer(
|
||||
"📭 Нет пользователей для экспорта.", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("📭 Нет пользователей для экспорта.", reply_markup=builder.as_markup())
|
||||
return
|
||||
|
||||
csv_data = (
|
||||
"tg_id,username,first_name,last_name,language_code,is_bot,balance,trial\n"
|
||||
)
|
||||
csv_data = "tg_id,username,first_name,last_name,language_code,is_bot,balance,trial\n"
|
||||
for user in users:
|
||||
csv_data += f"{user['tg_id']},{user['username']},{user['first_name']},{user['last_name']},{user['language_code']},{user['is_bot']},{user['balance']},{user['trial']}\n"
|
||||
|
||||
@@ -253,9 +208,7 @@ async def export_payments_csv(callback_query: CallbackQuery, session: Any):
|
||||
)
|
||||
|
||||
if not payments:
|
||||
await callback_query.message.answer(
|
||||
"📭 Нет платежей для экспорта.", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("📭 Нет платежей для экспорта.", reply_markup=builder.as_markup())
|
||||
return
|
||||
|
||||
csv_data = "tg_id,username,first_name,last_name,amount,payment_system,status,created_at\n" # Заголовки CSV
|
||||
@@ -283,24 +236,10 @@ async def export_payments_csv(callback_query: CallbackQuery, session: Any):
|
||||
@router.callback_query(F.data == "send_to", IsAdminFilter())
|
||||
async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📢 Отправить всем", callback_data="send_to_all")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📢 Отправить с подпиской", callback_data="send_to_subscribed"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📢 Отправить без подписки", callback_data="send_to_unsubscribed"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📢 Рассылка по кластеру", callback_data="send_to_cluster"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📢 Отправить всем", callback_data="send_to_all"))
|
||||
builder.row(InlineKeyboardButton(text="📢 Отправить с подпиской", callback_data="send_to_subscribed"))
|
||||
builder.row(InlineKeyboardButton(text="📢 Отправить без подписки", callback_data="send_to_unsubscribed"))
|
||||
builder.row(InlineKeyboardButton(text="📢 Рассылка по кластеру", callback_data="send_to_cluster"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
await callback_query.message.answer(
|
||||
"✍️ Выберите группу пользователей и введите текст сообщения для рассылки:",
|
||||
@@ -311,34 +250,26 @@ async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext):
|
||||
@router.callback_query(F.data == "send_to_all", IsAdminFilter())
|
||||
async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext):
|
||||
await state.update_data(send_to="all")
|
||||
await callback_query.message.answer(
|
||||
"✍️ Введите текст сообщения для рассылки всем пользователям:"
|
||||
)
|
||||
await callback_query.message.answer("✍️ Введите текст сообщения для рассылки всем пользователям:")
|
||||
await state.set_state(UserEditorState.waiting_for_message)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "send_to_subscribed", IsAdminFilter())
|
||||
async def handle_send_to_subscribed(callback_query: CallbackQuery, state: FSMContext):
|
||||
await state.update_data(send_to="subscribed")
|
||||
await callback_query.message.answer(
|
||||
"✍️ Введите текст сообщения для рассылки пользователям с активной подпиской:"
|
||||
)
|
||||
await callback_query.message.answer("✍️ Введите текст сообщения для рассылки пользователям с активной подпиской:")
|
||||
await state.set_state(UserEditorState.waiting_for_message)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "send_to_unsubscribed", IsAdminFilter())
|
||||
async def handle_send_to_unsubscribed(callback_query: CallbackQuery, state: FSMContext):
|
||||
await state.update_data(send_to="unsubscribed")
|
||||
await callback_query.message.answer(
|
||||
"✍️ Введите текст сообщения для рассылки пользователям без активной подписки:"
|
||||
)
|
||||
await callback_query.message.answer("✍️ Введите текст сообщения для рассылки пользователям без активной подписки:")
|
||||
await state.set_state(UserEditorState.waiting_for_message)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "send_to_cluster", IsAdminFilter())
|
||||
async def handle_send_to_cluster(
|
||||
callback_query: CallbackQuery, state: FSMContext, session: Any
|
||||
):
|
||||
async def handle_send_to_cluster(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
clusters = await session.fetch("SELECT DISTINCT cluster_name FROM servers")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -368,9 +299,7 @@ async def handle_send_cluster(callback_query: CallbackQuery, state: FSMContext):
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_message, IsAdminFilter())
|
||||
async def process_message_to_all(
|
||||
message: types.Message, state: FSMContext, session: Any
|
||||
):
|
||||
async def process_message_to_all(message: types.Message, state: FSMContext, session: Any):
|
||||
text_message = message.text
|
||||
|
||||
try:
|
||||
@@ -424,9 +353,7 @@ async def process_message_to_all(
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
logger.error(
|
||||
f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"❌ Ошибка при отправке сообщения пользователю {tg_id}: {e}")
|
||||
|
||||
await message.answer(
|
||||
f"📤 Рассылка завершена:\n"
|
||||
@@ -442,13 +369,9 @@ async def process_message_to_all(
|
||||
|
||||
@router.callback_query(F.data == "backups", IsAdminFilter())
|
||||
async def handle_backup(callback_query: CallbackQuery, state: FSMContext):
|
||||
await callback_query.message.answer(
|
||||
"💾 Инициализация резервного копирования базы данных..."
|
||||
)
|
||||
await callback_query.message.answer("💾 Инициализация резервного копирования базы данных...")
|
||||
await backup_database()
|
||||
await callback_query.message.answer(
|
||||
"✅ Резервная копия успешно создана и отправлена администратору."
|
||||
)
|
||||
await callback_query.message.answer("✅ Резервная копия успешно создана и отправлена администратору.")
|
||||
|
||||
|
||||
@router.callback_query(F.data == "restart_bot", IsAdminFilter())
|
||||
@@ -456,9 +379,7 @@ async def handle_restart(callback_query: CallbackQuery, state: FSMContext):
|
||||
await state.set_state(UserEditorState.waiting_for_restart_confirmation)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="✅ Да, перезапустить", callback_data="confirm_restart"
|
||||
),
|
||||
InlineKeyboardButton(text="✅ Да, перезапустить", callback_data="confirm_restart"),
|
||||
InlineKeyboardButton(text="❌ Нет, отмена", callback_data="admin"),
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Вернуться в меню", callback_data="admin"))
|
||||
@@ -484,13 +405,9 @@ async def confirm_restart_bot(callback_query: CallbackQuery, state: FSMContext):
|
||||
text=True,
|
||||
)
|
||||
await state.clear()
|
||||
await callback_query.message.answer(
|
||||
"🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
|
||||
except subprocess.CalledProcessError:
|
||||
await callback_query.message.answer(
|
||||
"🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("🔄 Бот успешно перезапущен.", reply_markup=builder.as_markup())
|
||||
except Exception as e:
|
||||
await callback_query.message.answer(
|
||||
f"⚠️ Ошибка при перезагрузке бота: {e.stderr}",
|
||||
@@ -507,37 +424,20 @@ async def user_editor_menu(callback_query: CallbackQuery):
|
||||
callback_data="search_by_key_name",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🆔 Поиск по Telegram ID", callback_data="search_by_tg_id"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🌐 Поиск по Username", callback_data="search_by_username"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🆔 Поиск по Telegram ID", callback_data="search_by_tg_id"))
|
||||
builder.row(InlineKeyboardButton(text="🌐 Поиск по Username", callback_data="search_by_username"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Вернуться назад", callback_data="admin"))
|
||||
await callback_query.message.answer(
|
||||
"👇 Выберите способ поиска пользователя:", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("👇 Выберите способ поиска пользователя:", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "ban_user")
|
||||
async def handle_ban_user(callback_query: types.CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📄 Выгрузить в CSV", callback_data="export_to_csv")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🗑️ Удалить из БД", callback_data="delete_banned_users"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📄 Выгрузить в CSV", callback_data="export_to_csv"))
|
||||
builder.row(InlineKeyboardButton(text="🗑️ Удалить из БД", callback_data="delete_banned_users"))
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management"))
|
||||
await callback_query.message.answer(
|
||||
"🚫 Заблокировавшие бота\n\n"
|
||||
"Здесь можно просматривать и удалять пользователей, которые забанили вашего бота!",
|
||||
"🚫 Заблокировавшие бота\n\nЗдесь можно просматривать и удалять пользователей, которые забанили вашего бота!",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
@@ -559,14 +459,10 @@ async def export_banned_users_to_csv(callback_query: types.CallbackQuery):
|
||||
|
||||
csv_output.seek(0)
|
||||
|
||||
document = BufferedInputFile(
|
||||
file=csv_output.getvalue().encode("utf-8"), filename="banned_users.csv"
|
||||
)
|
||||
document = BufferedInputFile(file=csv_output.getvalue().encode("utf-8"), filename="banned_users.csv")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management"))
|
||||
|
||||
await callback_query.message.answer_document(
|
||||
document=document,
|
||||
@@ -575,9 +471,7 @@ async def export_banned_users_to_csv(callback_query: types.CallbackQuery):
|
||||
)
|
||||
except Exception as e:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management"))
|
||||
await callback_query.message.answer(
|
||||
text=f"Ошибка при выгрузке CSV: {e}",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -594,31 +488,23 @@ async def delete_banned_users(callback_query: types.CallbackQuery):
|
||||
blocked_ids = [record["tg_id"] for record in blocked_users]
|
||||
|
||||
if not blocked_ids:
|
||||
await callback_query.message.answer(
|
||||
"📂 Нет заблокировавших пользователей для удаления."
|
||||
)
|
||||
await callback_query.message.answer("📂 Нет заблокировавших пользователей для удаления.")
|
||||
return
|
||||
|
||||
for tg_id in blocked_ids:
|
||||
await delete_user_data(conn, tg_id)
|
||||
|
||||
await conn.execute(
|
||||
"DELETE FROM blocked_users WHERE tg_id = ANY($1)", blocked_ids
|
||||
)
|
||||
await conn.execute("DELETE FROM blocked_users WHERE tg_id = ANY($1)", blocked_ids)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management"))
|
||||
await callback_query.message.answer(
|
||||
text=f"🗑️ Удалено данные о {len(blocked_ids)} пользователях и связанных записях.",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
except Exception as e:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="bot_management"))
|
||||
await callback_query.message.answer(
|
||||
text=f"Ошибка при удалении записей: {e}",
|
||||
reply_markup=builder.as_markup(),
|
||||
|
||||
+31
-117
@@ -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
|
||||
@@ -33,15 +33,9 @@ async def handle_servers_editor(callback_query: types.CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for cluster_name, cluster_servers in servers.items():
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"⚙️ {cluster_name}", callback_data=f"manage_cluster|{cluster_name}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=f"⚙️ {cluster_name}", callback_data=f"manage_cluster|{cluster_name}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="➕ Добавить кластер", callback_data="add_cluster")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="➕ Добавить кластер", callback_data="add_cluster"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад в админку", callback_data="admin"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
@@ -72,11 +66,7 @@ async def handle_cluster_name_input(message: types.Message, state: FSMContext):
|
||||
if cluster_name == "❌ Отменить":
|
||||
await state.clear()
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔧 Управление кластерами", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔧 Управление кластерами", callback_data="servers_editor"))
|
||||
await message.answer(
|
||||
"Процесс создания кластера отменен. Вы вернулись в меню управления серверами.",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -90,9 +80,7 @@ async def handle_cluster_name_input(message: types.Message, state: FSMContext):
|
||||
await state.update_data(cluster_name=cluster_name)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor"))
|
||||
|
||||
await message.answer(
|
||||
f"<b>Введите имя сервера для кластера {cluster_name}:</b>\n\n"
|
||||
@@ -110,11 +98,7 @@ async def handle_server_name_input(message: types.Message, state: FSMContext):
|
||||
if server_name == "❌ Отменить":
|
||||
await state.clear()
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔧 Управление кластерами", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔧 Управление кластерами", callback_data="servers_editor"))
|
||||
await message.answer(
|
||||
"Процесс создания кластера был отменен. Вы вернулись в меню управления серверами.",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -127,9 +111,7 @@ async def handle_server_name_input(message: types.Message, state: FSMContext):
|
||||
|
||||
server_unique = await check_unique_server_name(server_name)
|
||||
if not server_unique:
|
||||
await message.answer(
|
||||
"❌ Сервер с таким именем уже существует. Пожалуйста, выберите другое имя."
|
||||
)
|
||||
await message.answer("❌ Сервер с таким именем уже существует. Пожалуйста, выберите другое имя.")
|
||||
return
|
||||
|
||||
user_data = await state.get_data()
|
||||
@@ -137,9 +119,7 @@ async def handle_server_name_input(message: types.Message, state: FSMContext):
|
||||
await state.update_data(server_name=server_name)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor"))
|
||||
|
||||
await message.answer(
|
||||
f"<b>Введите API URL для сервера {server_name} в кластере {cluster_name}:</b>\n\n"
|
||||
@@ -158,11 +138,7 @@ async def handle_api_url_input(message: types.Message, state: FSMContext):
|
||||
if api_url == "❌ Отменить":
|
||||
await state.clear()
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔧 Управление кластерами", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔧 Управление кластерами", callback_data="servers_editor"))
|
||||
await message.answer(
|
||||
"Процесс создания кластера был отменен. Вы вернулись в меню управления серверами.",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -183,9 +159,7 @@ async def handle_api_url_input(message: types.Message, state: FSMContext):
|
||||
await state.update_data(api_url=api_url)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor"))
|
||||
|
||||
await message.answer(
|
||||
f"<b>Введите subscription_url для сервера {server_name} в кластере {cluster_name}:</b>\n\n"
|
||||
@@ -205,11 +179,7 @@ async def handle_subscription_url_input(message: types.Message, state: FSMContex
|
||||
if subscription_url == "❌ Отменить":
|
||||
await state.clear()
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔧 Управление кластерами", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔧 Управление кластерами", callback_data="servers_editor"))
|
||||
await message.answer(
|
||||
"Процесс создания кластера был отменен. Вы вернулись в меню управления серверами.",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -230,9 +200,7 @@ async def handle_subscription_url_input(message: types.Message, state: FSMContex
|
||||
await state.update_data(subscription_url=subscription_url)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor"))
|
||||
|
||||
await message.answer(
|
||||
f"<b>Введите inbound_id для сервера {server_name} в кластере {cluster_name}:</b>\n\n"
|
||||
@@ -247,9 +215,7 @@ async def handle_inbound_id_input(message: types.Message, state: FSMContext):
|
||||
inbound_id = message.text.strip()
|
||||
|
||||
if not inbound_id.isdigit():
|
||||
await message.answer(
|
||||
"❌ inbound_id должен быть числовым значением. Попробуйте снова."
|
||||
)
|
||||
await message.answer("❌ inbound_id должен быть числовым значением. Попробуйте снова.")
|
||||
return
|
||||
|
||||
user_data = await state.get_data()
|
||||
@@ -273,11 +239,7 @@ async def handle_inbound_id_input(message: types.Message, state: FSMContext):
|
||||
await conn.close()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад к кластерам", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад к кластерам", callback_data="servers_editor"))
|
||||
|
||||
await message.answer(
|
||||
f"✅ Кластер {cluster_name} и сервер {server_name} успешно добавлены!",
|
||||
@@ -304,11 +266,7 @@ async def handle_manage_cluster(callback_query: types.CallbackQuery, state: FSMC
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="➕ Добавить сервер", callback_data=f"add_server|{cluster_name}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="➕ Добавить сервер", callback_data=f"add_server|{cluster_name}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
@@ -331,11 +289,7 @@ async def handle_manage_cluster(callback_query: types.CallbackQuery, state: FSMC
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад в управление кластерами", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад в управление кластерами", callback_data="servers_editor"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"🔧 Управление серверами для кластера {cluster_name}",
|
||||
@@ -361,11 +315,7 @@ async def sync_cluster_handler(callback_query: types.CallbackQuery):
|
||||
await callback_query.message.answer(
|
||||
f"❌ Нет ключей для синхронизации в кластере {cluster_name}.",
|
||||
reply_markup=InlineKeyboardBuilder()
|
||||
.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
.row(InlineKeyboardButton(text="🔙 Назад", callback_data="servers_editor"))
|
||||
.as_markup(),
|
||||
)
|
||||
return
|
||||
@@ -421,36 +371,24 @@ async def handle_check_server_availability(callback_query: types.CallbackQuery):
|
||||
"Это может занять до 1 минуты, пожалуйста, подождите..."
|
||||
)
|
||||
|
||||
availability_message = (
|
||||
f"🖥️ Проверка доступности серверов для кластера {cluster_name} завершена:\n\n"
|
||||
)
|
||||
availability_message = f"🖥️ Проверка доступности серверов для кластера {cluster_name} завершена:\n\n"
|
||||
|
||||
for server in cluster_servers:
|
||||
xui = AsyncApi(
|
||||
server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD
|
||||
)
|
||||
xui = AsyncApi(server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD)
|
||||
|
||||
try:
|
||||
await xui.login()
|
||||
|
||||
online_users = len(await xui.client.online())
|
||||
availability_message += (
|
||||
f"🌍 {server['server_name']}: {online_users} активных пользователей.\n"
|
||||
)
|
||||
availability_message += f"🌍 {server['server_name']}: {online_users} активных пользователей.\n"
|
||||
|
||||
except Exception as e:
|
||||
availability_message += f"❌ {server['server_name']}: Не удалось получить информацию. Ошибка: {e}\n"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data=f"manage_cluster|{cluster_name}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data=f"manage_cluster|{cluster_name}"))
|
||||
|
||||
await in_progress_message.edit_text(
|
||||
availability_message, reply_markup=builder.as_markup()
|
||||
)
|
||||
await in_progress_message.edit_text(availability_message, reply_markup=builder.as_markup())
|
||||
|
||||
await callback_query.answer()
|
||||
|
||||
@@ -464,9 +402,7 @@ async def handle_manage_server(callback_query: types.CallbackQuery, state: FSMCo
|
||||
server = None
|
||||
cluster_name = None
|
||||
for cluster, cluster_servers in servers.items():
|
||||
server = next(
|
||||
(s for s in cluster_servers if s["server_name"] == server_name), None
|
||||
)
|
||||
server = next((s for s in cluster_servers if s["server_name"] == server_name), None)
|
||||
if server:
|
||||
cluster_name = cluster
|
||||
break
|
||||
@@ -477,16 +413,8 @@ async def handle_manage_server(callback_query: types.CallbackQuery, state: FSMCo
|
||||
inbound_id = server["inbound_id"]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🗑️ Удалить", callback_data=f"delete_server|{server_name}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data=f"manage_cluster|{cluster_name}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🗑️ Удалить", callback_data=f"delete_server|{server_name}"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data=f"manage_cluster|{cluster_name}"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"<b>🔧 Информация о сервере {server_name}:</b>\n\n"
|
||||
@@ -505,12 +433,8 @@ async def handle_delete_server(callback_query: types.CallbackQuery, state: FSMCo
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="✅ Да", callback_data=f"confirm_delete_server|{server_name}"
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="❌ Нет", callback_data=f"manage_server|{server_name}"
|
||||
),
|
||||
InlineKeyboardButton(text="✅ Да", callback_data=f"confirm_delete_server|{server_name}"),
|
||||
InlineKeyboardButton(text="❌ Нет", callback_data=f"manage_server|{server_name}"),
|
||||
)
|
||||
|
||||
await callback_query.message.answer(
|
||||
@@ -520,9 +444,7 @@ async def handle_delete_server(callback_query: types.CallbackQuery, state: FSMCo
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("confirm_delete_server|"), IsAdminFilter())
|
||||
async def handle_confirm_delete_server(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def handle_confirm_delete_server(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
server_name = callback_query.data.split("|")[1]
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
@@ -535,15 +457,9 @@ async def handle_confirm_delete_server(
|
||||
await conn.close()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад в управление кластерами", callback_data="servers_editor"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад в управление кластерами", callback_data="servers_editor"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"🗑️ Сервер {server_name} успешно удален.", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(f"🗑️ Сервер {server_name} успешно удален.", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("add_server|"), IsAdminFilter())
|
||||
@@ -553,9 +469,7 @@ async def handle_add_server(callback_query: types.CallbackQuery, state: FSMConte
|
||||
await state.update_data(cluster_name=cluster_name)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="❌ Отменить", callback_data="servers_editor"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"<b>Введите имя сервера для кластера {cluster_name}:</b>\n\n"
|
||||
|
||||
@@ -43,9 +43,7 @@ class UserEditorState(StatesGroup):
|
||||
async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
await callback_query.message.answer(
|
||||
"🔍 Введите Telegram ID клиента:", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("🔍 Введите Telegram ID клиента:", reply_markup=builder.as_markup())
|
||||
await state.set_state(UserEditorState.waiting_for_tg_id)
|
||||
|
||||
|
||||
@@ -53,20 +51,14 @@ async def prompt_tg_id(callback_query: CallbackQuery, state: FSMContext):
|
||||
async def prompt_username(callback_query: CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
await callback_query.message.answer(
|
||||
"🔍 Введите Username клиента:", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("🔍 Введите Username клиента:", reply_markup=builder.as_markup())
|
||||
await state.set_state(UserEditorState.waiting_for_username)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_username, IsAdminFilter())
|
||||
async def handle_username_input(
|
||||
message: types.Message, state: FSMContext, session: Any
|
||||
):
|
||||
async def handle_username_input(message: types.Message, state: FSMContext, session: Any):
|
||||
username = message.text.strip().lstrip("@").replace("https://t.me/", "")
|
||||
user_record = await session.fetchrow(
|
||||
"SELECT tg_id FROM users WHERE username = $1", username
|
||||
)
|
||||
user_record = await session.fetchrow("SELECT tg_id FROM users WHERE username = $1", username)
|
||||
|
||||
if not user_record:
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -79,16 +71,10 @@ async def handle_username_input(
|
||||
return
|
||||
|
||||
tg_id = user_record["tg_id"]
|
||||
username = await session.fetchval(
|
||||
"SELECT username FROM users WHERE tg_id = $1", tg_id
|
||||
)
|
||||
balance = await session.fetchval(
|
||||
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
username = await session.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
|
||||
balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
||||
referral_count = await session.fetchval(
|
||||
"SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id
|
||||
)
|
||||
referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
||||
|
||||
if balance is None:
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -103,9 +89,7 @@ async def handle_username_input(
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for (email,) in key_records:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
@@ -120,21 +104,9 @@ async def handle_username_input(
|
||||
callback_data=f"restore_trial_{tg_id}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
|
||||
user_info = (
|
||||
@@ -153,9 +125,7 @@ async def handle_username_input(
|
||||
async def handle_send_message(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.data.split("_")[2]
|
||||
await state.update_data(target_tg_id=tg_id)
|
||||
await callback_query.message.answer(
|
||||
"✉️ Введите текст сообщения, которое вы хотите отправить пользователю."
|
||||
)
|
||||
await callback_query.message.answer("✉️ Введите текст сообщения, которое вы хотите отправить пользователю.")
|
||||
await state.set_state(UserEditorState.waiting_for_message_text)
|
||||
|
||||
|
||||
@@ -181,16 +151,10 @@ async def process_send_message(message: types.Message, state: FSMContext, bot: B
|
||||
@router.message(UserEditorState.waiting_for_tg_id, F.text.isdigit(), IsAdminFilter())
|
||||
async def handle_tg_id_input(message: types.Message, state: FSMContext, session: Any):
|
||||
tg_id = int(message.text)
|
||||
username = await session.fetchval(
|
||||
"SELECT username FROM users WHERE tg_id = $1", tg_id
|
||||
)
|
||||
balance = await session.fetchval(
|
||||
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
username = await session.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
|
||||
balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
||||
referral_count = await session.fetchval(
|
||||
"SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id
|
||||
)
|
||||
referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
||||
|
||||
if balance is None:
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -205,9 +169,7 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext, session:
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for (email,) in key_records:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
@@ -215,27 +177,15 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext, session:
|
||||
callback_data=f"change_balance_{tg_id}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Восстановить пробник",
|
||||
callback_data=f"restore_trial_{tg_id}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
|
||||
@@ -258,15 +208,9 @@ async def handle_restore_trial(callback_query: types.CallbackQuery, session: Any
|
||||
await restore_trial(tg_id, session)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад в меню администратора", callback_data="admin"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="admin"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"✅ Триал успешно восстановлен.", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("✅ Триал успешно восстановлен.", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("change_balance_"), IsAdminFilter())
|
||||
@@ -275,16 +219,12 @@ async def process_balance_change(callback_query: CallbackQuery, state: FSMContex
|
||||
await state.update_data(tg_id=tg_id)
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
await callback_query.message.answer(
|
||||
"💸 Введите новую сумму баланса:", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("💸 Введите новую сумму баланса:", reply_markup=builder.as_markup())
|
||||
await state.set_state(UserEditorState.waiting_for_new_balance)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_new_balance, IsAdminFilter())
|
||||
async def handle_new_balance_input(
|
||||
message: types.Message, state: FSMContext, session: Any
|
||||
):
|
||||
async def handle_new_balance_input(message: types.Message, state: FSMContext, session: Any):
|
||||
if not message.text.isdigit() or int(message.text) < 0:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
@@ -318,7 +258,6 @@ async def handle_new_balance_input(
|
||||
|
||||
|
||||
async def get_key_details(email, session):
|
||||
|
||||
record = await session.fetchrow(
|
||||
"""
|
||||
SELECT k.key, k.expiry_time, k.server_id, c.tg_id, c.balance
|
||||
@@ -399,25 +338,19 @@ async def process_key_edit(callback_query: CallbackQuery, session: Any):
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
response_message, reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "search_by_key_name", IsAdminFilter())
|
||||
async def prompt_key_name(callback_query: CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
await callback_query.message.answer(
|
||||
"🔑 Введите имя ключа:", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("🔑 Введите имя ключа:", reply_markup=builder.as_markup())
|
||||
await state.set_state(UserEditorState.waiting_for_key_name)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_key_name, IsAdminFilter())
|
||||
async def handle_key_name_input(
|
||||
message: types.Message, state: FSMContext, session: Any
|
||||
):
|
||||
async def handle_key_name_input(message: types.Message, state: FSMContext, session: Any):
|
||||
key_name = sanitize_key_name(message.text)
|
||||
key_details = await get_key_details(key_name, session)
|
||||
|
||||
@@ -474,18 +407,14 @@ async def prompt_expiry_change(callback_query: CallbackQuery, state: FSMContext)
|
||||
|
||||
|
||||
@router.message(UserEditorState.waiting_for_expiry_time, IsAdminFilter())
|
||||
async def handle_expiry_time_input(
|
||||
message: types.Message, state: FSMContext, session: Any
|
||||
):
|
||||
async def handle_expiry_time_input(message: types.Message, state: FSMContext, session: Any):
|
||||
user_data = await state.get_data()
|
||||
email = user_data.get("email")
|
||||
|
||||
if not email:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
await message.answer(
|
||||
"📧 Email не найден в состоянии. 🚫", reply_markup=builder.as_markup()
|
||||
)
|
||||
await message.answer("📧 Email не найден в состоянии. 🚫", reply_markup=builder.as_markup())
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
@@ -499,9 +428,7 @@ async def handle_expiry_time_input(
|
||||
client_id = await get_client_id_by_email(email)
|
||||
if client_id is None:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
await message.answer(
|
||||
f"🚫 Клиент с email {email} не найден. 🔍",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -509,14 +436,10 @@ async def handle_expiry_time_input(
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
record = await session.fetchrow(
|
||||
"SELECT server_id FROM keys WHERE client_id = $1", client_id
|
||||
)
|
||||
record = await session.fetchrow("SELECT server_id FROM keys WHERE client_id = $1", client_id)
|
||||
if not record:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
await message.answer(
|
||||
"🚫 Клиент не найден в базе данных. 🔍",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -547,7 +470,9 @@ async def handle_expiry_time_input(
|
||||
|
||||
await update_key_expiry(client_id, expiry_time)
|
||||
|
||||
response_message = f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
|
||||
response_message = (
|
||||
f"✅ Время истечения ключа для клиента {client_id} ({email}) успешно обновлено на всех серверах."
|
||||
)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
|
||||
@@ -565,20 +490,14 @@ async def handle_expiry_time_input(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("delete_key_admin|"), IsAdminFilter())
|
||||
async def process_callback_delete_key(
|
||||
callback_query: types.CallbackQuery, session: Any
|
||||
):
|
||||
async def process_callback_delete_key(callback_query: types.CallbackQuery, session: Any):
|
||||
email = callback_query.data.split("|")[1]
|
||||
client_id = await session.fetchval(
|
||||
"SELECT client_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
client_id = await session.fetchval("SELECT client_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if client_id is None:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
await callback_query.message.answer(
|
||||
"🔍 Ключ не найден. 🚫", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("🔍 Ключ не найден. 🚫", reply_markup=builder.as_markup())
|
||||
return
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -588,9 +507,7 @@ async def process_callback_delete_key(
|
||||
callback_data=f"confirm_delete_admin|{client_id}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="user_editor")
|
||||
)
|
||||
builder.row(types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="user_editor"))
|
||||
await callback_query.message.answer(
|
||||
"<b>❓ Вы уверены, что хотите удалить ключ?</b>",
|
||||
reply_markup=builder.as_markup(),
|
||||
@@ -598,13 +515,9 @@ async def process_callback_delete_key(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("confirm_delete_admin|"), IsAdminFilter())
|
||||
async def process_callback_confirm_delete(
|
||||
callback_query: types.CallbackQuery, session: Any
|
||||
):
|
||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery, session: Any):
|
||||
client_id = callback_query.data.split("|")[1]
|
||||
record = await session.fetchrow(
|
||||
"SELECT email FROM keys WHERE client_id = $1", client_id
|
||||
)
|
||||
record = await session.fetchrow("SELECT email FROM keys WHERE client_id = $1", client_id)
|
||||
|
||||
if record:
|
||||
email = record["email"]
|
||||
@@ -618,74 +531,38 @@ async def process_callback_confirm_delete(
|
||||
tasks = []
|
||||
for cluster_name, cluster_servers in clusters.items():
|
||||
for server in cluster_servers:
|
||||
tasks.append(
|
||||
delete_key_from_cluster(cluster_name, email, client_id)
|
||||
)
|
||||
tasks.append(delete_key_from_cluster(cluster_name, email, client_id))
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
await delete_key_from_servers(email, client_id)
|
||||
await delete_key_from_db(client_id, session)
|
||||
|
||||
await callback_query.message.answer(
|
||||
response_message, reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
else:
|
||||
response_message = "🚫 Ключ не найден или уже удален."
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="view_keys"))
|
||||
await callback_query.message.answer(
|
||||
response_message, reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("user_info|"), IsAdminFilter())
|
||||
async def handle_user_info(
|
||||
callback_query: types.CallbackQuery, state: FSMContext, session: Any
|
||||
):
|
||||
async def handle_user_info(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = int(callback_query.data.split("|")[1])
|
||||
username = await session.fetchval(
|
||||
"SELECT username FROM users WHERE tg_id = $1", tg_id
|
||||
)
|
||||
balance = await session.fetchval(
|
||||
"SELECT balance FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
username = await session.fetchval("SELECT username FROM users WHERE tg_id = $1", tg_id)
|
||||
balance = await session.fetchval("SELECT balance FROM connections WHERE tg_id = $1", tg_id)
|
||||
key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id)
|
||||
referral_count = await session.fetchval(
|
||||
"SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id
|
||||
)
|
||||
referral_count = await session.fetchval("SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
for (email,) in key_records:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="✉️ Отправить сообщение", callback_data=f"send_message_{tg_id}"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
|
||||
|
||||
user_info = (
|
||||
@@ -702,19 +579,13 @@ async def handle_user_info(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("confirm_delete_user_"), IsAdminFilter())
|
||||
async def confirm_delete_user(
|
||||
callback_query: types.CallbackQuery, state: FSMContext, session: Any
|
||||
):
|
||||
async def confirm_delete_user(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = int(callback_query.data.split("_")[3])
|
||||
|
||||
confirmation_markup = InlineKeyboardMarkup(
|
||||
row_width=2,
|
||||
inline_keyboard=[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text="✅ Подтвердить", callback_data=f"delete_user_{tg_id}"
|
||||
)
|
||||
],
|
||||
[InlineKeyboardButton(text="✅ Подтвердить", callback_data=f"delete_user_{tg_id}")],
|
||||
[InlineKeyboardButton(text="❌ Отменить", callback_data="user_editor")],
|
||||
],
|
||||
)
|
||||
@@ -729,9 +600,7 @@ async def confirm_delete_user(
|
||||
async def delete_user(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = int(callback_query.data.split("_")[2])
|
||||
|
||||
key_records = await session.fetch(
|
||||
"SELECT email, client_id FROM keys WHERE tg_id = $1", tg_id
|
||||
)
|
||||
key_records = await session.fetch("SELECT email, client_id FROM keys WHERE tg_id = $1", tg_id)
|
||||
|
||||
async def delete_keys_from_servers():
|
||||
try:
|
||||
@@ -742,9 +611,7 @@ async def delete_user(callback_query: types.CallbackQuery, session: Any):
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
await asyncio.gather(*tasks)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при удалении ключей с серверов для пользователя {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при удалении ключей с серверов для пользователя {tg_id}: {e}")
|
||||
|
||||
await delete_keys_from_servers()
|
||||
|
||||
@@ -754,13 +621,9 @@ async def delete_user(callback_query: types.CallbackQuery, session: Any):
|
||||
back_button = InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"🗑️ Пользователь с ID {tg_id} был удален.", reply_markup=keyboard
|
||||
)
|
||||
await callback_query.message.answer(f"🗑️ Пользователь с ID {tg_id} был удален.", reply_markup=keyboard)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при удалении данных из базы данных для пользователя {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при удалении данных из базы данных для пользователя {tg_id}: {e}")
|
||||
await callback_query.message.answer(
|
||||
f"❌ Произошла ошибка при удалении пользователя с ID {tg_id}. Попробуйте снова."
|
||||
)
|
||||
|
||||
@@ -3,6 +3,10 @@ PAY_2 = "Оплатить"
|
||||
BACK = "⬅️ Назад"
|
||||
CUSTOM_SUM = "💰 Ввести свою сумму"
|
||||
PROFILE = "👤 Личный кабинет"
|
||||
KEY_CREATION_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:"
|
||||
KEY_RENEWAL_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:"
|
||||
KEY_CREATION_PAYMENT_MESSAGE = (
|
||||
"Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:"
|
||||
)
|
||||
KEY_RENEWAL_PAYMENT_MESSAGE = (
|
||||
"Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:"
|
||||
)
|
||||
DEFAULT_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей. Перейдите по ссылке для оплаты:"
|
||||
|
||||
+16
-22
@@ -3,7 +3,7 @@ from typing import Any
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery
|
||||
from aiogram.types import CallbackQuery, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import CAPTCHA_EMOJIS
|
||||
@@ -13,7 +13,7 @@ from logger import logger
|
||||
router = Router()
|
||||
|
||||
|
||||
async def generate_captcha(state: FSMContext):
|
||||
async def generate_captcha(message: Message, state: FSMContext):
|
||||
"""Генерирует новую капчу и сохраняет правильный ответ в состоянии
|
||||
|
||||
Пример словаря CAPTCHA_EMOJIS:
|
||||
@@ -28,48 +28,42 @@ async def generate_captcha(state: FSMContext):
|
||||
"""
|
||||
# Выбираем случайный эмодзи и его описание из конфига
|
||||
correct_emoji, correct_text = random.choice(list(CAPTCHA_EMOJIS.items()))
|
||||
|
||||
|
||||
# Получаем 3 случайных неправильных эмодзи
|
||||
wrong_emojis = random.sample(
|
||||
[e for e in CAPTCHA_EMOJIS.keys() if e != correct_emoji], 3
|
||||
)
|
||||
|
||||
wrong_emojis = random.sample([e for e in CAPTCHA_EMOJIS.keys() if e != correct_emoji], 3)
|
||||
|
||||
# Создаем список всех эмодзи и перемешиваем их
|
||||
all_emojis = [correct_emoji] + wrong_emojis
|
||||
random.shuffle(all_emojis)
|
||||
|
||||
|
||||
# Сохраняем правильный ответ в состоянии
|
||||
await state.update_data(correct_emoji=correct_emoji)
|
||||
|
||||
await state.update_data(message=message)
|
||||
|
||||
# Создаем клавиатуру
|
||||
builder = InlineKeyboardBuilder()
|
||||
for emoji in all_emojis:
|
||||
builder.button(text=emoji, callback_data=f"captcha_{emoji}")
|
||||
builder.adjust(2, 2)
|
||||
|
||||
|
||||
return {
|
||||
"text": f"🔒 Для подтверждения что вы не робот,\nвыберите кнопку с {correct_text}",
|
||||
"markup": builder.as_markup()
|
||||
"markup": builder.as_markup(),
|
||||
}
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("captcha_"))
|
||||
async def check_captcha(
|
||||
callback: CallbackQuery, state: FSMContext, session: Any, admin: bool
|
||||
):
|
||||
async def check_captcha(callback: CallbackQuery, state: FSMContext, session: Any, admin: bool):
|
||||
"""Проверяет ответ пользователя на капчу"""
|
||||
selected_emoji = callback.data.split("captcha_")[1]
|
||||
state_data = await state.get_data()
|
||||
correct_emoji = state_data.get("correct_emoji")
|
||||
message = state_data.get("message", callback.message)
|
||||
|
||||
if selected_emoji == correct_emoji:
|
||||
logger.info(f"Пользователь {callback.message.chat.id} успешно прошел капчу")
|
||||
await start_command(callback.message, state, session, admin)
|
||||
await start_command(message, state, session, admin, False)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Пользователь {callback.message.chat.id} неверно ответил на капчу"
|
||||
)
|
||||
captcha = await generate_captcha(state)
|
||||
await callback.message.answer(
|
||||
text=captcha["text"], reply_markup=captcha["markup"]
|
||||
)
|
||||
logger.warning(f"Пользователь {callback.message.chat.id} неверно ответил на капчу")
|
||||
captcha = await generate_captcha(message, state)
|
||||
await callback.message.answer(text=captcha["text"], reply_markup=captcha["markup"])
|
||||
|
||||
+2
-6
@@ -18,9 +18,7 @@ router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "activate_coupon")
|
||||
async def handle_activate_coupon(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def handle_activate_coupon(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
@@ -40,9 +38,7 @@ async def process_coupon_code(message: types.Message, state: FSMContext, session
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await message.answer(
|
||||
activation_result, reply_markup=builder.as_markup()
|
||||
)
|
||||
await message.answer(activation_result, reply_markup=builder.as_markup())
|
||||
await state.clear()
|
||||
|
||||
|
||||
|
||||
+5
-17
@@ -22,11 +22,7 @@ async def process_donate(callback_query: types.CallbackQuery, state: FSMContext)
|
||||
await state.clear()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💰 Ввести сумму доната",
|
||||
@@ -44,14 +40,10 @@ async def process_donate(callback_query: types.CallbackQuery, state: FSMContext)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_donate_amount")
|
||||
async def process_enter_donate_amount(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_enter_donate_amount(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate"))
|
||||
await callback_query.message.answer(
|
||||
"💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup())
|
||||
await state.set_state(DonateState.entering_donate_amount)
|
||||
|
||||
|
||||
@@ -60,9 +52,7 @@ async def process_donate_amount_input(message: types.Message, state: FSMContext)
|
||||
if message.text.isdigit():
|
||||
amount = int(message.text)
|
||||
if amount // RUB_TO_XTR <= 0:
|
||||
await message.answer(
|
||||
f"Сумма доната должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:"
|
||||
)
|
||||
await message.answer(f"Сумма доната должна быть больше {RUB_TO_XTR}. Пожалуйста, введите сумму еще раз:")
|
||||
return
|
||||
|
||||
await state.update_data(amount=amount)
|
||||
@@ -99,9 +89,7 @@ async def on_successful_donate(message: types.Message, state: FSMContext):
|
||||
try:
|
||||
amount = float(message.successful_payment.invoice_payload.split("_")[0])
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
await message.answer(
|
||||
text=f"🙏 Спасибо за донат {amount} рублей! Ваша поддержка очень важна для нас. 💖",
|
||||
reply_markup=builder.as_markup(),
|
||||
|
||||
@@ -29,9 +29,7 @@ async def send_instructions(
|
||||
|
||||
if not os.path.isfile(image_path):
|
||||
if isinstance(callback_query_or_message, types.CallbackQuery):
|
||||
await callback_query_or_message.message.answer(
|
||||
"Файл изображения не найден."
|
||||
)
|
||||
await callback_query_or_message.message.answer("Файл изображения не найден.")
|
||||
else:
|
||||
await callback_query_or_message.answer("Файл изображения не найден.")
|
||||
return
|
||||
@@ -71,9 +69,7 @@ async def process_connect_pc(callback_query: types.CallbackQuery, session: Any):
|
||||
)
|
||||
|
||||
if not record:
|
||||
await callback_query.message.answer(
|
||||
"❌ <b>Ключ не найден. Проверьте имя ключа.</b> 🔍"
|
||||
)
|
||||
await callback_query.message.answer("❌ <b>Ключ не найден. Проверьте имя ключа.</b> 🔍")
|
||||
return
|
||||
|
||||
key = record["key"]
|
||||
@@ -81,14 +77,8 @@ async def process_connect_pc(callback_query: types.CallbackQuery, session: Any):
|
||||
instruction_message = f"{key_message}{INSTRUCTION_PC}"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="💻 Подключить MacOS", url=f"{CONNECT_MACOS}{key}")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text="💻 Подключить MacOS", url=f"{CONNECT_MACOS}{key}"))
|
||||
builder.row(InlineKeyboardButton(text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
@@ -104,11 +94,7 @@ async def process_connect_tv(callback_query: types.CallbackQuery):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="▶ Продолжить", callback_data=f"continue_tv|{key_name}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="▶ Продолжить", callback_data=f"continue_tv|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
@@ -146,13 +132,7 @@ async def process_continue_tv(callback_query: types.CallbackQuery):
|
||||
message_text = SUBSCRIPTION_DETAILS_TEXT.format(subscription_link=subscription_link)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="📖 Полная инструкция", url="https://vpn4tv.com/quick-guide.html"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📖 Полная инструкция", url="https://vpn4tv.com/quick-guide.html"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
text=message_text, reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(text=message_text, reply_markup=builder.as_markup())
|
||||
|
||||
@@ -55,16 +55,12 @@ class Form(StatesGroup):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "create_key")
|
||||
async def confirm_create_new_key(
|
||||
callback_query: CallbackQuery, state: FSMContext, session: Any
|
||||
):
|
||||
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
|
||||
logger.info(f"User {tg_id} confirmed creation of a new key.")
|
||||
|
||||
logger.info(
|
||||
f"Balance for user {tg_id} is sufficient. Proceeding with key creation."
|
||||
)
|
||||
logger.info(f"Balance for user {tg_id} is sufficient. Proceeding with key creation.")
|
||||
|
||||
await handle_key_creation(tg_id, state, session, callback_query)
|
||||
|
||||
@@ -83,9 +79,7 @@ async def handle_key_creation(
|
||||
expiry_time = current_time + timedelta(days=TRIAL_TIME)
|
||||
logger.info(f"Assigned 1-day trial to user {tg_id}.")
|
||||
|
||||
await session.execute(
|
||||
"UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id
|
||||
)
|
||||
await session.execute("UPDATE connections SET trial = 1 WHERE tg_id = $1", tg_id)
|
||||
await create_key(tg_id, expiry_time, state, session, message_or_query)
|
||||
else:
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -107,9 +101,7 @@ async def handle_key_creation(
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await message_or_query.message.answer(
|
||||
"💳 Выберите тарифный план для создания нового ключа:",
|
||||
@@ -153,12 +145,8 @@ async def select_tariff_plan(callback_query: CallbackQuery, session: Any):
|
||||
await handle_custom_amount_input(callback_query, session)
|
||||
else:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay")
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="💳 Пополнить баланс", callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
f"💳 Недостаточно средств. Для продолжения необходимо пополнить баланс на {required_amount}₽.",
|
||||
@@ -193,9 +181,7 @@ 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 name '{key_name}' already exists for user {tg_id}. Generating a new one.")
|
||||
|
||||
client_id = str(uuid.uuid4())
|
||||
email = key_name.lower()
|
||||
|
||||
@@ -33,9 +33,7 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
|
||||
server_name = server_info.get("server_name", "unknown")
|
||||
|
||||
if not inbound_id:
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск."
|
||||
)
|
||||
logger.warning(f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск.")
|
||||
return
|
||||
|
||||
if SUPERNODE:
|
||||
@@ -56,7 +54,7 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(inbound_id),
|
||||
sub_id=sub_id
|
||||
sub_id=sub_id,
|
||||
)
|
||||
|
||||
if SUPERNODE:
|
||||
@@ -73,8 +71,6 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
|
||||
raise e
|
||||
|
||||
|
||||
|
||||
|
||||
async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, total_gb):
|
||||
try:
|
||||
servers = await get_servers_from_db()
|
||||
@@ -95,9 +91,7 @@ async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, to
|
||||
server_name = server_info.get("server_name", "unknown")
|
||||
|
||||
if not inbound_id:
|
||||
logger.warning(
|
||||
f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск."
|
||||
)
|
||||
logger.warning(f"INBOUND_ID отсутствует для сервера {server_name}. Пропуск.")
|
||||
continue
|
||||
|
||||
if SUPERNODE:
|
||||
@@ -108,23 +102,13 @@ async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, to
|
||||
sub_id = unique_email
|
||||
|
||||
tasks.append(
|
||||
extend_client_key(
|
||||
xui,
|
||||
int(inbound_id),
|
||||
unique_email,
|
||||
new_expiry_time,
|
||||
client_id,
|
||||
total_gb,
|
||||
sub_id
|
||||
)
|
||||
extend_client_key(xui, int(inbound_id), unique_email, new_expiry_time, client_id, total_gb, sub_id)
|
||||
)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Не удалось продлить ключ {client_id} в кластере {cluster_id}: {e}"
|
||||
)
|
||||
logger.error(f"Не удалось продлить ключ {client_id} в кластере {cluster_id}: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
@@ -171,9 +155,7 @@ async def delete_key_from_cluster(cluster_id, email, client_id):
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Не удалось удалить ключ {client_id} в кластере {cluster_id}: {e}"
|
||||
)
|
||||
logger.error(f"Не удалось удалить ключ {client_id} в кластере {cluster_id}: {e}")
|
||||
raise e
|
||||
|
||||
|
||||
@@ -212,18 +194,14 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(inbound_id),
|
||||
sub_id=email
|
||||
sub_id=email,
|
||||
)
|
||||
)
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
logger.info(
|
||||
f"Ключ успешно обновлен для {client_id} на всех серверах в кластере {cluster_id}"
|
||||
)
|
||||
logger.info(f"Ключ успешно обновлен для {client_id} на всех серверах в кластере {cluster_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при обновлении ключа на серверах кластера {cluster_id} для {client_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при обновлении ключа на серверах кластера {cluster_id} для {client_id}: {e}")
|
||||
raise e
|
||||
|
||||
+32
-88
@@ -86,9 +86,7 @@ async def process_callback_or_message_view_keys(
|
||||
inline_keyboard, response_message = build_keys_response(records)
|
||||
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
await send_with_optional_image(
|
||||
send_message, send_photo, image_path, response_message, inline_keyboard
|
||||
)
|
||||
await send_with_optional_image(send_message, send_photo, image_path, response_message, inline_keyboard)
|
||||
except Exception as e:
|
||||
error_message = f"Ошибка при получении ключей: {e}"
|
||||
await send_message(text=error_message)
|
||||
@@ -105,15 +103,10 @@ def build_keys_response(records):
|
||||
key_name = record["email"]
|
||||
expiry_date = datetime.utcfromtimestamp(record["expiry_time"] / 1000).strftime("%d.%m.%Y")
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f"🔑 {key_name} (до {expiry_date})",
|
||||
callback_data=f"view_key|{key_name}"
|
||||
)
|
||||
InlineKeyboardButton(text=f"🔑 {key_name} (до {expiry_date})", callback_data=f"view_key|{key_name}")
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="➕ Добавить подписку", callback_data="create_key")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="➕ Добавить подписку", callback_data="create_key"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
@@ -125,18 +118,14 @@ def build_keys_response(records):
|
||||
return inline_keyboard, response_message
|
||||
|
||||
|
||||
async def send_with_optional_image(
|
||||
send_message, send_photo, image_path, text, keyboard
|
||||
):
|
||||
async def send_with_optional_image(send_message, send_photo, image_path, text, keyboard):
|
||||
"""
|
||||
Отправляет сообщение с изображением, если файл существует. В противном случае отправляет только текст.
|
||||
"""
|
||||
if os.path.isfile(image_path):
|
||||
with open(image_path, "rb") as image_file:
|
||||
await send_photo(
|
||||
photo=BufferedInputFile(
|
||||
image_file.read(), filename=os.path.basename(image_path)
|
||||
),
|
||||
photo=BufferedInputFile(image_file.read(), filename=os.path.basename(image_path)),
|
||||
caption=text,
|
||||
reply_markup=keyboard,
|
||||
)
|
||||
@@ -184,9 +173,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
|
||||
)
|
||||
|
||||
formatted_expiry_date = expiry_date.strftime("%d %B %Y года")
|
||||
response_message = key_message(
|
||||
key, formatted_expiry_date, days_left_message, server_name
|
||||
)
|
||||
response_message = key_message(key, formatted_expiry_date, days_left_message, server_name)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
@@ -200,51 +187,29 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=DOWNLOAD_IOS_BUTTON, url=DOWNLOAD_IOS),
|
||||
InlineKeyboardButton(
|
||||
text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID
|
||||
),
|
||||
InlineKeyboardButton(text=DOWNLOAD_ANDROID_BUTTON, url=DOWNLOAD_ANDROID),
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=IMPORT_IOS, url=f"{CONNECT_IOS}{key}"),
|
||||
InlineKeyboardButton(
|
||||
text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key}"
|
||||
),
|
||||
InlineKeyboardButton(text=IMPORT_ANDROID, url=f"{CONNECT_ANDROID}{key}"),
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"
|
||||
),
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{key_name}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{key_name}"),
|
||||
)
|
||||
|
||||
# ✅ Добавлена проверка флага ENABLE_DELETE_KEY_BUTTON
|
||||
if ENABLE_DELETE_KEY_BUTTON:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Продлить", callback_data=f"renew_key|{key_name}"
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text="❌ Удалить", callback_data=f"delete_key|{key_name}"
|
||||
),
|
||||
InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}"),
|
||||
InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete_key|{key_name}"),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="⏳ Продлить", callback_data=f"renew_key|{key_name}"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🔙 Назад", callback_data="view_keys"
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⏳ Продлить", callback_data=f"renew_key|{key_name}"))
|
||||
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="view_keys"))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
keyboard = builder.as_markup()
|
||||
|
||||
@@ -273,9 +238,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("update_subscription|"))
|
||||
async def process_callback_update_subscription(
|
||||
callback_query: types.CallbackQuery, session: Any
|
||||
):
|
||||
async def process_callback_update_subscription(callback_query: types.CallbackQuery, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
email = callback_query.data.split("|")[1]
|
||||
try:
|
||||
@@ -335,9 +298,7 @@ async def process_callback_update_subscription(
|
||||
else:
|
||||
await callback_query.message.answer("<b>Ключ не найден в базе данных.</b>")
|
||||
except Exception as e:
|
||||
await handle_error(
|
||||
tg_id, callback_query, f"Ошибка при обновлении подписки: {e}"
|
||||
)
|
||||
await handle_error(tg_id, callback_query, f"Ошибка при обновлении подписки: {e}")
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("delete_key|"))
|
||||
@@ -352,11 +313,7 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery):
|
||||
callback_data=f"confirm_delete|{client_id}",
|
||||
)
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="❌ Нет, отменить", callback_data="view_keys"
|
||||
)
|
||||
],
|
||||
[types.InlineKeyboardButton(text="❌ Нет, отменить", callback_data="view_keys")],
|
||||
]
|
||||
)
|
||||
|
||||
@@ -391,43 +348,39 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery, sessio
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 1 месяц ({RENEWAL_PLANS["1"]["price"]} руб.)',
|
||||
text=f"📅 1 месяц ({RENEWAL_PLANS['1']['price']} руб.)",
|
||||
callback_data=f"renew_plan|1|{client_id}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 3 месяца ({RENEWAL_PLANS["3"]["price"]} руб.) {DISCOUNTS["3"]}% скидка',
|
||||
text=f"📅 3 месяца ({RENEWAL_PLANS['3']['price']} руб.) {DISCOUNTS['3']}% скидка",
|
||||
callback_data=f"renew_plan|3|{client_id}",
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=f'📅 6 месяцев ({RENEWAL_PLANS["6"]["price"]} руб.) {DISCOUNTS["6"]}% скидка',
|
||||
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"]}% 🔥)',
|
||||
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"
|
||||
)
|
||||
back_button = InlineKeyboardButton(text="🔙 Назад", callback_data="view_keys")
|
||||
builder.row(back_button)
|
||||
|
||||
balance = await get_balance(tg_id)
|
||||
|
||||
response_message = PLAN_SELECTION_MSG.format(
|
||||
balance=balance,
|
||||
expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime(
|
||||
"%Y-%m-%d %H:%M:%S"
|
||||
),
|
||||
expiry_date=datetime.utcfromtimestamp(expiry_time / 1000).strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
|
||||
await callback_query.message.answer(
|
||||
@@ -441,21 +394,15 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery, sessio
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("confirm_delete|"))
|
||||
async def process_callback_confirm_delete(
|
||||
callback_query: types.CallbackQuery, session: Any
|
||||
):
|
||||
async def process_callback_confirm_delete(callback_query: types.CallbackQuery, session: Any):
|
||||
email = callback_query.data.split("|")[1]
|
||||
try:
|
||||
record = await session.fetchrow(
|
||||
"SELECT client_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
record = await session.fetchrow("SELECT client_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if record:
|
||||
client_id = record["client_id"]
|
||||
response_message = "Ключ успешно удален."
|
||||
back_button = types.InlineKeyboardButton(
|
||||
text="Назад", callback_data="view_keys"
|
||||
)
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await delete_key(client_id)
|
||||
@@ -470,9 +417,7 @@ async def process_callback_confirm_delete(
|
||||
try:
|
||||
tasks = []
|
||||
for cluster_id, cluster in servers.items():
|
||||
tasks.append(
|
||||
delete_key_from_cluster(cluster_id, email, client_id)
|
||||
)
|
||||
tasks.append(delete_key_from_cluster(cluster_id, email, client_id))
|
||||
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
@@ -485,9 +430,7 @@ async def process_callback_confirm_delete(
|
||||
|
||||
else:
|
||||
response_message = "Ключ не найден или уже удален."
|
||||
back_button = types.InlineKeyboardButton(
|
||||
text="Назад", callback_data="view_keys"
|
||||
)
|
||||
back_button = types.InlineKeyboardButton(text="Назад", callback_data="view_keys")
|
||||
keyboard = types.InlineKeyboardMarkup(inline_keyboard=[[back_button]])
|
||||
|
||||
await callback_query.message.answer(
|
||||
@@ -529,7 +472,9 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery, sessi
|
||||
if balance < cost:
|
||||
required_amount = cost - balance
|
||||
|
||||
logger.info(f"[RENEW] Пользователю {tg_id} не хватает {required_amount}₽. Запуск доплаты через {USE_NEW_PAYMENT_FLOW}")
|
||||
logger.info(
|
||||
f"[RENEW] Пользователю {tg_id} не хватает {required_amount}₽. Запуск доплаты через {USE_NEW_PAYMENT_FLOW}"
|
||||
)
|
||||
|
||||
await save_temporary_data(
|
||||
session,
|
||||
@@ -610,4 +555,3 @@ async def complete_key_renewal(tg_id, client_id, email, new_expiry_time, total_g
|
||||
logger.info(f"[RENEW] Ключ {client_id} успешно продлён на {plan} мес. для пользователя {tg_id}.")
|
||||
|
||||
await renew_key_on_servers()
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from logger import logger
|
||||
# Глобальная переменная для пула соединений
|
||||
db_pool = None
|
||||
|
||||
|
||||
async def init_db_pool():
|
||||
"""
|
||||
Инициализация пула соединений, если он ещё не создан.
|
||||
@@ -21,6 +22,7 @@ async def init_db_pool():
|
||||
if not db_pool:
|
||||
db_pool = await asyncpg.create_pool(dsn=DATABASE_URL, min_size=5, max_size=20)
|
||||
|
||||
|
||||
async def fetch_url_content(url, tg_id):
|
||||
try:
|
||||
logger.info(f"Получение URL: {url} для tg_id: {tg_id}")
|
||||
@@ -32,9 +34,7 @@ async def fetch_url_content(url, tg_id):
|
||||
logger.info(f"Успешно получен контент с {url} для tg_id: {tg_id}")
|
||||
return base64.b64decode(content).decode("utf-8").split("\n")
|
||||
else:
|
||||
logger.error(
|
||||
f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}"
|
||||
)
|
||||
logger.error(f"Не удалось получить {url} для tg_id: {tg_id}, статус: {response.status}")
|
||||
return []
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"Таймаут при получении {url} для tg_id: {tg_id}")
|
||||
@@ -43,6 +43,7 @@ async def fetch_url_content(url, tg_id):
|
||||
logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}")
|
||||
return []
|
||||
|
||||
|
||||
async def combine_unique_lines(urls, tg_id, query_string):
|
||||
if SUPERNODE:
|
||||
logger.info(f"Режим SUPERNODE активен. Возвращаем первую ссылку для tg_id: {tg_id}")
|
||||
@@ -51,9 +52,7 @@ async def combine_unique_lines(urls, tg_id, query_string):
|
||||
url_with_query = f"{urls[0]}?{query_string}" if query_string else urls[0]
|
||||
return await fetch_url_content(url_with_query, tg_id)
|
||||
|
||||
logger.info(
|
||||
f"Начинаем объединение подписок для tg_id: {tg_id}, запрос: {query_string}"
|
||||
)
|
||||
logger.info(f"Начинаем объединение подписок для tg_id: {tg_id}, запрос: {query_string}")
|
||||
|
||||
urls_with_query = [f"{url}?{query_string}" if query_string else url for url in urls]
|
||||
logger.info(f"Составлены URL-адреса: {urls_with_query}")
|
||||
@@ -65,19 +64,17 @@ async def combine_unique_lines(urls, tg_id, query_string):
|
||||
for lines in results:
|
||||
all_lines.update(filter(None, lines))
|
||||
|
||||
logger.info(
|
||||
f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}"
|
||||
)
|
||||
logger.info(f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}")
|
||||
|
||||
return list(all_lines)
|
||||
|
||||
|
||||
transition_date = datetime.strptime(TRANSITION_DATE_STR, "%Y-%m-%d %H:%M:%S")
|
||||
transition_timestamp_ms = int(transition_date.timestamp() * 1000)
|
||||
transition_timestamp_ms_adjusted = transition_timestamp_ms - (3 * 60 * 60 * 1000)
|
||||
|
||||
logger.info(
|
||||
f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}"
|
||||
)
|
||||
logger.info(f"Время перехода (с поправкой на часовой пояс): {transition_timestamp_ms_adjusted}")
|
||||
|
||||
|
||||
async def handle_old_subscription(request):
|
||||
email = request.match_info.get("email")
|
||||
@@ -95,9 +92,7 @@ async def handle_old_subscription(request):
|
||||
await init_db_pool()
|
||||
|
||||
async with db_pool.acquire() as conn:
|
||||
key_info = await conn.fetchrow(
|
||||
"SELECT created_at, server_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
key_info = await conn.fetchrow("SELECT created_at, server_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if not key_info:
|
||||
logger.warning(f"Клиент с email {email} не найден в базе.")
|
||||
@@ -115,14 +110,10 @@ async def handle_old_subscription(request):
|
||||
status=400,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Значение created_at для клиента с email {email}: {created_at_ms}, кластер: {cluster_name}"
|
||||
)
|
||||
logger.info(f"Значение created_at для клиента с email {email}: {created_at_ms}, кластер: {cluster_name}")
|
||||
|
||||
created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000)
|
||||
logger.info(
|
||||
f"Время создания клиента в формате datetime (UTC): {created_at_datetime}"
|
||||
)
|
||||
logger.info(f"Время создания клиента в формате datetime (UTC): {created_at_datetime}")
|
||||
|
||||
if created_at_ms >= transition_timestamp_ms_adjusted:
|
||||
logger.info(f"Клиент с email {email} является новым.")
|
||||
@@ -135,23 +126,18 @@ async def handle_old_subscription(request):
|
||||
cluster_servers = servers.get(cluster_name, [])
|
||||
logger.info(f"Сервера в кластере: {cluster_servers}")
|
||||
|
||||
urls = [
|
||||
f"{server['subscription_url']}/{email}" for server in cluster_servers
|
||||
]
|
||||
urls = [f"{server['subscription_url']}/{email}" for server in cluster_servers]
|
||||
|
||||
combined_subscriptions = await combine_unique_lines(urls, email, "")
|
||||
|
||||
base64_encoded = base64.b64encode(
|
||||
"\n".join(combined_subscriptions).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
base64_encoded = base64.b64encode("\n".join(combined_subscriptions).encode("utf-8")).decode("utf-8")
|
||||
|
||||
encoded_project_name = f"{PROJECT_NAME} - {SUB_MESSAGE}"
|
||||
headers = {
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Content-Disposition": "inline",
|
||||
"profile-update-interval": "7",
|
||||
"profile-title": "base64:"
|
||||
+ base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
|
||||
"profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
|
||||
}
|
||||
|
||||
logger.info(f"Возвращаем объединенные подписки для email: {email}")
|
||||
@@ -175,9 +161,7 @@ async def handle_new_subscription(request):
|
||||
await init_db_pool()
|
||||
|
||||
async with db_pool.acquire() as conn:
|
||||
client_data = await conn.fetchrow(
|
||||
"SELECT tg_id, server_id FROM keys WHERE email = $1", email
|
||||
)
|
||||
client_data = await conn.fetchrow("SELECT tg_id, server_id FROM keys WHERE email = $1", email)
|
||||
|
||||
if not client_data:
|
||||
logger.warning(f"Клиент с email {email} не найден в базе.")
|
||||
@@ -199,18 +183,14 @@ async def handle_new_subscription(request):
|
||||
servers = await get_servers_from_db()
|
||||
cluster_servers = servers.get(cluster_name, [])
|
||||
|
||||
urls = [
|
||||
f"{server['subscription_url']}/{email}" for server in cluster_servers
|
||||
]
|
||||
urls = [f"{server['subscription_url']}/{email}" for server in cluster_servers]
|
||||
|
||||
query_string = request.query_string
|
||||
logger.info(f"Извлечен query string: {query_string}")
|
||||
|
||||
combined_subscriptions = await combine_unique_lines(urls, tg_id, query_string)
|
||||
|
||||
base64_encoded = base64.b64encode(
|
||||
"\n".join(combined_subscriptions).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
base64_encoded = base64.b64encode("\n".join(combined_subscriptions).encode("utf-8")).decode("utf-8")
|
||||
|
||||
encoded_project_name = f"{PROJECT_NAME} - {SUB_MESSAGE}"
|
||||
|
||||
@@ -218,8 +198,7 @@ async def handle_new_subscription(request):
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
"Content-Disposition": "inline",
|
||||
"profile-update-interval": "7",
|
||||
"profile-title": "base64:"
|
||||
+ base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
|
||||
"profile-title": "base64:" + base64.b64encode(encoded_project_name.encode("utf-8")).decode("utf-8"),
|
||||
}
|
||||
|
||||
logger.info(f"Возвращаем объединенные подписки для email: {email}")
|
||||
|
||||
@@ -16,9 +16,7 @@ from logger import logger
|
||||
|
||||
async def create_trial_key(tg_id: int, session: Any):
|
||||
try:
|
||||
trial_status = await session.fetchval(
|
||||
"SELECT trial FROM connections WHERE tg_id = $1", tg_id
|
||||
)
|
||||
trial_status = await session.fetchval("SELECT trial FROM connections WHERE tg_id = $1", tg_id)
|
||||
if trial_status == 1:
|
||||
return {"error": "Вы уже использовали пробную версию."}
|
||||
except Exception as e:
|
||||
@@ -67,7 +65,7 @@ async def create_trial_key(tg_id: int, session: Any):
|
||||
enable=True,
|
||||
flow="xtls-rprx-vision",
|
||||
inbound_id=int(server_info["inbound_id"]),
|
||||
sub_id=base_email
|
||||
sub_id=base_email,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+21
-42
@@ -36,6 +36,7 @@ from logger import logger
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
async def periodic_expired_keys_check(bot: Bot):
|
||||
"""Периодическая проверка истекших ключей с кастомным интервалом."""
|
||||
while True:
|
||||
@@ -54,7 +55,6 @@ async def periodic_expired_keys_check(bot: Bot):
|
||||
await asyncio.sleep(EXPIRED_KEYS_CHECK_INTERVAL)
|
||||
|
||||
|
||||
|
||||
async def notify_expiring_keys(bot: Bot):
|
||||
conn = None
|
||||
try:
|
||||
@@ -84,21 +84,16 @@ async def notify_expiring_keys(bot: Bot):
|
||||
logger.info("Соединение с базой данных закрыто.")
|
||||
|
||||
|
||||
|
||||
async def is_bot_blocked(bot: Bot, chat_id: int) -> bool:
|
||||
if DEV_MODE:
|
||||
return False
|
||||
try:
|
||||
member = await bot.get_chat_member(chat_id, bot.id)
|
||||
blocked = member.status == "left"
|
||||
logger.info(
|
||||
f"Статус бота для пользователя {chat_id}: {'заблокирован' if blocked else 'активен'}"
|
||||
)
|
||||
logger.info(f"Статус бота для пользователя {chat_id}: {'заблокирован' if blocked else 'активен'}")
|
||||
return blocked
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Не удалось проверить статус бота для пользователя {chat_id}: {e}"
|
||||
)
|
||||
logger.warning(f"Не удалось проверить статус бота для пользователя {chat_id}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -137,7 +132,11 @@ async def process_10h_record(record, bot, conn):
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
days_left_message = (
|
||||
"Ключ истек" if time_left.total_seconds() <= 0 else f"{time_left.days}" if time_left.days > 0 else f"{time_left.seconds // 3600}"
|
||||
"Ключ истек"
|
||||
if time_left.total_seconds() <= 0
|
||||
else f"{time_left.days}"
|
||||
if time_left.days > 0
|
||||
else f"{time_left.seconds // 3600}"
|
||||
)
|
||||
|
||||
message = KEY_EXPIRY_10H.format(
|
||||
@@ -200,7 +199,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"]
|
||||
@@ -213,7 +211,11 @@ async def process_24h_record(record, bot, conn):
|
||||
time_left = expiry_date - current_date
|
||||
|
||||
days_left_message = (
|
||||
"Ключ истек" if time_left.total_seconds() <= 0 else f"{time_left.days}" if time_left.days > 0 else f"{time_left.seconds // 3600}"
|
||||
"Ключ истек"
|
||||
if time_left.total_seconds() <= 0
|
||||
else f"{time_left.days}"
|
||||
if time_left.days > 0
|
||||
else f"{time_left.seconds // 3600}"
|
||||
)
|
||||
|
||||
message_24h = KEY_EXPIRY_24H.format(
|
||||
@@ -287,9 +289,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
username = user.get("username", "Пользователь")
|
||||
|
||||
try:
|
||||
can_notify = await check_notification_time(
|
||||
tg_id, "inactive_trial", hours=24, session=conn
|
||||
)
|
||||
can_notify = await check_notification_time(tg_id, "inactive_trial", hours=24, session=conn)
|
||||
|
||||
if can_notify:
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -299,11 +299,7 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
callback_data="create_key",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="profile"
|
||||
)
|
||||
)
|
||||
builder.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
keyboard = builder.as_markup()
|
||||
|
||||
message = (
|
||||
@@ -315,20 +311,14 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||||
|
||||
try:
|
||||
await bot.send_message(tg_id, message, reply_markup=keyboard)
|
||||
logger.info(
|
||||
f"Отправлено уведомление неактивному пользователю {tg_id}."
|
||||
)
|
||||
logger.info(f"Отправлено уведомление неактивному пользователю {tg_id}.")
|
||||
await add_notification(tg_id, "inactive_trial", session=conn)
|
||||
|
||||
except TelegramForbiddenError:
|
||||
logger.warning(
|
||||
f"Бот заблокирован пользователем {tg_id}. Добавляем в blocked_users."
|
||||
)
|
||||
logger.warning(f"Бот заблокирован пользователем {tg_id}. Добавляем в blocked_users.")
|
||||
await add_blocked_user(tg_id, conn)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка при отправке уведомления пользователю {tg_id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обработке пользователя {tg_id}: {e}")
|
||||
@@ -378,13 +368,7 @@ async def process_key(record, bot, conn):
|
||||
)
|
||||
|
||||
keyboard = types.InlineKeyboardMarkup(
|
||||
inline_keyboard=[
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="👤 Личный кабинет", callback_data="profile"
|
||||
)
|
||||
]
|
||||
]
|
||||
inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]]
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -446,15 +430,12 @@ async def process_key(record, bot, conn):
|
||||
logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}")
|
||||
|
||||
|
||||
|
||||
async def check_online_users():
|
||||
servers = await get_servers_from_db()
|
||||
|
||||
for cluster_id, cluster in servers.items():
|
||||
for server_id, server in enumerate(cluster):
|
||||
xui = AsyncApi(
|
||||
server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD
|
||||
)
|
||||
xui = AsyncApi(server["api_url"], username=ADMIN_USERNAME, password=ADMIN_PASSWORD)
|
||||
await xui.login()
|
||||
try:
|
||||
online_users = len(await xui.client.online())
|
||||
@@ -462,6 +443,4 @@ async def check_online_users():
|
||||
f"Сервер '{server['server_name']}' доступен, текущее количество активных пользователей: {online_users}."
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Не удалось проверить пользователей на сервере {server_id}: {e}"
|
||||
)
|
||||
logger.error(f"Не удалось проверить пользователей на сервере {server_id}: {e}")
|
||||
|
||||
+2
-8
@@ -54,15 +54,9 @@ async def handle_pay(callback_query: CallbackQuery):
|
||||
)
|
||||
)
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="🎟️ Активировать купон", callback_data="activate_coupon"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🎟️ Активировать купон", callback_data="activate_coupon"))
|
||||
if DONATIONS_ENABLE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
|
||||
@@ -52,9 +52,7 @@ if ROBOKASSA_ENABLE:
|
||||
|
||||
def generate_payment_link(amount, inv_id, description, tg_id):
|
||||
"""Генерация ссылки на оплату."""
|
||||
logger.debug(
|
||||
f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}"
|
||||
)
|
||||
logger.debug(f"Generating payment link for amount: {amount}, inv_id: {inv_id}, description: {description}")
|
||||
payment_link = robokassa._payment.link.generate_by_script(
|
||||
out_sum=amount,
|
||||
inv_id=inv_id,
|
||||
@@ -66,9 +64,7 @@ def generate_payment_link(amount, inv_id, description, tg_id):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay_robokassa")
|
||||
async def process_callback_pay_robokassa(
|
||||
callback_query: types.CallbackQuery, state: FSMContext, session: Any
|
||||
):
|
||||
async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
|
||||
tg_id = callback_query.message.chat.id
|
||||
logger.info(f"User {tg_id} initiated Robokassa payment.")
|
||||
|
||||
@@ -78,18 +74,18 @@ async def process_callback_pay_robokassa(
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i]['callback_data']}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i + 1]["text"],
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i + 1]['callback_data']}",
|
||||
),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i]['callback_data']}",
|
||||
)
|
||||
)
|
||||
builder.row(
|
||||
@@ -117,9 +113,7 @@ async def process_callback_pay_robokassa(
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("robokassa_amount|"))
|
||||
async def process_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
logger.info(f"Получены данные callback_data: {callback_query.data}")
|
||||
|
||||
data = callback_query.data.split("|")
|
||||
@@ -173,9 +167,7 @@ async def robokassa_webhook(request):
|
||||
shp_id = params.get("shp_id")
|
||||
signature_value = params.get("SignatureValue")
|
||||
|
||||
logger.info(
|
||||
f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}"
|
||||
)
|
||||
logger.info(f"OutSum: {amount}, InvId: {inv_id}, shp_id: {shp_id}, SignatureValue: {signature_value}")
|
||||
|
||||
if not check_payment_signature(params):
|
||||
logger.error("Неверная подпись или данные запроса.")
|
||||
@@ -214,9 +206,7 @@ def check_payment_signature(params):
|
||||
|
||||
logger.info(f"Signature string before hashing: {signature_string}")
|
||||
|
||||
expected_signature = (
|
||||
hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
|
||||
)
|
||||
expected_signature = hashlib.md5(signature_string.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
logger.info(f"Expected signature: {expected_signature}")
|
||||
logger.info(f"Received signature: {signature_value}")
|
||||
@@ -225,9 +215,7 @@ def check_payment_signature(params):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_amount_robokassa")
|
||||
async def process_custom_amount_selection(
|
||||
callback_query: types.CallbackQuery, state: FSMContext
|
||||
):
|
||||
async def process_custom_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
tg_id = callback_query.message.chat.id
|
||||
logger.info(f"User {tg_id} chose to enter a custom amount.")
|
||||
|
||||
@@ -239,13 +227,13 @@ async def process_custom_amount_selection(
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
await state.set_state(
|
||||
ReplenishBalanceState.waiting_for_payment_confirmation_robokassa
|
||||
)
|
||||
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
|
||||
|
||||
@router.message(ReplenishBalanceState.waiting_for_payment_confirmation_robokassa)
|
||||
async def handle_custom_amount_input(message: types.Message | types.CallbackQuery, state: FSMContext = None, session: Any = None):
|
||||
async def handle_custom_amount_input(
|
||||
message: types.Message | types.CallbackQuery, state: FSMContext = None, session: Any = None
|
||||
):
|
||||
if isinstance(message, types.CallbackQuery):
|
||||
tg_id = message.message.chat.id
|
||||
else:
|
||||
@@ -255,7 +243,6 @@ async def handle_custom_amount_input(message: types.Message | types.CallbackQuer
|
||||
inv_id = 0
|
||||
|
||||
try:
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
user_data = await get_temporary_data(conn, tg_id)
|
||||
await conn.close()
|
||||
@@ -283,9 +270,13 @@ async def handle_custom_amount_input(message: types.Message | types.CallbackQuer
|
||||
)
|
||||
|
||||
if state_type == "waiting_for_payment":
|
||||
message_text = f"Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:"
|
||||
message_text = (
|
||||
f"Вы выбрали пополнение на {amount} рублей для создания нового ключа. Перейдите по ссылке для оплаты:"
|
||||
)
|
||||
elif state_type == "waiting_for_renewal_payment":
|
||||
message_text = f"Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:"
|
||||
message_text = (
|
||||
f"Вы выбрали пополнение на {amount} рублей для продления ключа. Перейдите по ссылке для оплаты:"
|
||||
)
|
||||
else:
|
||||
await message.answer("Некорректное состояние данных. Попробуйте снова.")
|
||||
return
|
||||
|
||||
+9
-19
@@ -51,9 +51,7 @@ async def process_callback_view_profile(
|
||||
try:
|
||||
trial_status = await get_trial(chat_id, conn)
|
||||
|
||||
profile_message = profile_message_send(
|
||||
username, chat_id, int(balance), key_count
|
||||
)
|
||||
profile_message = profile_message_send(username, chat_id, int(balance), key_count)
|
||||
|
||||
if key_count == 0:
|
||||
profile_message += "\n<pre>🔧 <i>Нажмите кнопку ➕ Подписка, чтобы настроить VPN-подключение</i></pre>"
|
||||
@@ -81,9 +79,7 @@ async def process_callback_view_profile(
|
||||
InlineKeyboardButton(text=INSTRUCTIONS, callback_data="instructions"),
|
||||
)
|
||||
if admin:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="start"))
|
||||
|
||||
if os.path.isfile(image_path):
|
||||
@@ -122,10 +118,8 @@ async def balance_handler(callback_query: types.CallbackQuery):
|
||||
builder.row(InlineKeyboardButton(text=BALANCE_HISTORY, callback_data="balance_history"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
"💰 Управление балансом:",
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer("💰 Управление балансом:", reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "balance_history")
|
||||
async def balance_history_handler(callback_query: types.CallbackQuery, session: Any):
|
||||
@@ -145,10 +139,10 @@ async def balance_history_handler(callback_query: types.CallbackQuery, session:
|
||||
if records:
|
||||
history_text = "📊 <b>Последние 3 операции с балансом:</b>\n\n"
|
||||
for record in records:
|
||||
amount = record['amount']
|
||||
payment_system = record['payment_system']
|
||||
status = record['status']
|
||||
date = record['created_at'].strftime('%Y-%m-%d %H:%M:%S')
|
||||
amount = record["amount"]
|
||||
payment_system = record["payment_system"]
|
||||
status = record["status"]
|
||||
date = record["created_at"].strftime("%Y-%m-%d %H:%M:%S")
|
||||
history_text += (
|
||||
f"<b>Сумма:</b> {amount}₽\n"
|
||||
f"<b>Способ оплаты:</b> {payment_system}\n"
|
||||
@@ -158,11 +152,7 @@ async def balance_history_handler(callback_query: types.CallbackQuery, session:
|
||||
else:
|
||||
history_text = "❌ У вас пока нет операций с балансом."
|
||||
|
||||
await callback_query.message.answer(
|
||||
history_text,
|
||||
reply_markup=builder.as_markup()
|
||||
)
|
||||
|
||||
await callback_query.message.answer(history_text, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.message(F.text == "/tariffs")
|
||||
|
||||
+32
-76
@@ -13,6 +13,7 @@ from aiogram.types import (
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from config import (
|
||||
CAPTCHA_ENABLE,
|
||||
CHANNEL_EXISTS,
|
||||
CHANNEL_URL,
|
||||
CONNECT_ANDROID,
|
||||
@@ -21,7 +22,6 @@ from config import (
|
||||
DOWNLOAD_ANDROID,
|
||||
DOWNLOAD_IOS,
|
||||
SUPPORT_CHAT_URL,
|
||||
CAPTCHA_ENABLE,
|
||||
)
|
||||
from database import (
|
||||
add_connection,
|
||||
@@ -38,32 +38,32 @@ from handlers.buttons.add_subscribe import (
|
||||
PC_BUTTON,
|
||||
TV_BUTTON,
|
||||
)
|
||||
from handlers.captcha import generate_captcha
|
||||
from handlers.keys.key_management import create_key
|
||||
from handlers.keys.trial_key import create_trial_key
|
||||
from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn
|
||||
from logger import logger
|
||||
from handlers.captcha import generate_captcha
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "start")
|
||||
async def handle_start_callback_query(
|
||||
callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool
|
||||
callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool, captcha: bool = False
|
||||
):
|
||||
await start_command(callback_query.message, state, session, admin)
|
||||
await start_command(callback_query.message, state, session, admin, captcha)
|
||||
|
||||
|
||||
@router.message(Command("start"))
|
||||
async def start_command(message: Message, state: FSMContext, session: Any, admin: bool):
|
||||
async def start_command(message: Message, state: FSMContext, session: Any, admin: bool, captcha: bool = True):
|
||||
"""Обрабатывает команду /start, включает логику рефералов и подарков."""
|
||||
logger.info(f"Вызвана функция start_command для пользователя {message.chat.id}")
|
||||
|
||||
await state.clear()
|
||||
|
||||
# Проверка капчи, если включена
|
||||
if CAPTCHA_ENABLE:
|
||||
captcha = await generate_captcha(state)
|
||||
if CAPTCHA_ENABLE and captcha:
|
||||
captcha = await generate_captcha(message, state)
|
||||
await message.answer(text=captcha["text"], reply_markup=captcha["markup"])
|
||||
return
|
||||
|
||||
@@ -74,9 +74,7 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
|
||||
if not connection_exists:
|
||||
await add_connection(tg_id=message.chat.id, session=session)
|
||||
logger.info(
|
||||
f"Пользователь {message.chat.id} успешно добавлен в базу данных."
|
||||
)
|
||||
logger.info(f"Пользователь {message.chat.id} успешно добавлен в базу данных.")
|
||||
|
||||
if "gift_" in message.text:
|
||||
logger.info(f"Обнаружена ссылка на подарок: {message.text}")
|
||||
@@ -91,36 +89,24 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
)
|
||||
|
||||
if gift_info is None:
|
||||
logger.warning(
|
||||
f"Подарок с ID {gift_id} уже был использован или не существует."
|
||||
)
|
||||
await message.answer(
|
||||
"Этот подарок уже был использован или не существует."
|
||||
)
|
||||
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} попытался активировать подарок, который был отправлен им самим."
|
||||
)
|
||||
await message.answer(
|
||||
"❌ Вы не можете получить подарок от самого себя."
|
||||
)
|
||||
await message.answer("❌ Вы не можете получить подарок от самого себя.")
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
selected_months = gift_info["selected_months"]
|
||||
expiry_time = gift_info["expiry_time"]
|
||||
expiry_time_naive = expiry_time.replace(tzinfo=None)
|
||||
logger.info(
|
||||
f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}."
|
||||
)
|
||||
logger.info(f"Подарок с ID {gift_id} успешно найден для пользователя {recipient_tg_id}.")
|
||||
|
||||
await create_key(
|
||||
recipient_tg_id, expiry_time_naive, state, session, message
|
||||
)
|
||||
logger.info(
|
||||
f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев."
|
||||
)
|
||||
await create_key(recipient_tg_id, expiry_time_naive, state, session, message)
|
||||
logger.info(f"Ключ создан для пользователя {recipient_tg_id} на срок {selected_months} месяцев.")
|
||||
|
||||
await session.execute(
|
||||
"UPDATE gifts SET is_used = TRUE, recipient_tg_id = $1 WHERE gift_id = $2",
|
||||
@@ -131,9 +117,7 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
await message.answer(
|
||||
f"🎉 Ваш подарок на {selected_months} {'месяц' if selected_months == 1 else 'месяца' if selected_months in [2, 3, 4] else 'месяцев'} активирован!"
|
||||
)
|
||||
logger.info(
|
||||
f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}."
|
||||
)
|
||||
logger.info(f"Подарок на {selected_months} месяцев активирован для пользователя {recipient_tg_id}.")
|
||||
return
|
||||
|
||||
elif "referral_" in message.text:
|
||||
@@ -141,21 +125,13 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
referrer_tg_id = int(message.text.split("referral_")[1])
|
||||
|
||||
if connection_exists:
|
||||
logger.info(
|
||||
f"Пользователь {message.chat.id} уже зарегистрирован и не может стать рефералом."
|
||||
)
|
||||
await message.answer(
|
||||
"❌ Вы уже зарегистрированы и не можете использовать реферальную ссылку."
|
||||
)
|
||||
logger.info(f"Пользователь {message.chat.id} уже зарегистрирован и не может стать рефералом.")
|
||||
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(
|
||||
"❌ Вы не можете быть рефералом самого себя."
|
||||
)
|
||||
logger.warning(f"Пользователь {message.chat.id} попытался стать рефералом самого себя.")
|
||||
await message.answer("❌ Вы не можете быть рефералом самого себя.")
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
existing_referral = await session.fetchrow(
|
||||
@@ -168,9 +144,7 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
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}"
|
||||
)
|
||||
logger.info(f"Реферал {message.chat.id} использовал ссылку от пользователя {referrer_tg_id}")
|
||||
return await show_start_menu(message, admin, session)
|
||||
|
||||
except (ValueError, IndexError) as e:
|
||||
@@ -178,16 +152,12 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
|
||||
return
|
||||
|
||||
else:
|
||||
logger.info(
|
||||
f"Пользователь {message.chat.id} зашел без реферальной ссылки или подарка."
|
||||
)
|
||||
logger.info(f"Пользователь {message.chat.id} зашел без реферальной ссылки или подарка.")
|
||||
|
||||
await show_start_menu(message, admin, session)
|
||||
|
||||
except (ValueError, IndexError) as e:
|
||||
logger.error(
|
||||
f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}"
|
||||
)
|
||||
logger.error(f"Ошибка при обработке сообщения пользователя {message.chat.id}: {e}")
|
||||
await message.answer("❌ Произошла ошибка. Пожалуйста, попробуйте снова.")
|
||||
else:
|
||||
await show_start_menu(message, admin, session)
|
||||
@@ -202,26 +172,20 @@ async def show_start_menu(message: Message, admin: bool, session: Any):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if trial_status == 0:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔗 Подключить VPN", callback_data="connect_vpn"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
if CHANNEL_EXISTS:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL),
|
||||
InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL)
|
||||
InlineKeyboardButton(text="📢 Канал", url=CHANNEL_URL),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
|
||||
if admin:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
|
||||
|
||||
builder.row(InlineKeyboardButton(text="🌐 О VPN", callback_data="about_vpn"))
|
||||
|
||||
@@ -267,24 +231,20 @@ async def handle_connect_vpn(callback_query: CallbackQuery, session: Any):
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=IMPORT_IOS,
|
||||
url=f'{CONNECT_IOS}{trial_key_info["key"]}',
|
||||
url=f"{CONNECT_IOS}{trial_key_info['key']}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=IMPORT_ANDROID,
|
||||
url=f'{CONNECT_ANDROID}{trial_key_info["key"]}',
|
||||
url=f"{CONNECT_ANDROID}{trial_key_info['key']}",
|
||||
),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=PC_BUTTON, callback_data=f"connect_pc|{email}"),
|
||||
InlineKeyboardButton(text=TV_BUTTON, callback_data=f"connect_tv|{email}"),
|
||||
)
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
key_message, reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(key_message, reply_markup=builder.as_markup())
|
||||
|
||||
|
||||
@router.callback_query(F.data == "about_vpn")
|
||||
@@ -292,9 +252,7 @@ async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if DONATIONS_ENABLE:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
|
||||
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL),
|
||||
@@ -305,6 +263,4 @@ async def handle_about_vpn(callback_query: CallbackQuery):
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="start"))
|
||||
|
||||
await callback_query.message.answer(
|
||||
get_about_vpn("3.2.3-minor"), reply_markup=builder.as_markup()
|
||||
)
|
||||
await callback_query.message.answer(get_about_vpn("3.2.3-minor"), reply_markup=builder.as_markup())
|
||||
|
||||
+3
-1
@@ -12,7 +12,9 @@ router = Router()
|
||||
@router.my_chat_member(ChatMemberUpdatedFilter(member_status_changed=KICKED))
|
||||
async def user_blocked_bot(event: ChatMemberUpdated, session: Any):
|
||||
logger.info(f"User {event.from_user.id} blocked the bot.")
|
||||
await session.execute("INSERT INTO blocked_users (tg_id) VALUES ($1) ON CONFLICT (tg_id) DO NOTHING", event.from_user.id)
|
||||
await session.execute(
|
||||
"INSERT INTO blocked_users (tg_id) VALUES ($1) ON CONFLICT (tg_id) DO NOTHING", event.from_user.id
|
||||
)
|
||||
|
||||
|
||||
@router.my_chat_member(ChatMemberUpdatedFilter(member_status_changed=MEMBER))
|
||||
|
||||
+3
-9
@@ -14,9 +14,7 @@ from logger import logger
|
||||
async def get_usd_rate():
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(
|
||||
"https://www.cbr-xml-daily.ru/daily_json.js"
|
||||
) as response:
|
||||
async with session.get("https://www.cbr-xml-daily.ru/daily_json.js") as response:
|
||||
if response.status == 200:
|
||||
data = await response.text()
|
||||
usd = float(json.loads(data)["Valute"]["USD"]["Value"])
|
||||
@@ -86,9 +84,7 @@ async def get_least_loaded_cluster() -> str:
|
||||
return least_loaded_cluster
|
||||
|
||||
|
||||
async def handle_error(
|
||||
tg_id: int, callback_query: object | None = None, message: str = ""
|
||||
) -> None:
|
||||
async def handle_error(tg_id: int, callback_query: object | None = None, message: str = "") -> None:
|
||||
"""
|
||||
Обрабатывает ошибку, отправляя сообщение пользователю.
|
||||
|
||||
@@ -100,9 +96,7 @@ async def handle_error(
|
||||
try:
|
||||
if callback_query and hasattr(callback_query, "message"):
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=tg_id, message_id=callback_query.message.message_id
|
||||
)
|
||||
await bot.delete_message(chat_id=tg_id, message_id=callback_query.message.message_id)
|
||||
except Exception as delete_error:
|
||||
logger.warning(f"Не удалось удалить сообщение: {delete_error}")
|
||||
|
||||
|
||||
@@ -16,9 +16,7 @@ class DeleteMessageMiddleware(BaseMiddleware):
|
||||
if isinstance(event, Message):
|
||||
if not event.text or not event.text.startswith("/start"):
|
||||
try:
|
||||
await event.bot.delete_message(
|
||||
event.chat.id, event.message_id - 1
|
||||
)
|
||||
await event.bot.delete_message(event.chat.id, event.message_id - 1)
|
||||
except Exception:
|
||||
pass
|
||||
await event.delete()
|
||||
|
||||
@@ -59,8 +59,6 @@ class ThrottlingMiddleware(BaseMiddleware):
|
||||
)
|
||||
self.caches[key][user.id] = None
|
||||
else:
|
||||
logger.debug(
|
||||
f"No throttling key provided for user {user.id}, proceeding without throttle."
|
||||
)
|
||||
logger.debug(f"No throttling key provided for user {user.id}, proceeding without throttle.")
|
||||
|
||||
return await handler(event, data)
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ exclude = [
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "tab"
|
||||
indent-style = "space"
|
||||
|
||||
[tool.darker]
|
||||
src = ["."]
|
||||
|
||||
+13
-42
@@ -16,9 +16,7 @@ try:
|
||||
from config import CLUSTERS
|
||||
except ImportError:
|
||||
CLUSTERS = None
|
||||
logger.warning(
|
||||
"Переменная CLUSTERS не найдена в конфигурации. Добавьте сервера через админ-панель!"
|
||||
)
|
||||
logger.warning("Переменная CLUSTERS не найдена в конфигурации. Добавьте сервера через админ-панель!")
|
||||
|
||||
|
||||
async def sync_servers_with_db():
|
||||
@@ -27,9 +25,7 @@ async def sync_servers_with_db():
|
||||
Если CLUSTERS не найден, синхронизация не будет выполнена.
|
||||
"""
|
||||
if CLUSTERS is None:
|
||||
logger.info(
|
||||
"Конфигурация CLUSTERS не найдена. Синхронизация не будет выполнена."
|
||||
)
|
||||
logger.info("Конфигурация CLUSTERS не найдена. Синхронизация не будет выполнена.")
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -59,13 +55,9 @@ async def sync_servers_with_db():
|
||||
server_info["SUBSCRIPTION"],
|
||||
server_info["INBOUND_ID"],
|
||||
)
|
||||
logger.info(
|
||||
f"Сервер {server_info['name']} из кластера {cluster_name} добавлен в базу данных."
|
||||
)
|
||||
logger.info(f"Сервер {server_info['name']} из кластера {cluster_name} добавлен в базу данных.")
|
||||
else:
|
||||
logger.info(
|
||||
f"Сервер {server_info['name']} из кластера {cluster_name} уже существует."
|
||||
)
|
||||
logger.info(f"Сервер {server_info['name']} из кластера {cluster_name} уже существует.")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при синхронизации серверов: {e}")
|
||||
@@ -104,25 +96,14 @@ async def notify_admin(server_name: str):
|
||||
current_time = datetime.now()
|
||||
last_notification_time = last_notification_times.get(server_name)
|
||||
|
||||
if (
|
||||
last_notification_time
|
||||
and current_time - last_notification_time < timedelta(minutes=3)
|
||||
):
|
||||
logger.info(
|
||||
f"Не отправляем уведомление для сервера {server_name}, так как прошло менее 3 минут."
|
||||
)
|
||||
if last_notification_time and current_time - last_notification_time < timedelta(minutes=3):
|
||||
logger.info(f"Не отправляем уведомление для сервера {server_name}, так как прошло менее 3 минут.")
|
||||
return
|
||||
|
||||
logger.info(
|
||||
f"Отправка уведомлений администратору о недоступности сервера {server_name}..."
|
||||
)
|
||||
logger.info(f"Отправка уведомлений администратору о недоступности сервера {server_name}...")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text="Управление сервером", callback_data=f"manage_server|{server_name}"
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="Управление сервером", callback_data=f"manage_server|{server_name}"))
|
||||
|
||||
for admin_id in ADMIN_ID:
|
||||
await bot.send_message(
|
||||
@@ -134,9 +115,7 @@ async def notify_admin(server_name: str):
|
||||
),
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
logger.info(
|
||||
f"Уведомление отправлено администратору с ID {admin_id} о сервере {server_name}."
|
||||
)
|
||||
logger.info(f"Уведомление отправлено администратору с ID {admin_id} о сервере {server_name}.")
|
||||
|
||||
last_notification_times[server_name] = current_time
|
||||
except Exception as e:
|
||||
@@ -160,9 +139,7 @@ async def check_servers():
|
||||
server_name = server["server_name"]
|
||||
|
||||
server_host = extract_host(original_api_url)
|
||||
logger.debug(
|
||||
f"Проверка доступности сервера '{server_name}' с хостом {server_host}"
|
||||
)
|
||||
logger.debug(f"Проверка доступности сервера '{server_name}' с хостом {server_host}")
|
||||
|
||||
is_online = await ping_server(server_host)
|
||||
|
||||
@@ -170,18 +147,12 @@ async def check_servers():
|
||||
last_ping_times[server_name] = current_time
|
||||
else:
|
||||
last_ping_time = last_ping_times.get(server_name)
|
||||
if last_ping_time and current_time - last_ping_time > timedelta(
|
||||
minutes=3
|
||||
):
|
||||
logger.warning(
|
||||
f"Сервер {server_name} не отвечает более 3 минут. Отправляю уведомление."
|
||||
)
|
||||
if last_ping_time and current_time - last_ping_time > timedelta(minutes=3):
|
||||
logger.warning(f"Сервер {server_name} не отвечает более 3 минут. Отправляю уведомление.")
|
||||
await notify_admin(server_name)
|
||||
elif not last_ping_time:
|
||||
last_ping_times[server_name] = current_time
|
||||
logger.info(
|
||||
f"Сервер {server_name} не отвечал ранее, но теперь зарегистрирован."
|
||||
)
|
||||
logger.info(f"Сервер {server_name} не отвечал ранее, но теперь зарегистрирован.")
|
||||
|
||||
logger.info("Завершена проверка всех серверов.")
|
||||
await asyncio.sleep(PING_TIME)
|
||||
|
||||
Reference in New Issue
Block a user