diff --git a/cli_launcher.py b/cli_launcher.py
index 1decdea5..54eea89f 100755
--- a/cli_launcher.py
+++ b/cli_launcher.py
@@ -28,7 +28,7 @@ console = Console()
BACK_DIR = os.path.expanduser("~/.solobot_backup")
TEMP_DIR = os.path.expanduser("~/.solobot_tmp")
PROJECT_DIR = os.path.abspath(os.path.dirname(__file__))
-IS_ROOT_DIR = PROJECT_DIR == '/root'
+IS_ROOT_DIR = PROJECT_DIR == "/root"
if IS_ROOT_DIR:
console.print("[bold red]⛔ КРИТИЧЕСКАЯ ОШИБКА:[/bold red]")
@@ -73,12 +73,12 @@ def fix_permissions():
"""Устанавливает корректные права на файлы проекта"""
console.print("[yellow]🔧 Устанавливаю права на файлы...[/yellow]")
try:
- user = os.getenv('SUDO_USER') or os.getenv('USER')
+ user = os.getenv("SUDO_USER") or os.getenv("USER")
if user:
subprocess.run(["sudo", "chown", "-R", f"{user}:{user}", PROJECT_DIR], check=True)
-
+
subprocess.run(["sudo", "chmod", "-R", "u=rwX,go=rX", PROJECT_DIR], check=True)
-
+
console.print("[green]✅ Права успешно установлены[/green]")
except subprocess.CalledProcessError as e:
console.print(f"[red]❌ Ошибка при установке прав: {e}[/red]")
@@ -195,7 +195,7 @@ def update_from_beta():
if not Confirm.ask("[yellow]🔁 Подтвердите обновление Solobot с ветки DEV[/yellow]"):
return
-
+
console.print("[red]⚠️ ВНИМАНИЕ! Папка бота будет перезаписана![/red]")
if not Confirm.ask("[red]❓ Продолжить обновление?[/red]"):
return
@@ -215,11 +215,11 @@ def update_from_beta():
subprocess.run(["sudo", "rm", "-rf", os.path.join(PROJECT_DIR, "venv")])
clean_project_dir_safe(update_buttons=update_buttons)
-
+
exclude_options = "--exclude=img"
if not update_buttons:
exclude_options += " --exclude=handlers/buttons.py"
-
+
subprocess.run(f"rsync -a {exclude_options} {TEMP_DIR}/ {PROJECT_DIR}/", shell=True)
subprocess.run(["rm", "-rf", TEMP_DIR])
@@ -258,8 +258,7 @@ def update_from_release():
console.print(f"[cyan]{idx}.[/cyan] {tag}")
selected = Prompt.ask(
- "[bold blue]Выберите номер релиза[/bold blue]",
- choices=[str(i) for i in range(1, len(tag_choices) + 1)]
+ "[bold blue]Выберите номер релиза[/bold blue]", choices=[str(i) for i in range(1, len(tag_choices) + 1)]
)
tag_name = tag_choices[int(selected) - 1]
@@ -277,7 +276,7 @@ def update_from_release():
exclude_options = "--exclude=img"
if not update_buttons:
exclude_options += " --exclude=handlers/buttons.py"
-
+
subprocess.run(f"rsync -a {exclude_options} {TEMP_DIR}/ {PROJECT_DIR}/", shell=True)
subprocess.run(["rm", "-rf", TEMP_DIR])
@@ -291,7 +290,6 @@ def update_from_release():
def show_update_menu():
-
if IS_ROOT_DIR:
console.print("[red]⛔ Обновление невозможно: бот находится в /root[/red]")
console.print("[yellow]Перенесите бота в отдельную папку и повторите попытку[/yellow]")
@@ -335,9 +333,9 @@ def update_cli_launcher():
try:
url = "https://raw.githubusercontent.com/Vladless/Solo_bot/dev/cli_launcher.py"
response = requests.get(url, timeout=10)
-
+
if response.status_code == 200:
- with open(os.path.join(PROJECT_DIR, "cli_launcher.py"), 'w', encoding='utf-8') as f:
+ with open(os.path.join(PROJECT_DIR, "cli_launcher.py"), "w", encoding="utf-8") as f:
f.write(response.text)
console.print("[green]✅ CLI лаунчер успешно обновлён[/green]")
os.chmod(os.path.join(PROJECT_DIR, "cli_launcher.py"), 0o755)
diff --git a/handlers/admin/management/management_handler.py b/handlers/admin/management/management_handler.py
index 176d3bfc..4372956a 100644
--- a/handlers/admin/management/management_handler.py
+++ b/handlers/admin/management/management_handler.py
@@ -6,10 +6,10 @@ from asyncpg import Connection
from filters.admin import IsAdminFilter
from logger import logger
+from middlewares import maintenance
from ..panel.keyboard import build_admin_back_kb
from .keyboard import AdminPanelCallback, build_management_kb
-from middlewares import maintenance
router = Router()
@@ -78,10 +78,9 @@ async def process_new_domain(message: Message, state: FSMContext, session: Conne
@router.callback_query(AdminPanelCallback.filter(F.action == "toggle_maintenance"))
async def toggle_maintenance_mode(callback: CallbackQuery):
-
maintenance.maintenance_mode = not maintenance.maintenance_mode
new_status = "включён" if maintenance.maintenance_mode else "выключен"
await callback.answer(f"🛠️ Режим обслуживания {new_status}.", show_alert=True)
-
- await callback.message.edit_reply_markup(reply_markup=build_management_kb())
\ No newline at end of file
+
+ await callback.message.edit_reply_markup(reply_markup=build_management_kb())
diff --git a/handlers/captcha.py b/handlers/captcha.py
index 7a2da14a..880bc76b 100644
--- a/handlers/captcha.py
+++ b/handlers/captcha.py
@@ -24,22 +24,36 @@ async def generate_captcha(message: Message, state: FSMContext):
all_emojis = [correct_emoji] + wrong_emojis
random.shuffle(all_emojis)
- from_user = message.from_user or message.chat
+ state_data = await state.get_data()
- await state.update_data(
- correct_emoji=correct_emoji,
- message_id=message.message_id,
- chat_id=message.chat.id,
- original_text=message.text,
- user_data={
- "tg_id": from_user.id,
- "username": getattr(from_user, "username", None),
- "first_name": getattr(from_user, "first_name", None),
- "last_name": getattr(from_user, "last_name", None),
- "language_code": getattr(from_user, "language_code", None),
- "is_bot": getattr(from_user, "is_bot", False),
- }
- )
+ if "user_data" not in state_data:
+ from_user = message.from_user
+ if not from_user:
+ logger.warning("[CAPTCHA] ❗ from_user отсутствует — невозможно сохранить user_data")
+ return None
+
+ await state.update_data(
+ user_data={
+ "tg_id": from_user.id,
+ "username": getattr(from_user, "username", None),
+ "first_name": getattr(from_user, "first_name", None),
+ "last_name": getattr(from_user, "last_name", None),
+ "language_code": getattr(from_user, "language_code", None),
+ "is_bot": getattr(from_user, "is_bot", False),
+ }
+ )
+
+ update_data = {
+ "correct_emoji": correct_emoji,
+ "message_id": message.message_id,
+ "chat_id": message.chat.id,
+ }
+
+ state_data = await state.get_data()
+ if "original_text" not in state_data:
+ update_data["original_text"] = message.text
+
+ await state.update_data(**update_data)
builder = InlineKeyboardBuilder()
for emoji in all_emojis:
@@ -59,38 +73,28 @@ async def check_captcha(callback: CallbackQuery, state: FSMContext, session: Any
selected_emoji = callback.data.split("captcha_")[1]
state_data = await state.get_data()
correct_emoji = state_data.get("correct_emoji")
- message_id = state_data.get("message_id")
- chat_id = state_data.get("chat_id")
original_text = state_data.get("original_text")
user_data = state_data.get("user_data")
- if not message_id or not chat_id:
- target_message = callback.message
- else:
- try:
- target_message = await callback.bot.edit_message_text(
- chat_id=chat_id,
- message_id=message_id,
- text=callback.message.text,
- )
- except Exception:
- target_message = callback.message
+ target_message = callback.message
if selected_emoji == correct_emoji:
logger.info(f"Пользователь {callback.from_user.id} успешно прошел капчу")
+ logger.debug(f"[CAPTCHA] user_data передано в process_start_logic: {user_data}")
await process_start_logic(
message=target_message,
state=state,
session=session,
admin=admin,
text_to_process=original_text,
- user_data=user_data
+ user_data=user_data,
)
else:
logger.warning(f"Пользователь {callback.from_user.id} неверно ответил на капчу")
captcha = await generate_captcha(target_message, state)
- await edit_or_send_message(
- target_message=target_message,
- text=captcha["text"],
- reply_markup=captcha["markup"],
- )
+ if captcha:
+ await edit_or_send_message(
+ target_message=target_message,
+ text=captcha["text"],
+ reply_markup=captcha["markup"],
+ )
diff --git a/handlers/keys/key_mode/key_country_mode.py b/handlers/keys/key_mode/key_country_mode.py
index 532e5098..1d414a8f 100644
--- a/handlers/keys/key_mode/key_country_mode.py
+++ b/handlers/keys/key_mode/key_country_mode.py
@@ -3,8 +3,8 @@ import uuid
from datetime import datetime
from typing import Any
-import asyncpg
+import asyncpg
import pytz
from aiogram import F, Router
@@ -14,10 +14,10 @@ from aiogram.utils.keyboard import InlineKeyboardBuilder
from py3xui import AsyncApi
from config import (
- DATABASE_URL,
ADMIN_PASSWORD,
ADMIN_USERNAME,
CONNECT_PHONE_BUTTON,
+ DATABASE_URL,
PUBLIC_LINK,
REMNAWAVE_LOGIN,
REMNAWAVE_PASSWORD,
@@ -39,7 +39,12 @@ from handlers.texts import (
SELECT_COUNTRY_MSG,
key_message_success,
)
-from handlers.utils import edit_or_send_message, generate_random_email, get_least_loaded_cluster, is_full_remnawave_cluster
+from handlers.utils import (
+ edit_or_send_message,
+ generate_random_email,
+ get_least_loaded_cluster,
+ is_full_remnawave_cluster,
+)
from logger import logger
from panels.remnawave import RemnawaveAPI
from panels.three_xui import delete_client, get_xui_instance
diff --git a/handlers/keys/key_view.py b/handlers/keys/key_view.py
index 8b3fe11b..896a8561 100644
--- a/handlers/keys/key_view.py
+++ b/handlers/keys/key_view.py
@@ -232,7 +232,9 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
f"Осталось: {format_days(days)}, {format_hours(hours)}, {format_minutes(minutes)}"
)
- formatted_expiry_date = f"{expiry_date.strftime('%d')} {get_russian_month(expiry_date)} {expiry_date.strftime('%Y')} года"
+ formatted_expiry_date = (
+ f"{expiry_date.strftime('%d')} {get_russian_month(expiry_date)} {expiry_date.strftime('%Y')} года"
+ )
response_message = key_message(
final_link,
formatted_expiry_date,
diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py
index c6ed0f83..f571ae57 100644
--- a/handlers/keys/subscriptions.py
+++ b/handlers/keys/subscriptions.py
@@ -12,8 +12,8 @@ from aiohttp import web
from config import (
DATABASE_URL,
- RANDOM_SUBSCRIPTIONS,
PROJECT_NAME,
+ RANDOM_SUBSCRIPTIONS,
SUPERNODE,
SUPPORT_CHAT_URL,
USERNAME_BOT,
@@ -41,7 +41,9 @@ async def fetch_url_content(url: str, identifier: str) -> tuple[list[str], dict[
return [], {}
-async def combine_unique_lines(urls: list[str], identifier: str, query_string: str) -> tuple[list[str], list[dict[str, str]]]:
+async def combine_unique_lines(
+ urls: list[str], identifier: str, query_string: str
+) -> tuple[list[str], list[dict[str, str]]]:
if SUPERNODE:
logger.info(f"Режим SUPERNODE активен. Возвращаем первую ссылку для идентификатора: {identifier}")
if not urls:
@@ -82,7 +84,9 @@ async def get_subscription_urls(server_id: str, email: str, conn, include_remnaw
return urls
-def calculate_traffic(cleaned_subscriptions: list[str], expiry_time_ms: int | None, headers_list: list[dict[str, str]]) -> str:
+def calculate_traffic(
+ cleaned_subscriptions: list[str], expiry_time_ms: int | None, headers_list: list[dict[str, str]]
+) -> str:
logger.debug(f"Calculating traffic with subscriptions: {cleaned_subscriptions}, headers: {headers_list}")
expire_timestamp = int(expiry_time_ms / 1000) if expiry_time_ms else 0
diff --git a/handlers/notifications/general_notifications.py b/handlers/notifications/general_notifications.py
index d6951065..d19adce9 100644
--- a/handlers/notifications/general_notifications.py
+++ b/handlers/notifications/general_notifications.py
@@ -1,9 +1,12 @@
import asyncio
+
from datetime import datetime, timedelta
+
import asyncpg
-from aiogram import Bot, Router
import pytz
+from aiogram import Bot, Router
+
from config import (
DATABASE_URL,
NOTIFICATION_TIME,
@@ -176,7 +179,9 @@ async def notify_24h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int,
if NOTIFY_RENEW:
try:
- await process_auto_renew_or_notify(bot, conn, key, notification_id, 1, "notify_24h.jpg", notification_text)
+ await process_auto_renew_or_notify(
+ bot, conn, key, notification_id, 1, "notify_24h.jpg", notification_text
+ )
except Exception as e:
logger.error(f"Ошибка авто-продления/уведомления для пользователя {tg_id}: {e}")
continue
@@ -193,14 +198,16 @@ async def notify_24h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int,
if messages:
results = await send_messages_with_limit(bot, messages, conn=conn)
sent_count = 0
- for msg, result in zip(messages, results):
+ for msg, result in zip(messages, results, strict=False):
tg_id = msg["tg_id"]
if result:
await add_notification(tg_id, msg["notification_id"], session=conn)
sent_count += 1
logger.info(f"📢 Отправлено уведомление об истекающей подписке {msg['email']} пользователю {tg_id}.")
else:
- logger.warning(f"📢 Не удалось отправить уведомление об истекающей подписке {msg['email']} пользователю {tg_id}.")
+ logger.warning(
+ f"📢 Не удалось отправить уведомление об истекающей подписке {msg['email']} пользователю {tg_id}."
+ )
logger.info(f"Отправлено {sent_count} уведомлений об истечении подписки через 24 часа.")
logger.info("Обработка всех уведомлений за 24 часа завершена.")
@@ -273,14 +280,16 @@ async def notify_10h_keys(bot: Bot, conn: asyncpg.Connection, current_time: int,
if messages:
results = await send_messages_with_limit(bot, messages, conn=conn)
sent_count = 0
- for msg, result in zip(messages, results):
+ for msg, result in zip(messages, results, strict=False):
tg_id = msg["tg_id"]
if result:
await add_notification(tg_id, msg["notification_id"], session=conn)
sent_count += 1
logger.info(f"📢 Отправлено уведомление об истекающей подписке {msg['email']} пользователю {tg_id}.")
else:
- logger.warning(f"📢 Не удалось отправить уведомление об истекающей подписке {msg['email']} пользователю {tg_id}.")
+ logger.warning(
+ f"📢 Не удалось отправить уведомление об истекающей подписке {msg['email']} пользователю {tg_id}."
+ )
logger.info(f"Отправлено {sent_count} уведомлений об истечении подписки через 10 часов.")
logger.info("Обработка всех уведомлений за 10 часов завершена.")
@@ -395,7 +404,7 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
if messages:
results = await send_messages_with_limit(bot, messages, conn=conn)
sent_count = 0
- for msg, result in zip(messages, results):
+ for msg, result in zip(messages, results, strict=False):
tg_id = msg["tg_id"]
email = msg["email"]
if result:
@@ -403,7 +412,9 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
sent_count += 1
logger.info(f"📢 Отправлено уведомление об истекшем ключе для подписки {email} пользователю {tg_id}.")
else:
- logger.warning(f"📢 Не удалось отправить уведомление об истекшем ключе для подписки {email} пользователю {tg_id}.")
+ logger.warning(
+ f"📢 Не удалось отправить уведомление об истекшем ключе для подписки {email} пользователю {tg_id}."
+ )
logger.info(f"Отправлено {sent_count} уведомлений об истекших ключах.")
logger.info("Обработка истекших ключей завершена.")
diff --git a/handlers/notifications/notify_utils.py b/handlers/notifications/notify_utils.py
index 7092c91b..1d70682b 100644
--- a/handlers/notifications/notify_utils.py
+++ b/handlers/notifications/notify_utils.py
@@ -3,6 +3,7 @@ import os
import aiofiles
import asyncpg
+
from aiogram import Bot
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError, TelegramRetryAfter
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup
@@ -16,7 +17,7 @@ async def send_messages_with_limit(
messages: list[dict],
conn: asyncpg.Connection = None,
source_file: str = None,
- messages_per_second: int = 25
+ messages_per_second: int = 25,
):
"""
Отправляет сообщения с ограничением по количеству сообщений в секунду.
@@ -28,16 +29,10 @@ async def send_messages_with_limit(
batch = messages[i : i + batch_size]
tasks = []
for msg in batch:
- tasks.append(send_notification(
- bot,
- msg["tg_id"],
- msg.get("photo"),
- msg["text"],
- msg.get("keyboard")
- ))
+ tasks.append(send_notification(bot, msg["tg_id"], msg.get("photo"), msg["text"], msg.get("keyboard")))
batch_results = await asyncio.gather(*tasks, return_exceptions=True)
processed_results = []
- for msg, result in zip(batch, batch_results):
+ for msg, result in zip(batch, batch_results, strict=False):
tg_id = msg["tg_id"]
if isinstance(result, bool) and result:
processed_results.append(True)
@@ -94,6 +89,7 @@ def rate_limited_send(func):
tg_id = kwargs.get("tg_id") or args[1]
logger.error(f"❌ Ошибка отправки сообщения пользователю {tg_id}: {e}")
return False
+
return wrapper
diff --git a/handlers/notifications/special_notifications.py b/handlers/notifications/special_notifications.py
index 9f806027..39466173 100644
--- a/handlers/notifications/special_notifications.py
+++ b/handlers/notifications/special_notifications.py
@@ -1,8 +1,10 @@
import asyncio
+
from datetime import datetime, timedelta
import asyncpg
import pytz
+
from aiogram import Bot, Router, types
from aiogram.utils.keyboard import InlineKeyboardBuilder
@@ -23,6 +25,7 @@ from handlers.texts import (
)
from handlers.utils import format_days
from logger import logger
+
from .notify_utils import send_messages_with_limit, send_notification
@@ -76,14 +79,10 @@ async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
})
if messages:
results = await send_messages_with_limit(
- bot,
- messages,
- conn=conn,
- source_file="special_notifications",
- messages_per_second=25
+ bot, messages, conn=conn, source_file="special_notifications", messages_per_second=25
)
sent_count = 0
- for msg, result in zip(messages, results):
+ for msg, result in zip(messages, results, strict=False):
tg_id = msg["tg_id"]
if result:
await add_notification(tg_id, msg["notification_id"], session=conn)
@@ -148,9 +147,7 @@ async def notify_users_no_traffic(bot: Bot, conn: asyncpg.Connection, current_ti
)
try:
- await conn.execute(
- "UPDATE keys SET notified = TRUE WHERE tg_id = $1 AND client_id = $2", tg_id, client_id
- )
+ await conn.execute("UPDATE keys SET notified = TRUE WHERE tg_id = $1 AND client_id = $2", tg_id, client_id)
except Exception as e:
logger.error(f"Ошибка обновления notified для пользователя {tg_id} (client_id: {client_id}): {e}")
continue
@@ -171,14 +168,10 @@ async def notify_users_no_traffic(bot: Bot, conn: asyncpg.Connection, current_ti
if messages:
results = await send_messages_with_limit(
- bot,
- messages,
- conn=conn,
- source_file="special_notifications",
- messages_per_second=25
+ bot, messages, conn=conn, source_file="special_notifications", messages_per_second=25
)
sent_count = 0
- for msg, result in zip(messages, results):
+ for msg, result in zip(messages, results, strict=False):
tg_id = msg["tg_id"]
if result:
sent_count += 1
diff --git a/handlers/start.py b/handlers/start.py
index 9ea7a3ce..586ceacd 100644
--- a/handlers/start.py
+++ b/handlers/start.py
@@ -142,7 +142,7 @@ async def process_start_logic(
session: Any,
admin: bool,
text_to_process: str = None,
- user_data: dict | None = None
+ user_data: dict | None = None,
):
text = text_to_process or message.text or message.caption
user_data = user_data or {
diff --git a/handlers/utils.py b/handlers/utils.py
index 9bc59208..7d737b02 100644
--- a/handlers/utils.py
+++ b/handlers/utils.py
@@ -4,13 +4,14 @@ import re
import secrets
import string
+from datetime import datetime
+
import aiofiles
import aiohttp
import asyncpg
from aiogram.types import BufferedInputFile, InlineKeyboardMarkup, InputMediaPhoto, Message
-from datetime import datetime
from bot import bot
from config import ADMIN_ID, DATABASE_URL
from database import get_all_keys, get_servers
@@ -146,12 +147,14 @@ def format_days(days: int) -> str:
return "0 дней"
return f"{days} {get_plural_form(days, 'день', 'дня', 'дней')}"
+
def format_hours(hours: int) -> str:
"""Форматирует количество часов с правильным склонением"""
if hours <= 0:
return "0 часов"
return f"{hours} {get_plural_form(hours, 'час', 'часа', 'часов')}"
+
def format_minutes(minutes: int) -> str:
"""Форматирует количество минут с правильным склонением"""
if minutes <= 0:
@@ -272,28 +275,28 @@ def sanitize_key_name(key_name: str) -> str:
RUSSIAN_MONTHS = {
- 'January': 'Января',
- 'February': 'Февраля',
- 'March': 'Марта',
- 'April': 'Апреля',
- 'May': 'Мая',
- 'June': 'Июня',
- 'July': 'Июля',
- 'August': 'Августа',
- 'September': 'Сентября',
- 'October': 'Октября',
- 'November': 'Ноября',
- 'December': 'Декабря'
+ "January": "Января",
+ "February": "Февраля",
+ "March": "Марта",
+ "April": "Апреля",
+ "May": "Мая",
+ "June": "Июня",
+ "July": "Июля",
+ "August": "Августа",
+ "September": "Сентября",
+ "October": "Октября",
+ "November": "Ноября",
+ "December": "Декабря",
}
def get_russian_month(date: datetime) -> str:
"""
Преобразует английское название месяца в русское.
-
+
Args:
date: Объект datetime, из которого извлекается месяц.
-
+
Returns:
Название месяца на русском языке.
"""
diff --git a/middlewares/__init__.py b/middlewares/__init__.py
index 51e4b236..05cfe773 100644
--- a/middlewares/__init__.py
+++ b/middlewares/__init__.py
@@ -5,11 +5,10 @@ from aiogram.dispatcher.middlewares.base import BaseMiddleware
from .admin import AdminMiddleware
from .loggings import LoggingMiddleware
+from .maintenance import MaintenanceModeMiddleware
from .session import SessionMiddleware
from .throttling import ThrottlingMiddleware
from .user import UserMiddleware
-from .maintenance import MaintenanceModeMiddleware
-
def register_middleware(
@@ -17,13 +16,12 @@ def register_middleware(
middlewares: Iterable[BaseMiddleware | type[BaseMiddleware]] | None = None,
exclude: Iterable[str] | None = None,
) -> None:
- """Регистрирует middleware в диспетчере.
- """
+ """Регистрирует middleware в диспетчере."""
if middlewares is None:
available_middlewares = {
"admin": AdminMiddleware(),
"session": SessionMiddleware(),
- "maintenance": MaintenanceModeMiddleware(),
+ "maintenance": MaintenanceModeMiddleware(),
"logging": LoggingMiddleware(),
"throttling": ThrottlingMiddleware(),
"user": UserMiddleware(),
diff --git a/middlewares/maintenance.py b/middlewares/maintenance.py
index bbcef0a6..09a41ae3 100644
--- a/middlewares/maintenance.py
+++ b/middlewares/maintenance.py
@@ -1,9 +1,12 @@
from aiogram import BaseMiddleware
-from aiogram.types import Message, CallbackQuery
+from aiogram.types import CallbackQuery, Message
+
from config import ADMIN_ID
+
maintenance_mode = False
+
class MaintenanceModeMiddleware(BaseMiddleware):
async def __call__(self, handler, event, data):
if maintenance_mode:
@@ -16,5 +19,5 @@ class MaintenanceModeMiddleware(BaseMiddleware):
if user_id and user_id not in ADMIN_ID:
await event.answer("⚙️ Бот временно недоступен. Ведутся технические работы.")
return
-
+
return await handler(event, data)
diff --git a/utils/csv_export.py b/utils/csv_export.py
index 40d99dca..565909fc 100644
--- a/utils/csv_export.py
+++ b/utils/csv_export.py
@@ -1,6 +1,6 @@
import csv
-from datetime import datetime
+from datetime import datetime
from io import StringIO
from typing import Any
@@ -185,9 +185,9 @@ async def export_keys_csv(session) -> BufferedInputFile:
buffer.write("tg_id,client_id,email,created_at,expiry_time,key,server_id,is_frozen,alias\n")
for row in keys:
- created_at = datetime.utcfromtimestamp(row['created_at'] / 1000).strftime("%Y-%m-%d %H:%M:%S")
- expiry_time = datetime.utcfromtimestamp(row['expiry_time'] / 1000).strftime("%Y-%m-%d %H:%M:%S")
-
+ created_at = datetime.utcfromtimestamp(row["created_at"] / 1000).strftime("%Y-%m-%d %H:%M:%S")
+ expiry_time = datetime.utcfromtimestamp(row["expiry_time"] / 1000).strftime("%Y-%m-%d %H:%M:%S")
+
buffer.write(
f"{row['tg_id']},{row['client_id']},{row['email']},"
f"{created_at},{expiry_time},{row['key']},"