diff --git a/handlers/admin/admin_panel.py b/handlers/admin/admin_panel.py index 81161293..9e26b137 100644 --- a/handlers/admin/admin_panel.py +++ b/handlers/admin/admin_panel.py @@ -58,7 +58,7 @@ async def handle_admin_message(message: types.Message, state: FSMContext): ) ) builder.row( - InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to_alls") + InlineKeyboardButton(text="📢 Массовая рассылка", callback_data="send_to") ) builder.row( InlineKeyboardButton( @@ -261,14 +261,40 @@ async def export_payments_csv(callback_query: CallbackQuery, session: Any): ) -@router.callback_query(F.data == "send_to_alls", IsAdminFilter()) +@router.callback_query(F.data == "send_to", IsAdminFilter()) async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext): builder = InlineKeyboardBuilder() + builder.row(InlineKeyboardButton(text="📢 Отправить всем", callback_data="send_to_all")) + builder.row(InlineKeyboardButton(text="📢 Отправить с подпиской", callback_data="send_to_subscribed")) + builder.row(InlineKeyboardButton(text="📢 Отправить без подписки", callback_data="send_to_unsubscribed")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="admin")) await callback_query.message.answer( - "✍️ Введите текст сообщения, который вы хотите отправить всем клиентам 📢🌐:", + "✍️ Выберите группу пользователей и введите текст сообщения для рассылки:", reply_markup=builder.as_markup(), ) + +@router.callback_query(F.data == "send_to_all", IsAdminFilter()) +async def handle_send_to_all(callback_query: CallbackQuery, state: FSMContext): + await state.update_data(send_to="all") + await callback_query.message.answer( + "✍️ Введите текст сообщения для рассылки всем пользователям:" + ) + await state.set_state(UserEditorState.waiting_for_message) + +@router.callback_query(F.data == "send_to_subscribed", IsAdminFilter()) +async def handle_send_to_subscribed(callback_query: CallbackQuery, state: FSMContext): + await state.update_data(send_to="subscribed") + await callback_query.message.answer( + "✍️ Введите текст сообщения для рассылки пользователям с активной подпиской:" + ) + await state.set_state(UserEditorState.waiting_for_message) + +@router.callback_query(F.data == "send_to_unsubscribed", IsAdminFilter()) +async def handle_send_to_unsubscribed(callback_query: CallbackQuery, state: FSMContext): + await state.update_data(send_to="unsubscribed") + await callback_query.message.answer( + "✍️ Введите текст сообщения для рассылки пользователям без активной подписки:" + ) await state.set_state(UserEditorState.waiting_for_message) @@ -279,7 +305,26 @@ async def process_message_to_all( text_message = message.text try: - tg_ids = await session.fetch("SELECT tg_id FROM connections") + state_data = await state.get_data() + send_to = state_data.get('send_to', 'all') + + if send_to == 'all': + tg_ids = await session.fetch("SELECT DISTINCT c.tg_id FROM connections c") + elif send_to == 'subscribed': + tg_ids = await session.fetch(""" + SELECT DISTINCT c.tg_id + FROM connections c + JOIN keys k ON c.tg_id = k.tg_id + WHERE k.expiry_time > CURRENT_TIMESTAMP + """) + elif send_to == 'unsubscribed': + tg_ids = await session.fetch(""" + SELECT c.tg_id + FROM connections c + LEFT JOIN keys k ON c.tg_id = k.tg_id + GROUP BY c.tg_id + HAVING COUNT(k.tg_id) = 0 OR MAX(k.expiry_time) <= CURRENT_TIMESTAMP + """) total_users = len(tg_ids) success_count = 0 diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 18fa6fc1..0f484846 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -115,6 +115,7 @@ async def handle_username_input( ) ) builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}")) + builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}")) builder.row( InlineKeyboardButton( text="✉️ Отправить сообщение", @@ -202,6 +203,7 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext, session: ) ) builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}")) + builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}")) builder.row( InlineKeyboardButton( text="🔄 Восстановить пробник", @@ -647,6 +649,13 @@ async def handle_user_info( builder.row(InlineKeyboardButton(text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}")) builder.row(InlineKeyboardButton(text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}")) builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}")) + builder.row(InlineKeyboardButton(text="🔄 Обновить клиента", callback_data=f"user_info|{tg_id}")) + builder.row( + InlineKeyboardButton( + text="✉️ Отправить сообщение", + callback_data=f"send_message_{tg_id}" + ) + ) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) user_info = ( diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py index 8db9f50b..eb039ff2 100644 --- a/handlers/keys/keys.py +++ b/handlers/keys/keys.py @@ -190,6 +190,13 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton( + text="🔄 Обновить подписку", + callback_data=f"update_subscription|{key_name}", + ) + ) + builder.row( InlineKeyboardButton(text="🍏 Скачать для iOS", url=DOWNLOAD_IOS), InlineKeyboardButton( @@ -222,15 +229,7 @@ async def process_callback_view_key(callback_query: types.CallbackQuery, session InlineKeyboardButton( text="❌ Удалить", callback_data=f"delete_key|{key_name}" ), - ) - - if not key.startswith(PUBLIC_LINK): - builder.row( - InlineKeyboardButton( - text="🔄 Обновить подписку", - callback_data=f"update_subscription|{key_name}", - ) - ) + ) builder.row( InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile") diff --git a/handlers/notifications.py b/handlers/notifications.py index 7a2b9271..4678a80a 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -413,3 +413,29 @@ async def check_online_users(): logger.error( f"Не удалось проверить пользователей на сервере {server_id}: {e}" ) + + +async def update_all_keys(): + try: + conn = await asyncpg.connect(DATABASE_URL) + keys = await conn.fetch("SELECT tg_id, client_id, email, expiry_time, server_id FROM keys") + + for key in keys: + tg_id = key['tg_id'] + client_id = key['client_id'] + email = key['email'] + expiry_time = key['expiry_time'] + cluster_id = key['server_id'] + + try: + await update_key_on_cluster(tg_id, client_id, email, expiry_time, cluster_id) + await store_key(tg_id, client_id, email, expiry_time, key['key'], cluster_id, conn) + logger.info(f"Ключ {client_id} успешно обновлен и сохранен") + except Exception as e: + logger.error(f"Ошибка при обновлении и сохранении ключа {client_id}: {e}") + + logger.info("Все ключи успешно обновлены и сохранены") + except Exception as e: + logger.error(f"Ошибка при обновлении всех ключей: {e}") + finally: + await conn.close() diff --git a/handlers/utils.py b/handlers/utils.py index eeed52d8..a2d26b0b 100644 --- a/handlers/utils.py +++ b/handlers/utils.py @@ -1,6 +1,8 @@ import random import re +import json +import aiohttp import asyncpg from bot import bot @@ -9,6 +11,21 @@ from database import get_servers_from_db from logger import logger +async def get_usd_rate(): + try: + async with aiohttp.ClientSession() as session: + async with session.get('https://www.cbr-xml-daily.ru/daily_json.js') as response: + if response.status == 200: + data = await response.text() + usd = float(json.loads(data)['Valute']['USD']['Value']) + else: + usd = float(100) # Default value if request fails + except Exception as e: + logger.exception(f"Error fetching USD rate: {e}") + usd = float(100) # Default value if an exception occurs + return usd + + def sanitize_key_name(key_name: str) -> str: """ Очищает название ключа, оставляя только допустимые символы.