221 lines
8.9 KiB
Python
221 lines
8.9 KiB
Python
from datetime import datetime, timedelta
|
||
|
||
import pytz
|
||
|
||
from aiogram import Bot, Router, types
|
||
from aiogram.types import InlineKeyboardButton, WebAppInfo
|
||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from config import (
|
||
NOTIFY_EXTRA_DAYS,
|
||
NOTIFY_INACTIVE,
|
||
NOTIFY_INACTIVE_TRAFFIC,
|
||
REMNAWAVE_WEBAPP,
|
||
SUPPORT_CHAT_URL,
|
||
)
|
||
from core.bootstrap import MODES_CONFIG, NOTIFICATIONS_CONFIG
|
||
from database import (
|
||
add_notification,
|
||
check_notifications_bulk,
|
||
mark_trial_extended,
|
||
update_key_notified,
|
||
)
|
||
from database.tariffs import get_tariffs
|
||
from handlers.buttons import CONNECT_DEVICE, MAIN_MENU
|
||
from handlers.keys.operations import get_user_traffic
|
||
from handlers.notifications.notify_utils import send_messages_with_limit
|
||
from handlers.texts import (
|
||
TRIAL_INACTIVE_BONUS_MSG,
|
||
TRIAL_INACTIVE_FIRST_MSG,
|
||
ZERO_TRAFFIC_MSG,
|
||
)
|
||
from handlers.utils import format_days, is_full_remnawave_cluster
|
||
from hooks.hook_buttons import insert_hook_buttons
|
||
from hooks.hooks import run_hooks
|
||
from logger import logger
|
||
|
||
|
||
router = Router()
|
||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||
|
||
|
||
async def notify_inactive_trial_users(bot: Bot, session: AsyncSession):
|
||
logger.info("Проверка пользователей, не активировавших пробный период...")
|
||
|
||
inactive_hours = int(NOTIFICATIONS_CONFIG.get("INACTIVE_USER_ENABLED", NOTIFY_INACTIVE))
|
||
extra_days = int(NOTIFICATIONS_CONFIG.get("EXTRA_DAYS_AFTER_EXPIRY", NOTIFY_EXTRA_DAYS))
|
||
|
||
if inactive_hours <= 0:
|
||
logger.info("INACTIVE_USER_ENABLED <= 0, уведомления для неактивных триалов отключены.")
|
||
return
|
||
|
||
users = await check_notifications_bulk(session, "inactive_trial", inactive_hours)
|
||
logger.info(f"Найдено {len(users)} неактивных пользователей для уведомления.")
|
||
messages = []
|
||
|
||
trial_tariffs = await get_tariffs(session, group_code="trial")
|
||
if not trial_tariffs:
|
||
logger.error("[Notifications] Триальный тариф не найден")
|
||
return
|
||
|
||
trial_days = trial_tariffs[0]["duration_days"]
|
||
|
||
for user in users:
|
||
tg_id = user["tg_id"]
|
||
username = user["username"]
|
||
first_name = user["first_name"]
|
||
last_name = user["last_name"]
|
||
display_name = username or first_name or last_name or "Пользователь"
|
||
|
||
builder = InlineKeyboardBuilder()
|
||
builder.row(types.InlineKeyboardButton(text="🚀 Активировать пробный период", callback_data="create_key"))
|
||
builder.row(types.InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||
keyboard = builder.as_markup()
|
||
|
||
trial_extended = user["last_notification_time"] is not None
|
||
|
||
if trial_extended and extra_days > 0:
|
||
total_days = extra_days + trial_days
|
||
message = TRIAL_INACTIVE_BONUS_MSG.format(
|
||
display_name=display_name,
|
||
extra_days_formatted=format_days(extra_days),
|
||
total_days_formatted=format_days(total_days),
|
||
)
|
||
await mark_trial_extended(tg_id, session)
|
||
else:
|
||
message = TRIAL_INACTIVE_FIRST_MSG.format(
|
||
display_name=display_name,
|
||
trial_time_formatted=format_days(trial_days),
|
||
)
|
||
|
||
messages.append({
|
||
"tg_id": tg_id,
|
||
"text": message,
|
||
"keyboard": keyboard,
|
||
"notification_id": "inactive_trial",
|
||
})
|
||
|
||
if messages:
|
||
results = await send_messages_with_limit(
|
||
bot,
|
||
messages,
|
||
session=session,
|
||
source_file="special_notifications",
|
||
messages_per_second=25,
|
||
)
|
||
sent_count = 0
|
||
for msg, result in zip(messages, results, strict=False):
|
||
if result:
|
||
await add_notification(session, msg["tg_id"], msg["notification_id"])
|
||
sent_count += 1
|
||
logger.info(f"Отправлено {sent_count} уведомлений неактивным пользователям.")
|
||
logger.info("Проверка пользователей с неактивным пробным периодом завершена.")
|
||
|
||
|
||
async def notify_users_no_traffic(bot: Bot, session: AsyncSession, current_time: int, keys: list):
|
||
logger.info("Проверка пользователей с нулевым трафиком...")
|
||
current_dt = datetime.fromtimestamp(current_time / 1000, tz=moscow_tz)
|
||
messages = []
|
||
|
||
inactive_traffic_hours = int(NOTIFICATIONS_CONFIG.get("INACTIVE_TRAFFIC_ENABLED", NOTIFY_INACTIVE_TRAFFIC))
|
||
if inactive_traffic_hours <= 0:
|
||
logger.info("INACTIVE_TRAFFIC_ENABLED <= 0, уведомления о нулевом трафике отключены.")
|
||
return
|
||
|
||
remnawave_webapp_enabled = bool(MODES_CONFIG.get("REMNAWAVE_WEBAPP_ENABLED", REMNAWAVE_WEBAPP))
|
||
|
||
for key in keys:
|
||
tg_id = key.tg_id
|
||
email = key.email
|
||
created_at = key.created_at
|
||
client_id = key.client_id
|
||
expiry_time = key.expiry_time
|
||
notified = key.notified
|
||
|
||
if created_at is None or notified:
|
||
continue
|
||
|
||
created_at_dt = pytz.utc.localize(datetime.fromtimestamp(created_at / 1000)).astimezone(moscow_tz)
|
||
if current_dt < created_at_dt + timedelta(hours=inactive_traffic_hours):
|
||
continue
|
||
|
||
if expiry_time:
|
||
expiry_dt = pytz.utc.localize(datetime.fromtimestamp(expiry_time / 1000)).astimezone(moscow_tz)
|
||
if current_dt > expiry_dt:
|
||
continue
|
||
|
||
try:
|
||
traffic_data = await get_user_traffic(session, tg_id, email)
|
||
except Exception as error:
|
||
logger.error(f"Ошибка получения трафика для {email}: {error}")
|
||
continue
|
||
|
||
if traffic_data.get("status") != "success":
|
||
logger.warning(f"Ошибка при получении трафика для {email}: {traffic_data.get('message')}")
|
||
continue
|
||
|
||
total_traffic = sum(
|
||
value if isinstance(value, int | float) else 0 for value in traffic_data.get("traffic", {}).values()
|
||
)
|
||
|
||
if total_traffic == 0:
|
||
logger.info(f"У пользователя {tg_id} ({email}) 0 ГБ трафика. Отправляем уведомление.")
|
||
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:
|
||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, web_app=WebAppInfo(url=final_link)))
|
||
else:
|
||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{email}"))
|
||
except Exception as error:
|
||
logger.error(f"Ошибка при определении типа панели для {email}: {error}")
|
||
builder.row(InlineKeyboardButton(text=CONNECT_DEVICE, callback_data=f"connect_device|{email}"))
|
||
|
||
builder.row(InlineKeyboardButton(text="🔧 Написать в поддержку", 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 error:
|
||
logger.warning(f"[ZERO_TRAFFIC_NOTIFICATION] Ошибка при применении хуков: {error}")
|
||
|
||
keyboard = builder.as_markup()
|
||
message = ZERO_TRAFFIC_MSG.format(email=email)
|
||
messages.append({
|
||
"tg_id": tg_id,
|
||
"text": message,
|
||
"keyboard": keyboard,
|
||
"client_id": client_id,
|
||
})
|
||
|
||
try:
|
||
await update_key_notified(session, tg_id, client_id)
|
||
except Exception as error:
|
||
logger.error(f"Ошибка обновления notified для {tg_id} ({client_id}): {error}")
|
||
|
||
if messages:
|
||
results = await send_messages_with_limit(
|
||
bot,
|
||
messages,
|
||
session=session,
|
||
source_file="special_notifications",
|
||
messages_per_second=25,
|
||
)
|
||
sent_count = sum(result for result in results if result)
|
||
logger.info(f"Отправлено {sent_count} уведомлений о нулевом трафике.")
|
||
|
||
logger.info("Обработка пользователей с нулевым трафиком завершена.")
|