Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 118 additions & 93 deletions handlers/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
ADMIN_USERNAME,
AUTO_DELETE_EXPIRED_KEYS,
AUTO_RENEW_KEYS,
SUPPORT_CHAT_URL,
DATABASE_URL,
DELETE_KEYS_DELAY,
DEV_MODE,
Expand All @@ -34,6 +35,7 @@
update_balance,
update_key_expiry,
)
from handlers.buttons.profile import ADD_SUB
from handlers.keys.key_utils import delete_key_from_cluster, renew_key_in_cluster
from handlers.texts import KEY_EXPIRY_10H, KEY_EXPIRY_24H, KEY_RENEWED
from logger import logger
Expand Down Expand Up @@ -206,6 +208,7 @@ async def process_24h_record(record, bot, conn):
tg_id = record["tg_id"]
email = record["email"]
expiry_time = record["expiry_time"]
client_id = record["client_id"]

moscow_tz = pytz.timezone("Europe/Moscow")

Expand Down Expand Up @@ -240,7 +243,14 @@ async def process_24h_record(record, bot, conn):
await renew_key_in_cluster(cluster_id, email, record["client_id"], new_expiry_time, TOTAL_GB)
logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")

await conn.execute("UPDATE keys SET notified_24h = TRUE WHERE client_id = $1", record["client_id"])
if flag == "notified":
await conn.execute("UPDATE keys SET notified = TRUE WHERE client_id = $1", client_id)

elif flag == "notified_24h":
await conn.execute("UPDATE keys SET notified_24h = TRUE WHERE client_id = $1", client_id)

except Exception as e:
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")

