from __future__ import annotations from datetime import datetime, timedelta import pytz from aiogram import Bot from aiogram.types import InlineKeyboardButton, WebAppInfo from aiogram.utils.keyboard import InlineKeyboardBuilder from sqlalchemy import update from sqlalchemy.ext.asyncio import AsyncSession from config import NOTIFY_INACTIVE_TRAFFIC, REMNAWAVE_WEBAPP, REMNAWAVE_WEBAPP_OPEN_IN_BROWSER, SUPPORT_CHAT_URL from core.bootstrap import MODES_CONFIG, NOTIFICATIONS_CONFIG from database.models import Key from database.tariffs import get_tariffs from handlers.buttons import CONNECT_DEVICE, MAIN_MENU, SUPPORT from handlers.keys.utils import build_key_callback from handlers.notifications.sender import send_messages_with_limit from handlers.texts import ZERO_TRAFFIC_MSG from handlers.utils import is_full_remnawave_cluster from hooks.hook_buttons import insert_hook_buttons from hooks.hooks import run_hooks from logger import logger from panels.remnawave_runtime import fetch_all_remnawave_traffic moscow_tz = pytz.timezone("Europe/Moscow") async def process_zero_traffic( bot: Bot, session: AsyncSession, current_time: int, keys: list, ): inactive_traffic_hours = int(NOTIFICATIONS_CONFIG.get("INACTIVE_TRAFFIC_ENABLED", NOTIFY_INACTIVE_TRAFFIC)) if inactive_traffic_hours <= 0: return trial_tariffs = await get_tariffs(session, group_code="trial") trial_tariff_ids = {t["id"] for t in trial_tariffs} if trial_tariffs else set() if not trial_tariff_ids: return current_dt = datetime.fromtimestamp(current_time / 1000, tz=moscow_tz) remnawave_webapp_enabled = bool(MODES_CONFIG.get("REMNAWAVE_WEBAPP_ENABLED", REMNAWAVE_WEBAPP)) open_in_browser = bool(MODES_CONFIG.get("REMNAWAVE_WEBAPP_OPEN_IN_BROWSER", REMNAWAVE_WEBAPP_OPEN_IN_BROWSER)) candidate_keys = [] for key in keys: if key.tariff_id not in trial_tariff_ids: continue if key.created_at is None or key.notified: continue created_at_dt = pytz.utc.localize(datetime.fromtimestamp(key.created_at / 1000)).astimezone(moscow_tz) if current_dt < created_at_dt + timedelta(hours=inactive_traffic_hours): continue if key.expiry_time: expiry_dt = pytz.utc.localize(datetime.fromtimestamp(key.expiry_time / 1000)).astimezone(moscow_tz) if current_dt > expiry_dt: continue candidate_keys.append(key) if not candidate_keys: return needed_uuids = {k.client_id for k in candidate_keys if k.client_id} logger.info(f"[ZeroTraffic] Кандидатов: {len(candidate_keys)}, UUID: {len(needed_uuids)}") try: traffic_map = await fetch_all_remnawave_traffic(session, needed_uuids=needed_uuids) except Exception as e: logger.error(f"[ZeroTraffic] Ошибка получения трафика: {e}") return messages = [] keys_to_mark = [k.client_id for k in candidate_keys] for key in candidate_keys: tg_id = key.tg_id email = key.email client_id = key.client_id used_bytes = traffic_map.get(client_id) if used_bytes is None or used_bytes > 0: continue builder = InlineKeyboardBuilder() server_id = key.server_id try: is_full_remnawave = await is_full_remnawave_cluster(server_id, session) final_link = key.key or key.remnawave_link if is_full_remnawave and final_link and remnawave_webapp_enabled: if open_in_browser: builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, url=final_link)) else: builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, web_app=WebAppInfo(url=final_link))) else: builder.row(InlineKeyboardButton( text=CONNECT_DEVICE, callback_data=build_key_callback("connect_device", client_id, email), )) except Exception as e: logger.error(f"Ошибка типа панели для {email}: {e}") builder.row(InlineKeyboardButton( text=CONNECT_DEVICE, callback_data=build_key_callback("connect_device", client_id, email), )) builder.row(InlineKeyboardButton(text=SUPPORT, url=SUPPORT_CHAT_URL)) builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile")) try: hook_commands = await run_hooks( "zero_traffic_notification", chat_id=tg_id, admin=False, session=session, email=email, ) if hook_commands: builder = insert_hook_buttons(builder, hook_commands) except Exception as e: logger.warning(f"[ZeroTraffic] Ошибка хуков: {e}") messages.append({ "tg_id": tg_id, "text": ZERO_TRAFFIC_MSG.format(email=email), "keyboard": builder.as_markup(), "client_id": client_id, }) if keys_to_mark: try: await session.execute(update(Key).where(Key.client_id.in_(keys_to_mark)).values(notified=True)) logger.info(f"[ZeroTraffic] Отмечено {len(keys_to_mark)} ключей как notified") except Exception as e: logger.error(f"[ZeroTraffic] Ошибка обновления notified: {e}") if messages: results = await send_messages_with_limit(bot, messages, messages_per_second=25) sent_count = sum(1 for r in results if r) logger.info(f"[ZeroTraffic] Отправлено {sent_count} уведомлений")