Merge pull request #147 from Capybara-z/patch-5

reorganizing notifications
This commit is contained in:
Vladislav Lisitsyn
2025-02-02 02:14:08 +03:00
committed by GitHub
+150 -131
View File
@@ -16,6 +16,7 @@ from config import (
ADMIN_USERNAME,
AUTO_DELETE_EXPIRED_KEYS,
AUTO_RENEW_KEYS,
SUPPORT_CHAT_URL,
DATABASE_URL,
DELETE_KEYS_DELAY,
DEV_MODE,
@@ -34,6 +35,7 @@ from database import (
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
@@ -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")
@@ -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(
@@ -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):
@@ -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}")
@@ -362,177 +372,186 @@ 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
WHERE expiry_time <= $1 AND expiry_time > $2
""",
threshold_time,
current_time - DELETE_KEYS_DELAY * 1000,
current_time,
)
logger.info(f"Найдено {len(expiring_keys)} подписок, срок действия которых скоро истекает.")
if DELETE_KEYS_DELAY > 0:
expired_keys_query = """
SELECT tg_id, client_id, email, server_id 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
WHERE expiry_time <= $1
"""
params = (current_time,)
for record in expiring_keys:
await process_key(record, bot, conn, current_time)
expired_keys_query = """
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 "")
)
balance = await get_balance(record["tg_id"])
expiry_time_value = record["expiry_time"]
current_time_utc = int(datetime.utcnow().timestamp() * 1000)
time_since_expiry = current_time_utc - expiry_time_value
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
await process_key(record, bot, conn, current_time, renew=True)
else:
await process_key(record, bot, conn, current_time)
if time_since_expiry >= DELETE_KEYS_DELAY * 1000:
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']}")
else:
remaining_time = (DELETE_KEYS_DELAY * 1000 - time_since_expiry) // 1000
logger.info(f"Подписка {record['client_id']} не удалена. Осталось времени до удаления: {remaining_time} сек. (Удаление через {DELETE_KEYS_DELAY} сек после истечения)")
except TelegramForbiddenError:
logger.warning(f"Бот заблокирован пользователем {record['tg_id']}. Уведомление не отправлено.")
except Exception as e:
logger.error(f"Ошибка при удалении ключа {record['client_id']}: {e}")
logger.error(f"Ошибка при удалении подписки {record['client_id']}: {e}")
async def process_key(record, bot, conn):
async def process_key(record, bot, conn, current_time, renew=False):
tg_id = record["tg_id"]
client_id = record["client_id"]
email = record["email"]
balance = await get_balance(tg_id)
expiry_time = record["expiry_time"]
expiry_time_value = record["expiry_time"]
moscow_tz = pytz.timezone("Europe/Moscow")
expiry_date = datetime.fromtimestamp(expiry_time / 1000, tz=moscow_tz)
expiry_date = datetime.fromtimestamp(expiry_time_value / 1000, tz=moscow_tz)
current_date = datetime.now(moscow_tz)
time_left = expiry_date - current_date
logger.info(
f"Время истечения ключа: {expiry_time} (МСК: {expiry_date}), "
f"Текущее время (МСК): {current_date}, "
f"Оставшееся время: {time_left}"
f"Время истечения подписки: {expiry_time_value} (МСК: {expiry_date}), "
f"Текущее время (МСК): {current_date}"
)
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")
current_time_utc = int(datetime.utcnow().timestamp() * 1000)
time_since_expiry = current_time_utc - expiry_time_value
try:
if AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
if not renew:
if current_time_utc >= expiry_time_value:
if time_since_expiry <= DELETE_KEYS_DELAY * 500:
message = (
f"🔔 <b>Уведомление:</b>\n\n"
f"📅 Ваша подписка: {record['email']} истекла. Пополните баланс для продления.\n\n"
)
remaining_time = (expiry_time_value + DELETE_KEYS_DELAY * 1000) - current_time_utc
if remaining_time > 0:
message += f"⏳ Подписка будет удалена через {format_time_until_deletion(remaining_time//1000)}."
await send_notification(bot, tg_id, message, "notify_expired.jpg", email)
else:
if (expiry_time_value - current_time_utc) <= (EXPIRED_KEYS_CHECK_INTERVAL * 1000):
await send_notification(
bot,
tg_id,
f"Ваша подписка {email} скоро истечет. Пополните баланс для продления.",
"notify_expiring.jpg",
email
)
elif renew and AUTO_RENEW_KEYS and balance >= RENEWAL_PLANS["1"]["price"]:
await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"], conn)
new_expiry_time = int((datetime.now(moscow_tz) + timedelta(days=30)).timestamp() * 1000)
await update_key_expiry(client_id, new_expiry_time, conn)
servers = await get_servers(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}.")
await conn.execute(
"""
UPDATE keys
SET notified = FALSE, notified_24h = FALSE
WHERE client_id = $1
""",
client_id,
)
logger.info(f"Флаги notified сброшены для клиента {client_id}.")
logger.info(f"Подписка {tg_id} продлена в кластере {cluster_id}.")
try:
image_path = os.path.join("img", "notify_expired.jpg")
caption = KEY_RENEWED.format(email=email)
if os.path.isfile(image_path):
async with aiofiles.open(image_path, "rb") as image_file:
image_data = await image_file.read()
async with aiofiles.open(image_path, "rb") as f:
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(),
photo=BufferedInputFile(await f.read(), filename="notify_expired.jpg"),
caption=caption,
reply_markup=InlineKeyboardBuilder().as_markup()
)
else:
await bot.send_message(tg_id, text=KEY_RENEWED, reply_markup=keyboard.as_markup())
logger.info(f"Уведомление об успешном продлении отправлено клиенту {tg_id}.")
await bot.send_message(tg_id, text=caption)
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).")
logger.error(f"Ошибка отправки уведомления {tg_id}: {e}")
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="notify_expired.jpg"),
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} заблокировал бота")