image_path = os.path.join("img", "notify_24h.jpg")
keyboard = types.InlineKeyboardMarkup(
Expand All @@ -264,7 +274,7 @@ async def process_24h_record(record, bot, conn):
except Exception as e:
logger.error(f"Ошибка при продлении подписки для клиента {tg_id}: {e}")
else:
await send_renewal_notification(bot, tg_id, email, message_24h, conn, record["client_id"], "notified_24h")
await send_renewal_notification(bot, tg_id, email, message_24h, conn, client_id, "notified_24h")


async def send_renewal_notification(bot, tg_id, email, message, conn, client_id, flag):
Expand All @@ -290,7 +300,7 @@ async def send_renewal_notification(bot, tg_id, email, message, conn, client_id,

logger.info(f"Уведомление отправлено пользователю {tg_id}.")

await conn.execute("UPDATE keys SET notified_24h = $1 WHERE client_id = $2", flag, client_id)
await conn.execute("UPDATE keys SET notified_24h = TRUE WHERE client_id = $1", client_id)

except Exception as e:
logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}")
Expand Down Expand Up @@ -362,7 +372,7 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
logger.info("Проверка подписок, срок действия которых скоро истекает...")

threshold_time = int((datetime.utcnow() + timedelta(seconds=EXPIRED_KEYS_CHECK_INTERVAL * 1.5)).timestamp() * 1000)

expiring_keys = await conn.fetch(
"""
SELECT tg_id, client_id, expiry_time, email, server_id FROM keys
Expand All @@ -371,39 +381,77 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time:
threshold_time,
current_time - DELETE_KEYS_DELAY * 1000,
)

logger.info(f"Найдено {len(expiring_keys)} подписок, срок действия которых скоро истекает.")

for record in expiring_keys:
await process_key(record, bot, conn)

if DELETE_KEYS_DELAY > 0:
expired_keys_query = """
SELECT tg_id, client_id, email, server_id FROM keys
SELECT tg_id, client_id, email, server_id, expiry_time FROM keys
WHERE expiry_time <= (CAST($1 AS bigint) - $2 * 1000)
"""
params = (current_time, DELETE_KEYS_DELAY)
else:
expired_keys_query = """
SELECT tg_id, client_id, email, server_id FROM keys
SELECT tg_id, client_id, email, server_id, expiry_time FROM keys
WHERE expiry_time <= $1
"""
params = (current_time,)

expired_keys = await conn.fetch(expired_keys_query, *params)

logger.info(f"Найдено {len(expired_keys)} истёкших ключей.")
logger.info(f"Найдено {len(expired_keys)} истёкших подписок.")

for record in expired_keys:
try:
await delete_key_from_cluster(
cluster_id=record["server_id"], email=record["email"], client_id=record["client_id"]
)
await delete_key(record["client_id"], conn)
logger.info(
f"Ключ {record['client_id']} удалён"
+ (f" после задержки {DELETE_KEYS_DELAY} сек." if DELETE_KEYS_DELAY > 0 else "")
)
except Exception as e:
logger.error(f"Ошибка при удалении ключа {record['client_id']}: {e}")
balance = await get_balance(record["tg_id"])
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
await process_key(record, bot, conn)
else:
await delete_key_from_cluster(
cluster_id=record["server_id"],
email=record["email"],
client_id=record["client_id"]
)
await delete_key(record["client_id"], conn)
logger.info(f"Подписка {record['client_id']} удалена")

message = (
f"🔔 <b>Уведомление</b>\n\n"
f"📅 Ваша подписка: {record['email']} была удалена из-за истечения срока действия.\n\n"
f"⏳ Чтобы продолжить использовать наши услуги, пожалуйста, создайте новую подписку.\n\n"
f"💬 Если у вас возникли вопросы, не стесняйтесь обращаться в поддержку!"
)

keyboard = InlineKeyboardBuilder()
keyboard.row(types.InlineKeyboardButton(text=ADD_SUB, callback_data="create_key"))
keyboard.row(types.InlineKeyboardButton(text="📞 Поддержка", url=SUPPORT_CHAT_URL))
keyboard.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))

image_path = os.path.join("img", "notify_expired.jpg")

if os.path.isfile(image_path):
async with aiofiles.open(image_path, "rb") as image_file:
image_data = await image_file.read()
await bot.send_photo(
record["tg_id"],
photo=BufferedInputFile(image_data, filename="notify_expired.jpg"),
caption=message,
reply_markup=keyboard.as_markup(),
)
else:
await bot.send_message(
record["tg_id"],
text=message,
reply_markup=keyboard.as_markup()
)

logger.info(f"Уведомление об удалении отправлено пользователю {record['tg_id']}")

except TelegramForbiddenError:
logger.warning(f"Бот заблокирован пользователем {record['tg_id']}. Уведомление не отправлено.")
except Exception as e:
logger.error(f"Ошибка при удалении подписки {record['client_id']}: {e}")

async def process_key(record, bot, conn):
tg_id = record["tg_id"]
Expand All @@ -418,21 +466,37 @@ async def process_key(record, bot, conn):
time_left = expiry_date - current_date

logger.info(
f"Время истечения ключа: {expiry_time} (МСК: {expiry_date}), "
f"Время истечения подписки: {expiry_time} (МСК: {expiry_date}), "
f"Текущее время (МСК): {current_date}, "
f"Оставшееся время: {time_left}"
)

keyboard = InlineKeyboardBuilder()
current_time_utc = int(datetime.utcnow().timestamp() * 1000)
time_since_expiry = current_time_utc - expiry_time

if DELETE_KEYS_DELAY > 0:
keyboard.row(types.InlineKeyboardButton(text="🔄 Продлить", callback_data=f"renew_key|{email}"))
try:
if DELETE_KEYS_DELAY > 0 and current_date < expiry_date:
message = f"Ваша подписка {email} скоро истечет. Пополните баланс для продления."
await send_notification(bot, tg_id, message, "notify_expiring.jpg", email)

elif current_time_utc >= expiry_time:
message_expired = f"Ваша подписка {email} истекла. Пополните баланс для продления."
deletion_time = expiry_time + DELETE_KEYS_DELAY * 1000
remaining_time = deletion_time - current_time_utc

if remaining_time > 0:
time_until_deletion = format_time_until_deletion(remaining_time // 1000)
message_expired += f"\n\n⏳ Подписка будет удалена через {time_until_deletion}."

keyboard.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
logger.info(
f"Подписка {client_id} не удалена. "
f"Осталось времени до удаления: {remaining_time // 1000} сек. "
f"(Удаление через {DELETE_KEYS_DELAY} сек после истечения)"
)

image_path = os.path.join("img", "notify_expired.jpg")
if time_since_expiry <= EXPIRED_KEYS_CHECK_INTERVAL * 1000:
await send_notification(bot, tg_id, message_expired, "notify_expired.jpg", email)

try:
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"], conn)

Expand All @@ -443,7 +507,7 @@ async def process_key(record, bot, conn):

for cluster_id in servers:
await renew_key_in_cluster(cluster_id, email, client_id, new_expiry_time, TOTAL_GB)
logger.info(f"Ключ для пользователя {tg_id} успешно продлен в кластере {cluster_id}.")
logger.info(f"Подписка для пользователя {tg_id} успешно продлена в кластере {cluster_id}.")

await conn.execute(
"""
Expand All @@ -456,83 +520,44 @@ async def process_key(record, bot, conn):
logger.info(f"Флаги notified сброшены для клиента {client_id}.")

try:
image_path = os.path.join("img", "notify_expired.jpg")
if os.path.isfile(image_path):
async with aiofiles.open(image_path, "rb") as image_file:
image_data = await image_file.read()
await bot.send_photo(
tg_id,
photo=BufferedInputFile(image_data, filename="notify_expired.jpg"),
caption=KEY_RENEWED.format(email=email),
reply_markup=keyboard.as_markup(),
reply_markup=InlineKeyboardBuilder().as_markup(),
)
else:
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard.as_markup())
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=InlineKeyboardBuilder().as_markup())
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
except Exception as e:
logger.error(f"Не удалось отправить уведомление о продлении клиенту {tg_id}: {e}")

