diff --git a/client.py b/client.py index 96c105f5..c7609ec8 100644 --- a/client.py +++ b/client.py @@ -72,7 +72,14 @@ async def add_client(xui: py3xui.AsyncApi, config: ClientConfig) -> dict[str, An async def extend_client_key( - xui: py3xui.AsyncApi, inbound_id: int, email: str, new_expiry_time: int, client_id: str, total_gb: int, sub_id: str, tg_id: int + xui: py3xui.AsyncApi, + inbound_id: int, + email: str, + new_expiry_time: int, + client_id: str, + total_gb: int, + sub_id: str, + tg_id: int, ) -> bool | None: """ Обновляет срок действия ключа клиента. diff --git a/handlers/admin/admin_users.py b/handlers/admin/admin_users.py index 9e658fb9..99a4ec98 100644 --- a/handlers/admin/admin_users.py +++ b/handlers/admin/admin_users.py @@ -12,7 +12,6 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, Message from aiogram.utils.keyboard import InlineKeyboardBuilder from config import TOTAL_GB -from utils.csv_export import export_referrals_csv from database import ( delete_key, @@ -27,7 +26,7 @@ from database import ( from filters.admin import IsAdminFilter from handlers.keys.key_utils import delete_key_from_cluster, get_user_traffic, renew_key_in_cluster, update_subscription from handlers.utils import sanitize_key_name -from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb, build_admin_back_btn +from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_btn, build_admin_back_kb from keyboards.admin.users_kb import ( AdminUserEditorCallback, AdminUserKeyEditorCallback, @@ -43,6 +42,7 @@ from keyboards.admin.users_kb import ( build_users_key_show_kb, ) from logger import logger +from utils.csv_export import export_referrals_csv MOSCOW_TZ = pytz.timezone("Europe/Moscow") @@ -733,10 +733,11 @@ async def confirm_restore_trials(callback_query: types.CallbackQuery): await callback_query.message.edit_text( text="⚠ Вы уверены, что хотите восстановить пробники для пользователей? \n\n" - "Только для тех, у кого нет активной подписки!", - reply_markup=builder.as_markup() + "Только для тех, у кого нет активной подписки!", + reply_markup=builder.as_markup(), ) + @router.callback_query(AdminPanelCallback.filter(F.action == "confirm_restore_trials"), IsAdminFilter()) async def restore_trials(callback_query: types.CallbackQuery, session: Any): """ @@ -759,15 +760,13 @@ async def restore_trials(callback_query: types.CallbackQuery, session: Any): await callback_query.message.edit_text( text="✅ Пробники успешно восстановлены для пользователей, у которых нет активных подписок.", - reply_markup=builder.as_markup() + reply_markup=builder.as_markup(), ) @router.callback_query(AdminUserEditorCallback.filter(F.action == "users_export_referrals"), IsAdminFilter()) async def handle_users_export_referrals( - callback_query: types.CallbackQuery, - callback_data: AdminUserEditorCallback, - session: Any + callback_query: types.CallbackQuery, callback_data: AdminUserEditorCallback, session: Any ): """ Обработчик: получает tg_id реферера из callback_data, @@ -783,6 +782,5 @@ async def handle_users_export_referrals( return await callback_query.message.answer_document( - document=csv_file, - caption=f"Список рефералов для пользователя {referrer_tg_id}." - ) \ No newline at end of file + document=csv_file, caption=f"Список рефералов для пользователя {referrer_tg_id}." + ) diff --git a/handlers/buttons/yookassa.py b/handlers/buttons/yookassa.py index 387fc745..5aaded81 100644 --- a/handlers/buttons/yookassa.py +++ b/handlers/buttons/yookassa.py @@ -4,4 +4,4 @@ BACK = "⬅️ Назад" CUSTOM_SUM = "💰 Ввести свою сумму" CUSTOM_SUM_ANSWER = "Пожалуйста, введите сумму пополнения." PROFILE = "👤 Личный кабинет" -DEFAULT_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей. Перейдите по ссылке для оплаты:" \ No newline at end of file +DEFAULT_PAYMENT_MESSAGE = "Вы выбрали пополнение на {amount} рублей. Перейдите по ссылке для оплаты:" diff --git a/handlers/captcha.py b/handlers/captcha.py index 8f294895..8738522b 100644 --- a/handlers/captcha.py +++ b/handlers/captcha.py @@ -8,6 +8,7 @@ from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, Message from aiogram.utils.keyboard import InlineKeyboardBuilder from config import CAPTCHA_EMOJIS + from handlers.texts import CAPTCHA_PROMPT_MSG from logger import logger diff --git a/handlers/coupons.py b/handlers/coupons.py index 807aa2f2..42d2b5f6 100644 --- a/handlers/coupons.py +++ b/handlers/coupons.py @@ -14,11 +14,12 @@ from database import ( update_coupon_usage_count, ) from handlers.texts import ( + COUPON_ACTIVATED_SUCCESS_MSG, + COUPON_ALREADY_USED_MSG, COUPON_INPUT_PROMPT, COUPON_NOT_FOUND_MSG, - COUPON_ALREADY_USED_MSG, - COUPON_ACTIVATED_SUCCESS_MSG, ) + from .utils import edit_or_send_message diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py index 9debef3e..42f074b7 100644 --- a/handlers/keys/key_management.py +++ b/handlers/keys/key_management.py @@ -54,12 +54,12 @@ from handlers.keys.key_utils import create_client_on_server, create_key_on_clust from handlers.payments.robokassa_pay import handle_custom_amount_input from handlers.payments.yookassa_pay import process_custom_amount_input from handlers.texts import ( - DISCOUNTS, - key_message_success, CREATING_CONNECTION_MSG, - SELECT_TARIFF_PLAN_MSG, + DISCOUNTS, INSUFFICIENT_FUNDS_MSG, SELECT_COUNTRY_MSG, + SELECT_TARIFF_PLAN_MSG, + key_message_success, ) from handlers.utils import edit_or_send_message, generate_random_email, get_least_loaded_cluster from logger import logger diff --git a/handlers/keys/key_utils.py b/handlers/keys/key_utils.py index d9540275..ada5d1bd 100644 --- a/handlers/keys/key_utils.py +++ b/handlers/keys/key_utils.py @@ -2,10 +2,11 @@ import asyncio from typing import Any -from config import ADMIN_PASSWORD, ADMIN_USERNAME, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB, DATABASE_URL -from py3xui import AsyncApi import asyncpg +from config import ADMIN_PASSWORD, ADMIN_USERNAME, DATABASE_URL, LIMIT_IP, PUBLIC_LINK, SUPERNODE, TOTAL_GB +from py3xui import AsyncApi + from client import ClientConfig, add_client, delete_client, extend_client_key, get_client_traffic, toggle_client from database import get_servers, store_key from handlers.utils import get_least_loaded_cluster diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py index 2f6d2b15..9c3d0517 100644 --- a/handlers/keys/keys.py +++ b/handlers/keys/keys.py @@ -23,10 +23,10 @@ from config import ( ENABLE_UPDATE_SUBSCRIPTION_BUTTON, PUBLIC_LINK, RENEWAL_PLANS, + TOGGLE_CLIENT, TOTAL_GB, USE_COUNTRY_SELECTION, USE_NEW_PAYMENT_FLOW, - TOGGLE_CLIENT ) from bot import bot @@ -53,27 +53,27 @@ from handlers.buttons.add_subscribe import ( from handlers.keys.key_utils import ( delete_key_from_cluster, renew_key_in_cluster, - update_subscription, toggle_client_on_cluster, + update_subscription, ) from handlers.payments.robokassa_pay import handle_custom_amount_input from handlers.payments.yookassa_pay import process_custom_amount_input from handlers.texts import ( + DELETE_KEY_CONFIRM_MSG, DISCOUNTS, + FREEZE_SUBSCRIPTION_CONFIRM_MSG, + FROZEN_SUBSCRIPTION_MSG, + INSUFFICIENT_FUNDS_RENEWAL_MSG, + KEY_DELETED_MSG_SIMPLE, KEY_NOT_FOUND_MSG, + NO_SUBSCRIPTIONS_MSG, PLAN_SELECTION_MSG, SUBSCRIPTION_DESCRIPTION, - SUCCESS_RENEWAL_MSG, - key_message, - NO_SUBSCRIPTIONS_MSG, - FROZEN_SUBSCRIPTION_MSG, - UNFREEZE_SUBSCRIPTION_CONFIRM_MSG, - SUBSCRIPTION_UNFROZEN_MSG, - FREEZE_SUBSCRIPTION_CONFIRM_MSG, SUBSCRIPTION_FROZEN_MSG, - DELETE_KEY_CONFIRM_MSG, - KEY_DELETED_MSG_SIMPLE, - INSUFFICIENT_FUNDS_RENEWAL_MSG, + SUBSCRIPTION_UNFROZEN_MSG, + SUCCESS_RENEWAL_MSG, + UNFREEZE_SUBSCRIPTION_CONFIRM_MSG, + key_message, ) from handlers.utils import edit_or_send_message, handle_error from logger import logger @@ -177,7 +177,7 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any) reply_markup=keyboard, media_path=image_path, ) - + else: key = record["key"] expiry_time = record["expiry_time"] @@ -188,17 +188,13 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any) time_left = expiry_date - current_date if time_left.total_seconds() <= 0: - days_left_message = ( - "🕒 Статус подписки:\n🔴 Истекла\nОсталось часов: 0\nОсталось минут: 0" - ) + days_left_message = "🕒 Статус подписки:\n🔴 Истекла\nОсталось часов: 0\nОсталось минут: 0" else: total_seconds = int(time_left.total_seconds()) days = total_seconds // 86400 hours = (total_seconds % 86400) // 3600 minutes = (total_seconds % 3600) // 60 - days_left_message = ( - f"Осталось: {days} дней, {hours} часов, {minutes} минут" - ) + days_left_message = f"Осталось: {days} дней, {hours} часов, {minutes} минут" formatted_expiry_date = expiry_date.strftime("%d %B %Y года") response_message = key_message( @@ -248,18 +244,12 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any) ) else: builder.row( - InlineKeyboardButton( - text="⏳ Продлить подписку", - callback_data=f"renew_key|{key_name}" - ) + InlineKeyboardButton(text="⏳ Продлить подписку", callback_data=f"renew_key|{key_name}") ) if USE_COUNTRY_SELECTION: builder.row( - InlineKeyboardButton( - text="🌍 Сменить локацию", - callback_data=f"change_location|{key_name}" - ) + InlineKeyboardButton(text="🌍 Сменить локацию", callback_data=f"change_location|{key_name}") ) if TOGGLE_CLIENT: @@ -355,7 +345,7 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac """, new_expiry_time, record["tg_id"], - client_id + client_id, ) await renew_key_in_cluster( @@ -363,13 +353,11 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac email=email, client_id=client_id, new_expiry_time=new_expiry_time, - total_gb=TOTAL_GB + total_gb=TOTAL_GB, ) text_ok = SUBSCRIPTION_UNFROZEN_MSG builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}") - ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}")) await edit_or_send_message( target_message=callback_query.message, text=text_ok, @@ -377,13 +365,10 @@ async def process_callback_unfreeze_subscription_confirm(callback_query: Callbac ) else: text_error = ( - "Произошла ошибка при включении подписки.\n" - f"Детали: {result.get('error') or result.get('results')}" + f"Произошла ошибка при включении подписки.\nДетали: {result.get('error') or result.get('results')}" ) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}") - ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}")) await edit_or_send_message( target_message=callback_query.message, text=text_error, @@ -399,7 +384,6 @@ async def process_callback_freeze_subscription(callback_query: CallbackQuery, se """ Показывает пользователю диалог подтверждения заморозки (отключения) подписки. """ - tg_id = callback_query.message.chat.id key_name = callback_query.data.split("|")[1] confirm_text = FREEZE_SUBSCRIPTION_CONFIRM_MSG @@ -447,9 +431,9 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ now_ms = int(time.time() * 1000) time_left = record["expiry_time"] - now_ms if time_left < 0: - time_left = 0 + time_left = 0 - update_result = await session.execute( + await session.execute( """ UPDATE keys SET expiry_time = $1, @@ -459,14 +443,12 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ """, time_left, record["tg_id"], - client_id + client_id, ) text_ok = SUBSCRIPTION_FROZEN_MSG builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}") - ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}")) await edit_or_send_message( target_message=callback_query.message, text=text_ok, @@ -474,13 +456,10 @@ async def process_callback_freeze_subscription_confirm(callback_query: CallbackQ ) else: text_error = ( - "Произошла ошибка при заморозке подписки.\n" - f"Детали: {result.get('error') or result.get('results')}" + f"Произошла ошибка при заморозке подписки.\nДетали: {result.get('error') or result.get('results')}" ) builder = InlineKeyboardBuilder() - builder.row( - InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}") - ) + builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data=f"view_key|{key_name}")) await edit_or_send_message( target_message=callback_query.message, text=text_error, @@ -568,14 +547,10 @@ async def process_callback_delete_key(callback_query: CallbackQuery): if callback_query.message.caption: await callback_query.message.edit_caption( - caption=DELETE_KEY_CONFIRM_MSG, - reply_markup=confirmation_keyboard + caption=DELETE_KEY_CONFIRM_MSG, reply_markup=confirmation_keyboard ) else: - await callback_query.message.edit_text( - text=DELETE_KEY_CONFIRM_MSG, - reply_markup=confirmation_keyboard - ) + await callback_query.message.edit_text(text=DELETE_KEY_CONFIRM_MSG, reply_markup=confirmation_keyboard) except Exception as e: logger.error(f"Ошибка при обработке запроса на удаление ключа {client_id}: {e}") diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index d789359a..412fd6c3 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -328,7 +328,7 @@ async def handle_subscription(request: web.Request, old_subscription: bool = Fal stored_tg_id = client_data.get("tg_id") server_id = client_data["server_id"] - if not old_subscription and str(tg_id) != str(stored_tg_id): + if not old_subscription and int(tg_id) != int(stored_tg_id): logger.warning(f"Неверный tg_id для клиента с email {email}.") return web.Response(text="❌ Неверные данные. Получите свой ключ в боте.", status=403) diff --git a/handlers/notifications/general_notifications.py b/handlers/notifications/general_notifications.py index 2cfaa6f3..7d330c7c 100644 --- a/handlers/notifications/general_notifications.py +++ b/handlers/notifications/general_notifications.py @@ -1,5 +1,7 @@ import asyncio + from datetime import datetime, timedelta + import asyncpg import pytz @@ -9,14 +11,15 @@ from config import ( NOTIFICATION_TIME, NOTIFY_DELETE_DELAY, NOTIFY_DELETE_KEY, + NOTIFY_INACTIVE_TRAFFIC, NOTIFY_MAXPRICE, NOTIFY_RENEW, NOTIFY_RENEW_EXPIRED, RENEWAL_PRICES, TOTAL_GB, TRIAL_TIME_DISABLE, - NOTIFY_INACTIVE_TRAFFIC ) + from database import ( add_notification, check_notification_time, @@ -29,25 +32,28 @@ from database import ( ) from handlers.keys.key_utils import delete_key_from_cluster, renew_key_in_cluster from handlers.texts import ( - KEY_EXPIRY_10H, - KEY_EXPIRY_24H, - KEY_RENEWED, - KEY_RENEWED_TEMP_MSG, KEY_DELETED_MSG, KEY_EXPIRED_DELAY_HOURS_MINUTES_MSG, KEY_EXPIRED_DELAY_HOURS_MSG, KEY_EXPIRED_DELAY_MINUTES_MSG, KEY_EXPIRED_NO_DELAY_MSG, + KEY_EXPIRY_10H, + KEY_EXPIRY_24H, + KEY_RENEWED, + KEY_RENEWED_TEMP_MSG, ) from keyboards.notifications.notify_kb import build_notification_expired_kb, build_notification_kb from logger import logger + from .notify_utils import send_notification from .special_notifications import notify_inactive_trial_users, notify_users_no_traffic + router = Router() moscow_tz = pytz.timezone("Europe/Moscow") + async def periodic_notifications(bot: Bot): """ Обработчик, который: @@ -98,6 +104,7 @@ async def periodic_notifications(bot: Bot): await asyncio.sleep(NOTIFICATION_TIME) + async def notify_24h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int, threshold_time_24h: int, keys: list): logger.info("Начало проверки подписок, истекающих через 24 часа.") @@ -149,6 +156,7 @@ async def notify_24h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int, logger.info("✅ Обработка всех уведомлений за 24 часа завершена.") await asyncio.sleep(1) + async def notify_10h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int, threshold_time_10h: int, keys: list): """ Отправляет уведомления пользователям о том, что их подписка истекает через 10 часов. @@ -208,6 +216,7 @@ async def notify_10h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int, logger.info("✅ Обработка всех уведомлений за 10 часов завершена.") await asyncio.sleep(1) + async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: int, keys: list): """ Обрабатывает истекшие ключи, проверяя продление или удаление. @@ -319,6 +328,7 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: logger.info("✅ Обработка истекших ключей завершена.") await asyncio.sleep(1) + async def process_auto_renew_or_notify( bot, conn, key: dict, notification_id: str, renewal_period_months: int, standard_photo: str, standard_caption: str ): diff --git a/handlers/notifications/special_notifications.py b/handlers/notifications/special_notifications.py index 9ca81461..a63c1ea8 100644 --- a/handlers/notifications/special_notifications.py +++ b/handlers/notifications/special_notifications.py @@ -15,8 +15,8 @@ from database import ( check_notification_time, create_blocked_user, ) -from handlers.texts import TRIAL_INACTIVE_FIRST_MSG, TRIAL_INACTIVE_BONUS_MSG, ZERO_TRAFFIC_MSG from handlers.keys.key_utils import get_user_traffic +from handlers.texts import TRIAL_INACTIVE_BONUS_MSG, TRIAL_INACTIVE_FIRST_MSG, ZERO_TRAFFIC_MSG from logger import logger @@ -84,16 +84,11 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection): if trial_extended: total_days = NOTIFY_EXTRA_DAYS + TRIAL_TIME message = TRIAL_INACTIVE_BONUS_MSG.format( - display_name=display_name, - NOTIFY_EXTRA_DAYS=NOTIFY_EXTRA_DAYS, - total_days=total_days + display_name=display_name, NOTIFY_EXTRA_DAYS=NOTIFY_EXTRA_DAYS, total_days=total_days ) await conn.execute("UPDATE connections SET trial = -1 WHERE tg_id = $1", tg_id) else: - message = TRIAL_INACTIVE_FIRST_MSG.format( - display_name=display_name, - TRIAL_TIME=TRIAL_TIME - ) + message = TRIAL_INACTIVE_FIRST_MSG.format(display_name=display_name, TRIAL_TIME=TRIAL_TIME) try: await bot.send_message(tg_id, message, reply_markup=keyboard) diff --git a/handlers/pay.py b/handlers/pay.py index 036df442..d922ae21 100644 --- a/handlers/pay.py +++ b/handlers/pay.py @@ -9,7 +9,9 @@ from config import ( YOOKASSA_ENABLE, YOOMONEY_ENABLE, ) + from handlers.texts import PAYMENT_METHODS_MSG + from .utils import edit_or_send_message diff --git a/handlers/profile.py b/handlers/profile.py index f11c410e..e2083c6c 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -41,12 +41,12 @@ from handlers.buttons.profile import ( PAYMENT, ) from handlers.texts import ( + BALANCE_HISTORY_HEADER, + BALANCE_MANAGEMENT_TEXT, + INVITE_TEXT_NON_INLINE, get_referral_link, invite_message_send, profile_message_send, - BALANCE_MANAGEMENT_TEXT, - BALANCE_HISTORY_HEADER, - INVITE_TEXT_NON_INLINE ) from keyboards.admin.panel_kb import AdminPanelCallback from logger import logger diff --git a/handlers/start.py b/handlers/start.py index 10a8667e..3b38f344 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -1,4 +1,5 @@ import os + from typing import Any from aiogram import F, Router @@ -32,16 +33,16 @@ from database import ( from handlers.captcha import generate_captcha from handlers.keys.key_management import create_key from handlers.texts import ( + COUPON_SUCCESS_MSG, + GIFT_ALREADY_USED_OR_NOT_EXISTS_MSG, + NEW_REFERRAL_NOTIFICATION, + NOT_SUBSCRIBED_YET_MSG, + REFERRAL_SUCCESS_MSG, + SUBSCRIPTION_CHECK_ERROR_MSG, + SUBSCRIPTION_CONFIRMED_MSG, + SUBSCRIPTION_REQUIRED_MSG, WELCOME_TEXT, get_about_vpn, - SUBSCRIPTION_REQUIRED_MSG, - NOT_SUBSCRIBED_YET_MSG, - SUBSCRIPTION_CONFIRMED_MSG, - SUBSCRIPTION_CHECK_ERROR_MSG, - GIFT_ALREADY_USED_OR_NOT_EXISTS_MSG, - REFERRAL_SUCCESS_MSG, - NEW_REFERRAL_NOTIFICATION, - COUPON_SUCCESS_MSG, ) from keyboards.admin.panel_kb import AdminPanelCallback from logger import logger @@ -274,8 +275,7 @@ async def process_start_logic( await message.answer(REFERRAL_SUCCESS_MSG.format(referrer_tg_id=referrer_tg_id)) try: await bot.send_message( - referrer_tg_id, - NEW_REFERRAL_NOTIFICATION.format(referred_id=message.chat.id) + referrer_tg_id, NEW_REFERRAL_NOTIFICATION.format(referred_id=message.chat.id) ) logger.info( f"Уведомление отправлено пользователю {referrer_tg_id} о новом реферале {message.chat.id}" diff --git a/handlers/utils.py b/handlers/utils.py index b08c2475..4020c904 100644 --- a/handlers/utils.py +++ b/handlers/utils.py @@ -66,7 +66,7 @@ async def get_least_loaded_cluster() -> str: """ servers = await get_servers() server_to_cluster = {} - cluster_loads = {cluster: 0 for cluster in servers.keys()} + cluster_loads = dict.fromkeys(servers.keys(), 0) for cluster_name, cluster_servers in servers.items(): for server in cluster_servers: server_to_cluster[server["server_name"]] = cluster_name diff --git a/middlewares/loggings.py b/middlewares/loggings.py index db4a2ebf..f329759f 100644 --- a/middlewares/loggings.py +++ b/middlewares/loggings.py @@ -1,5 +1,5 @@ from collections.abc import Awaitable, Callable -from typing import Any,TypedDict +from typing import Any, TypedDict from aiogram import BaseMiddleware from aiogram.types import CallbackQuery, InlineQuery, Message, TelegramObject, User diff --git a/middlewares/throttling.py b/middlewares/throttling.py index 50e20bf4..710744c5 100644 --- a/middlewares/throttling.py +++ b/middlewares/throttling.py @@ -28,4 +28,4 @@ class ThrottlingMiddleware(BaseMiddleware): else: self.cache[user_id] = current_count + 1 - return await handler(event, data) \ No newline at end of file + return await handler(event, data) diff --git a/servers.py b/servers.py index d23bda94..b403e24f 100644 --- a/servers.py +++ b/servers.py @@ -60,10 +60,7 @@ async def notify_admin(server_name: str, status: str, down_duration: timedelta = ) else: downtime = str(down_duration).split(".")[0] - message = ( - f"✅ Сервер '{server_name}' снова в сети!\n\n" - f"⏳ Время простоя: {downtime}." - ) + message = f"✅ Сервер '{server_name}' снова в сети!\n\n⏳ Время простоя: {downtime}." for admin_id in ADMIN_ID: logger.info(f"📨 Отправляем уведомление '{status}' администратору {admin_id} о сервере {server_name}") @@ -119,11 +116,13 @@ async def check_servers(): if last_ping_time is None: last_ping_times[server_name] = current_time - last_down_times[server_name] = current_time + last_down_times[server_name] = current_time if last_ping_time and (current_time - last_ping_time > timedelta(seconds=PING_TIME * 3)): if server_name not in notified_servers: - logger.warning(f"🚨 Уведомление: сервер {server_name} не отвечает более {PING_TIME * 3} секунд!") + logger.warning( + f"🚨 Уведомление: сервер {server_name} не отвечает более {PING_TIME * 3} секунд!" + ) await notify_admin(server_name, "down") notified_servers.add(server_name) last_down_times[server_name] = current_time @@ -145,4 +144,4 @@ async def check_servers(): def extract_host(api_url: str) -> str: """Извлекает хост из `api_url`.""" match = re.match(r"(https?://)?([^:/]+)", api_url) - return match.group(2) if match else api_url \ No newline at end of file + return match.group(2) if match else api_url diff --git a/utils/csv_export.py b/utils/csv_export.py index 276c79ea..d4f2ed30 100644 --- a/utils/csv_export.py +++ b/utils/csv_export.py @@ -1,6 +1,7 @@ +import csv + from io import StringIO from typing import Any -import csv from aiogram.types import BufferedInputFile @@ -115,14 +116,14 @@ async def export_referrals_csv(referrer_tg_id: int, session: Any) -> BufferedInp WHERE r.referrer_tg_id = $1 ORDER BY r.referred_tg_id """, - referrer_tg_id + referrer_tg_id, ) if not rows: return None output = StringIO() - writer = csv.writer(output, delimiter=';') + writer = csv.writer(output, delimiter=";") writer.writerow(["Приглашённый (tg_id)", "Имя"]) for row in rows: @@ -136,4 +137,4 @@ async def export_referrals_csv(referrer_tg_id: int, session: Any) -> BufferedInp csv_data = output.getvalue().encode("utf-8") filename = f"referrals_{referrer_tg_id}.csv" - return BufferedInputFile(file=csv_data, filename=filename) \ No newline at end of file + return BufferedInputFile(file=csv_data, filename=filename)