ruff format/ minor fixes and improvements

This commit is contained in:
Vladless
2025-10-24 09:39:54 +03:00
parent a573fdc4d4
commit 3c25fb3015
24 changed files with 183 additions and 124 deletions
View File
Binary file not shown.
Binary file not shown.
+9 -23
View File
@@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from sqlalchemy import delete, exists, or_, select, update, func from sqlalchemy import delete, exists, func, or_, select, update
from sqlalchemy.dialects.postgresql import insert from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@@ -11,12 +11,12 @@ from database.models import (
CouponUsage, CouponUsage,
Gift, Gift,
GiftUsage, GiftUsage,
Key,
Notification, Notification,
Payment, Payment,
Referral, Referral,
TemporaryData, TemporaryData,
User, User,
Key,
) )
from logger import logger from logger import logger
@@ -81,17 +81,13 @@ async def check_user_exists(session: AsyncSession, tg_id: int) -> bool:
async def get_balance(session: AsyncSession, tg_id: int) -> float: async def get_balance(session: AsyncSession, tg_id: int) -> float:
result = await session.execute( result = await session.execute(select(func.coalesce(User.balance, 0.0)).where(User.tg_id == tg_id))
select(func.coalesce(User.balance, 0.0)).where(User.tg_id == tg_id)
)
return round(float(result.scalar_one()), 1) return round(float(result.scalar_one()), 1)
async def set_user_balance(session: AsyncSession, tg_id: int, balance: float) -> None: async def set_user_balance(session: AsyncSession, tg_id: int, balance: float) -> None:
try: try:
await session.execute( await session.execute(update(User).where(User.tg_id == tg_id).values(balance=balance))
update(User).where(User.tg_id == tg_id).values(balance=balance)
)
await session.commit() await session.commit()
except SQLAlchemyError as e: except SQLAlchemyError as e:
logger.error(f"Ошибка при установке баланса для пользователя {tg_id}: {e}") logger.error(f"Ошибка при установке баланса для пользователя {tg_id}: {e}")
@@ -100,9 +96,7 @@ async def set_user_balance(session: AsyncSession, tg_id: int, balance: float) ->
async def update_trial(session: AsyncSession, tg_id: int, status: int): async def update_trial(session: AsyncSession, tg_id: int, status: int):
try: try:
await session.execute( await session.execute(update(User).where(User.tg_id == tg_id).values(trial=status))
update(User).where(User.tg_id == tg_id).values(trial=status)
)
await session.commit() await session.commit()
logger.info(f"[DB] Триал статус обновлён для пользователя {tg_id}: {status}") logger.info(f"[DB] Триал статус обновлён для пользователя {tg_id}: {status}")
except SQLAlchemyError as e: except SQLAlchemyError as e:
@@ -111,9 +105,7 @@ async def update_trial(session: AsyncSession, tg_id: int, status: int):
async def get_trial(session: AsyncSession, tg_id: int) -> int: async def get_trial(session: AsyncSession, tg_id: int) -> int:
result = await session.execute( result = await session.execute(select(func.coalesce(User.trial, 0)).where(User.tg_id == tg_id))
select(func.coalesce(User.trial, 0)).where(User.tg_id == tg_id)
)
return int(result.scalar_one()) return int(result.scalar_one())
@@ -189,19 +181,13 @@ async def delete_user_data(session: AsyncSession, tg_id: int):
try: try:
await session.execute(delete(Notification).where(Notification.tg_id == tg_id)) await session.execute(delete(Notification).where(Notification.tg_id == tg_id))
await session.execute( await session.execute(
delete(GiftUsage).where( delete(GiftUsage).where(GiftUsage.gift_id.in_(select(Gift.gift_id).where(Gift.sender_tg_id == tg_id)))
GiftUsage.gift_id.in_(select(Gift.gift_id).where(Gift.sender_tg_id == tg_id))
)
) )
await session.execute(delete(Gift).where(Gift.sender_tg_id == tg_id)) await session.execute(delete(Gift).where(Gift.sender_tg_id == tg_id))
await session.execute( await session.execute(update(Gift).where(Gift.recipient_tg_id == tg_id).values(recipient_tg_id=None))
update(Gift).where(Gift.recipient_tg_id == tg_id).values(recipient_tg_id=None)
)
await session.execute(delete(Payment).where(Payment.tg_id == tg_id)) await session.execute(delete(Payment).where(Payment.tg_id == tg_id))
await session.execute( await session.execute(
delete(Referral).where( delete(Referral).where(or_(Referral.referrer_tg_id == tg_id, Referral.referred_tg_id == tg_id))
or_(Referral.referrer_tg_id == tg_id, Referral.referred_tg_id == tg_id)
)
) )
await session.execute(delete(CouponUsage).where(CouponUsage.user_id == tg_id)) await session.execute(delete(CouponUsage).where(CouponUsage.user_id == tg_id))
await delete_key(session, tg_id) await delete_key(session, tg_id)
+5 -3
View File
@@ -537,7 +537,7 @@ async def handle_sync_server(
expire_iso = ( expire_iso = (
datetime.utcfromtimestamp(key["expiry_time"] / 1000).replace(tzinfo=timezone.utc).isoformat() datetime.utcfromtimestamp(key["expiry_time"] / 1000).replace(tzinfo=timezone.utc).isoformat()
) )
remna = RemnawaveAPI(key["api_url"]) remna = RemnawaveAPI(key["api_url"])
if not await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD): if not await remna.login(REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD):
logger.error(f"Не удалось авторизоваться в Remnawave для сервера {server_name}") logger.error(f"Не удалось авторизоваться в Remnawave для сервера {server_name}")
@@ -561,7 +561,7 @@ async def handle_sync_server(
traffic_limit_bytes=traffic_limit_bytes, traffic_limit_bytes=traffic_limit_bytes,
hwid_device_limit=hwid_limit, hwid_device_limit=hwid_limit,
) )
if not success: if not success:
logger.warning("[Sync] ошибка обновления, пробуем пересоздать") logger.warning("[Sync] ошибка обновления, пробуем пересоздать")
@@ -673,7 +673,9 @@ async def handle_sync_cluster(
filtered_servers = cluster_servers filtered_servers = cluster_servers
if subgroup_title: if subgroup_title:
filtered_servers = [s for s in cluster_servers if subgroup_title in s.get("tariff_subgroups", [])] filtered_servers = [
s for s in cluster_servers if subgroup_title in s.get("tariff_subgroups", [])
]
if not filtered_servers: if not filtered_servers:
logger.warning( logger.warning(
f"[Sync] В кластере {cluster_name} не найдено серверов для подгруппы '{subgroup_title}'. Использую весь кластер." f"[Sync] В кластере {cluster_name} не найдено серверов для подгруппы '{subgroup_title}'. Использую весь кластер."
+3 -1
View File
@@ -23,7 +23,9 @@ async def build_panel_kb(admin_role: str) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row( builder.row(
InlineKeyboardButton(text="👤 Поиск пользователя", callback_data=AdminPanelCallback(action="search_user").pack()), InlineKeyboardButton(
text="👤 Поиск пользователя", callback_data=AdminPanelCallback(action="search_user").pack()
),
InlineKeyboardButton(text="🔑 Поиск подписок", callback_data=AdminPanelCallback(action="search_key").pack()), InlineKeyboardButton(text="🔑 Поиск подписок", callback_data=AdminPanelCallback(action="search_key").pack()),
) )
+7 -7
View File
@@ -137,14 +137,14 @@ async def process_callback_delete_server(
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
for s_name, key_count in all_servers: for s_name, key_count in all_servers:
callback_data = f"transfer_to_server|{s_name}|{server_name}" callback_data = f"transfer_to_server|{s_name}|{server_name}"
if len(callback_data.encode('utf-8')) > 64: if len(callback_data.encode("utf-8")) > 64:
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=f"❌ Ошибка: название сервера '{s_name}' слишком длинное.\n\n" text=f"❌ Ошибка: название сервера '{s_name}' слишком длинное.\n\n"
f"Пожалуйста, переименуйте сервер в более короткое название и попробуйте снова.", f"Пожалуйста, переименуйте сервер в более короткое название и попробуйте снова.",
reply_markup=build_admin_back_kb("clusters"), reply_markup=build_admin_back_kb("clusters"),
) )
return return
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text=f"{s_name} ({key_count})", text=f"{s_name} ({key_count})",
@@ -199,14 +199,14 @@ async def process_callback_delete_server(
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
for cl_name, key_count in all_clusters: for cl_name, key_count in all_clusters:
callback_data = f"transfer_to_cluster|{cl_name}|{cluster_name}|{server_name}" callback_data = f"transfer_to_cluster|{cl_name}|{cluster_name}|{server_name}"
if len(callback_data.encode('utf-8')) > 64: if len(callback_data.encode("utf-8")) > 64:
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=f"❌ Ошибка: название сервера '{server_name}' или кластера '{cl_name}' слишком длинное.\n\n" text=f"❌ Ошибка: название сервера '{server_name}' или кластера '{cl_name}' слишком длинное.\n\n"
f"Пожалуйста, переименуйте сервер в более короткое название и попробуйте снова.", f"Пожалуйста, переименуйте сервер в более короткое название и попробуйте снова.",
reply_markup=build_admin_back_kb("clusters"), reply_markup=build_admin_back_kb("clusters"),
) )
return return
builder.row( builder.row(
InlineKeyboardButton( InlineKeyboardButton(
text=f"{cl_name} ({key_count})", text=f"{cl_name} ({key_count})",
@@ -448,7 +448,7 @@ async def apply_field_edit(message: types.Message, state: FSMContext, session: A
reply_markup=build_admin_back_kb("clusters"), reply_markup=build_admin_back_kb("clusters"),
) )
return return
success = await update_server_name_with_keys(session, server_name, value) success = await update_server_name_with_keys(session, server_name, value)
if success: if success:
server_name = value server_name = value
+3 -2
View File
@@ -1226,7 +1226,9 @@ async def change_expiry_time(expiry_time: int, email: str, session: AsyncSession
key_subgroup = None key_subgroup = None
if tariff_id: if tariff_id:
result = await session.execute( result = await session.execute(
select(Tariff.traffic_limit, Tariff.device_limit, Tariff.subgroup_title).where(Tariff.id == tariff_id, Tariff.is_active.is_(True)) select(Tariff.traffic_limit, Tariff.device_limit, Tariff.subgroup_title).where(
Tariff.id == tariff_id, Tariff.is_active.is_(True)
)
) )
tariff = result.first() tariff = result.first()
if tariff: if tariff:
@@ -1234,7 +1236,6 @@ async def change_expiry_time(expiry_time: int, email: str, session: AsyncSession
device_limit = int(tariff[1]) if tariff[1] is not None else 0 device_limit = int(tariff[1]) if tariff[1] is not None else 0
key_subgroup = tariff[2] key_subgroup = tariff[2]
servers = await get_servers(session=session) servers = await get_servers(session=session)
if server_id in servers: if server_id in servers:
+1 -1
View File
@@ -227,7 +227,7 @@ async def handle_key_extension(
key_subgroup = None key_subgroup = None
if tariff: if tariff:
key_subgroup = tariff.get('subgroup_title') key_subgroup = tariff.get("subgroup_title")
await renew_key_in_cluster( await renew_key_in_cluster(
cluster_id=key.server_id, cluster_id=key.server_id,
+26 -10
View File
@@ -38,7 +38,17 @@ from database import (
update_trial, update_trial,
) )
from database.models import Key, Server, Tariff from database.models import Key, Server, Tariff
from handlers.buttons import BACK, CONNECT_DEVICE, CONNECT_PHONE, MAIN_MENU, MY_SUB, PC_BUTTON, SUPPORT, TV_BUTTON, ROUTER_BUTTON from handlers.buttons import (
BACK,
CONNECT_DEVICE,
CONNECT_PHONE,
MAIN_MENU,
MY_SUB,
PC_BUTTON,
ROUTER_BUTTON,
SUPPORT,
TV_BUTTON,
)
from handlers.keys.operations import create_client_on_server from handlers.keys.operations import create_client_on_server
from handlers.keys.operations.aggregated_links import make_aggregated_link from handlers.keys.operations.aggregated_links import make_aggregated_link
from handlers.texts import SELECT_COUNTRY_MSG, key_message_success from handlers.texts import SELECT_COUNTRY_MSG, key_message_success
@@ -509,14 +519,16 @@ async def finalize_key_creation(
) )
subgroup_code = tariff.subgroup_title if tariff and tariff.subgroup_title else None subgroup_code = tariff.subgroup_title if tariff and tariff.subgroup_title else None
cluster_all = [{ cluster_all = [
"server_name": server_info.server_name, {
"api_url": server_info.api_url, "server_name": server_info.server_name,
"panel_type": server_info.panel_type, "api_url": server_info.api_url,
"inbound_id": getattr(server_info, "inbound_id", None), "panel_type": server_info.panel_type,
"enabled": True, "inbound_id": getattr(server_info, "inbound_id", None),
"max_keys": getattr(server_info, "max_keys", None), "enabled": True,
}] "max_keys": getattr(server_info, "max_keys", None),
}
]
link_to_show = await make_aggregated_link( link_to_show = await make_aggregated_link(
session=session, session=session,
@@ -575,7 +587,11 @@ async def finalize_key_creation(
is_full_remnawave = await is_full_remnawave_cluster(cluster_name, session) is_full_remnawave = await is_full_remnawave_cluster(cluster_name, session)
is_vless = bool(public_link and public_link.lower().startswith("vless://")) or bool(need_vless_key) is_vless = bool(public_link and public_link.lower().startswith("vless://")) or bool(need_vless_key)
final_link = public_link or remnawave_link final_link = public_link or remnawave_link
webapp_url = final_link if isinstance(final_link, str) and final_link.strip().lower().startswith(("http://", "https://")) else None webapp_url = (
final_link
if isinstance(final_link, str) and final_link.strip().lower().startswith(("http://", "https://"))
else None
)
if panel_type == "remnawave" or is_full_remnawave: if panel_type == "remnawave" or is_full_remnawave:
if is_vless: if is_vless:
+2 -5
View File
@@ -77,16 +77,13 @@ async def process_callback_renew_key(callback_query: CallbackQuery, state: FSMCo
try: try:
hook_commands = await run_hooks( hook_commands = await run_hooks(
"process_callback_renew_key", "process_callback_renew_key", callback_query=callback_query, state=state, session=session
callback_query=callback_query,
state=state,
session=session
) )
if hook_commands: if hook_commands:
kb = insert_hook_buttons(kb, hook_commands) kb = insert_hook_buttons(kb, hook_commands)
except Exception as e: except Exception as e:
logger.warning(f"[RENEW] Ошибка при применении хуков: {e}") logger.warning(f"[RENEW] Ошибка при применении хуков: {e}")
await edit_or_send_message( await edit_or_send_message(
target_message=callback_query.message, target_message=callback_query.message,
text=f"Продление доступно с {dt_msk}", text=f"Продление доступно с {dt_msk}",
+2 -2
View File
@@ -134,7 +134,7 @@ async def build_keys_response(records, session):
formatted_date_full = "без срока действия" formatted_date_full = "без срока действия"
is_vless = False is_vless = False
if hasattr(record, 'tariff_id') and record.tariff_id: if hasattr(record, "tariff_id") and record.tariff_id:
try: try:
tariff = await get_tariff_by_id(session, record.tariff_id) tariff = await get_tariff_by_id(session, record.tariff_id)
if tariff and tariff.get("vless"): if tariff and tariff.get("vless"):
@@ -143,7 +143,7 @@ async def build_keys_response(records, session):
pass pass
icon = "📶" if is_vless else "🔑" icon = "📶" if is_vless else "🔑"
key_button = InlineKeyboardButton(text=f"{icon} {key_display}", callback_data=f"view_key|{email}") key_button = InlineKeyboardButton(text=f"{icon} {key_display}", callback_data=f"view_key|{email}")
rename_button = InlineKeyboardButton(text=ALIAS, callback_data=f"rename_key|{client_id}") rename_button = InlineKeyboardButton(text=ALIAS, callback_data=f"rename_key|{client_id}")
builder.row(key_button, rename_button) builder.row(key_button, rename_button)
+11 -4
View File
@@ -1,19 +1,24 @@
import asyncio import asyncio
from datetime import datetime, timezone from datetime import datetime, timezone
from sqlalchemy import delete, select from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE from config import PUBLIC_LINK, REMNAWAVE_LOGIN, REMNAWAVE_PASSWORD, SUPERNODE
from database import get_servers, store_key, filter_cluster_by_subgroup from database import filter_cluster_by_subgroup, get_servers, store_key
from database.models import Key, Tariff from database.models import Key, Tariff
from handlers.utils import get_least_loaded_cluster from handlers.utils import get_least_loaded_cluster
from logger import CLOGGER as logger, PANEL_REMNA, PANEL_XUI from logger import (
CLOGGER as logger,
PANEL_REMNA,
PANEL_XUI,
)
from panels._3xui import ClientConfig, add_client, get_xui_instance from panels._3xui import ClientConfig, add_client, get_xui_instance
from panels.remnawave import RemnawaveAPI from panels.remnawave import RemnawaveAPI
from .deletion import delete_key_from_cluster
from .aggregated_links import make_aggregated_link from .aggregated_links import make_aggregated_link
from .deletion import delete_key_from_cluster
async def update_key_on_cluster( async def update_key_on_cluster(
@@ -229,7 +234,9 @@ async def update_subscription(
if subgroup_code: if subgroup_code:
prefiltered = await filter_cluster_by_subgroup(session, cluster_servers, subgroup_code, new_cluster_id) prefiltered = await filter_cluster_by_subgroup(session, cluster_servers, subgroup_code, new_cluster_id)
if not prefiltered: if not prefiltered:
logger.warning(f"[Update] Пересоздание пропущено: нет серверов под подгруппу {subgroup_code} в {new_cluster_id}.") logger.warning(
f"[Update] Пересоздание пропущено: нет серверов под подгруппу {subgroup_code} в {new_cluster_id}."
)
return return
cluster_servers = prefiltered cluster_servers = prefiltered
+1 -1
View File
@@ -203,7 +203,7 @@ def prepare_headers(
"announce": "base64:" + base64.b64encode(announce_str.encode("utf-8")).decode("utf-8"), "announce": "base64:" + base64.b64encode(announce_str.encode("utf-8")).decode("utf-8"),
"profile-web-page-url": f"https://t.me/{USERNAME_BOT}", "profile-web-page-url": f"https://t.me/{USERNAME_BOT}",
"subscription-userinfo": subscription_userinfo, "subscription-userinfo": subscription_userinfo,
#"routing": "happ://routing/onadd/...", # "routing": "happ://routing/onadd/...",
} }
elif "Hiddify" in user_agent: elif "Hiddify" in user_agent:
parts = subscription_info.split(" - ")[0].split(": ") parts = subscription_info.split(" - ")[0].split(": ")
@@ -536,7 +536,7 @@ async def process_auto_renew_or_notify(
keyboard = build_notification_kb(email) keyboard = build_notification_kb(email)
await add_notification(conn, tg_id, notification_id) await add_notification(conn, tg_id, notification_id)
text_to_send = message_text if 'message_text' in locals() else standard_caption text_to_send = message_text if "message_text" in locals() else standard_caption
await send_notification(bot, tg_id, standard_photo, text_to_send, keyboard) await send_notification(bot, tg_id, standard_photo, text_to_send, keyboard)
return return
@@ -566,7 +566,7 @@ async def process_auto_renew_or_notify(
f"Продление подписки {email} на {duration_days} дней для пользователя {tg_id}. Баланс: {balance}, списываем: {renewal_cost}" f"Продление подписки {email} на {duration_days} дней для пользователя {tg_id}. Баланс: {balance}, списываем: {renewal_cost}"
) )
key_subgroup = selected_tariff.get('subgroup_title') key_subgroup = selected_tariff.get("subgroup_title")
await renew_key_in_cluster( await renew_key_in_cluster(
cluster_id=server_id, cluster_id=server_id,
+7 -3
View File
@@ -1,4 +1,5 @@
import os import os
from typing import Any from typing import Any
from aiogram import F, Router from aiogram import F, Router
@@ -6,6 +7,7 @@ from aiogram.filters import Command
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.utils.keyboard import InlineKeyboardBuilder
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from bot import bot from bot import bot
@@ -25,6 +27,7 @@ from database import (
get_user_snapshot, get_user_snapshot,
upsert_source_if_empty, upsert_source_if_empty,
) )
from database.models import TrackingSource
from handlers.buttons import ( from handlers.buttons import (
ABOUT_VPN, ABOUT_VPN,
BACK, BACK,
@@ -55,8 +58,7 @@ from logger import logger
from .admin.panel.keyboard import AdminPanelCallback from .admin.panel.keyboard import AdminPanelCallback
from .refferal import handle_referral_link from .refferal import handle_referral_link
from .utils import edit_or_send_message, extract_user_data from .utils import edit_or_send_message, extract_user_data
from sqlalchemy import select
from database.models import TrackingSource
router = Router() router = Router()
processing_gifts = set() processing_gifts = set()
@@ -235,7 +237,9 @@ async def show_start_menu(
key_cnt = key_count or 0 key_cnt = key_count or 0
show_trial = (trial_status in (-1, 0)) and (not TRIAL_TIME_DISABLE) and (key_cnt == 0) show_trial = (trial_status in (-1, 0)) and (not TRIAL_TIME_DISABLE) and (key_cnt == 0)
show_profile = (key_cnt > 0) or (((not SHOW_START_MENU_ONCE) or (trial_status not in (-1, 0)) or TRIAL_TIME_DISABLE) and (not show_trial)) show_profile = (key_cnt > 0) or (
((not SHOW_START_MENU_ONCE) or (trial_status not in (-1, 0)) or TRIAL_TIME_DISABLE) and (not show_trial)
)
if show_trial: if show_trial:
kb.row(InlineKeyboardButton(text=TRIAL_SUB, callback_data="create_key")) kb.row(InlineKeyboardButton(text=TRIAL_SUB, callback_data="create_key"))
+92 -49
View File
@@ -1,3 +1,4 @@
import asyncio
import html import html
import os import os
import re import re
@@ -5,8 +6,6 @@ import secrets
import string import string
from collections import OrderedDict from collections import OrderedDict
import asyncio
from datetime import datetime, timedelta from datetime import datetime, timedelta
import aiofiles import aiofiles
@@ -14,9 +13,9 @@ import aiofiles
from aiogram.types import ( from aiogram.types import (
BufferedInputFile, BufferedInputFile,
InlineKeyboardMarkup, InlineKeyboardMarkup,
InputMediaAnimation,
InputMediaPhoto, InputMediaPhoto,
InputMediaVideo, InputMediaVideo,
InputMediaAnimation,
Message, Message,
) )
from sqlalchemy import func, select from sqlalchemy import func, select
@@ -201,20 +200,20 @@ def format_hours(hours: int) -> str:
def get_media_type(media_path: str) -> str: def get_media_type(media_path: str) -> str:
if not media_path: if not media_path:
return 'photo' return "photo"
ext = os.path.splitext(media_path.lower())[1] ext = os.path.splitext(media_path.lower())[1]
if ext in ['.jpg', '.jpeg', '.png', '.webp']: if ext in [".jpg", ".jpeg", ".png", ".webp"]:
return 'photo' return "photo"
if ext in ['.mp4', '.mov', '.avi']: if ext in [".mp4", ".mov", ".avi"]:
return 'video' return "video"
if ext == '.gif': if ext == ".gif":
return 'animation' return "animation"
return 'photo' return "photo"
async def edit_or_send_message( async def edit_or_send_message(
@@ -227,8 +226,10 @@ async def edit_or_send_message(
disable_cache: bool = False, disable_cache: bool = False,
): ):
if not hasattr(edit_or_send_message, "cache"): if not hasattr(edit_or_send_message, "cache"):
from collections import OrderedDict
import asyncio import asyncio
from collections import OrderedDict
edit_or_send_message.cache = OrderedDict() edit_or_send_message.cache = OrderedDict()
edit_or_send_message.lock = asyncio.Lock() edit_or_send_message.lock = asyncio.Lock()
edit_or_send_message.max = 256 edit_or_send_message.max = 256
@@ -236,49 +237,70 @@ async def edit_or_send_message(
def find_media_file(original_path: str) -> str | None: def find_media_file(original_path: str) -> str | None:
if not original_path: if not original_path:
return None return None
if os.path.isfile(original_path): if os.path.isfile(original_path):
return original_path return original_path
base_name = os.path.splitext(original_path)[0] base_name = os.path.splitext(original_path)[0]
supported_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.mp4', '.mov', '.avi'] supported_extensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".mp4", ".mov", ".avi"]
for ext in supported_extensions: for ext in supported_extensions:
fallback_path = base_name + ext fallback_path = base_name + ext
if os.path.isfile(fallback_path): if os.path.isfile(fallback_path):
return fallback_path return fallback_path
return None return None
if media_path: if media_path:
actual_media_path = find_media_file(media_path) actual_media_path = find_media_file(media_path)
if actual_media_path: if actual_media_path:
media_type = get_media_type(actual_media_path) media_type = get_media_type(actual_media_path)
cached_id = None cached_id = None
if not disable_cache: if not disable_cache:
async with edit_or_send_message.lock: async with edit_or_send_message.lock:
cached_id = edit_or_send_message.cache.get(actual_media_path) cached_id = edit_or_send_message.cache.get(actual_media_path)
if cached_id: if cached_id:
edit_or_send_message.cache.move_to_end(actual_media_path) edit_or_send_message.cache.move_to_end(actual_media_path)
if cached_id: if cached_id:
try: try:
if media_type == 'photo': if media_type == "photo":
await target_message.edit_media(InputMediaPhoto(media=cached_id, caption=text), reply_markup=reply_markup) await target_message.edit_media(
elif media_type == 'video': InputMediaPhoto(media=cached_id, caption=text), reply_markup=reply_markup
await target_message.edit_media(InputMediaVideo(media=cached_id, caption=text), reply_markup=reply_markup) )
elif media_type == 'animation': elif media_type == "video":
await target_message.edit_media(InputMediaAnimation(media=cached_id, caption=text), reply_markup=reply_markup) await target_message.edit_media(
InputMediaVideo(media=cached_id, caption=text), reply_markup=reply_markup
)
elif media_type == "animation":
await target_message.edit_media(
InputMediaAnimation(media=cached_id, caption=text), reply_markup=reply_markup
)
return return
except Exception: except Exception:
try: try:
if media_type == 'photo': if media_type == "photo":
await target_message.answer_photo(photo=cached_id, caption=text, reply_markup=reply_markup, disable_web_page_preview=disable_web_page_preview) await target_message.answer_photo(
elif media_type == 'video': photo=cached_id,
await target_message.answer_video(video=cached_id, caption=text, reply_markup=reply_markup, disable_web_page_preview=disable_web_page_preview) caption=text,
elif media_type == 'animation': reply_markup=reply_markup,
await target_message.answer_animation(animation=cached_id, caption=text, reply_markup=reply_markup, disable_web_page_preview=disable_web_page_preview) disable_web_page_preview=disable_web_page_preview,
)
elif media_type == "video":
await target_message.answer_video(
video=cached_id,
caption=text,
reply_markup=reply_markup,
disable_web_page_preview=disable_web_page_preview,
)
elif media_type == "animation":
await target_message.answer_animation(
animation=cached_id,
caption=text,
reply_markup=reply_markup,
disable_web_page_preview=disable_web_page_preview,
)
return return
except Exception: except Exception:
pass pass
@@ -286,30 +308,51 @@ async def edit_or_send_message(
async with aiofiles.open(actual_media_path, "rb") as f: async with aiofiles.open(actual_media_path, "rb") as f:
data = await f.read() data = await f.read()
upload = BufferedInputFile(data, filename=os.path.basename(actual_media_path)) upload = BufferedInputFile(data, filename=os.path.basename(actual_media_path))
try: try:
if media_type == 'photo': if media_type == "photo":
msg = await target_message.edit_media(InputMediaPhoto(media=upload, caption=text), reply_markup=reply_markup) msg = await target_message.edit_media(
elif media_type == 'video': InputMediaPhoto(media=upload, caption=text), reply_markup=reply_markup
msg = await target_message.edit_media(InputMediaVideo(media=upload, caption=text), reply_markup=reply_markup) )
elif media_type == 'animation': elif media_type == "video":
msg = await target_message.edit_media(InputMediaAnimation(media=upload, caption=text), reply_markup=reply_markup) msg = await target_message.edit_media(
InputMediaVideo(media=upload, caption=text), reply_markup=reply_markup
)
elif media_type == "animation":
msg = await target_message.edit_media(
InputMediaAnimation(media=upload, caption=text), reply_markup=reply_markup
)
except Exception: except Exception:
if media_type == 'photo': if media_type == "photo":
msg = await target_message.answer_photo(photo=upload, caption=text, reply_markup=reply_markup, disable_web_page_preview=disable_web_page_preview) msg = await target_message.answer_photo(
elif media_type == 'video': photo=upload,
msg = await target_message.answer_video(video=upload, caption=text, reply_markup=reply_markup, disable_web_page_preview=disable_web_page_preview) caption=text,
elif media_type == 'animation': reply_markup=reply_markup,
msg = await target_message.answer_animation(animation=upload, caption=text, reply_markup=reply_markup, disable_web_page_preview=disable_web_page_preview) disable_web_page_preview=disable_web_page_preview,
)
elif media_type == "video":
msg = await target_message.answer_video(
video=upload,
caption=text,
reply_markup=reply_markup,
disable_web_page_preview=disable_web_page_preview,
)
elif media_type == "animation":
msg = await target_message.answer_animation(
animation=upload,
caption=text,
reply_markup=reply_markup,
disable_web_page_preview=disable_web_page_preview,
)
file_id = None file_id = None
if hasattr(msg, 'photo') and msg.photo: if hasattr(msg, "photo") and msg.photo:
file_id = msg.photo[-1].file_id file_id = msg.photo[-1].file_id
elif hasattr(msg, 'video') and msg.video: elif hasattr(msg, "video") and msg.video:
file_id = msg.video.file_id file_id = msg.video.file_id
elif hasattr(msg, 'animation') and msg.animation: elif hasattr(msg, "animation") and msg.animation:
file_id = msg.animation.file_id file_id = msg.animation.file_id
if file_id and not disable_cache: if file_id and not disable_cache:
async with edit_or_send_message.lock: async with edit_or_send_message.lock:
if actual_media_path not in edit_or_send_message.cache: if actual_media_path not in edit_or_send_message.cache:
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -3
View File
@@ -3,17 +3,16 @@ from collections.abc import Iterable
from aiogram import Dispatcher from aiogram import Dispatcher
from aiogram.dispatcher.middlewares.base import BaseMiddleware from aiogram.dispatcher.middlewares.base import BaseMiddleware
from config import DISABLE_DIRECT_START, CHANNEL_REQUIRED from config import CHANNEL_REQUIRED, DISABLE_DIRECT_START
from middlewares.ban_checker import BanCheckerMiddleware from middlewares.ban_checker import BanCheckerMiddleware
from middlewares.subscription import SubscriptionMiddleware from middlewares.subscription import SubscriptionMiddleware
from .probe import StreamProbeMiddleware, MiddlewareProbe, TailHandlerProbe
from .admin import AdminMiddleware from .admin import AdminMiddleware
from .answer import CallbackAnswerMiddleware from .answer import CallbackAnswerMiddleware
from .direct_start_blocker import DirectStartBlockerMiddleware from .direct_start_blocker import DirectStartBlockerMiddleware
from .loggings import LoggingMiddleware from .loggings import LoggingMiddleware
from .maintenance import MaintenanceModeMiddleware from .maintenance import MaintenanceModeMiddleware
from .probe import MiddlewareProbe, StreamProbeMiddleware, TailHandlerProbe
from .session import SessionMiddleware from .session import SessionMiddleware
from .throttling import ThrottlingMiddleware from .throttling import ThrottlingMiddleware
from .user import UserMiddleware from .user import UserMiddleware
+7 -5
View File
@@ -1,10 +1,12 @@
import time import time
from aiogram.dispatcher.middlewares.base import BaseMiddleware from aiogram.dispatcher.middlewares.base import BaseMiddleware
from logger import logger from logger import logger
class StreamProbeMiddleware(BaseMiddleware): class StreamProbeMiddleware(BaseMiddleware):
def __init__(self, name: str = "global"): def __init__(self, name: str = "global") -> None:
self.name = name self.name = name
async def __call__(self, handler, event, data): async def __call__(self, handler, event, data):
@@ -21,7 +23,7 @@ class StreamProbeMiddleware(BaseMiddleware):
class MiddlewareProbe(BaseMiddleware): class MiddlewareProbe(BaseMiddleware):
def __init__(self, inner: BaseMiddleware, name: str): def __init__(self, inner: BaseMiddleware, name: str) -> None:
self.inner = inner self.inner = inner
self.name = name self.name = name
@@ -29,7 +31,7 @@ class MiddlewareProbe(BaseMiddleware):
now = time.perf_counter() now = time.perf_counter()
t0 = data.setdefault("_mw_t0", now) t0 = data.setdefault("_mw_t0", now)
prev = data.setdefault("_mw_prev", now) prev = data.setdefault("_mw_prev", now)
logger.info(f"[mw:{self.name}] +{(now - prev)*1000:.2f} ms total {(now - t0)*1000:.2f} ms") logger.info(f"[mw:{self.name}] +{(now - prev) * 1000:.2f} ms total {(now - t0) * 1000:.2f} ms")
downstream_ms = 0.0 downstream_ms = 0.0
@@ -54,14 +56,14 @@ class MiddlewareProbe(BaseMiddleware):
class TailHandlerProbe(BaseMiddleware): class TailHandlerProbe(BaseMiddleware):
def __init__(self, name: str = "handler"): def __init__(self, name: str = "handler") -> None:
self.name = name self.name = name
async def __call__(self, handler, event, data): async def __call__(self, handler, event, data):
now = time.perf_counter() now = time.perf_counter()
t0 = data.setdefault("_mw_t0", now) t0 = data.setdefault("_mw_t0", now)
prev = data.setdefault("_mw_prev", now) prev = data.setdefault("_mw_prev", now)
logger.info(f"[mw:{self.name}:enter] +{(now - prev)*1000:.2f} ms total {(now - t0)*1000:.2f} ms") logger.info(f"[mw:{self.name}:enter] +{(now - prev) * 1000:.2f} ms total {(now - t0) * 1000:.2f} ms")
start = time.perf_counter() start = time.perf_counter()
try: try:
return await handler(event, data) return await handler(event, data)
+2 -2
View File
@@ -1,9 +1,9 @@
from aiohttp.web_urldispatcher import UrlDispatcher from aiohttp.web_urldispatcher import UrlDispatcher
from utils.modules_loader import load_module_webhooks
from handlers.payments.heleket.webhook import heleket_webhook from handlers.payments.heleket.webhook import heleket_webhook
from handlers.payments.kassai.webhook import kassai_webhook from handlers.payments.kassai.webhook import kassai_webhook
from utils.modules_loader import load_module_webhooks
from .wata_payment import wata_payment_webhook from .wata_payment import wata_payment_webhook