else:
current_time_utc = int(datetime.utcnow().timestamp() * 1000)

if current_time_utc >= expiry_time:
time_since_expiry = current_time_utc - expiry_time
if time_since_expiry <= EXPIRED_KEYS_CHECK_INTERVAL * 1000:
message_expired = f"Ваша подписка {email} истекла. Пополните баланс для продления."

if DELETE_KEYS_DELAY > 0:
time_until_deletion = format_time_until_deletion(DELETE_KEYS_DELAY)
if time_until_deletion != "0 минут":
message_expired += f"\n\n⏳ Ключ будет удалён через {time_until_deletion}."

try:
if os.path.isfile(image_path):
async with aiofiles.open(image_path, "rb") as image_file:
image_data = await image_file.read()
await bot.send_photo(
tg_id,
photo=BufferedInputFile(image_data, filename="notify_expired.jpg"),
caption=message_expired,
reply_markup=keyboard.as_markup(),
)
else:
await bot.send_message(tg_id, text=message_expired, reply_markup=keyboard.as_markup())
logger.info(f"Уведомление об истечении подписки отправлено пользователю {tg_id}.")
except Exception as e:
logger.error(f"Не удалось отправить уведомление об истечении клиенту {tg_id}: {e}")
else:
logger.info(f"Пропуск {client_id}: просрочено {time_since_expiry // 1000} сек назад.")

if AUTO_DELETE_EXPIRED_KEYS:
current_time = int(datetime.utcnow().timestamp() * 1000)

if DELETE_KEYS_DELAY == 0 or current_time >= expiry_time + (DELETE_KEYS_DELAY * 1000):
servers = await get_servers(conn)

for cluster_id in servers:
try:
await delete_key_from_cluster(cluster_id, email, client_id)
logger.info(f"Клиент {client_id} удален из кластера {cluster_id}.")
except Exception as e:
logger.error(f"Ошибка при удалении клиента {client_id} из кластера {cluster_id}: {e}")

try:
await delete_key(client_id)
log_msg = f"Ключ {client_id} удалён из базы данных"
if DELETE_KEYS_DELAY > 0:
log_msg += f" после задержки {DELETE_KEYS_DELAY} сек."
logger.info(log_msg)
except Exception as e:
logger.error(f"Ошибка при удалении ключа {client_id} из базы данных: {e}")
else:
remaining_time = (expiry_time + (DELETE_KEYS_DELAY * 1000) - current_time) // 1000
logger.info(
f"Ключ {client_id} не удалён. "
f"Осталось времени до удаления: {remaining_time} сек. "
f"(Удаление через {DELETE_KEYS_DELAY} сек после истечения)"
)

else:
logger.info(f"Ключ {client_id} не был удалён (AUTO_DELETE_EXPIRED_KEYS=False).")

except Exception as e:
logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}")
logger.error(f"Ошибка при обработке подписки для клиента {tg_id}: {e}")

async def send_notification(bot, tg_id, message, image_name, email):
keyboard = InlineKeyboardBuilder()
if DELETE_KEYS_DELAY > 0:
keyboard.row(types.InlineKeyboardButton(text="🔄 Продлить", callback_data=f"renew_key|{email}"))
keyboard.row(types.InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))

image_path = os.path.join("img", "notify_expired.jpg")

try:
if os.path.isfile(image_path):
async with aiofiles.open(image_path, "rb") as f:
await bot.send_photo(
tg_id,
photo=BufferedInputFile(await f.read(), filename=image_name),
caption=message,
reply_markup=keyboard.as_markup()
)
else:
await bot.send_message(tg_id, text=message, reply_markup=keyboard.as_markup())

except TelegramForbiddenError:
logger.warning(f"Пользователь {tg_id} заблокировал бота")
Loading