diff --git a/.flake8 b/.flake8
deleted file mode 100644
index b51e9062..00000000
--- a/.flake8
+++ /dev/null
@@ -1,6 +0,0 @@
-[flake8]
-max-line-length = 120
-ignore = E203, E266, E501, W503, F541, E704, W293, W291, E126, E121, E123, E128, E302, E131, E231, W292, E402, E261, E305, E701
-max-complexity = 15
-select = B, C, E, F, W, T4, B9
-exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,.txt,config.py
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 2a739bd8..aef0b00f 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,6 @@
formatting:
- @echo "Running black..." && black .
- @echo "Running isort..." && isort .
- @echo "Running flake8..." && flake8 --config .flake8
- # @echo "Running pylint..." && pylint .
+ @echo "Running Ruff..." && ruff check . --fix
+ @echo "Running Ruff format..." && ruff format .
+
+lint:
+ @echo "Running Ruff checks..." && ruff check .
\ No newline at end of file
diff --git a/backup.py b/backup.py
index ccee564c..329d7a22 100644
--- a/backup.py
+++ b/backup.py
@@ -1,11 +1,10 @@
-from datetime import datetime
import os
import subprocess
-from typing import Union
+from datetime import datetime
from aiogram.types import BufferedInputFile
-
from config import ADMIN_ID, BACK_DIR, DB_NAME, DB_PASSWORD, DB_USER
+
from logger import logger
@@ -57,8 +56,10 @@ 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))
- admin_ids: Union[int, list[int]] = ADMIN_ID
+ 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:
await bot.send_document(id, backup_input_file)
diff --git a/bot.py b/bot.py
index 62c86a2a..8799cd3c 100644
--- a/bot.py
+++ b/bot.py
@@ -5,8 +5,8 @@ from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.fsm.storage.memory import MemoryStorage
from aiogram.types import ErrorEvent
-
from config import API_TOKEN
+
from logger import logger
from middlewares.admin import AdminMiddleware
from middlewares.database import DatabaseMiddleware
diff --git a/client.py b/client.py
index 660214e0..76451786 100644
--- a/client.py
+++ b/client.py
@@ -44,7 +44,9 @@ async def add_client(
return {"status": "failed", "error": str(e)}
-async def extend_client_key(xui, inbound_id, email: str, new_expiry_time: int, client_id: str, total_gb: int):
+async def extend_client_key(
+ xui, inbound_id, email: str, new_expiry_time: int, client_id: str, total_gb: int
+):
"""
Функция для обновления срока действия ключа клиента по email.
"""
@@ -60,7 +62,9 @@ async def extend_client_key(xui, inbound_id, email: str, new_expiry_time: int, c
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
@@ -73,7 +77,9 @@ async def extend_client_key(xui, inbound_id, email: str, new_expiry_time: int, c
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}")
diff --git a/database.py b/database.py
index ad91821f..2a293fd0 100644
--- a/database.py
+++ b/database.py
@@ -7,21 +7,21 @@ from config import DATABASE_URL, REFERRAL_BONUS_PERCENTAGES
from logger import logger
-async def init_db(file_path: str = 'assets/schema.sql'):
- with open(file_path, 'r') as file:
+async def init_db(file_path: str = "assets/schema.sql"):
+ with open(file_path) as file:
sql_content = file.read()
# Split the file content into individual SQL statements and connect to the database
- statements = [stmt.strip() for stmt in sql_content.split(';') if stmt.strip()]
+ statements = [stmt.strip() for stmt in sql_content.split(";") if stmt.strip()]
conn = await asyncpg.connect(DATABASE_URL)
try:
for statement in statements:
await conn.execute(statement)
except Exception as e:
- logger.error(f'Error while executing SQL statement: {e}')
+ logger.error(f"Error while executing SQL statement: {e}")
finally:
- logger.info('Tables created successfully')
+ logger.info("Tables created successfully")
await conn.close()
@@ -34,14 +34,18 @@ 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
+):
"""
Создает новый купон в базе данных.
@@ -180,7 +184,9 @@ 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
@@ -212,7 +218,9 @@ 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
+):
"""
Добавляет новое подключение для пользователя в базу данных.
@@ -239,7 +247,9 @@ async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0, sessi
f"Успешно добавлено новое подключение для пользователя {tg_id} с балансом {balance} и статусом триала {trial}"
)
except Exception as e:
- logger.error(f"Не удалось добавить подключение для пользователя {tg_id}. Причина: {e}")
+ logger.error(
+ f"Не удалось добавить подключение для пользователя {tg_id}. Причина: {e}"
+ )
raise
@@ -276,7 +286,15 @@ async def check_connection_exists(tg_id: int):
await conn.close()
-async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, key: str, server_id: str, session: Any):
+async def store_key(
+ tg_id: int,
+ client_id: str,
+ email: str,
+ expiry_time: int,
+ key: str,
+ server_id: str,
+ session: Any,
+):
"""
Сохраняет информацию о ключе в базу данных.
@@ -305,7 +323,9 @@ async def store_key(tg_id: int, client_id: str, email: str, expiry_time: int, ke
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
@@ -371,10 +391,14 @@ 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:
@@ -398,10 +422,14 @@ 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:
@@ -424,7 +452,9 @@ 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:
@@ -482,11 +512,15 @@ 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
@@ -510,7 +544,9 @@ 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:
@@ -550,7 +586,9 @@ 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
@@ -584,7 +622,9 @@ 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)
@@ -601,26 +641,30 @@ async def handle_referral_on_balance_update(tg_id: int, amount: float):
if not referral:
break
- referrer_tg_id = referral['referrer_tg_id']
- referral_chain.append({'tg_id': referrer_tg_id, 'level': level})
+ referrer_tg_id = referral["referrer_tg_id"]
+ referral_chain.append({"tg_id": referrer_tg_id, "level": level})
current_tg_id = referrer_tg_id
for referral in referral_chain:
- referrer_tg_id = referral['tg_id']
- level = referral['level']
+ referrer_tg_id = referral["tg_id"]
+ level = referral["level"]
bonus_percent = REFERRAL_BONUS_PERCENTAGES.get(level, 0)
bonus = amount * bonus_percent
bonus = max(bonus, 0)
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()
@@ -696,7 +740,10 @@ async def get_referral_stats(referrer_tg_id: int):
# Преобразование результатов в словарь
referrals_by_level = {
- record['level']: {'total': record['level_count'], 'active': record['active_level_count']}
+ record["level"]: {
+ "total": record["level_count"],
+ "active": record["active_level_count"],
+ }
for record in referrals_by_level_records
}
logger.debug(f"Получена статистика рефералов по уровням: {referrals_by_level}")
@@ -714,7 +761,9 @@ async def get_referral_stats(referrer_tg_id: int):
""",
referrer_tg_id,
)
- logger.debug(f"Получена общая сумма бонусов от рефералов: {total_referral_bonus}")
+ logger.debug(
+ f"Получена общая сумма бонусов от рефералов: {total_referral_bonus}"
+ )
return {
"total_referrals": total_referrals,
@@ -724,7 +773,9 @@ 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:
@@ -746,7 +797,9 @@ 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(
"""
@@ -760,7 +813,9 @@ 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:
@@ -781,7 +836,9 @@ 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(
"""
@@ -815,7 +872,9 @@ 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(
"""
@@ -853,7 +912,9 @@ 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(
"""
@@ -894,9 +955,13 @@ 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}")
@@ -939,7 +1004,9 @@ 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(
"""
@@ -986,7 +1053,9 @@ 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(
"""
@@ -997,7 +1066,9 @@ 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
@@ -1030,13 +1101,19 @@ 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:
"""
Проверяет, прошло ли указанное количество часов с момента последнего уведомления.
@@ -1081,7 +1158,9 @@ async def check_notification_time(tg_id: int, notification_type: str, hours: int
return can_notify
except Exception as e:
- logger.error(f"Ошибка при проверке времени уведомления для пользователя {tg_id}: {e}")
+ logger.error(
+ f"Ошибка при проверке времени уведомления для пользователя {tg_id}: {e}"
+ )
return False
finally:
@@ -1103,16 +1182,16 @@ async def get_servers_from_db():
servers = {}
for row in result:
- cluster_name = row['cluster_name']
+ cluster_name = row["cluster_name"]
if cluster_name not in servers:
servers[cluster_name] = []
servers[cluster_name].append(
{
- 'server_name': row['server_name'],
- 'api_url': row['api_url'],
- 'subscription_url': row['subscription_url'],
- 'inbound_id': row['inbound_id'],
+ "server_name": row["server_name"],
+ "api_url": row["api_url"],
+ "subscription_url": row["subscription_url"],
+ "inbound_id": row["inbound_id"],
}
)
diff --git a/filters/admin.py b/filters/admin.py
index b902294b..d940b31f 100644
--- a/filters/admin.py
+++ b/filters/admin.py
@@ -1,15 +1,13 @@
-from typing import Union
from aiogram.filters import BaseFilter
from aiogram.types import Message
-
from config import ADMIN_ID
class IsAdminFilter(BaseFilter):
async def __call__(self, message: Message) -> bool:
try:
- admin_ids: Union[int, list[int]] = ADMIN_ID
+ 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
diff --git a/handlers/__init__.py b/handlers/__init__.py
index 5037c650..f984092c 100644
--- a/handlers/__init__.py
+++ b/handlers/__init__.py
@@ -1,4 +1,4 @@
-__all__ = ('router',)
+__all__ = ("router",)
from aiogram import Router
@@ -13,7 +13,7 @@ from .payments import router as payments_router
from .profile import router as profile_router
from .start import router as start_router
-router = Router(name='handlers_main_router')
+router = Router(name="handlers_main_router")
router.include_routers(
start_router,
diff --git a/handlers/admin/__init__.py b/handlers/admin/__init__.py
index 8f83973d..39109027 100644
--- a/handlers/admin/__init__.py
+++ b/handlers/admin/__init__.py
@@ -1,4 +1,4 @@
-__all__ = ('router',)
+__all__ = ("router",)
from aiogram import Router
@@ -7,7 +7,7 @@ from .admin_panel import router as panel_router
from .admin_servers import router as servers_router
from .admin_user_editor import router as user_editor_router
-router = Router(name='admins_main_router')
+router = Router(name="admins_main_router")
router.include_routers(
panel_router,
diff --git a/handlers/admin/admin_coupons.py b/handlers/admin/admin_coupons.py
index 5ea3ca98..27e3071e 100644
--- a/handlers/admin/admin_coupons.py
+++ b/handlers/admin/admin_coupons.py
@@ -19,13 +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 == "coupons", IsAdminFilter())
@@ -35,7 +41,9 @@ async def show_coupon_list(callback_query: types.CallbackQuery, session: Any):
if not coupons:
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
+ builder.row(
+ InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
+ )
await callback_query.message.answer(
"❌ На данный момент нет доступных купонов. 🚫\nВы можете вернуться в меню управления. 🔙",
@@ -61,8 +69,12 @@ async def show_coupon_list(callback_query: types.CallbackQuery, session: Any):
)
)
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor"))
- await callback_query.message.answer(coupon_list, reply_markup=builder.as_markup())
+ builder.row(
+ InlineKeyboardButton(text="🔙 Назад", callback_data="coupons_editor")
+ )
+ await callback_query.message.answer(
+ coupon_list, reply_markup=builder.as_markup()
+ )
except Exception as e:
logger.error(f"Ошибка при получении списка купонов: {e}")
@@ -141,7 +153,9 @@ 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()
diff --git a/handlers/admin/admin_panel.py b/handlers/admin/admin_panel.py
index b0adf30a..47865ad8 100644
--- a/handlers/admin/admin_panel.py
+++ b/handlers/admin/admin_panel.py
@@ -1,6 +1,6 @@
+import subprocess
from datetime import datetime
from io import BytesIO
-import subprocess
from typing import Any
from aiogram import F, Router, types
@@ -35,13 +35,35 @@ async def handle_admin_message(message: types.Message, state: FSMContext):
await state.clear()
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_alls"))
- builder.row(InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups"))
- builder.row(InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot"))
+ 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_alls")
+ )
+ builder.row(
+ InlineKeyboardButton(text="💾 Создать резервную копию", callback_data="backups")
+ )
+ builder.row(
+ InlineKeyboardButton(text="🔄 Перезагрузить бота", callback_data="restart_bot")
+ )
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await message.answer("🤖 Панель администратора", reply_markup=builder.as_markup())
@@ -59,7 +81,9 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any):
total_payments_week = await session.fetchval(
"SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('week', 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"
+ )
active_keys = await session.fetchval(
"SELECT COUNT(*) FROM keys WHERE expiry_time > $1",
@@ -83,12 +107,27 @@ async def user_stats_menu(callback_query: CallbackQuery, session: Any):
)
builder = InlineKeyboardBuilder()
- 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="🔄 Обновить", 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")
+ )
- 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}")
@@ -115,7 +154,9 @@ 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
@@ -128,14 +169,17 @@ async def export_users_csv(callback_query: CallbackQuery, session: Any):
file = BufferedInputFile(file_name.getvalue(), filename="users_export.csv")
await callback_query.message.answer_document(
- file, caption="📥 Экспорт пользователей в CSV", reply_markup=builder.as_markup()
+ file,
+ caption="📥 Экспорт пользователей в CSV",
+ reply_markup=builder.as_markup(),
)
file_name.close()
except Exception as e:
logger.error(f"Ошибка при экспорте пользователей в CSV: {e}")
await callback_query.message.answer(
- "❗ Произошла ошибка при экспорте пользователей.", reply_markup=builder.as_markup()
+ "❗ Произошла ошибка при экспорте пользователей.",
+ reply_markup=builder.as_markup(),
)
@@ -161,7 +205,9 @@ 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
@@ -181,7 +227,8 @@ async def export_payments_csv(callback_query: CallbackQuery, session: Any):
except Exception as e:
logger.error(f"Ошибка при экспорте платежей в CSV: {e}")
await callback_query.message.answer(
- "❗ Произошла ошибка при экспорте платежей.", reply_markup=builder.as_markup()
+ "❗ Произошла ошибка при экспорте платежей.",
+ reply_markup=builder.as_markup(),
)
@@ -197,7 +244,9 @@ async def handle_send_to_all(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:
@@ -214,7 +263,9 @@ async def process_message_to_all(message: types.Message, state: FSMContext, sess
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"
@@ -230,9 +281,13 @@ async def process_message_to_all(message: types.Message, state: FSMContext, sess
@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())
@@ -240,7 +295,9 @@ 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"))
@@ -266,12 +323,17 @@ 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}", reply_markup=builder.as_markup()
+ f"⚠️ Ошибка при перезагрузке бота: {e.stderr}",
+ reply_markup=builder.as_markup(),
)
@@ -284,7 +346,17 @@ 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()
+ )
diff --git a/handlers/admin/admin_servers.py b/handlers/admin/admin_servers.py
index b9260c66..8a030119 100644
--- a/handlers/admin/admin_servers.py
+++ b/handlers/admin/admin_servers.py
@@ -1,13 +1,13 @@
+import asyncpg
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
-import asyncpg
+from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL
from py3xui import AsyncApi
from backup import create_backup_and_send_to_admins
-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
@@ -29,9 +29,15 @@ 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(
@@ -64,7 +70,11 @@ 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(),
@@ -78,7 +88,9 @@ 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"Введите имя сервера для кластера {cluster_name}:\n\n"
@@ -97,7 +109,11 @@ 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(),
@@ -110,15 +126,19 @@ 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()
- cluster_name = user_data.get('cluster_name')
+ cluster_name = user_data.get("cluster_name")
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"Введите API URL для сервера {server_name} в кластере {cluster_name}:\n\n"
@@ -138,7 +158,11 @@ 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(),
@@ -152,15 +176,17 @@ async def handle_api_url_input(message: types.Message, state: FSMContext):
)
return
- api_url = api_url.rstrip('/')
+ api_url = api_url.rstrip("/")
user_data = await state.get_data()
- cluster_name = user_data.get('cluster_name')
- server_name = user_data.get('server_name')
+ cluster_name = user_data.get("cluster_name")
+ server_name = user_data.get("server_name")
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"Введите subscription_url для сервера {server_name} в кластере {cluster_name}:\n\n"
@@ -181,7 +207,11 @@ 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(),
@@ -195,15 +225,17 @@ async def handle_subscription_url_input(message: types.Message, state: FSMContex
)
return
- subscription_url = subscription_url.rstrip('/')
+ subscription_url = subscription_url.rstrip("/")
user_data = await state.get_data()
- cluster_name = user_data.get('cluster_name')
- server_name = user_data.get('server_name')
+ cluster_name = user_data.get("cluster_name")
+ server_name = user_data.get("server_name")
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"Введите inbound_id для сервера {server_name} в кластере {cluster_name}:\n\n"
@@ -219,14 +251,16 @@ 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()
- cluster_name = user_data.get('cluster_name')
- server_name = user_data.get('server_name')
- api_url = user_data.get('api_url')
- subscription_url = user_data.get('subscription_url')
+ cluster_name = user_data.get("cluster_name")
+ server_name = user_data.get("server_name")
+ api_url = user_data.get("api_url")
+ subscription_url = user_data.get("subscription_url")
conn = await asyncpg.connect(DATABASE_URL)
await conn.execute(
@@ -243,10 +277,15 @@ 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} успешно добавлены!", reply_markup=builder.as_markup()
+ f"✅ Кластер {cluster_name} и сервер {server_name} успешно добавлены!",
+ reply_markup=builder.as_markup(),
)
await state.clear()
@@ -264,22 +303,40 @@ async def handle_manage_cluster(callback_query: types.CallbackQuery, state: FSMC
for server in cluster_servers:
builder.row(
InlineKeyboardButton(
- text=f"🌍 {server['server_name']}", callback_data=f"manage_server|{server['server_name']}"
+ text=f"🌍 {server['server_name']}",
+ callback_data=f"manage_server|{server['server_name']}",
)
)
- builder.row(InlineKeyboardButton(text="➕ Добавить сервер", callback_data=f"add_server|{cluster_name}"))
-
builder.row(
- InlineKeyboardButton(text="🌐 Доступность серверов", callback_data=f"server_availability|{cluster_name}")
+ InlineKeyboardButton(
+ text="➕ Добавить сервер", callback_data=f"add_server|{cluster_name}"
+ )
)
- builder.row(InlineKeyboardButton(text="💾 Создать бэкап кластера", callback_data=f"backup_cluster|{cluster_name}"))
+ builder.row(
+ InlineKeyboardButton(
+ text="🌐 Доступность серверов",
+ callback_data=f"server_availability|{cluster_name}",
+ )
+ )
- builder.row(InlineKeyboardButton(text="🔙 Назад в управление кластерами", callback_data="servers_editor"))
+ builder.row(
+ InlineKeyboardButton(
+ text="💾 Создать бэкап кластера",
+ callback_data=f"backup_cluster|{cluster_name}",
+ )
+ )
+
+ builder.row(
+ InlineKeyboardButton(
+ text="🔙 Назад в управление кластерами", callback_data="servers_editor"
+ )
+ )
await callback_query.message.answer(
- f"🔧 Управление серверами для кластера {cluster_name}", reply_markup=builder.as_markup()
+ f"🔧 Управление серверами для кластера {cluster_name}",
+ reply_markup=builder.as_markup(),
)
@@ -299,24 +356,36 @@ 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()
@@ -330,19 +399,29 @@ 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
if server:
- api_url = server['api_url']
- subscription_url = server['subscription_url']
- inbound_id = server['inbound_id']
+ api_url = server["api_url"]
+ subscription_url = server["subscription_url"]
+ 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"🔧 Информация о сервере {server_name}:\n\n"
@@ -362,17 +441,24 @@ 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(
- f"🗑️ Вы уверены, что хотите удалить сервер {server_name}?", reply_markup=builder.as_markup()
+ f"🗑️ Вы уверены, что хотите удалить сервер {server_name}?",
+ reply_markup=builder.as_markup(),
)
@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)
@@ -385,9 +471,15 @@ async def handle_confirm_delete_server(callback_query: types.CallbackQuery, stat
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())
@@ -397,7 +489,9 @@ 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"Введите имя сервера для кластера {cluster_name}:\n\n"
diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py
index 7a9bd856..7d52d3ac 100644
--- a/handlers/admin/admin_user_editor.py
+++ b/handlers/admin/admin_user_editor.py
@@ -7,11 +7,21 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
-
from config import TOTAL_GB
-from database import get_client_id_by_email, get_servers_from_db, restore_trial, update_key_expiry, delete_user_data
+
+from database import (
+ get_client_id_by_email,
+ get_servers_from_db,
+ restore_trial,
+ update_key_expiry,
+)
+
from filters.admin import IsAdminFilter
-from handlers.keys.key_utils import delete_key_from_cluster, delete_key_from_db, renew_key_in_cluster
+from handlers.keys.key_utils import (
+ delete_key_from_cluster,
+ delete_key_from_db,
+ renew_key_in_cluster,
+)
from handlers.utils import sanitize_key_name
from logger import logger
@@ -31,7 +41,9 @@ 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)
@@ -39,39 +51,59 @@ 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("@")
- 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()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
- await message.answer("🔍 Пользователь с указанным username не найден. 🚫", reply_markup=builder.as_markup())
+ await message.answer(
+ "🔍 Пользователь с указанным username не найден. 🚫",
+ reply_markup=builder.as_markup(),
+ )
await state.clear()
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()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
- await message.answer("🚫 Пользователь с указанным tg_id не найден. 🔍", reply_markup=builder.as_markup())
+ await message.answer(
+ "🚫 Пользователь с указанным tg_id не найден. 🔍",
+ reply_markup=builder.as_markup(),
+ )
await state.clear()
return
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(
@@ -104,22 +136,33 @@ async def handle_username_input(message: types.Message, state: FSMContext, sessi
@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()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
- await message.answer("❌ Пользователь с указанным tg_id не найден. 🔍", reply_markup=builder.as_markup())
+ await message.answer(
+ "❌ Пользователь с указанным tg_id не найден. 🔍",
+ reply_markup=builder.as_markup(),
+ )
await state.clear()
return
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(
@@ -156,9 +199,15 @@ 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())
@@ -167,17 +216,22 @@ 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"))
await message.answer(
- "❌ Пожалуйста, введите корректную сумму для изменения баланса.", reply_markup=builder.as_markup()
+ "❌ Пожалуйста, введите корректную сумму для изменения баланса.",
+ reply_markup=builder.as_markup(),
)
return
@@ -222,11 +276,13 @@ async def get_key_details(email, session):
cluster_name = "Неизвестный кластер"
for cluster_name, cluster_servers in servers.items():
- if any(server['inbound_id'] == record['server_id'] for server in cluster_servers):
+ if any(
+ server["inbound_id"] == record["server_id"] for server in cluster_servers
+ ):
cluster_name = cluster_name
break
- expiry_date = datetime.utcfromtimestamp(record['expiry_time'] / 1000)
+ expiry_date = datetime.utcfromtimestamp(record["expiry_time"] / 1000)
current_date = datetime.utcnow()
time_left = expiry_date - current_date
@@ -239,12 +295,12 @@ async def get_key_details(email, session):
days_left_message = f"Осталось часов: {hours_left}"
return {
- 'key': record['key'],
- 'expiry_date': expiry_date.strftime("%d %B %Y года"),
- 'days_left_message': days_left_message,
- 'server_name': cluster_name,
- 'balance': record['balance'],
- 'tg_id': record['tg_id'],
+ "key": record["key"],
+ "expiry_date": expiry_date.strftime("%d %B %Y года"),
+ "days_left_message": days_left_message,
+ "server_name": cluster_name,
+ "balance": record["balance"],
+ "tg_id": record["tg_id"],
}
@@ -257,7 +313,8 @@ async def process_key_edit(callback_query: CallbackQuery, session: Any):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
await callback_query.message.answer(
- "🔍 Информация о ключе не найдена. 🚫", reply_markup=builder.as_markup()
+ "🔍 Информация о ключе не найдена. 🚫",
+ reply_markup=builder.as_markup(),
)
return
@@ -289,19 +346,25 @@ async def process_key_edit(callback_query: CallbackQuery, session: Any):
)
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin"))
- 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)
@@ -358,34 +421,52 @@ 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
try:
expiry_time_str = message.text
- expiry_time = int(datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() * 1000)
+ expiry_time = int(
+ datetime.strptime(expiry_time_str, "%Y-%m-%d %H:%M:%S").timestamp() * 1000
+ )
client_id = await get_client_id_by_email(email)
if client_id is None:
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
- await message.answer(f"🚫 Клиент с email {email} не найден. 🔍", reply_markup=builder.as_markup())
+ builder.row(
+ InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")
+ )
+ await message.answer(
+ f"🚫 Клиент с email {email} не найден. 🔍",
+ reply_markup=builder.as_markup(),
+ )
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"))
- await message.answer("🚫 Клиент не найден в базе данных. 🔍", reply_markup=builder.as_markup())
+ builder.row(
+ InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")
+ )
+ await message.answer(
+ "🚫 Клиент не найден в базе данных. 🔍",
+ reply_markup=builder.as_markup(),
+ )
await state.clear()
return
@@ -412,9 +493,7 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext, se
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"))
@@ -423,7 +502,8 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext, se
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor"))
await message.answer(
- "❌ Пожалуйста, используйте формат: YYYY-MM-DD HH:MM:SS.", reply_markup=builder.as_markup()
+ "❌ Пожалуйста, используйте формат: YYYY-MM-DD HH:MM:SS.",
+ reply_markup=builder.as_markup(),
)
except Exception as e:
logger.error(e)
@@ -431,14 +511,20 @@ async def handle_expiry_time_input(message: types.Message, state: FSMContext, se
@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()
@@ -448,7 +534,9 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery, sessi
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(
"❓ Вы уверены, что хотите удалить ключ?",
reply_markup=builder.as_markup(),
@@ -456,9 +544,13 @@ async def process_callback_delete_key(callback_query: types.CallbackQuery, sessi
@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"]
@@ -472,32 +564,48 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s
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}"))
diff --git a/handlers/coupons.py b/handlers/coupons.py
index ed487671..15b3aa55 100644
--- a/handlers/coupons.py
+++ b/handlers/coupons.py
@@ -18,7 +18,9 @@ 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"))
@@ -38,7 +40,9 @@ 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(), parse_mode="HTML")
+ await message.answer(
+ activation_result, reply_markup=builder.as_markup(), parse_mode="HTML"
+ )
await state.clear()
diff --git a/handlers/donate.py b/handlers/donate.py
index d441463a..6a37edc8 100644
--- a/handlers/donate.py
+++ b/handlers/donate.py
@@ -3,8 +3,8 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
-
from config import RUB_TO_XTR
+
from logger import logger
@@ -22,7 +22,11 @@ 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="💰 Ввести сумму доната",
@@ -40,10 +44,14 @@ 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(f"💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup())
+ await callback_query.message.answer(
+ "💸 Введите сумму доната в рублях:", reply_markup=builder.as_markup()
+ )
await state.set_state(DonateState.entering_donate_amount)
@@ -52,7 +60,9 @@ 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)
@@ -89,7 +99,9 @@ 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(),
diff --git a/handlers/instructions/__init__.py b/handlers/instructions/__init__.py
index b8121003..06350a7c 100644
--- a/handlers/instructions/__init__.py
+++ b/handlers/instructions/__init__.py
@@ -1,10 +1,10 @@
-__all__ = ('router',)
+__all__ = ("router",)
from aiogram import Router
from .instructions import router as instructions_router
-router = Router(name='instructions_main_router')
+router = Router(name="instructions_main_router")
router.include_routers(
instructions_router,
diff --git a/handlers/instructions/instructions.py b/handlers/instructions/instructions.py
index 44d954c4..b8745d92 100644
--- a/handlers/instructions/instructions.py
+++ b/handlers/instructions/instructions.py
@@ -4,8 +4,8 @@ from typing import Any
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
-
from config import CONNECT_IOS, CONNECT_WINDOWS, SUPPORT_CHAT_URL
+
from handlers.texts import INSTRUCTION_PC, INSTRUCTIONS, KEY_MESSAGE
router = Router()
@@ -49,7 +49,9 @@ async def process_connect_pc(callback_query: types.CallbackQuery, session: Any):
)
if not record:
- await callback_query.message.answer("❌ Ключ не найден. Проверьте имя ключа. 🔍")
+ await callback_query.message.answer(
+ "❌ Ключ не найден. Проверьте имя ключа. 🔍"
+ )
return
key = record["key"]
@@ -57,8 +59,14 @@ 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_IOS}{key}"))
+ builder.row(
+ InlineKeyboardButton(
+ text="💻 Подключить Windows", url=f"{CONNECT_WINDOWS}{key}"
+ )
+ )
+ builder.row(
+ InlineKeyboardButton(text="💻 Подключить MacOS", url=f"{CONNECT_IOS}{key}")
+ )
builder.row(InlineKeyboardButton(text="🆘 Поддержка", url=f"{SUPPORT_CHAT_URL}"))
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
diff --git a/handlers/keys/__init__.py b/handlers/keys/__init__.py
index 45256dad..064024e6 100644
--- a/handlers/keys/__init__.py
+++ b/handlers/keys/__init__.py
@@ -1,11 +1,11 @@
-__all__ = ('router',)
+__all__ = ("router",)
from aiogram import Router
from .key_management import router as management_router
from .keys import router as keys_router
-router = Router(name='keys_main_router')
+router = Router(name="keys_main_router")
router.include_routers(
keys_router,
diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py
index 9efe8ec1..1ef37a36 100644
--- a/handlers/keys/key_management.py
+++ b/handlers/keys/key_management.py
@@ -1,28 +1,27 @@
import asyncio
-from datetime import datetime, timedelta
-from typing import Any, Union
import uuid
+from datetime import datetime, timedelta
+from typing import Any
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
-
from config import (
CONNECT_ANDROID,
CONNECT_IOS,
DOWNLOAD_ANDROID,
DOWNLOAD_IOS,
PUBLIC_LINK,
- RENEWAL_PLANS,
RENEWAL_PRICES,
SUPPORT_CHAT_URL,
TRIAL_TIME,
)
+
from database import get_balance, get_trial, store_key, update_balance
from handlers.keys.key_utils import create_key_on_cluster
-from handlers.texts import DISCOUNTS, KEY, NULL_BALANCE, key_message_success
+from handlers.texts import DISCOUNTS, KEY, key_message_success
from handlers.utils import generate_random_email, get_least_loaded_cluster
from logger import logger
@@ -37,7 +36,9 @@ class Form(StatesGroup):
@router.callback_query(F.data == "create_key")
-async def process_callback_create_key(callback_query: CallbackQuery, state: FSMContext, session: Any):
+async def process_callback_create_key(
+ callback_query: CallbackQuery, state: FSMContext, session: Any
+):
server_id = "все сервера"
await state.update_data(selected_server_id=server_id)
await select_server(callback_query, state, session)
@@ -48,9 +49,14 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext, sessio
if trial_status == 1:
builder = InlineKeyboardBuilder()
builder.row(
- InlineKeyboardButton(text="✅ Да, подключить новое устройство", callback_data="confirm_create_new_key")
+ InlineKeyboardButton(
+ text="✅ Да, подключить новое устройство",
+ callback_data="confirm_create_new_key",
+ )
+ )
+ builder.row(
+ InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
)
- builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await callback_query.message.answer(
text=KEY,
@@ -58,22 +64,31 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext, sessio
)
await state.update_data(creating_new_key=True)
else:
- await handle_key_creation(callback_query.message.chat.id, state, session, callback_query)
+ await handle_key_creation(
+ callback_query.message.chat.id, state, session, callback_query
+ )
@router.callback_query(F.data == "confirm_create_new_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)
async def handle_key_creation(
- tg_id: int, state: FSMContext, session: Any, message_or_query: Union[Message, CallbackQuery]
+ tg_id: int,
+ state: FSMContext,
+ session: Any,
+ message_or_query: Message | CallbackQuery,
):
"""Создание ключа с учётом выбора тарифного плана."""
current_time = datetime.utcnow()
@@ -83,7 +98,9 @@ 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()
@@ -100,21 +117,27 @@ async def handle_key_creation(
builder.row(
InlineKeyboardButton(
- text=f"📅 {plan_id} мес. - {price}₽{discount_text}", callback_data=f"select_plan_{plan_id}"
+ text=f"📅 {plan_id} мес. - {price}₽{discount_text}",
+ callback_data=f"select_plan_{plan_id}",
)
)
- builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
+ builder.row(
+ InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
+ )
await message_or_query.message.answer(
- "💳 Выберите тарифный план для создания нового ключа:", reply_markup=builder.as_markup()
+ "💳 Выберите тарифный план для создания нового ключа:",
+ reply_markup=builder.as_markup(),
)
await state.update_data(tg_id=tg_id)
await state.set_state(Form.waiting_for_server_selection)
@router.callback_query(F.data.startswith("select_plan_"))
-async def select_tariff_plan(callback_query: CallbackQuery, state: FSMContext, session: Any):
+async def select_tariff_plan(
+ callback_query: CallbackQuery, state: FSMContext, session: Any
+):
tg_id = callback_query.message.chat.id
plan_id = callback_query.data.split("_")[-1]
plan_price = RENEWAL_PRICES.get(plan_id)
@@ -128,8 +151,12 @@ async def select_tariff_plan(callback_query: CallbackQuery, state: FSMContext, s
balance = await get_balance(tg_id)
if balance < plan_price:
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(
"💳 Недостаточно средств для создания подписки. Пополните баланс в личном кабинете.",
reply_markup=builder.as_markup(),
@@ -145,7 +172,11 @@ async def select_tariff_plan(callback_query: CallbackQuery, state: FSMContext, s
async def create_key(
- tg_id: int, expiry_time: datetime, state: FSMContext, session: Any, message_or_query: Union[Message, CallbackQuery]
+ tg_id: int,
+ expiry_time: datetime,
+ state: FSMContext,
+ session: Any,
+ message_or_query: Message | CallbackQuery,
):
"""Создаёт ключ с заданным сроком действия."""
while True:
@@ -159,7 +190,9 @@ 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()
@@ -184,11 +217,21 @@ async def create_key(
await asyncio.gather(*tasks)
logger.info(f"Key created on cluster {least_loaded_cluster} for user {tg_id}.")
- await store_key(tg_id, client_id, email, expiry_timestamp, public_link, least_loaded_cluster, session)
+ await store_key(
+ tg_id,
+ client_id,
+ email,
+ expiry_timestamp,
+ public_link,
+ least_loaded_cluster,
+ session,
+ )
except Exception as e:
logger.error(f"Error while creating the key for user {tg_id} on cluster: {e}")
- await message_or_query.message.answer("❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова.")
+ await message_or_query.message.answer(
+ "❌ Произошла ошибка при создании ключа. Пожалуйста, попробуйте снова."
+ )
return
builder = InlineKeyboardBuilder()
@@ -198,10 +241,18 @@ async def create_key(
InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
)
builder.row(
- InlineKeyboardButton(text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{public_link}"),
- InlineKeyboardButton(text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{public_link}"),
+ InlineKeyboardButton(
+ text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{public_link}"
+ ),
+ InlineKeyboardButton(
+ text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{public_link}"
+ ),
+ )
+ builder.row(
+ InlineKeyboardButton(
+ text="💻 Windows/Linux", callback_data=f"connect_pc|{email}"
+ )
)
- builder.row(InlineKeyboardButton(text="💻 Windows/Linux", callback_data=f"connect_pc|{email}"))
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
remaining_time = expiry_time - datetime.utcnow()
diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py
index 8d39c8b9..a66e1bec 100644
--- a/handlers/keys/key_utils.py
+++ b/handlers/keys/key_utils.py
@@ -1,10 +1,10 @@
import asyncio
import asyncpg
+from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, TOTAL_GB
from py3xui import AsyncApi
from client import add_client, delete_client, extend_client_key
-from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, TOTAL_GB
from database import get_servers_from_db
from logger import logger
@@ -33,7 +33,9 @@ async def create_key_on_cluster(cluster_id, tg_id, client_id, email, expiry_time
continue
conn = await asyncpg.connect(DATABASE_URL)
- existing_key = await conn.fetchrow("SELECT 1 FROM keys WHERE email = $1", email)
+ existing_key = await conn.fetchrow(
+ "SELECT 1 FROM keys WHERE email = $1", email
+ )
if existing_key:
raise ValueError(f"Email {email} уже существует в базе данных.")
@@ -98,7 +100,9 @@ async def renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, to
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
@@ -145,7 +149,9 @@ 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
@@ -189,8 +195,12 @@ async def update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id
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
diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py
index 236545b4..48d5e7d8 100644
--- a/handlers/keys/keys.py
+++ b/handlers/keys/keys.py
@@ -1,15 +1,30 @@
import asyncio
-from datetime import datetime, timedelta
import locale
import os
+from datetime import datetime, timedelta
from typing import Any
from aiogram import F, Router, types
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
+from config import (
+ CONNECT_ANDROID,
+ CONNECT_IOS,
+ DOWNLOAD_ANDROID,
+ DOWNLOAD_IOS,
+ PUBLIC_LINK,
+ RENEWAL_PLANS,
+ TOTAL_GB,
+)
-from config import CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, PUBLIC_LINK, RENEWAL_PLANS, TOTAL_GB
-from database import delete_key, get_balance, get_servers_from_db, store_key, update_balance, update_key_expiry
+from database import (
+ delete_key,
+ get_balance,
+ get_servers_from_db,
+ store_key,
+ update_balance,
+ update_key_expiry,
+)
from handlers.keys.key_utils import (
delete_key_from_cluster,
delete_key_from_db,
@@ -48,20 +63,29 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery, sessio
builder = InlineKeyboardBuilder()
for record in records:
key_name = record["email"]
- builder.row(InlineKeyboardButton(text=f"🔑 {key_name}", callback_data=f"view_key|{key_name}"))
+ builder.row(
+ InlineKeyboardButton(
+ text=f"🔑 {key_name}", callback_data=f"view_key|{key_name}"
+ )
+ )
- builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
+ builder.row(
+ InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
+ )
inline_keyboard = builder.as_markup()
response_message = (
- "🔑 Список ваших устройств\n\n" "👇 Выберите устройство для управления подпиской:"
+ "🔑 Список ваших устройств\n\n"
+ "👇 Выберите устройство для управления подпиской:"
)
image_path = os.path.join("img", "pic_keys.jpg")
if os.path.isfile(image_path):
with open(image_path, "rb") as image_file:
await callback_query.message.answer_photo(
- photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
+ photo=BufferedInputFile(
+ image_file.read(), filename="pic_keys.jpg"
+ ),
caption=response_message,
reply_markup=inline_keyboard,
)
@@ -74,8 +98,14 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery, sessio
else:
response_message = NO_KEYS
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="➕ Создать подписку", callback_data="create_key"))
- builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
+ builder.row(
+ InlineKeyboardButton(
+ text="➕ Создать подписку", callback_data="create_key"
+ )
+ )
+ builder.row(
+ InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
+ )
keyboard = builder.as_markup()
@@ -84,7 +114,9 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery, sessio
if os.path.isfile(image_path):
with open(image_path, "rb") as image_file:
await callback_query.message.answer_photo(
- photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"),
+ photo=BufferedInputFile(
+ image_file.read(), filename="pic_keys.jpg"
+ ),
caption=response_message,
reply_markup=keyboard,
)
@@ -121,7 +153,9 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
time_left = expiry_date - current_date
if time_left.total_seconds() <= 0:
- days_left_message = "🕒 Статус подписки:\n🔴 Истекла\nОсталось часов: 0"
+ days_left_message = (
+ "🕒 Статус подписки:\n🔴 Истекла\nОсталось часов: 0"
+ )
elif time_left.days > 0:
days_left_message = f"Осталось дней: {time_left.days}"
else:
@@ -129,33 +163,54 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session
days_left_message = f"Осталось часов: {hours_left}"
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()
builder.row(
InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS),
- InlineKeyboardButton(text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID),
+ InlineKeyboardButton(
+ text="🤖 Скачать для Android", url=DOWNLOAD_ANDROID
+ ),
)
builder.row(
- InlineKeyboardButton(text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{key}"),
- InlineKeyboardButton(text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{key}"),
+ InlineKeyboardButton(
+ text="🍏 Подключить на iOS", url=f"{CONNECT_IOS}{key}"
+ ),
+ InlineKeyboardButton(
+ text="🤖 Подключить на Android", url=f"{CONNECT_ANDROID}{key}"
+ ),
)
- builder.row(InlineKeyboardButton(text="💻 Windows/Linux", callback_data=f"connect_pc|{key_name}"))
+ builder.row(
+ InlineKeyboardButton(
+ text="💻 Windows/Linux", callback_data=f"connect_pc|{key_name}"
+ )
+ )
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}"
+ ),
)
if not key.startswith(PUBLIC_LINK):
builder.row(
- InlineKeyboardButton(text="🔄 Обновить подписку", callback_data=f"update_subscription|{key_name}")
+ InlineKeyboardButton(
+ text="🔄 Обновить подписку",
+ callback_data=f"update_subscription|{key_name}",
+ )
)
- builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
+ builder.row(
+ InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
+ )
keyboard = builder.as_markup()
@@ -184,7 +239,9 @@ 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:
@@ -244,7 +301,9 @@ async def process_callback_update_subscription(callback_query: types.CallbackQue
else:
await callback_query.message.answer("Ключ не найден в базе данных.")
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|"))
@@ -259,7 +318,11 @@ 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"
+ )
+ ],
]
)
@@ -319,14 +382,18 @@ async def process_callback_renew_key(callback_query: types.CallbackQuery, sessio
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(
@@ -340,15 +407,21 @@ 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)
@@ -363,7 +436,9 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s
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)
@@ -376,7 +451,9 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s
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(
@@ -388,7 +465,9 @@ async def process_callback_confirm_delete(callback_query: types.CallbackQuery, s
@router.callback_query(F.data.startswith("renew_plan|"))
-async def process_callback_renew_plan(callback_query: types.CallbackQuery, session: Any):
+async def process_callback_renew_plan(
+ callback_query: types.CallbackQuery, session: Any
+):
tg_id = callback_query.message.chat.id
plan, client_id = (
callback_query.data.split("|")[1],
@@ -411,17 +490,27 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery, sessi
current_time = datetime.utcnow().timestamp() * 1000
if expiry_time <= current_time:
- new_expiry_time = int(current_time + timedelta(days=days_to_extend).total_seconds() * 1000)
+ new_expiry_time = int(
+ current_time + timedelta(days=days_to_extend).total_seconds() * 1000
+ )
else:
- new_expiry_time = int(expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000)
+ new_expiry_time = int(
+ expiry_time + timedelta(days=days_to_extend).total_seconds() * 1000
+ )
cost = RENEWAL_PLANS[plan]["price"]
balance = await get_balance(tg_id)
if balance < cost:
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(
INSUFFICIENT_FUNDS_MSG,
@@ -429,11 +518,17 @@ async def process_callback_renew_plan(callback_query: types.CallbackQuery, sessi
)
return
- response_message = SUCCESS_RENEWAL_MSG.format(months=RENEWAL_PLANS[plan]["months"])
+ response_message = SUCCESS_RENEWAL_MSG.format(
+ months=RENEWAL_PLANS[plan]["months"]
+ )
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
+ builder.row(
+ InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
+ )
- await callback_query.message.answer(response_message, reply_markup=builder.as_markup())
+ await callback_query.message.answer(
+ response_message, reply_markup=builder.as_markup()
+ )
servers = await get_servers_from_db()
diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py
index 0f735199..4073dc71 100644
--- a/handlers/keys/subscriptions.py
+++ b/handlers/keys/subscriptions.py
@@ -2,10 +2,10 @@ import base64
from datetime import datetime
import aiohttp
-from aiohttp import web
import asyncpg
-
+from aiohttp import web
from config import DATABASE_URL, TRANSITION_DATE_STR
+
from database import get_servers_from_db
from logger import logger
import urllib.parse
@@ -23,7 +23,9 @@ 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 Exception as e:
logger.error(f"Ошибка при получении {url} для tg_id: {tg_id}: {e}")
@@ -32,7 +34,9 @@ async def fetch_url_content(url, tg_id):
async def combine_unique_lines(urls, tg_id, query_string):
all_lines = []
- 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}" for url in urls]
logger.info(f"Составлены URL-адреса: {urls_with_query}")
@@ -42,7 +46,9 @@ async def combine_unique_lines(urls, tg_id, query_string):
all_lines.extend(lines)
all_lines = list(set(filter(None, all_lines)))
- logger.info(f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}")
+ logger.info(
+ f"Объединено {len(all_lines)} строк после фильтрации и удаления дубликатов для tg_id: {tg_id}"
+ )
return all_lines
@@ -53,7 +59,9 @@ 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):
@@ -70,7 +78,11 @@ async def handle_old_subscription(request):
conn = await asyncpg.connect(DATABASE_URL)
try:
- key_info = await conn.fetchrow("SELECT created_at, cluster_name FROM keys WHERE email = $1", email)
+
+ key_info = await conn.fetchrow(
+ "SELECT created_at FROM keys WHERE email = $1", email
+ )
+
if not key_info:
logger.warning(f"Клиент с email {email} не найден в базе.")
@@ -84,7 +96,9 @@ async def handle_old_subscription(request):
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} является новым.")
@@ -104,7 +118,9 @@ async def handle_old_subscription(request):
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 = urllib.parse.quote(f"{PROJECT_NAME}\n{NEWS_MESSAGE}")
headers = {
@@ -136,7 +152,11 @@ async def handle_new_subscription(request):
conn = await asyncpg.connect(DATABASE_URL)
try:
- client_data = await conn.fetchrow("SELECT tg_id, server_id FROM keys WHERE email = $1", email)
+
+ client_data = await conn.fetchrow(
+ "SELECT tg_id FROM keys WHERE email = $1", email
+ )
+
if not client_data:
logger.warning(f"Клиент с email {email} не найден в базе.")
@@ -171,7 +191,9 @@ async def handle_new_subscription(request):
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 = urllib.parse.quote(f"{PROJECT_NAME}\n{NEWS_MESSAGE}")
headers = {
diff --git a/handlers/keys/trial_key.py b/handlers/keys/trial_key.py
index 819d66da..fa0f9745 100644
--- a/handlers/keys/trial_key.py
+++ b/handlers/keys/trial_key.py
@@ -1,12 +1,12 @@
import asyncio
+import uuid
from datetime import datetime, timedelta
from typing import Any
-import uuid
+from config import ADMIN_PASSWORD, ADMIN_USERNAME, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
from py3xui import AsyncApi
from client import add_client
-from config import ADMIN_PASSWORD, ADMIN_USERNAME, PUBLIC_LINK, TOTAL_GB, TRIAL_TIME
from database import get_servers_from_db, store_key, use_trial
from handlers.texts import INSTRUCTIONS
from handlers.utils import generate_random_email, get_least_loaded_cluster
diff --git a/handlers/notifications.py b/handlers/notifications.py
index 7ac8ccf2..c16832f8 100644
--- a/handlers/notifications.py
+++ b/handlers/notifications.py
@@ -1,12 +1,20 @@
import asyncio
from datetime import datetime, timedelta
+import asyncpg
from aiogram import Bot, Router, types
from aiogram.utils.keyboard import InlineKeyboardBuilder
-import asyncpg
+from config import (
+ ADMIN_PASSWORD,
+ ADMIN_USERNAME,
+ DATABASE_URL,
+ DEV_MODE,
+ RENEWAL_PLANS,
+ TOTAL_GB,
+ TRIAL_TIME,
+)
from py3xui import AsyncApi
-from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, DEV_MODE, RENEWAL_PLANS, TOTAL_GB, TRIAL_TIME
from database import (
add_notification,
check_notification_time,
@@ -30,8 +38,12 @@ async def notify_expiring_keys(bot: Bot):
logger.info("Подключение к базе данных успешно.")
current_time = int(datetime.utcnow().timestamp() * 1000)
- threshold_time_10h = int((datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000)
- threshold_time_24h = int((datetime.utcnow() + timedelta(days=1)).timestamp() * 1000)
+ threshold_time_10h = int(
+ (datetime.utcnow() + timedelta(hours=10)).timestamp() * 1000
+ )
+ threshold_time_24h = int(
+ (datetime.utcnow() + timedelta(days=1)).timestamp() * 1000
+ )
logger.info("Начало обработки уведомлений.")
@@ -60,10 +72,14 @@ async def is_bot_blocked(bot: Bot, chat_id: int) -> bool:
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
@@ -110,7 +126,9 @@ async def notify_10h_keys(
if not await is_bot_blocked(bot, tg_id) and not DEV_MODE:
try:
keyboard = InlineKeyboardBuilder()
- keyboard.button(text="🔄 Продлить VPN", callback_data=f'renew_key|{email}')
+ keyboard.button(
+ text="🔄 Продлить VPN", callback_data=f"renew_key|{email}"
+ )
keyboard.button(text="💳 Пополнить баланс", callback_data="pay")
keyboard.button(text="👤 Личный кабинет", callback_data="profile")
keyboard.adjust(1)
@@ -118,7 +136,9 @@ async def notify_10h_keys(
await bot.send_message(tg_id, message, reply_markup=keyboard)
logger.info(f"Уведомление отправлено пользователю {tg_id}.")
except Exception as e:
- logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
+ logger.error(
+ f"Ошибка при отправке уведомления пользователю {tg_id}: {e}"
+ )
continue
await conn.execute(
@@ -177,7 +197,7 @@ async def notify_24h_keys(
builder.row(
types.InlineKeyboardButton(
text="🔄 Продлить VPN",
- callback_data=f'renew_key|{email}',
+ callback_data=f"renew_key|{email}",
)
)
builder.row(
@@ -196,14 +216,18 @@ async def notify_24h_keys(
await bot.send_message(tg_id, message_24h, reply_markup=keyboard)
logger.info(f"Уведомление за 24 часа отправлено пользователю {tg_id}.")
except Exception as e:
- logger.error(f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}")
+ logger.error(
+ f"Ошибка при отправке уведомления за 24 часа пользователю {tg_id}: {e}"
+ )
continue
await conn.execute(
"UPDATE keys SET notified_24h = TRUE WHERE client_id = $1",
record["client_id"],
)
- logger.info(f"Обновлено поле notified_24h для клиента {record['client_id']}.")
+ logger.info(
+ f"Обновлено поле notified_24h для клиента {record['client_id']}."
+ )
await asyncio.sleep(1)
@@ -225,18 +249,27 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
logger.info(f"Найдено {len(inactive_trial_users)} неактивных пользователей.")
for user in inactive_trial_users:
- tg_id = user['tg_id']
- username = user.get('username', 'Пользователь')
+ tg_id = user["tg_id"]
+ 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 and not await is_bot_blocked(bot, tg_id):
builder = InlineKeyboardBuilder()
builder.row(
- types.InlineKeyboardButton(text="🚀 Активировать пробный период", callback_data="create_key")
+ types.InlineKeyboardButton(
+ text="🚀 Активировать пробный период",
+ 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 = (
@@ -249,10 +282,12 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
await bot.send_message(tg_id, message, reply_markup=keyboard)
logger.info(f"Отправлено уведомление неактивному пользователю {tg_id}.")
- await add_notification(tg_id, 'inactive_trial', session=conn)
+ await add_notification(tg_id, "inactive_trial", session=conn)
except Exception as e:
- logger.error(f"Ошибка при отправке уведомления неактивному пользователю {tg_id}: {e}")
+ logger.error(
+ f"Ошибка при отправке уведомления неактивному пользователю {tg_id}: {e}"
+ )
await asyncio.sleep(1)
@@ -286,20 +321,32 @@ async def process_key(record, bot, conn):
f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}"
)
keyboard = types.InlineKeyboardMarkup(
- inline_keyboard=[[types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")]]
+ inline_keyboard=[
+ [
+ types.InlineKeyboardButton(
+ text="👤 Личный кабинет", callback_data="profile"
+ )
+ ]
+ ]
)
try:
if balance >= RENEWAL_PLANS["1"]["price"]:
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"])
- new_expiry_time = int((datetime.utcnow() + timedelta(days=30)).timestamp() * 1000)
+ new_expiry_time = int(
+ (datetime.utcnow() + timedelta(days=30)).timestamp() * 1000
+ )
await update_key_expiry(client_id, new_expiry_time)
servers = await get_servers_from_db()
for cluster_id in servers:
- await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, TOTAL_GB)
- logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")
+ await renew_key_in_cluster(
+ cluster_id, email, client_id, new_expiry_time, TOTAL_GB
+ )
+ logger.info(
+ f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}."
+ )
await conn.execute(
"""
@@ -309,10 +356,14 @@ async def process_key(record, bot, conn):
""",
client_id,
)
- logger.info(f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}.")
+ logger.info(
+ f"Флаги notified и notified_24 сброшены для клиента с ID {client_id}."
+ )
try:
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard)
- logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
+ logger.info(
+ f"Уведомление об успешном продлении отправлено клиенту {tg_id}."
+ )
except Exception as e:
logger.error(f"Ошибка при отправке уведомления клиенту {tg_id}: {e}")
@@ -320,10 +371,16 @@ async def process_key(record, bot, conn):
message_expired = "Ваша подписка истекла и была удалена. Получите новую через личный кабинет"
try:
- await bot.send_message(tg_id, text=message_expired, reply_markup=keyboard)
- logger.info(f"Уведомление об истечении подписки и удалении ключа отправлено пользователю {tg_id}.")
+ await bot.send_message(
+ tg_id, text=message_expired, reply_markup=keyboard
+ )
+ logger.info(
+ f"Уведомление об истечении подписки и удалении ключа отправлено пользователю {tg_id}."
+ )
except Exception as e:
- logger.error(f"Ошибка при отправке уведомления об истечении подписки пользователю {tg_id}: {e}")
+ logger.error(
+ f"Ошибка при отправке уведомления об истечении подписки пользователю {tg_id}: {e}"
+ )
servers = await get_servers_from_db()
@@ -343,7 +400,9 @@ async def check_online_users():
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())
@@ -351,4 +410,6 @@ 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}"
+ )
diff --git a/handlers/pay.py b/handlers/pay.py
index bd709cd0..a2ef2e01 100644
--- a/handlers/pay.py
+++ b/handlers/pay.py
@@ -1,8 +1,13 @@
from aiogram import F, Router
from aiogram.types import CallbackQuery, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
-
-from config import CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, ROBOKASSA_ENABLE, STARS_ENABLE, YOOKASSA_ENABLE
+from config import (
+ CRYPTO_BOT_ENABLE,
+ FREEKASSA_ENABLE,
+ ROBOKASSA_ENABLE,
+ STARS_ENABLE,
+ YOOKASSA_ENABLE,
+)
router = Router()
@@ -46,8 +51,14 @@ async def handle_pay(callback_query: CallbackQuery):
callback_data="pay_robokassa",
)
)
- builder.row(InlineKeyboardButton(text="🎟️ Активировать купон", callback_data="activate_coupon"))
- builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
+ builder.row(
+ InlineKeyboardButton(
+ text="🎟️ Активировать купон", callback_data="activate_coupon"
+ )
+ )
+ builder.row(
+ InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
+ )
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
await callback_query.message.answer(
diff --git a/handlers/payments/__init__.py b/handlers/payments/__init__.py
index 231dd68a..2b7063b5 100644
--- a/handlers/payments/__init__.py
+++ b/handlers/payments/__init__.py
@@ -1,15 +1,21 @@
-__all__ = ('router',)
+__all__ = ("router",)
from aiogram import Router
+from config import (
+ CRYPTO_BOT_ENABLE,
+ FREEKASSA_ENABLE,
+ ROBOKASSA_ENABLE,
+ STARS_ENABLE,
+ YOOKASSA_ENABLE,
+)
-from config import ROBOKASSA_ENABLE, STARS_ENABLE, CRYPTO_BOT_ENABLE, FREEKASSA_ENABLE, YOOKASSA_ENABLE
from .cryprobot_pay import router as cryprobot_router
from .freekassa_pay import router as freekassa_router
from .robokassa_pay import router as robokassa_router
from .stars_pay import router as stars_router
from .yookassa_pay import router as yookassa_router
-router = Router(name='payments_main_router')
+router = Router(name="payments_main_router")
if YOOKASSA_ENABLE:
router.include_router(yookassa_router)
diff --git a/handlers/payments/cryprobot_pay.py b/handlers/payments/cryprobot_pay.py
index 6265d4db..a70d8758 100644
--- a/handlers/payments/cryprobot_pay.py
+++ b/handlers/payments/cryprobot_pay.py
@@ -7,9 +7,15 @@ from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
-
from config import CRYPTO_BOT_ENABLE, CRYPTO_BOT_TOKEN, RUB_TO_USDT
-from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
+
+from database import (
+ add_connection,
+ add_payment,
+ check_connection_exists,
+ get_key_count,
+ update_balance,
+)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
@@ -27,7 +33,9 @@ class ReplenishBalanceState(StatesGroup):
@router.callback_query(F.data == "pay_cryptobot")
-async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
+async def process_callback_pay_cryptobot(
+ callback_query: types.CallbackQuery, state: FSMContext, session: Any
+):
builder = InlineKeyboardBuilder()
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
@@ -59,7 +67,12 @@ async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, st
if key_count == 0:
exists = await check_connection_exists(callback_query.message.chat.id)
if not exists:
- await add_connection(tg_id=callback_query.message.chat.id, balance=0.0, trial=0, session=session)
+ await add_connection(
+ tg_id=callback_query.message.chat.id,
+ balance=0.0,
+ trial=0,
+ session=session,
+ )
await callback_query.message.answer(
"Выберите сумму пополнения:",
reply_markup=builder.as_markup(),
@@ -68,7 +81,9 @@ async def process_callback_pay_cryptobot(callback_query: types.CallbackQuery, st
@router.callback_query(F.data.startswith("crypto_amount|"))
-async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
+async def process_amount_selection(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
data = callback_query.data.split("|", 1)
if len(data) != 2:
@@ -95,7 +110,9 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
if hasattr(invoice, "bot_invoice_url"):
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url))
+ builder.row(
+ InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url)
+ )
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
await callback_query.message.answer(
text=f"Вы выбрали пополнение на {amount} рублей.",
@@ -115,7 +132,9 @@ async def cryptobot_webhook(request):
await process_crypto_payment(data["payload"])
return web.Response(status=200)
else:
- logger.warning(f"Неподдерживаемый тип обновления: {data.get('update_type')}")
+ logger.warning(
+ f"Неподдерживаемый тип обновления: {data.get('update_type')}"
+ )
return web.Response(status=400)
except Exception as e:
logger.error(f"Ошибка обработки вебхука: {e}")
@@ -140,8 +159,9 @@ async def process_crypto_payment(payload):
@router.callback_query(F.data == "enter_custom_amount_crypto")
-async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
-
+async def process_enter_custom_amount(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_cryptobot"))
@@ -158,11 +178,15 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if message.text.isdigit():
amount = int(message.text)
if amount // RUB_TO_USDT <= 0:
- await message.answer(f"Сумма должна быть больше {RUB_TO_USDT}. Пожалуйста, введите сумму еще раз:")
+ await message.answer(
+ f"Сумма должна быть больше {RUB_TO_USDT}. Пожалуйста, введите сумму еще раз:"
+ )
return
await state.update_data(amount=amount)
- await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_crypto)
+ await state.set_state(
+ ReplenishBalanceState.waiting_for_payment_confirmation_crypto
+ )
try:
invoice = await crypto.create_invoice(
asset="USDT",
@@ -173,7 +197,9 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if hasattr(invoice, "bot_invoice_url"):
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url))
+ builder.row(
+ InlineKeyboardButton(text="Пополнить", url=invoice.bot_invoice_url)
+ )
builder.row(
InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"),
)
diff --git a/handlers/payments/freekassa_pay.py b/handlers/payments/freekassa_pay.py
index 7c423129..52b50116 100644
--- a/handlers/payments/freekassa_pay.py
+++ b/handlers/payments/freekassa_pay.py
@@ -4,15 +4,15 @@ import logging
import time
import uuid
+import requests
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
-import requests
-
from config import FREEKASSA_API_KEY, FREEKASSA_SHOP_ID
+
from database import add_payment, update_balance
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
@@ -49,7 +49,9 @@ async def create_payment(user_id, amount, email, ip):
params["signature"] = generate_signature(params, FREEKASSA_API_KEY)
try:
- response = requests.post("https://api.freekassa.com/v1/orders/create", json=params)
+ response = requests.post(
+ "https://api.freekassa.com/v1/orders/create", json=params
+ )
response_data = response.json()
logging.debug(f"Ответ от FreeKassa при создании платежа: {response_data}")
@@ -83,7 +85,9 @@ async def freekassa_webhook(request):
@router.callback_query(lambda c: c.data == "pay_freekassa")
-async def process_callback_pay_freekassa(callback_query: types.CallbackQuery, state: FSMContext):
+async def process_callback_pay_freekassa(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
builder = InlineKeyboardBuilder()
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
@@ -121,7 +125,9 @@ async def process_callback_pay_freekassa(callback_query: types.CallbackQuery, st
@router.callback_query(F.data.startswith("freekassa_amount|"))
-async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
+async def process_amount_selection(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
data = callback_query.data.split("|", 1)
amount_str = data[1]
try:
@@ -132,12 +138,18 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
user_email = f"{callback_query.message.chat.id}@solo.net"
user_ip = callback_query.message.chat.id
- payment_url = await create_payment(callback_query.message.chat.id, amount, user_email, user_ip)
+ payment_url = await create_payment(
+ callback_query.message.chat.id, amount, user_email, user_ip
+ )
if payment_url:
confirm_keyboard = InlineKeyboardMarkup(
inline_keyboard=[
- [InlineKeyboardButton(text=f"Оплатить {amount} рублей", url=payment_url)],
+ [
+ InlineKeyboardButton(
+ text=f"Оплатить {amount} рублей", url=payment_url
+ )
+ ],
[InlineKeyboardButton(text="⬅️ Назад", callback_data="pay")],
]
)
@@ -153,7 +165,9 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
@router.callback_query(F.data == "enter_custom_amount_freekassa")
-async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
+async def process_enter_custom_amount(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
await callback_query.message.answer(text="Введите сумму пополнения:")
await state.set_state(ReplenishBalanceState.entering_custom_amount_freekassa)
@@ -163,7 +177,9 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if message.text.isdigit():
amount = int(message.text)
if amount <= 0:
- await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
+ await message.answer(
+ "Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
+ )
return
user_email = f"{message.chat.id}@solo.net"
@@ -171,7 +187,9 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
payment_url = await create_payment(message.chat.id, amount, user_email, user_ip)
if payment_url:
- keyboard = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton("Оплатить", url=payment_url)]])
+ keyboard = InlineKeyboardMarkup(
+ inline_keyboard=[[InlineKeyboardButton("Оплатить", url=payment_url)]]
+ )
await message.answer(
f"Вы выбрали оплату на {amount} рублей. Перейдите по ссылке для завершения оплаты:",
reply_markup=keyboard,
diff --git a/handlers/payments/robokassa_pay.py b/handlers/payments/robokassa_pay.py
index 9ae90a29..2c1de2e5 100644
--- a/handlers/payments/robokassa_pay.py
+++ b/handlers/payments/robokassa_pay.py
@@ -7,10 +7,22 @@ from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
+from config import (
+ ROBOKASSA_ENABLE,
+ ROBOKASSA_LOGIN,
+ ROBOKASSA_PASSWORD1,
+ ROBOKASSA_PASSWORD2,
+ ROBOKASSA_TEST_MODE,
+)
from robokassa import HashAlgorithm, Robokassa
-from config import ROBOKASSA_ENABLE, ROBOKASSA_LOGIN, ROBOKASSA_PASSWORD1, ROBOKASSA_PASSWORD2, ROBOKASSA_TEST_MODE
-from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
+from database import (
+ add_connection,
+ add_payment,
+ check_connection_exists,
+ get_key_count,
+ update_balance,
+)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
@@ -37,7 +49,9 @@ 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,
@@ -49,7 +63,9 @@ 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.")
@@ -98,7 +114,9 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
@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("|")
@@ -152,7 +170,9 @@ 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("Неверная подпись или данные запроса.")
@@ -191,7 +211,9 @@ 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}")
@@ -200,7 +222,9 @@ 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.")
@@ -212,7 +236,9 @@ async def process_custom_amount_selection(callback_query: types.CallbackQuery, s
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)
@@ -246,4 +272,6 @@ async def handle_custom_amount_input(message: types.Message, state: FSMContext):
await state.clear()
except ValueError as e:
logger.error(f"Некорректная сумма от пользователя {tg_id}: {e}")
- await message.answer(text="Введите корректную сумму в рублях (целое положительное число).")
+ await message.answer(
+ text="Введите корректную сумму в рублях (целое положительное число)."
+ )
diff --git a/handlers/payments/stars_pay.py b/handlers/payments/stars_pay.py
index 698444b8..f993d3f9 100644
--- a/handlers/payments/stars_pay.py
+++ b/handlers/payments/stars_pay.py
@@ -5,9 +5,15 @@ from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
-
from config import RUB_TO_XTR
-from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
+
+from database import (
+ add_connection,
+ add_payment,
+ check_connection_exists,
+ get_key_count,
+ update_balance,
+)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
@@ -22,11 +28,17 @@ class ReplenishBalanceState(StatesGroup):
@router.callback_query(F.data == "pay_stars")
-async def process_callback_pay_stars(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
+async def process_callback_pay_stars(
+ callback_query: types.CallbackQuery, state: FSMContext, session: Any
+):
tg_id = callback_query.message.chat.id
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"))
+ builder.row(
+ InlineKeyboardButton(
+ text="🤖 Бот для покупки звезд", url="https://t.me/PremiumBot"
+ )
+ )
for i in range(0, len(PAYMENT_OPTIONS), 2):
if i + 1 < len(PAYMENT_OPTIONS):
@@ -76,7 +88,9 @@ async def process_callback_pay_stars(callback_query: types.CallbackQuery, state:
@router.callback_query(F.data.startswith("stars_amount|"))
-async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
+async def process_amount_selection(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
data = callback_query.data.split("|", 1)
if len(data) != 2:
@@ -104,7 +118,6 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_stars)
try:
-
builder = InlineKeyboardBuilder()
builder.row(
InlineKeyboardButton(text="Пополнить", pay=True),
@@ -128,8 +141,9 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
@router.callback_query(F.data == "enter_custom_amount_stars")
-async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
-
+async def process_enter_custom_amount(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_stars"))
@@ -146,11 +160,15 @@ async def process_custom_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)
- await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_stars)
+ await state.set_state(
+ ReplenishBalanceState.waiting_for_payment_confirmation_stars
+ )
try:
builder = InlineKeyboardBuilder()
builder.row(
diff --git a/handlers/payments/utils.py b/handlers/payments/utils.py
index 21015090..430c12f0 100644
--- a/handlers/payments/utils.py
+++ b/handlers/payments/utils.py
@@ -8,7 +8,9 @@ from logger import logger
async def send_payment_success_notification(user_id: int, amount: float):
try:
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
+ builder.row(
+ InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")
+ )
await bot.send_message(
chat_id=user_id,
text=f"Ваш баланс успешно пополнен на {amount} рублей. Спасибо за оплату!",
diff --git a/handlers/payments/yookassa_pay.py b/handlers/payments/yookassa_pay.py
index 01618660..752f2910 100644
--- a/handlers/payments/yookassa_pay.py
+++ b/handlers/payments/yookassa_pay.py
@@ -1,5 +1,5 @@
-from typing import Any
import uuid
+from typing import Any
from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
@@ -7,10 +7,16 @@ from aiogram.fsm.state import State, StatesGroup
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiohttp import web
+from config import YOOKASSA_ENABLE, YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
from yookassa import Configuration, Payment
-from config import YOOKASSA_ENABLE, YOOKASSA_SECRET_KEY, YOOKASSA_SHOP_ID
-from database import add_connection, add_payment, check_connection_exists, get_key_count, update_balance
+from database import (
+ add_connection,
+ add_payment,
+ check_connection_exists,
+ get_key_count,
+ update_balance,
+)
from handlers.payments.utils import send_payment_success_notification
from handlers.texts import PAYMENT_OPTIONS
from logger import logger
@@ -31,7 +37,9 @@ class ReplenishBalanceState(StatesGroup):
@router.callback_query(F.data == "pay_yookassa")
-async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, state: FSMContext, session: Any):
+async def process_callback_pay_yookassa(
+ callback_query: types.CallbackQuery, state: FSMContext, session: Any
+):
tg_id = callback_query.message.chat.id
builder = InlineKeyboardBuilder()
@@ -79,7 +87,9 @@ async def process_callback_pay_yookassa(callback_query: types.CallbackQuery, sta
@router.callback_query(F.data.startswith("yookassa_amount|"))
-async def process_amount_selection(callback_query: types.CallbackQuery, state: FSMContext):
+async def process_amount_selection(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
data = callback_query.data.split("|", 1)
if len(data) != 2:
@@ -92,7 +102,9 @@ async def process_amount_selection(callback_query: types.CallbackQuery, state: F
return
await state.update_data(amount=amount)
- await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_yookassa)
+ await state.set_state(
+ ReplenishBalanceState.waiting_for_payment_confirmation_yookassa
+ )
# state_data = await state.get_data()
customer_name = callback_query.from_user.full_name
@@ -167,8 +179,9 @@ async def yookassa_webhook(request):
@router.callback_query(F.data == "enter_custom_amount_yookassa")
-async def process_enter_custom_amount(callback_query: types.CallbackQuery, state: FSMContext):
-
+async def process_enter_custom_amount(
+ callback_query: types.CallbackQuery, state: FSMContext
+):
builder = InlineKeyboardBuilder()
builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="pay_yookassa"))
@@ -185,11 +198,15 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext)
if message.text.isdigit():
amount = int(message.text)
if amount <= 0:
- await message.answer("Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:")
+ await message.answer(
+ "Сумма должна быть больше нуля. Пожалуйста, введите сумму еще раз:"
+ )
return
await state.update_data(amount=amount)
- await state.set_state(ReplenishBalanceState.waiting_for_payment_confirmation_yookassa)
+ await state.set_state(
+ ReplenishBalanceState.waiting_for_payment_confirmation_yookassa
+ )
try:
payment = Payment.create(
diff --git a/handlers/profile.py b/handlers/profile.py
index 3a43d47f..49477276 100644
--- a/handlers/profile.py
+++ b/handlers/profile.py
@@ -4,8 +4,8 @@ from aiogram import F, Router, types
from aiogram.fsm.context import FSMContext
from aiogram.types import BufferedInputFile, InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
-
from config import NEWS_MESSAGE, RENEWAL_PLANS
+
from database import get_balance, get_key_count, get_referral_stats
from handlers.texts import get_referral_link, invite_message_send, profile_message_send
@@ -13,7 +13,9 @@ router = Router()
@router.callback_query(F.data == "profile")
-async def process_callback_view_profile(callback_query: types.CallbackQuery, state: FSMContext, admin: bool):
+async def process_callback_view_profile(
+ callback_query: types.CallbackQuery, state: FSMContext, admin: bool
+):
chat_id = callback_query.message.chat.id
username = callback_query.from_user.full_name
image_path = os.path.join("img", "pic.jpg")
@@ -46,7 +48,9 @@ async def process_callback_view_profile(callback_query: types.CallbackQuery, sta
)
builder.row(InlineKeyboardButton(text="💡 Тарифы", callback_data="view_tariffs"))
if admin:
- builder.row(InlineKeyboardButton(text="🔧 Администратор", callback_data="admin"))
+ builder.row(
+ InlineKeyboardButton(text="🔧 Администратор", callback_data="admin")
+ )
builder.row(InlineKeyboardButton(text="⬅️ Главное меню", callback_data="start"))
if os.path.isfile(image_path):
diff --git a/handlers/start.py b/handlers/start.py
index 93df5511..04d0847d 100644
--- a/handlers/start.py
+++ b/handlers/start.py
@@ -4,11 +4,29 @@ from typing import Any
from aiogram import F, Router
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
-from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, Message
+from aiogram.types import (
+ BufferedInputFile,
+ CallbackQuery,
+ InlineKeyboardButton,
+ Message,
+)
from aiogram.utils.keyboard import InlineKeyboardBuilder
+from config import (
+ CHANNEL_URL,
+ CONNECT_ANDROID,
+ CONNECT_IOS,
+ DOWNLOAD_ANDROID,
+ DOWNLOAD_IOS,
+ SUPPORT_CHAT_URL,
+)
-from config import CHANNEL_URL, CONNECT_ANDROID, CONNECT_IOS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, SUPPORT_CHAT_URL
-from database import add_connection, add_referral, check_connection_exists, get_trial, use_trial
+from database import (
+ add_connection,
+ add_referral,
+ check_connection_exists,
+ get_trial,
+ use_trial,
+)
from handlers.keys.trial_key import create_trial_key
from handlers.texts import INSTRUCTIONS_TRIAL, WELCOME_TEXT, get_about_vpn
@@ -16,7 +34,9 @@ router = Router()
@router.callback_query(F.data == "start")
-async def handle_start_callback_query(callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool):
+async def handle_start_callback_query(
+ callback_query: CallbackQuery, state: FSMContext, session: Any, admin: bool
+):
await start_command(callback_query.message, state, session, admin)
@@ -37,7 +57,9 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
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"))
builder.row(
@@ -46,7 +68,9 @@ async def start_command(message: Message, state: FSMContext, session: Any, admin
)
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"))
if os.path.isfile(image_path):
@@ -98,16 +122,26 @@ async def handle_connect_vpn(callback_query: CallbackQuery, session: Any):
url=f'{CONNECT_ANDROID}{trial_key_info["key"]}',
),
)
- builder.row(InlineKeyboardButton(text="💻 Windows/Linux", callback_data=f"connect_pc|{email}"))
- builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
+ builder.row(
+ InlineKeyboardButton(
+ text="💻 Windows/Linux", callback_data=f"connect_pc|{email}"
+ )
+ )
+ 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")
async def handle_about_vpn(callback_query: CallbackQuery):
builder = InlineKeyboardBuilder()
- builder.row(InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate"))
+ builder.row(
+ InlineKeyboardButton(text="💰 Поддержать проект", callback_data="donate")
+ )
builder.row(
InlineKeyboardButton(text="📞 Техническая поддержка", url=SUPPORT_CHAT_URL),
)
@@ -116,4 +150,6 @@ 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.21-Release"), reply_markup=builder.as_markup())
+ await callback_query.message.answer(
+ get_about_vpn("3.2.21-Release"), reply_markup=builder.as_markup()
+ )
diff --git a/handlers/utils.py b/handlers/utils.py
index 95871cf6..97ba02e9 100644
--- a/handlers/utils.py
+++ b/handlers/utils.py
@@ -1,11 +1,10 @@
import random
import re
-from typing import Optional
import asyncpg
+from config import DATABASE_URL
from bot import bot
-from config import DATABASE_URL
from database import get_servers_from_db
from logger import logger
@@ -68,7 +67,9 @@ async def get_least_loaded_cluster() -> str:
return least_loaded_cluster
-async def handle_error(tg_id: int, callback_query: Optional[object] = None, message: str = "") -> None:
+async def handle_error(
+ tg_id: int, callback_query: object | None = None, message: str = ""
+) -> None:
"""
Обрабатывает ошибку, отправляя сообщение пользователю.
@@ -80,7 +81,9 @@ async def handle_error(tg_id: int, callback_query: Optional[object] = None, mess
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}")
diff --git a/logger.py b/logger.py
index d12aace2..7130c5e9 100644
--- a/logger.py
+++ b/logger.py
@@ -1,7 +1,7 @@
-from datetime import timedelta
import logging
import os
import sys
+from datetime import timedelta
from loguru import logger
diff --git a/middlewares/admin.py b/middlewares/admin.py
index 57aa0692..85819d32 100644
--- a/middlewares/admin.py
+++ b/middlewares/admin.py
@@ -1,24 +1,24 @@
-from typing import Any, Awaitable, Callable, Dict, Union
+from collections.abc import Awaitable, Callable
+from typing import Any
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
-
from config import ADMIN_ID
class AdminMiddleware(BaseMiddleware):
async def __call__(
self,
- handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
+ handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
- data: Dict[str, Any],
+ data: dict[str, Any],
) -> Any:
data["admin"] = self._check_admin_access(event)
return await handler(event, data)
def _check_admin_access(self, event: TelegramObject) -> bool:
try:
- admin_ids: Union[int, list[int]] = ADMIN_ID
+ admin_ids: int | list[int] = ADMIN_ID
if isinstance(admin_ids, list):
return event.from_user.id in admin_ids
return event.from_user.id == admin_ids
diff --git a/middlewares/database.py b/middlewares/database.py
index b1687aa6..c625fec3 100644
--- a/middlewares/database.py
+++ b/middlewares/database.py
@@ -1,18 +1,18 @@
-from typing import Any, Awaitable, Callable, Dict
+from collections.abc import Awaitable, Callable
+from typing import Any
+import asyncpg
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
-import asyncpg
-
from config import DATABASE_URL
class DatabaseMiddleware(BaseMiddleware):
async def __call__(
self,
- handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
+ handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
- data: Dict[str, Any],
+ data: dict[str, Any],
) -> Any:
conn = await asyncpg.connect(DATABASE_URL)
try:
diff --git a/middlewares/delete.py b/middlewares/delete.py
index 356f8831..7804b81c 100644
--- a/middlewares/delete.py
+++ b/middlewares/delete.py
@@ -1,4 +1,5 @@
-from typing import Any, Awaitable, Callable, Dict
+from collections.abc import Awaitable, Callable
+from typing import Any
from aiogram import BaseMiddleware
from aiogram.types import CallbackQuery, Message, TelegramObject
@@ -7,15 +8,17 @@ from aiogram.types import CallbackQuery, Message, TelegramObject
class DeleteMessageMiddleware(BaseMiddleware):
async def __call__(
self,
- handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
+ handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
- data: Dict[str, Any],
+ data: dict[str, Any],
) -> Any:
if isinstance(event, (Message, CallbackQuery)):
if isinstance(event, Message):
if 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()
diff --git a/middlewares/logging.py b/middlewares/logging.py
index fbe31bae..1d0c1b38 100644
--- a/middlewares/logging.py
+++ b/middlewares/logging.py
@@ -1,4 +1,5 @@
-from typing import Any, Awaitable, Callable, Dict, Optional
+from collections.abc import Awaitable, Callable
+from typing import Any
from aiogram import BaseMiddleware
from aiogram.types import CallbackQuery, Message, TelegramObject
@@ -9,9 +10,9 @@ from logger import logger
class LoggingMiddleware(BaseMiddleware):
async def __call__(
self,
- handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
+ handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
- data: Dict[str, Any],
+ data: dict[str, Any],
) -> Any:
user_info = self._extract_user_info(event)
@@ -23,7 +24,7 @@ class LoggingMiddleware(BaseMiddleware):
)
return await handler(event, data)
- def _extract_user_info(self, event: TelegramObject) -> Dict[str, Optional[str]]:
+ def _extract_user_info(self, event: TelegramObject) -> dict[str, str | None]:
user_id = None
username = None
action = None
diff --git a/middlewares/user.py b/middlewares/user.py
index 969020ef..6d276e75 100644
--- a/middlewares/user.py
+++ b/middlewares/user.py
@@ -1,4 +1,5 @@
-from typing import Any, Awaitable, Callable, Dict
+from collections.abc import Awaitable, Callable
+from typing import Any
from aiogram import BaseMiddleware
from aiogram.types import TelegramObject, User
@@ -9,9 +10,9 @@ from database import upsert_user
class UserMiddleware(BaseMiddleware):
async def __call__(
self,
- handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
+ handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
- data: Dict[str, Any],
+ data: dict[str, Any],
) -> Any:
if user := data.get("event_from_user"):
await self._process_user(user)
diff --git a/pyproject.toml b/pyproject.toml
index fb4c1abc..1ea6e345 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,27 +1,17 @@
-[tool.black]
-line-length = 120 # Совпадает с flake8
-target-version = ['py39','py310','py311'] # Укажите версию Python вашего проекта
-skip-string-normalization = true # Отключает нормализацию кавычек
-include = '\.pyi?$' # Включает Python-файлы
-exclude = '''
-/(
- \.git
- | \.hg
- | \.mypy_cache
- | \.tox
- | \.venv
- | _build
- | buck-out
- | build
- | dist
-)/
-''' # Исключает системные папки
+[tool.ruff]
+line-length = 120
+target-version = "py310"
-[tool.isort]
-profile = "black" # Устанавливает совместимость с black
-line_length = 120
-multi_line_output = 3 # Формат многострочных импортов
-include_trailing_comma = true # Совместимость с black
-force_sort_within_sections = true # Сортировка внутри секций
-sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
-skip_gitignore = true # Учитывать .gitignore
+[tool.ruff.lint]
+select = ["E", "F", "W", "I", "N", "UP", "ANN", "ASYNC", "S", "BLE", "FBT", "B", "A", "C4", "DTZ", "T10", "ISC", "ICN", "G", "PIE"]
+ignore = ["ANN101", "ANN102", "S101",'ANN201','ANN001','BLE001']
+
+[tool.ruff.format]
+quote-style = "single"
+indent-style = "space"
+
+[tool.darker]
+src = ["."]
+revision = "HEAD"
+diff = false
+check = false
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 77ed916d..72b819da 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -30,8 +30,5 @@ aiocryptopay
py3xui
sqlalchemy
robokassa
-flake8
-black
-isort
-pylint
-ping3
\ No newline at end of file
+ping3
+ruff
\ No newline at end of file
diff --git a/servers.py b/servers.py
index fbb81968..8dde3f5c 100644
--- a/servers.py
+++ b/servers.py
@@ -1,14 +1,14 @@
import asyncio
-from datetime import datetime, timedelta
import re
+from datetime import datetime, timedelta
+import asyncpg
from aiogram.types import InlineKeyboardButton
from aiogram.utils.keyboard import InlineKeyboardBuilder
-import asyncpg
+from config import ADMIN_ID, DATABASE_URL
from ping3 import ping
from bot import bot
-from config import ADMIN_ID, DATABASE_URL
from database import get_servers_from_db
from logger import logger
@@ -16,7 +16,9 @@ try:
from config import CLUSTERS
except ImportError:
CLUSTERS = None
- logger.warning("Переменная CLUSTERS не найдена в конфигурации. Добавьте сервера через админ-панель!")
+ logger.warning(
+ "Переменная CLUSTERS не найдена в конфигурации. Добавьте сервера через админ-панель!"
+ )
async def sync_servers_with_db():
@@ -25,7 +27,9 @@ async def sync_servers_with_db():
Если CLUSTERS не найден, синхронизация не будет выполнена.
"""
if CLUSTERS is None:
- logger.info("Конфигурация CLUSTERS не найдена. Синхронизация не будет выполнена.")
+ logger.info(
+ "Конфигурация CLUSTERS не найдена. Синхронизация не будет выполнена."
+ )
return
try:
@@ -55,14 +59,18 @@ 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}")
finally:
- if 'conn' in locals():
+ if "conn" in locals():
await conn.close()
@@ -96,14 +104,24 @@ 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(
@@ -116,7 +134,9 @@ async def notify_admin(server_name: str):
parse_mode="HTML",
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:
@@ -140,7 +160,9 @@ 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)
@@ -148,12 +170,18 @@ 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(30)