ruff formatting
This commit is contained in:
+11
-13
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
await callback.message.edit_reply_markup(reply_markup=build_management_kb())
|
||||
|
||||
+38
-34
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -232,7 +232,9 @@ async def process_callback_view_key(callback_query: CallbackQuery, session: Any)
|
||||
f"Осталось: <b>{format_days(days)}</b>, <b>{format_hours(hours)}</b>, <b>{format_minutes(minutes)}</b>"
|
||||
)
|
||||
|
||||
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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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("Обработка истекших ключей завершена.")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+18
-15
@@ -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:
|
||||
Название месяца на русском языке.
|
||||
"""
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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)
|
||||
|
||||
+4
-4
@@ -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']},"
|
||||
|
||||
Reference in New Issue
Block a user