From c01c888f0ee22c8d4a5ec76faa0b455cf7d0ce27 Mon Sep 17 00:00:00 2001 From: Vladless Date: Wed, 13 Nov 2024 03:59:34 +0300 Subject: [PATCH] redesign/bug fixes/admin trial --- client.py | 10 +++- database.py | 16 +++++- handlers/admin/admin_commands.py | 2 +- handlers/admin/admin_panel.py | 7 +-- handlers/admin/admin_user_editor.py | 40 +++++++++++--- handlers/keys/key_management.py | 39 ++++++++++---- handlers/keys/keys.py | 78 ++++++++++++++++++++-------- handlers/keys/pic_keys.jpg | Bin 0 -> 17239 bytes handlers/notifications.py | 72 ++++++++++--------------- handlers/payments/freekassa_pay.py | 3 +- handlers/payments/stars_pay.py | 3 +- handlers/payments/yookassa_pay.py | 37 ++++++------- handlers/pic_invite.jpg | Bin 0 -> 17239 bytes handlers/profile.py | 39 +++++++++++--- requirements.txt | 6 +-- 15 files changed, 232 insertions(+), 120 deletions(-) create mode 100644 handlers/keys/pic_keys.jpg create mode 100644 handlers/pic_invite.jpg diff --git a/client.py b/client.py index fc865000..ecdb403d 100644 --- a/client.py +++ b/client.py @@ -1,6 +1,8 @@ import py3xui from loguru import logger +from config import TOTAL_GB + async def add_client( xui, @@ -64,9 +66,14 @@ async def extend_client_key(xui, email: str, new_expiry_time: int, client_id: st logger.info( f"Обновление ключа клиента {client.email} с ID {client.id} до нового времени: {new_expiry_time}" ) + client.id = client_id client.expiry_time = new_expiry_time - + + if TOTAL_GB > 0: + client.total_gb = TOTAL_GB + logger.info(f"Установлен объем трафика для клиента {client.email}: {TOTAL_GB} ГБ") + await xui.client.update(client.id, client) logger.info( f"Ключ клиента {client.email} успешно продлён до {new_expiry_time}." @@ -76,6 +83,7 @@ async def extend_client_key(xui, email: str, new_expiry_time: int, client_id: st logger.error(f"Ошибка при обновлении клиента с email {email}: {e}") + async def delete_client( xui, email: str, diff --git a/database.py b/database.py index fd03473b..901f1c4c 100644 --- a/database.py +++ b/database.py @@ -1,5 +1,5 @@ from datetime import datetime - +from loguru import logger import asyncpg from config import DATABASE_URL @@ -74,6 +74,20 @@ async def init_db(): await conn.close() +async def restore_trial(tg_id: int): + conn = await asyncpg.connect(DATABASE_URL) + try: + await conn.execute( + "UPDATE connections SET trial = 0 WHERE tg_id = $1", tg_id + ) + except Exception as e: + + logger.error(f"Ошибка при установке значения триала: {e}") + finally: + await conn.close() + + + async def add_connection(tg_id: int, balance: float = 0.0, trial: int = 0): conn = await asyncpg.connect(DATABASE_URL) diff --git a/handlers/admin/admin_commands.py b/handlers/admin/admin_commands.py index ebae8b95..8a701b18 100644 --- a/handlers/admin/admin_commands.py +++ b/handlers/admin/admin_commands.py @@ -3,7 +3,7 @@ from aiogram import Router, types from aiogram.filters import Command from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from filters.admin import IsAdminFilter +from handlers.filters.admin import IsAdminFilter from loguru import logger from bot import bot diff --git a/handlers/admin/admin_panel.py b/handlers/admin/admin_panel.py index 19dccfd2..1ab09779 100644 --- a/handlers/admin/admin_panel.py +++ b/handlers/admin/admin_panel.py @@ -3,17 +3,18 @@ from datetime import datetime import asyncpg from aiogram import F, Router, types + from aiogram.filters import Command from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, InlineKeyboardButton, Message from aiogram.utils.keyboard import InlineKeyboardBuilder -from filters.admin import IsAdminFilter +from handlers.filters.admin import IsAdminFilter from backup import backup_database from bot import bot -from config import DATABASE_URL -from handlers.commands import send_message_to_all_clients +from config import DATABASE_URL, ADMIN_ID +from handlers.admin.admin_commands import send_message_to_all_clients router = Router() diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 305aae89..bab13ab0 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -7,12 +7,12 @@ from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder -from filters.admin import IsAdminFilter +from handlers.filters.admin import IsAdminFilter from loguru import logger from bot import bot from config import DATABASE_URL, SERVERS -from database import get_client_id_by_email, update_key_expiry +from database import get_client_id_by_email, update_key_expiry, restore_trial from handlers.admin.admin_panel import back_to_admin_menu from handlers.keys.key_utils import delete_key_from_server, renew_server_key from handlers.utils import sanitize_key_name @@ -55,7 +55,6 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext): builder = InlineKeyboardBuilder() - # Добавляем кнопки ключей for (email,) in key_records: builder.row( InlineKeyboardButton( @@ -63,14 +62,18 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext): ) ) - # Кнопка изменения баланса 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="back_to_user_editor") ) @@ -90,6 +93,23 @@ async def handle_tg_id_input(message: types.Message, state: FSMContext): await conn.close() +@router.callback_query(F.data.startswith("restore_trial_"), IsAdminFilter()) +async def handle_restore_trial(callback_query: types.CallbackQuery): + tg_id = int(callback_query.data.split("_")[2]) + + await restore_trial(tg_id) + + builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="back_to_user_editor") + ) + + await callback_query.message.edit_text( + "✅ Триал успешно восстановлен.", + reply_markup=builder.as_markup() + ) + + @router.callback_query(F.data.startswith("change_balance_"), IsAdminFilter()) async def process_balance_change(callback_query: CallbackQuery, state: FSMContext): tg_id = int(callback_query.data.split("_")[2]) @@ -239,7 +259,15 @@ async def handle_key_name_input(message: types.Message, state: FSMContext): ) if not user_records: - await message.reply("🚫 Пользователь с указанным именем ключа не найден.") + builder = InlineKeyboardBuilder() + builder.row( + InlineKeyboardButton(text="🔙 Назад в меню администратора", callback_data="back_to_user_editor") + ) + + await message.reply( + "🚫 Пользователь с указанным именем ключа не найден.", + reply_markup=builder.as_markup(), + ) await state.clear() return diff --git a/handlers/keys/key_management.py b/handlers/keys/key_management.py index aa8399b4..c2b46cbe 100644 --- a/handlers/keys/key_management.py +++ b/handlers/keys/key_management.py @@ -1,6 +1,7 @@ import asyncio import uuid from datetime import datetime, timedelta +from loguru import logger import asyncpg from aiogram import F, Router @@ -9,7 +10,7 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message from bot import bot, dp -from config import APP_URL, DATABASE_URL, PUBLIC_LINK, SERVERS +from config import DATABASE_URL, PUBLIC_LINK, SERVERS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, CONNECT_ANDROID, CONNECT_IOS from database import add_connection, get_balance, store_key, update_balance from handlers.instructions.instructions import send_instructions from handlers.keys.key_utils import create_key_on_server @@ -92,6 +93,8 @@ async def select_server(callback_query: CallbackQuery, state: FSMContext): async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext): tg_id = callback_query.from_user.id + logger.info(f"User {tg_id} confirmed creation of a new key.") + balance = await get_balance(tg_id) if balance < RENEWAL_PLANS["1"]["price"]: replenish_button = InlineKeyboardButton( @@ -102,33 +105,38 @@ async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContex await state.clear() return - await callback_query.message.edit_text( - "🔑 Пожалуйста, введите имя подключаемого устройства:" - ) + logger.info(f"Balance for user {tg_id} is sufficient. Asking for device name.") + + await callback_query.message.edit_text("🔑 Пожалуйста, введите имя подключаемого устройства:") await state.set_state(Form.waiting_for_key_name) + logger.info(f"State set to waiting_for_key_name for user {tg_id}") await state.update_data(creating_new_key=True) await callback_query.answer() - @dp.callback_query(F.data == "cancel_create_key") async def cancel_create_key(callback_query: CallbackQuery, state: FSMContext): await process_callback_view_profile(callback_query, state) await callback_query.answer() +@router.message(Form.waiting_for_key_name) async def handle_key_name_input(message: Message, state: FSMContext): tg_id = message.from_user.id key_name = sanitize_key_name(message.text) + logger.info(f"User {tg_id} is attempting to create a key with the name: {key_name}") + if not key_name: await message.bot.send_message( tg_id, "📝 Пожалуйста, назовите устройство на английском языке." ) + logger.warning(f"User {tg_id} entered an invalid key name: {key_name}") return conn = await asyncpg.connect(DATABASE_URL) try: + logger.info(f"Checking if key name '{key_name}' already exists in the database.") existing_key = await conn.fetchrow( "SELECT * FROM keys WHERE email = $1", key_name.lower() ) @@ -137,6 +145,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): tg_id, "❌ Упс! Это имя уже используется. Выберите другое уникальное название для ключа.", ) + logger.warning(f"Key name '{key_name}' already exists in the database for user {tg_id}.") await state.set_state(Form.waiting_for_key_name) return finally: @@ -149,6 +158,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): conn = await asyncpg.connect(DATABASE_URL) try: + logger.info(f"Checking trial status for user {tg_id}.") existing_connection = await conn.fetchrow( "SELECT trial FROM connections WHERE tg_id = $1", tg_id ) @@ -159,6 +169,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): if trial_status == 0: expiry_time = current_time + timedelta(days=1, hours=3) + logger.info(f"Assigned 1-day trial to user {tg_id}.") else: balance = await get_balance(tg_id) if balance < RENEWAL_PLANS["1"]["price"]: @@ -171,32 +182,36 @@ async def handle_key_name_input(message: Message, state: FSMContext): "💳 Недостаточно средств для создания подписки на новое устройство. Пополните баланс в личном кабинете.", reply_markup=keyboard, ) + logger.warning(f"User {tg_id} has insufficient funds for key creation.") await state.clear() return await update_balance(tg_id, -RENEWAL_PLANS["1"]["price"]) expiry_time = current_time + timedelta(days=30, hours=3) + logger.info(f"User {tg_id} balance deducted for key creation.") expiry_timestamp = int(expiry_time.timestamp() * 1000) public_link = f"{PUBLIC_LINK}{email}" + logger.info(f"Generated public link for the key: {public_link}") + button_profile = InlineKeyboardButton( text="👤 Личный кабинет", callback_data="view_profile" ) button_iphone = InlineKeyboardButton( - text="🍏 Подключить", url=f"{APP_URL}/?url=v2raytun://import/{public_link}" + text="🍏 Подключить", url=f"{CONNECT_IOS}{public_link}" ) button_android = InlineKeyboardButton( text="🤖 Подключить", - url=f"{APP_URL}/?url=v2raytun://import-sub?url={public_link}", + url=f"{CONNECT_ANDROID}{public_link}", ) button_download_ios = InlineKeyboardButton( - text="🍏 Скачать", url="https://apps.apple.com/ru/app/v2raytun/id6476628951" + text="🍏 Скачать", url=DOWNLOAD_IOS ) button_download_android = InlineKeyboardButton( text="🤖 Скачать", - url="https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru", + url=DOWNLOAD_ANDROID, ) keyboard = InlineKeyboardMarkup( @@ -211,6 +226,8 @@ async def handle_key_name_input(message: Message, state: FSMContext): days = remaining_time.days key_message = key_message_success(public_link, f"⏳ Осталось дней: {days} 📅") + logger.info(f"Sending key message to user {tg_id} with the public link.") + await message.bot.send_message( tg_id, key_message, parse_mode="HTML", reply_markup=keyboard ) @@ -230,6 +247,7 @@ async def handle_key_name_input(message: Message, state: FSMContext): conn = await asyncpg.connect(DATABASE_URL) try: + logger.info(f"Updating trial status for user {tg_id} in the database.") existing_connection = await conn.fetchrow( "SELECT * FROM connections WHERE tg_id = $1", tg_id ) @@ -242,16 +260,19 @@ async def handle_key_name_input(message: Message, state: FSMContext): finally: await conn.close() + logger.info(f"Storing key for user {tg_id} in the database.") await store_key( tg_id, client_id, email, expiry_timestamp, public_link, "all_servers" ) except Exception as e: + logger.error(f"Error while creating the key for user {tg_id}: {e}") await message.bot.send_message(tg_id, f"❌ Ошибка при создании ключа: {e}") await state.clear() + @dp.callback_query(F.data == "instructions") async def handle_instructions(callback_query: CallbackQuery): await send_instructions(callback_query) diff --git a/handlers/keys/keys.py b/handlers/keys/keys.py index 19e4fc96..9f494db8 100644 --- a/handlers/keys/keys.py +++ b/handlers/keys/keys.py @@ -9,7 +9,7 @@ from aiogram.types import BufferedInputFile from loguru import logger from bot import bot -from config import APP_URL, DATABASE_URL, PUBLIC_LINK, SERVERS +from config import DATABASE_URL, PUBLIC_LINK, SERVERS, DOWNLOAD_ANDROID, DOWNLOAD_IOS, CONNECT_ANDROID, CONNECT_IOS from database import delete_key, get_balance, store_key, update_balance, update_key_expiry from handlers.keys.key_utils import delete_key_from_db, delete_key_from_server, renew_server_key, update_key_on_server from handlers.texts import INSUFFICIENT_FUNDS_MSG, KEY_NOT_FOUND_MSG, NO_KEYS, PLAN_SELECTION_MSG, RENEWAL_PLANS, SUCCESS_RENEWAL_MSG, key_message @@ -56,16 +56,32 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): "👆 Выберите устройство для управления подпиской:" ) - await bot.delete_message( - chat_id=tg_id, message_id=callback_query.message.message_id - ) + image_path = os.path.join(os.path.dirname(__file__), "pic_keys.jpg") + + try: + await bot.delete_message( + chat_id=tg_id, message_id=callback_query.message.message_id + ) + except Exception as e: + logger.error(f"Ошибка при удалении сообщения: {e}") + + if os.path.isfile(image_path): + with open(image_path, "rb") as image_file: + await bot.send_photo( + chat_id=tg_id, + photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"), + caption=response_message, + parse_mode="HTML", + reply_markup=inline_keyboard, + ) + else: + await bot.send_message( + chat_id=tg_id, + text=response_message, + reply_markup=inline_keyboard, + parse_mode="HTML", + ) - await bot.send_message( - chat_id=tg_id, - text=response_message, - reply_markup=inline_keyboard, - parse_mode="HTML", - ) else: response_message = NO_KEYS create_key_button = types.InlineKeyboardButton( @@ -79,16 +95,31 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): inline_keyboard=[[create_key_button], [back_button]] ) - await bot.delete_message( - chat_id=tg_id, message_id=callback_query.message.message_id - ) + try: + await bot.delete_message( + chat_id=tg_id, message_id=callback_query.message.message_id + ) + except Exception as e: + logger.error(f"Ошибка при удалении сообщения: {e}") - await bot.send_message( - chat_id=tg_id, - text=response_message, - reply_markup=keyboard, - parse_mode="HTML", - ) + image_path = os.path.join(os.path.dirname(__file__), "pic_keys.jpg") + + if os.path.isfile(image_path): + with open(image_path, "rb") as image_file: + await bot.send_photo( + chat_id=tg_id, + photo=BufferedInputFile(image_file.read(), filename="pic_keys.jpg"), + caption=response_message, + parse_mode="HTML", + reply_markup=keyboard, + ) + else: + await bot.send_message( + chat_id=tg_id, + text=response_message, + reply_markup=keyboard, + parse_mode="HTML", + ) finally: await conn.close() @@ -99,6 +130,7 @@ async def process_callback_view_keys(callback_query: types.CallbackQuery): await callback_query.answer() + @router.callback_query(F.data.startswith("view_key|")) async def process_callback_view_key(callback_query: types.CallbackQuery): tg_id = callback_query.from_user.id @@ -154,19 +186,19 @@ async def process_callback_view_key(callback_query: types.CallbackQuery): download_android_button = types.InlineKeyboardButton( text="🤖 Скачать", - url="https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru", + url=DOWNLOAD_ANDROID, ) download_iphone_button = types.InlineKeyboardButton( text="🍏 Скачать", - url="https://apps.apple.com/ru/app/v2raytun/id6476628951", + url=DOWNLOAD_IOS, ) connect_iphone_button = types.InlineKeyboardButton( - text="🍏 Подключить", url=f"{APP_URL}/?url=v2raytun://import/{key}" + text="🍏 Подключить", url=f"{CONNECT_IOS}{key}" ) connect_android_button = types.InlineKeyboardButton( text="🤖 Подключить", - url=f"{APP_URL}/?url=v2raytun://import-sub?url={key}", + url=f"{CONNECT_ANDROID}{key}", ) renew_button = types.InlineKeyboardButton( diff --git a/handlers/keys/pic_keys.jpg b/handlers/keys/pic_keys.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a6ec6b6b99a3a3c688cdad8a7443b89d5d616e27 GIT binary patch literal 17239 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 Rduq=e+y9;M1Mso>{{Y!~E%yKb literal 0 HcmV?d00001 diff --git a/handlers/notifications.py b/handlers/notifications.py index ecdf56f4..da1c0e05 100644 --- a/handlers/notifications.py +++ b/handlers/notifications.py @@ -217,13 +217,7 @@ async def notify_24h_keys( async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: float): logger.info("Проверка истекших ключей...") - current_time = datetime.utcnow().timestamp() * 1000 - adjusted_current_time = current_time + (3 * 60 * 60 * 1000) - - logger.info( - f"Текущее время: {current_time}, Скорректированное текущее время: {adjusted_current_time}" - ) - + adjusted_current_time = current_time + (3 * 60 * 60 * 1000) expiring_keys = await conn.fetch( """ SELECT tg_id, client_id, expiry_time, email FROM keys @@ -231,7 +225,6 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: """, adjusted_current_time, ) - logger.info(f"Найдено {len(expiring_keys)} истекающих ключей.") for record in expiring_keys: @@ -239,17 +232,17 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: client_id = record["client_id"] email = record["email"] balance = await get_balance(tg_id) - expiry_time = record["expiry_time"] expiry_date = datetime.utcfromtimestamp(expiry_time / 1000) current_date = datetime.utcnow() time_left = expiry_date - current_date + logger.info( f"Время истечения ключа: {expiry_time} (дата: {expiry_date}), Текущее время: {current_date}, Оставшееся время: {time_left}" ) message_expired = ( - "❌ Ваша подписка {email} истекла и была удалена!\n\n" + f"❌ Ваша подписка {email} истекла и была удалена!\n\n" "🔍 Перейдите в профиль для создания нового ключа.\n" "💡 Не откладывайте подключение VPN!" ) @@ -265,9 +258,6 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: (datetime.utcnow() + timedelta(days=30)).timestamp() * 1000 ) await update_key_expiry(client_id, new_expiry_time) - logger.info( - f"Ключ для клиента {tg_id} продлен до {datetime.utcfromtimestamp(new_expiry_time / 1000).strftime('%Y-%m-%d %H:%M:%S')}." - ) all_success = True for server_id in SERVERS: @@ -281,48 +271,38 @@ async def handle_expired_keys(bot: Bot, conn: asyncpg.Connection, current_time: ) if not success: all_success = False - logger.error( - f"Не удалось продлить ключ для пользователя {tg_id} на сервере {server_id}." - ) + logger.error(f"Не удалось продлить ключ для пользователя {tg_id} на сервере {server_id}.") if all_success: try: - await bot.send_message( - tg_id, KEY_RENEWED, reply_markup=keyboard - ) - logger.info( - f"Ключ для пользователя {tg_id} успешно продлен на месяц на всех серверах." - ) + await bot.send_message(tg_id, KEY_RENEWED, reply_markup=keyboard) + logger.info(f"Ключ для пользователя {tg_id} успешно продлен.") except Exception as e: - logger.error( - f"Ошибка при отправке уведомления о продлении ключа пользователю {tg_id}: {e}" - ) + logger.error(f"Ошибка при отправке уведомления пользователю {tg_id}: {e}") + else: try: - await bot.send_message( - tg_id, message_expired, reply_markup=keyboard - ) - await delete_key(client_id) - - for server_id in SERVERS: - xui = AsyncApi( - SERVERS[server_id]["API_URL"], - username=ADMIN_USERNAME, - password=ADMIN_PASSWORD, - ) - success = await delete_client(xui, email, client_id) - if success: - logger.info( - f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}." - ) - else: - logger.error( - f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}." - ) + await bot.send_message(tg_id, message_expired, reply_markup=keyboard) except Exception as e: - logger.error(f"Ошибка при удалении ключа для клиента {tg_id}: {e}") + if "chat not found" in str(e): + logger.warning(f"Чат для клиента {tg_id} не найден. Пропуск отправки сообщения.") + + await delete_key(client_id) + + for server_id in SERVERS: + xui = AsyncApi( + SERVERS[server_id]["API_URL"], + username=ADMIN_USERNAME, + password=ADMIN_PASSWORD, + ) + success = await delete_client(xui, email, client_id) + if success: + logger.info(f"Ключ для клиента {tg_id} успешно удален с сервера {server_id}.") + else: + logger.error(f"Не удалось удалить ключ для клиента {tg_id} на сервере {server_id}.") except Exception as e: logger.error(f"Ошибка при обработке ключа для клиента {tg_id}: {e}") await asyncio.sleep(1) + diff --git a/handlers/payments/freekassa_pay.py b/handlers/payments/freekassa_pay.py index 4a0ce507..e0db3b22 100644 --- a/handlers/payments/freekassa_pay.py +++ b/handlers/payments/freekassa_pay.py @@ -11,7 +11,8 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder from aiohttp import web -from telegram import InlineKeyboardMarkup +from aiogram.types import InlineKeyboardMarkup + from bot import bot from config import FREEKASSA_API_KEY, FREEKASSA_SHOP_ID diff --git a/handlers/payments/stars_pay.py b/handlers/payments/stars_pay.py index deb49a52..e655407b 100644 --- a/handlers/payments/stars_pay.py +++ b/handlers/payments/stars_pay.py @@ -4,7 +4,8 @@ from aiogram.fsm.state import State, StatesGroup from aiogram.types import InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder from loguru import logger -from telegram import LabeledPrice + +from aiogram.types import LabeledPrice from bot import bot from config import RUB_TO_XTR diff --git a/handlers/payments/yookassa_pay.py b/handlers/payments/yookassa_pay.py index 49d8fba0..e57cab33 100644 --- a/handlers/payments/yookassa_pay.py +++ b/handlers/payments/yookassa_pay.py @@ -63,25 +63,25 @@ async def process_callback_pay_yookassa( builder = InlineKeyboardBuilder() - for i in range(0, len(PAYMENT_OPTIONS), 2): - if i + 1 < len(PAYMENT_OPTIONS): - builder.row( - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i]["text"], - callback_data=PAYMENT_OPTIONS[i]["callback_data"], - ), - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i + 1]["text"], - callback_data=PAYMENT_OPTIONS[i + 1]["callback_data"], - ), - ) - else: - builder.row( - InlineKeyboardButton( - text=PAYMENT_OPTIONS[i]["text"], - callback_data=PAYMENT_OPTIONS[i]["callback_data"], - ) + for i in range(0, 4, 2): + builder.row( + InlineKeyboardButton( + text=PAYMENT_OPTIONS[i]["text"], + callback_data=PAYMENT_OPTIONS[i]["callback_data"], + ), + InlineKeyboardButton( + text=PAYMENT_OPTIONS[i + 1]["text"], + callback_data=PAYMENT_OPTIONS[i + 1]["callback_data"], + ), + ) + + for i in range(4, len(PAYMENT_OPTIONS)): + builder.row( + InlineKeyboardButton( + text=PAYMENT_OPTIONS[i]["text"], + callback_data=PAYMENT_OPTIONS[i]["callback_data"], ) + ) key_count = await get_key_count(tg_id) @@ -107,6 +107,7 @@ async def process_callback_pay_yookassa( await callback_query.answer() + @router.callback_query(F.data == "back_to_profile") async def back_to_profile_handler( callback_query: types.CallbackQuery, state: FSMContext diff --git a/handlers/pic_invite.jpg b/handlers/pic_invite.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a6ec6b6b99a3a3c688cdad8a7443b89d5d616e27 GIT binary patch literal 17239 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 Rduq=e+y9;M1Mso>{{Y!~E%yKb literal 0 HcmV?d00001 diff --git a/handlers/profile.py b/handlers/profile.py index 913307ed..0b5377c6 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -92,23 +92,48 @@ async def invite_handler(callback_query: types.CallbackQuery): invite_message = invite_message_send(referral_link, referral_stats) + image_path = os.path.join(os.path.dirname(__file__), "pic_invite.jpg") + builder = InlineKeyboardBuilder() builder.row( InlineKeyboardButton(text="⬅️ Вернуться в профиль", callback_data="view_profile") ) - await callback_query.message.delete() + try: + await callback_query.message.delete() + except Exception as e: + logger.error(f"Ошибка при удалении сообщения: {e}") - await bot.send_message( - chat_id=chat_id, - text=invite_message, - parse_mode="HTML", - reply_markup=builder.as_markup(), - ) + try: + if os.path.isfile(image_path): + with open(image_path, "rb") as image_file: + await bot.send_photo( + chat_id=chat_id, + photo=BufferedInputFile(image_file.read(), filename="pic_invite.jpg"), + caption=invite_message, + parse_mode="HTML", + reply_markup=builder.as_markup(), + ) + else: + await bot.send_message( + chat_id=chat_id, + text=invite_message, + parse_mode="HTML", + reply_markup=builder.as_markup(), + ) + except Exception as e: + await bot.send_message( + chat_id=chat_id, + text=f"❗️ Не удалось отправить сообщение. Техническая ошибка: {e}", + parse_mode="HTML", + reply_markup=builder.as_markup(), + ) await callback_query.answer() + + @router.callback_query(F.data == "view_profile") async def view_profile_handler(callback_query: types.CallbackQuery, state: FSMContext): await process_callback_view_profile(callback_query, state) diff --git a/requirements.txt b/requirements.txt index 2ee02a5d..5aad9d5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ annotated-types==0.7.0 async-timeout==4.0.3 asyncpg==0.30.0 attrs==24.2.0 -certifi==2024.8.30 +certifi charset-normalizer==3.4.0 Deprecated==1.2.14 distro==1.9.0 @@ -17,8 +17,8 @@ magic-filter==1.0.12 multidict==6.1.0 netaddr==1.3.0 propcache==0.2.0 -pydantic==2.9.2 -pydantic_core==2.23.4 +pydantic +pydantic_core requests==2.32.3 typing_extensions==4.12.2 urllib3==2.2.3