201 lines
9.7 KiB
Python
201 lines
9.7 KiB
Python
import asyncio
|
||
from datetime import datetime, timedelta
|
||
|
||
import asyncpg
|
||
import pytz
|
||
from aiogram import Bot, Router, types
|
||
from aiogram.exceptions import TelegramForbiddenError
|
||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||
|
||
from config import NOTIFY_EXTRA_DAYS, NOTIFY_INACTIVE, NOTIFY_INACTIVE_TRAFFIC, SUPPORT_CHAT_URL, TRIAL_TIME
|
||
from database import (
|
||
add_notification,
|
||
check_notification_time,
|
||
create_blocked_user,
|
||
)
|
||
from handlers.keys.key_utils import get_user_traffic
|
||
from logger import logger
|
||
|
||
router = Router()
|
||
|
||
moscow_tz = pytz.timezone("Europe/Moscow")
|
||
|
||
|
||
async def notify_inactive_trial_users(bot: Bot, conn: asyncpg.Connection):
|
||
"""
|
||
Проверяет пользователей, не активировавших пробный период, и отправляет им напоминания.
|
||
Первое уведомление — стандартное.
|
||
Если прошло 24 часа и триал не активирован, отправляется уведомление с бонусом +2 дня.
|
||
"""
|
||
logger.info("Проверка пользователей, не активировавших пробный период...")
|
||
|
||
inactive_trial_users = await conn.fetch(
|
||
"""
|
||
SELECT tg_id, username, first_name, last_name FROM users
|
||
WHERE tg_id IN (
|
||
SELECT tg_id FROM connections
|
||
WHERE trial = 0
|
||
)
|
||
AND tg_id NOT IN (
|
||
SELECT tg_id FROM blocked_users
|
||
)
|
||
AND tg_id NOT IN (
|
||
SELECT DISTINCT tg_id FROM keys
|
||
)
|
||
"""
|
||
)
|
||
logger.info(f"👥 Найдено {len(inactive_trial_users)} неактивных пользователей.")
|
||
|
||
for user in inactive_trial_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 "Пользователь"
|
||
|
||
try:
|
||
can_notify = await check_notification_time(tg_id, "inactive_trial", hours=NOTIFY_INACTIVE, session=conn)
|
||
|
||
if can_notify:
|
||
builder = InlineKeyboardBuilder()
|
||
builder.row(
|
||
types.InlineKeyboardButton(
|
||
text="🚀 Активировать пробный период",
|
||
callback_data="create_key",
|
||
)
|
||
)
|
||
builder.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||
keyboard = builder.as_markup()
|
||
|
||
trial_extended = await conn.fetchval(
|
||
"""
|
||
SELECT EXISTS (
|
||
SELECT 1 FROM notifications
|
||
WHERE tg_id = $1 AND notification_type = 'inactive_trial'
|
||
)
|
||
""",
|
||
tg_id,
|
||
)
|
||
|
||
if trial_extended:
|
||
message = (
|
||
f"<b>{display_name}</b>, у нас для тебя подарок! 🎁\n\n"
|
||
"<blockquote>"
|
||
f"Мы добавили тебе +{NOTIFY_EXTRA_DAYS} дополнительных дня к пробному периоду!\n"
|
||
f"Теперь у тебя есть еще шанс протестировать наш VPN целых {NOTIFY_EXTRA_DAYS + TRIAL_TIME} дня!\n"
|
||
"</blockquote>"
|
||
"Нажми на кнопку ниже, чтобы активировать доступ с бонусом +2 дня! 👇"
|
||
)
|
||
|
||
await conn.execute("UPDATE connections SET trial = -1 WHERE tg_id = $1", tg_id)
|
||
else:
|
||
message = (
|
||
f"👋 <b>Привет, {display_name}!</b>\n\n"
|
||
"<blockquote>"
|
||
f"🎉 У тебя есть бесплатный пробный период на {TRIAL_TIME} дней!\n"
|
||
"Не упусти возможность попробовать наш VPN прямо сейчас.\n"
|
||
"</blockquote>"
|
||
"Нажми на кнопку ниже, чтобы активировать пробный доступ! 👇"
|
||
)
|
||
|
||
try:
|
||
await bot.send_message(tg_id, message, reply_markup=keyboard)
|
||
logger.info(f"📩 Отправлено уведомление неактивному пользователю {tg_id}.")
|
||
await add_notification(tg_id, "inactive_trial", session=conn)
|
||
|
||
except TelegramForbiddenError:
|
||
logger.warning(f"🚫 Бот заблокирован пользователем {tg_id}. Добавляем в blocked_users.")
|
||
await create_blocked_user(tg_id, conn)
|
||
except Exception as e:
|
||
logger.error(f"⚠ Ошибка при отправке уведомления пользователю {tg_id}: {e}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"⚠ Ошибка при обработке пользователя {tg_id}: {e}")
|
||
|
||
await asyncio.sleep(1)
|
||
|
||
logger.info("✅ Проверка пользователей с неактивным пробным периодом завершена.")
|
||
|
||
|
||
async def notify_users_no_traffic(bot: Bot, conn: asyncpg.Connection, current_time: int, keys: list):
|
||
"""
|
||
Проверяет трафик пользователей, у которых ещё не отправлялось уведомление о нулевом трафике.
|
||
Если трафик 0 ГБ и прошло более 2 часов с момента создания ключа, отправляет уведомление и
|
||
обновляет запись в БД (notified = TRUE).
|
||
"""
|
||
logger.info("Проверка пользователей с нулевым трафиком...")
|
||
|
||
current_dt = datetime.fromtimestamp(current_time / 1000, tz=moscow_tz)
|
||
|
||
for key in keys:
|
||
tg_id = key.get("tg_id")
|
||
email = key.get("email")
|
||
created_at = key.get("created_at")
|
||
client_id = key.get("client_id")
|
||
notified = key.get("notified")
|
||
|
||
if created_at is None:
|
||
logger.warning(f"Для {email} нет значения created_at. Пропускаем.")
|
||
continue
|
||
|
||
if notified is True:
|
||
continue
|
||
|
||
created_at_dt = pytz.utc.localize(datetime.fromtimestamp(created_at / 1000)).astimezone(moscow_tz)
|
||
created_at_plus_2 = created_at_dt + timedelta(hours=NOTIFY_INACTIVE_TRAFFIC)
|
||
|
||
if current_dt < created_at_plus_2:
|
||
continue
|
||
|
||
try:
|
||
traffic_data = await get_user_traffic(conn, tg_id, email)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка получения трафика для {email}: {e}")
|
||
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()
|
||
)
|
||
logger.info(f"Ключ для {email}: общий трафик: {total_traffic} ГБ")
|
||
|
||
if total_traffic == 0:
|
||
logger.info(f"⚠ У пользователя {tg_id} ({email}) 0 ГБ трафика. Отправляем уведомление.")
|
||
|
||
builder = InlineKeyboardBuilder()
|
||
builder.row(types.InlineKeyboardButton(text="🔧 Написать в поддержку", url=SUPPORT_CHAT_URL))
|
||
builder.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||
keyboard = builder.as_markup()
|
||
|
||
message = (
|
||
"⚠ <b>Ваша VPN-подписка активна, но трафик не используется.</b>\n\n"
|
||
"<blockquote>Если у вас возникли сложности с подключением, "
|
||
"нажмите кнопку ниже, чтобы связаться с поддержкой.</blockquote>\n\n"
|
||
"🛠 Мы поможем вам разобраться! 💡"
|
||
)
|
||
|
||
try:
|
||
await bot.send_message(tg_id, message, reply_markup=keyboard)
|
||
logger.info(f"📩 Отправлено уведомление пользователю {tg_id} о нулевом трафике.")
|
||
await conn.execute(
|
||
"UPDATE keys SET notified = TRUE WHERE tg_id = $1 AND client_id = $2", tg_id, client_id
|
||
)
|
||
except TelegramForbiddenError:
|
||
logger.warning(f"🚫 Бот заблокирован пользователем {tg_id}.")
|
||
await create_blocked_user(tg_id, conn)
|
||
except Exception as e:
|
||
logger.error(f"⚠ Ошибка при отправке уведомления пользователю {tg_id}: {e}")
|
||
else:
|
||
try:
|
||
await conn.execute(
|
||
"UPDATE keys SET notified = TRUE WHERE tg_id = $1 AND client_id = $2", tg_id, client_id
|
||
)
|
||
logger.info(f"Ключ для {email} имеет трафик. Обновлено notified = TRUE.")
|
||
except Exception as e:
|
||
logger.error(f"Ошибка обновления notified для пользователя {tg_id}: {e}")
|
||
|
||
logger.info("✅ Обработка пользователей с нулевым трафиком завершена.")
|