Добавление нового middleware и рефакторинг обработчиков с поддержкой target_message
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
from aiogram import Router
|
||||
|
||||
from .router import router
|
||||
|
||||
|
||||
__all__ = ["router"]
|
||||
@@ -3,8 +3,7 @@ from typing import Any
|
||||
from aiogram import F, Router
|
||||
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 aiogram.types import CallbackQuery, Message
|
||||
|
||||
from database import (
|
||||
check_coupon_usage,
|
||||
@@ -13,8 +12,8 @@ from database import (
|
||||
update_balance,
|
||||
update_coupon_usage_count,
|
||||
)
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
from handlers.utils import edit_or_send_message
|
||||
from keyboards.coupons import get_coupon_keyboard
|
||||
|
||||
|
||||
class CouponActivationState(StatesGroup):
|
||||
@@ -26,14 +25,16 @@ router = Router()
|
||||
|
||||
@router.callback_query(F.data == "activate_coupon")
|
||||
@router.message(F.text == "/activate_coupon")
|
||||
async def handle_activate_coupon(callback_query_or_message: Message | CallbackQuery, state: FSMContext):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
async def handle_activate_coupon(state: FSMContext, target_message: Message):
|
||||
"""
|
||||
Обрабатывает запрос на активацию купона.
|
||||
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
target_message = callback_query_or_message.message
|
||||
else:
|
||||
target_message = callback_query_or_message
|
||||
Args:
|
||||
state: Контекст состояния FSM.
|
||||
chat_id: ID чата (добавлено middleware).
|
||||
target_message: Целевое сообщение для ответа (добавлено middleware).
|
||||
"""
|
||||
builder = get_coupon_keyboard()
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
@@ -46,18 +47,37 @@ async def handle_activate_coupon(callback_query_or_message: Message | CallbackQu
|
||||
|
||||
|
||||
@router.message(CouponActivationState.waiting_for_coupon_code)
|
||||
async def process_coupon_code(message: Message, state: FSMContext, session: Any):
|
||||
coupon_code = message.text.strip()
|
||||
activation_result = await activate_coupon(message.chat.id, coupon_code, session)
|
||||
async def process_coupon_code(message: Message, state: FSMContext, session: Any, chat_id: int):
|
||||
"""
|
||||
Обрабатывает введенный код купона.
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
Args:
|
||||
message: Сообщение с кодом купона.
|
||||
state: Контекст состояния FSM.
|
||||
session: Сессия базы данных.
|
||||
chat_id: ID чата (добавлено middleware).
|
||||
"""
|
||||
coupon_code = message.text.strip()
|
||||
activation_result = await activate_coupon(chat_id, coupon_code, session)
|
||||
|
||||
builder = get_coupon_keyboard()
|
||||
|
||||
await message.answer(activation_result, reply_markup=builder.as_markup())
|
||||
await state.clear()
|
||||
|
||||
|
||||
async def activate_coupon(user_id: int, coupon_code: str, session: Any):
|
||||
async def activate_coupon(user_id: int, coupon_code: str, session: Any) -> str:
|
||||
"""
|
||||
Активирует купон для пользователя.
|
||||
|
||||
Args:
|
||||
user_id: ID пользователя.
|
||||
coupon_code: Код купона.
|
||||
session: Сессия базы данных.
|
||||
|
||||
Returns:
|
||||
str: Сообщение о результате активации.
|
||||
"""
|
||||
coupon_record = await get_coupon_by_code(coupon_code, session)
|
||||
|
||||
if not coupon_record:
|
||||
+4
-4
@@ -20,7 +20,7 @@ router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "donate")
|
||||
async def process_donate(callback_query: CallbackQuery, state: FSMContext):
|
||||
async def process_donate(callback_query: CallbackQuery, state: FSMContext, target_message: Message):
|
||||
await state.clear()
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -40,20 +40,20 @@ async def process_donate(callback_query: CallbackQuery, state: FSMContext):
|
||||
)
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
target_message=target_message,
|
||||
text=text,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "enter_custom_donate_amount")
|
||||
async def process_enter_donate_amount(callback_query: CallbackQuery, state: FSMContext):
|
||||
async def process_enter_donate_amount(callback_query: CallbackQuery, state: FSMContext, target_message: Message):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="donate"))
|
||||
text = "💸 Введите сумму доната в рублях:"
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
target_message=target_message,
|
||||
text=text,
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ router = Router()
|
||||
|
||||
@router.callback_query(F.data == "instructions")
|
||||
@router.message(F.text == "/instructions")
|
||||
async def send_instructions(callback_query_or_message: CallbackQuery | Message):
|
||||
async def send_instructions(target_message: Message):
|
||||
instructions_message = INSTRUCTIONS
|
||||
image_path = os.path.join("img", "instructions.jpg")
|
||||
|
||||
@@ -35,11 +35,6 @@ async def send_instructions(callback_query_or_message: CallbackQuery | Message):
|
||||
builder.row(InlineKeyboardButton(text="💬 Поддержка", url=SUPPORT_CHAT_URL))
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
target_message = callback_query_or_message.message
|
||||
else:
|
||||
target_message = callback_query_or_message
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=instructions_message,
|
||||
@@ -49,14 +44,14 @@ async def send_instructions(callback_query_or_message: CallbackQuery | Message):
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("connect_pc|"))
|
||||
async def process_connect_pc(callback_query: CallbackQuery, session: Any):
|
||||
async def process_connect_pc(callback_query: CallbackQuery, session: Any, target_message: Message):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
record = await get_key_details(key_name, session)
|
||||
if not record:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
target_message=target_message,
|
||||
text="❌ <b>Ключ не найден. Проверьте имя ключа.</b> 🔍",
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
@@ -75,7 +70,7 @@ async def process_connect_pc(callback_query: CallbackQuery, session: Any):
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
target_message=target_message,
|
||||
text=instruction_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
@@ -83,7 +78,7 @@ async def process_connect_pc(callback_query: CallbackQuery, session: Any):
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("connect_tv|"))
|
||||
async def process_connect_tv(callback_query: CallbackQuery):
|
||||
async def process_connect_tv(callback_query: CallbackQuery, target_message: Message):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
@@ -92,7 +87,7 @@ async def process_connect_tv(callback_query: CallbackQuery):
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
target_message=target_message,
|
||||
text=CONNECT_TV_TEXT,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
@@ -101,7 +96,7 @@ async def process_connect_tv(callback_query: CallbackQuery):
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("continue_tv|"))
|
||||
async def process_continue_tv(callback_query: CallbackQuery, session: Any):
|
||||
async def process_continue_tv(callback_query: CallbackQuery, session: Any, target_message: Message):
|
||||
key_name = callback_query.data.split("|")[1]
|
||||
|
||||
record = await get_key_details(key_name, session)
|
||||
@@ -114,5 +109,5 @@ async def process_continue_tv(callback_query: CallbackQuery, session: Any):
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message, text=message_text, reply_markup=builder.as_markup(), media_path=None
|
||||
target_message=target_message, text=message_text, reply_markup=builder.as_markup(), media_path=None
|
||||
)
|
||||
|
||||
@@ -71,16 +71,18 @@ class Form(FSMContext):
|
||||
|
||||
|
||||
@router.callback_query(F.data == "create_key")
|
||||
async def confirm_create_new_key(callback_query: CallbackQuery, state: FSMContext, session: Any):
|
||||
async def confirm_create_new_key(
|
||||
callback_query: CallbackQuery, state: FSMContext, session: Any, target_message: Message
|
||||
):
|
||||
tg_id = callback_query.message.chat.id
|
||||
await handle_key_creation(tg_id, state, session, callback_query)
|
||||
await handle_key_creation(tg_id, state, session, target_message)
|
||||
|
||||
|
||||
async def handle_key_creation(
|
||||
tg_id: int,
|
||||
state: FSMContext,
|
||||
session: Any,
|
||||
message_or_query: Message | CallbackQuery,
|
||||
target_message: Message,
|
||||
):
|
||||
"""Создание ключа с учётом выбора тарифного плана."""
|
||||
current_time = datetime.now(moscow_tz)
|
||||
@@ -94,14 +96,12 @@ async def handle_key_creation(
|
||||
updated = await update_trial(tg_id, 1, session)
|
||||
if updated:
|
||||
await edit_or_send_message(
|
||||
target_message=message_or_query
|
||||
if isinstance(message_or_query, Message)
|
||||
else message_or_query.message,
|
||||
target_message=target_message,
|
||||
text="⏳ Пожалуйста, подождите, создаем вам подключение...",
|
||||
reply_markup=None,
|
||||
)
|
||||
|
||||
await create_key(tg_id, expiry_time, state, session, message_or_query)
|
||||
await create_key(tg_id, expiry_time, state, session, target_message)
|
||||
return
|
||||
else:
|
||||
logger.error(f"Не удалось обновить статус триального периода для пользователя {tg_id}.")
|
||||
@@ -122,11 +122,6 @@ async def handle_key_creation(
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
if isinstance(message_or_query, CallbackQuery):
|
||||
target_message = message_or_query.message
|
||||
else:
|
||||
target_message = message_or_query
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text="💳 Выберите тарифный план для создания нового ключа:",
|
||||
@@ -195,13 +190,12 @@ async def create_key(
|
||||
expiry_time: datetime,
|
||||
state: FSMContext | None,
|
||||
session: Any,
|
||||
message_or_query: Message | CallbackQuery | None = None,
|
||||
target_message: Message,
|
||||
old_key_name: str = None,
|
||||
plan: int = None,
|
||||
):
|
||||
"""Создаёт ключ с заданным сроком действия."""
|
||||
|
||||
target_message = message_or_query.message if isinstance(message_or_query, CallbackQuery) else message_or_query
|
||||
if not await check_connection_exists(tg_id):
|
||||
await add_connection(tg_id, balance=0.0, trial=0, session=session)
|
||||
logger.info(f"[Connection] Подключение создано для пользователя {tg_id}")
|
||||
|
||||
@@ -74,14 +74,9 @@ router = Router()
|
||||
|
||||
@router.callback_query(F.data == "view_keys")
|
||||
@router.message(F.text == "/subs")
|
||||
async def process_callback_or_message_view_keys(callback_query_or_message: Message | CallbackQuery, session: Any):
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
target_message = callback_query_or_message.message
|
||||
else:
|
||||
target_message = callback_query_or_message
|
||||
|
||||
async def process_callback_or_message_view_keys(session: Any, target_message: Message, chat_id: int):
|
||||
try:
|
||||
records = await get_keys(target_message.chat.id, session)
|
||||
records = await get_keys(chat_id, session)
|
||||
inline_keyboard, response_message = build_keys_response(records)
|
||||
image_path = os.path.join("img", "pic_keys.jpg")
|
||||
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
from aiogram import F, Router
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardButton, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import (
|
||||
CRYPTO_BOT_ENABLE,
|
||||
@@ -17,7 +17,7 @@ router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "pay")
|
||||
async def handle_pay(callback_query: CallbackQuery):
|
||||
async def handle_pay(callback_query: CallbackQuery, target_message: Message):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if YOOKASSA_ENABLE:
|
||||
@@ -71,7 +71,7 @@ async def handle_pay(callback_query: CallbackQuery):
|
||||
)
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
target_message=target_message,
|
||||
text=payment_text,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
|
||||
@@ -75,18 +75,18 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i]['callback_data']}",
|
||||
),
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i + 1]["text"],
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i + 1]["callback_data"]}',
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i + 1]['callback_data']}",
|
||||
),
|
||||
)
|
||||
else:
|
||||
builder.row(
|
||||
InlineKeyboardButton(
|
||||
text=PAYMENT_OPTIONS[i]["text"],
|
||||
callback_data=f'robokassa_amount|{PAYMENT_OPTIONS[i]["callback_data"]}',
|
||||
callback_data=f"robokassa_amount|{PAYMENT_OPTIONS[i]['callback_data']}",
|
||||
)
|
||||
)
|
||||
builder.row(InlineKeyboardButton(text="⬅️ Назад", callback_data="pay"))
|
||||
@@ -100,12 +100,12 @@ async def process_callback_pay_robokassa(callback_query: types.CallbackQuery, st
|
||||
logger.info(f"Created new connection for user {tg_id} with balance 0.0.")
|
||||
|
||||
await callback_query.message.delete()
|
||||
|
||||
|
||||
new_message = await callback_query.message.answer(
|
||||
text="Выберите сумму пополнения:",
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
await state.update_data(message_id=new_message.message_id, chat_id=new_message.chat.id)
|
||||
await state.update_data(message_id=new_message.message_id, chat_id=new_message.chat.id)
|
||||
await state.set_state(ReplenishBalanceState.choosing_amount_robokassa)
|
||||
logger.info(f"Displayed amount selection for user {tg_id}.")
|
||||
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
import html
|
||||
import os
|
||||
|
||||
from typing import Any
|
||||
|
||||
import asyncpg
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import (
|
||||
CallbackQuery,
|
||||
InlineKeyboardButton,
|
||||
InlineQuery,
|
||||
InlineQueryResultArticle,
|
||||
InputTextMessageContent,
|
||||
Message,
|
||||
)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import (
|
||||
DATABASE_URL,
|
||||
INLINE_MODE,
|
||||
INSTRUCTIONS_BUTTON,
|
||||
NEWS_MESSAGE,
|
||||
REFERRAL_OFFERS,
|
||||
RENEWAL_PLANS,
|
||||
TRIAL_TIME,
|
||||
USERNAME_BOT,
|
||||
)
|
||||
|
||||
from database import get_balance, get_key_count, get_last_payments, get_referral_stats, get_trial
|
||||
from handlers.buttons.profile import (
|
||||
ADD_SUB,
|
||||
BALANCE,
|
||||
BALANCE_HISTORY,
|
||||
GIFTS,
|
||||
INSTRUCTIONS,
|
||||
INVITE,
|
||||
MAIN_MENU,
|
||||
MY_SUBS,
|
||||
PAYMENT,
|
||||
)
|
||||
from handlers.texts import get_referral_link, invite_message_send, profile_message_send
|
||||
from keyboards.admin.panel_kb import AdminPanelCallback
|
||||
from logger import logger
|
||||
|
||||
from .utils import edit_or_send_message
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "profile")
|
||||
@router.message(F.text == "/profile")
|
||||
async def process_callback_view_profile(
|
||||
callback_query_or_message: Message | CallbackQuery,
|
||||
state: FSMContext,
|
||||
admin: bool,
|
||||
):
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
chat_id = callback_query_or_message.message.chat.id
|
||||
username = html.escape(callback_query_or_message.from_user.full_name)
|
||||
target_message = callback_query_or_message.message
|
||||
else:
|
||||
chat_id = callback_query_or_message.chat.id
|
||||
username = html.escape(callback_query_or_message.from_user.full_name)
|
||||
target_message = callback_query_or_message
|
||||
|
||||
image_path = os.path.join("img", "profile.jpg")
|
||||
logger.info(f"Переход в профиль. Используется изображение: {image_path}")
|
||||
|
||||
key_count = await get_key_count(chat_id)
|
||||
balance = await get_balance(chat_id) or 0
|
||||
|
||||
conn = await asyncpg.connect(DATABASE_URL)
|
||||
try:
|
||||
trial_status = await get_trial(chat_id, conn)
|
||||
|
||||
profile_message = profile_message_send(username, chat_id, int(balance), key_count)
|
||||
if key_count == 0:
|
||||
profile_message += (
|
||||
"\n<blockquote>🔧 <i>Нажмите кнопку ➕ Подписка, чтобы настроить VPN-подключение</i></blockquote>"
|
||||
)
|
||||
else:
|
||||
profile_message += f"\n<blockquote> <i>{NEWS_MESSAGE}</i></blockquote>"
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
if trial_status == 0 or key_count == 0:
|
||||
builder.row(InlineKeyboardButton(text=ADD_SUB, callback_data="create_key"))
|
||||
else:
|
||||
builder.row(InlineKeyboardButton(text=MY_SUBS, callback_data="view_keys"))
|
||||
builder.row(InlineKeyboardButton(text=BALANCE, callback_data="balance"))
|
||||
builder.row(
|
||||
InlineKeyboardButton(text=INVITE, callback_data="invite"),
|
||||
InlineKeyboardButton(text=GIFTS, callback_data="gifts"),
|
||||
)
|
||||
if INSTRUCTIONS_BUTTON:
|
||||
builder.row(InlineKeyboardButton(text=INSTRUCTIONS, callback_data="instructions"))
|
||||
if admin:
|
||||
builder.row(
|
||||
InlineKeyboardButton(text="🔧 Администратор", callback_data=AdminPanelCallback(action="admin").pack())
|
||||
)
|
||||
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="start"))
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
disable_web_page_preview=False,
|
||||
force_text=True,
|
||||
)
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "balance")
|
||||
async def balance_handler(callback_query: CallbackQuery, session: Any):
|
||||
result = await session.fetchrow(
|
||||
"SELECT balance FROM connections WHERE tg_id = $1",
|
||||
callback_query.from_user.id,
|
||||
)
|
||||
balance = result["balance"] if result else 0.0
|
||||
balance = int(balance)
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text=BALANCE_HISTORY, callback_data="balance_history"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
text = f"<b>Управление вашим балансом 💰</b>\n\nВаш баланс: {balance}"
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=text,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "balance_history")
|
||||
async def balance_history_handler(callback_query: CallbackQuery, session: Any):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=PAYMENT, callback_data="pay"))
|
||||
builder.row(InlineKeyboardButton(text=MAIN_MENU, callback_data="profile"))
|
||||
|
||||
records = await get_last_payments(callback_query.from_user.id, session)
|
||||
|
||||
if records:
|
||||
history_text = "📊 <b>Последние 3 операции с балансом:</b>\n\n"
|
||||
for record in records:
|
||||
amount = record["amount"]
|
||||
payment_system = record["payment_system"]
|
||||
status = record["status"]
|
||||
date = record["created_at"].strftime("%Y-%m-%d %H:%M:%S")
|
||||
history_text += (
|
||||
f"<b>Сумма:</b> {amount}₽\n"
|
||||
f"<b>Способ оплаты:</b> {payment_system}\n"
|
||||
f"<b>Статус:</b> {status}\n"
|
||||
f"<b>Дата:</b> {date}\n\n"
|
||||
)
|
||||
else:
|
||||
history_text = "❌ У вас пока нет операций с балансом."
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=history_text,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@router.message(F.text == "/tariffs")
|
||||
@router.callback_query(F.data == "view_tariffs")
|
||||
async def view_tariffs_handler(callback_query: CallbackQuery):
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
|
||||
image_path = os.path.join("img", "tariffs.jpg")
|
||||
tariffs_message = "<b>🚀 Доступные тарифы VPN:</b>\n\n" + "\n".join([
|
||||
f"{months} {'месяц' if months == '1' else 'месяца' if int(months) in [2, 3, 4] else 'месяцев'}: "
|
||||
f"{RENEWAL_PLANS[months]['price']} "
|
||||
f"{'💳' if months == '1' else '🌟' if months == '3' else '🔥' if months == '6' else '🚀'} рублей"
|
||||
for months in sorted(RENEWAL_PLANS.keys(), key=int)
|
||||
])
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=callback_query.message,
|
||||
text=tariffs_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "invite")
|
||||
@router.message(F.text == "/invite")
|
||||
async def invite_handler(callback_query_or_message: Message | CallbackQuery):
|
||||
chat_id = None
|
||||
if isinstance(callback_query_or_message, CallbackQuery):
|
||||
chat_id = callback_query_or_message.message.chat.id
|
||||
target_message = callback_query_or_message.message
|
||||
else:
|
||||
chat_id = callback_query_or_message.chat.id
|
||||
target_message = callback_query_or_message
|
||||
|
||||
referral_link = get_referral_link(chat_id)
|
||||
referral_stats = await get_referral_stats(chat_id)
|
||||
invite_message = invite_message_send(referral_link, referral_stats)
|
||||
image_path = os.path.join("img", "pic_invite.jpg")
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
if INLINE_MODE:
|
||||
builder.button(text="👥 Пригласить друга", switch_inline_query="invite")
|
||||
else:
|
||||
invite_text = f"\nПриглашаю тебя пользоваться действительно быстрым VPN вместе:\n\n{referral_link}"
|
||||
builder.button(text="👥 Пригласить друга", switch_inline_query=invite_text)
|
||||
builder.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
builder.adjust(1)
|
||||
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=invite_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@router.inline_query(F.query.in_(["referral", "ref", "invite"]))
|
||||
async def inline_referral_handler(inline_query: InlineQuery):
|
||||
referral_link = f"https://t.me/{USERNAME_BOT}?start=referral_{inline_query.from_user.id}"
|
||||
|
||||
results: list[InlineQueryResultArticle] = []
|
||||
|
||||
for index, offer in enumerate(REFERRAL_OFFERS):
|
||||
description = offer["description"][:64]
|
||||
message_text = offer["message"].format(trial_time=TRIAL_TIME)[:4096]
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text=offer["title"], url=referral_link))
|
||||
|
||||
results.append(
|
||||
InlineQueryResultArticle(
|
||||
id=str(index),
|
||||
title=offer["title"],
|
||||
description=description,
|
||||
input_message_content=InputTextMessageContent(message_text=message_text, parse_mode=ParseMode.HTML),
|
||||
reply_markup=builder.as_markup(),
|
||||
)
|
||||
)
|
||||
|
||||
await inline_query.answer(results=results, cache_time=86400, is_personal=True)
|
||||
@@ -0,0 +1,6 @@
|
||||
from aiogram import Router
|
||||
|
||||
from .router import router
|
||||
|
||||
|
||||
__all__ = ["router"]
|
||||
@@ -0,0 +1,175 @@
|
||||
import html
|
||||
import os
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, InlineQuery, InlineQueryResultArticle, InputTextMessageContent, Message
|
||||
from config import INLINE_MODE, REFERRAL_OFFERS, TRIAL_TIME, USERNAME_BOT
|
||||
|
||||
from database import get_balance, get_key_count, get_last_payments, get_referral_stats, get_trial
|
||||
from handlers.texts import get_referral_link, invite_message_send, profile_message_send
|
||||
from handlers.utils import edit_or_send_message
|
||||
from keyboards.profile import get_balance_keyboard, get_invite_keyboard, get_profile_keyboard
|
||||
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "profile")
|
||||
@router.message(F.text == "/profile")
|
||||
async def process_callback_view_profile(
|
||||
state: FSMContext,
|
||||
admin: bool,
|
||||
chat_id: int,
|
||||
target_message: Message,
|
||||
):
|
||||
"""
|
||||
Обрабатывает запрос на просмотр профиля пользователя.
|
||||
|
||||
Args:
|
||||
state: Контекст состояния FSM.
|
||||
admin: Флаг, указывающий, является ли пользователь администратором.
|
||||
chat_id: ID чата (добавлено middleware).
|
||||
target_message: Целевое сообщение для ответа (добавлено middleware).
|
||||
"""
|
||||
# Получаем информацию о профиле
|
||||
profile_message = await profile_message_send(chat_id)
|
||||
|
||||
# Получаем клавиатуру профиля
|
||||
builder = get_profile_keyboard(admin)
|
||||
|
||||
# Отправляем сообщение с профилем
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=profile_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "balance")
|
||||
async def balance_handler(callback_query: CallbackQuery, session: Any, chat_id: int, target_message: Message):
|
||||
"""
|
||||
Обрабатывает запрос на просмотр баланса.
|
||||
|
||||
Args:
|
||||
callback_query: Колбэк запрос.
|
||||
session: Сессия базы данных.
|
||||
chat_id: ID чата (добавлено middleware).
|
||||
target_message: Целевое сообщение для ответа (добавлено middleware).
|
||||
"""
|
||||
balance = await get_balance(chat_id, session)
|
||||
|
||||
# Получаем клавиатуру баланса
|
||||
builder = get_balance_keyboard()
|
||||
|
||||
# Отправляем сообщение с балансом
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=f"<b>💰 Ваш текущий баланс:</b> {balance} руб.\n\n"
|
||||
"Вы можете пополнить баланс через раздел <b>💸 Пополнить баланс</b> в личном кабинете.",
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "balance_history")
|
||||
async def balance_history_handler(callback_query: CallbackQuery, session: Any, chat_id: int, target_message: Message):
|
||||
"""
|
||||
Обрабатывает запрос на просмотр истории баланса.
|
||||
|
||||
Args:
|
||||
callback_query: Колбэк запрос.
|
||||
session: Сессия базы данных.
|
||||
chat_id: ID чата (добавлено middleware).
|
||||
target_message: Целевое сообщение для ответа (добавлено middleware).
|
||||
"""
|
||||
payments = await get_last_payments(chat_id, session)
|
||||
|
||||
if not payments:
|
||||
history_text = "<b>📊 История операций:</b>\n\nУ вас пока нет операций по балансу."
|
||||
else:
|
||||
history_text = "<b>📊 История операций:</b>\n\n"
|
||||
for payment in payments:
|
||||
amount = payment["amount"]
|
||||
date = payment["created_at"].strftime("%d.%m.%Y %H:%M")
|
||||
description = html.escape(payment["description"] or "")
|
||||
|
||||
if amount > 0:
|
||||
history_text += f"➕ <b>{amount}</b> руб. - {description} ({date})\n"
|
||||
else:
|
||||
history_text += f"➖ <b>{abs(amount)}</b> руб. - {description} ({date})\n"
|
||||
|
||||
# Получаем клавиатуру баланса
|
||||
builder = get_balance_keyboard()
|
||||
|
||||
# Отправляем сообщение с историей баланса
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=history_text,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=None,
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "invite")
|
||||
@router.message(F.text == "/invite")
|
||||
async def invite_handler(chat_id: int, target_message: Message):
|
||||
"""
|
||||
Обрабатывает запрос на приглашение друзей.
|
||||
|
||||
Args:
|
||||
chat_id: ID чата (добавлено middleware).
|
||||
target_message: Целевое сообщение для ответа (добавлено middleware).
|
||||
"""
|
||||
referral_link = get_referral_link(chat_id)
|
||||
referral_stats = await get_referral_stats(chat_id)
|
||||
invite_message = invite_message_send(referral_link, referral_stats)
|
||||
image_path = os.path.join("img", "pic_invite.jpg")
|
||||
|
||||
# Получаем клавиатуру для приглашений
|
||||
builder = get_invite_keyboard(chat_id, referral_link)
|
||||
|
||||
# Отправляем сообщение с приглашением
|
||||
await edit_or_send_message(
|
||||
target_message=target_message,
|
||||
text=invite_message,
|
||||
reply_markup=builder.as_markup(),
|
||||
media_path=image_path,
|
||||
disable_web_page_preview=False,
|
||||
)
|
||||
|
||||
|
||||
@router.inline_query(F.query.in_(["referral", "ref", "invite"]))
|
||||
async def inline_referral_handler(inline_query: InlineQuery):
|
||||
"""
|
||||
Обрабатывает инлайн-запрос для реферальной программы.
|
||||
|
||||
Args:
|
||||
inline_query: Инлайн-запрос.
|
||||
"""
|
||||
|
||||
results = []
|
||||
|
||||
for index, offer in enumerate(REFERRAL_OFFERS):
|
||||
description = offer["description"][:64]
|
||||
message_text = offer["message"].format(trial_time=TRIAL_TIME)[:4096]
|
||||
|
||||
results.append(
|
||||
InlineQueryResultArticle(
|
||||
id=f"ref_{index}",
|
||||
title=offer["title"],
|
||||
description=description,
|
||||
input_message_content=InputTextMessageContent(
|
||||
message_text=message_text,
|
||||
parse_mode="HTML",
|
||||
),
|
||||
thumbnail_url=offer.get("thumbnail_url"),
|
||||
thumbnail_width=100,
|
||||
thumbnail_height=100,
|
||||
)
|
||||
)
|
||||
|
||||
await inline_query.answer(results=results, cache_time=300)
|
||||
@@ -0,0 +1,4 @@
|
||||
from .keyboards import get_coupon_keyboard
|
||||
|
||||
|
||||
__all__ = ["get_coupon_keyboard"]
|
||||
@@ -0,0 +1,14 @@
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
|
||||
def get_coupon_keyboard() -> InlineKeyboardBuilder:
|
||||
"""
|
||||
Создает клавиатуру для работы с купонами.
|
||||
|
||||
Returns:
|
||||
InlineKeyboardBuilder: Построитель клавиатуры с кнопками для купонов.
|
||||
"""
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile"))
|
||||
return builder
|
||||
@@ -0,0 +1,4 @@
|
||||
from .keyboards import get_balance_keyboard, get_invite_keyboard, get_profile_keyboard
|
||||
|
||||
|
||||
__all__ = ["get_profile_keyboard", "get_balance_keyboard", "get_invite_keyboard"]
|
||||
@@ -0,0 +1,78 @@
|
||||
from aiogram.types import InlineKeyboardButton
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
from config import INLINE_MODE
|
||||
|
||||
|
||||
def get_profile_keyboard(admin: bool = False) -> InlineKeyboardBuilder:
|
||||
"""
|
||||
Создает клавиатуру для профиля пользователя.
|
||||
|
||||
Args:
|
||||
admin: Флаг, указывающий, является ли пользователь администратором.
|
||||
|
||||
Returns:
|
||||
InlineKeyboardBuilder: Построитель клавиатуры с кнопками профиля.
|
||||
"""
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
# Основные кнопки профиля
|
||||
builder.button(text="💰 Баланс", callback_data="balance")
|
||||
builder.button(text="🔑 Мои подписки", callback_data="my_subs")
|
||||
builder.button(text="💸 Пополнить баланс", callback_data="payment")
|
||||
builder.button(text="🎁 Подарки", callback_data="gifts")
|
||||
builder.button(text="👥 Пригласить друга", callback_data="invite")
|
||||
builder.button(text="📚 Инструкции", callback_data="instructions")
|
||||
builder.button(text="🎫 Активировать купон", callback_data="activate_coupon")
|
||||
|
||||
# Кнопка админ-панели для администраторов
|
||||
if admin:
|
||||
builder.button(text="⚙️ Админ-панель", callback_data="admin_panel")
|
||||
|
||||
# Кнопка главного меню
|
||||
builder.button(text="🏠 Главное меню", callback_data="main_menu")
|
||||
|
||||
# Настройка расположения кнопок (2 кнопки в ряд)
|
||||
builder.adjust(2)
|
||||
|
||||
return builder
|
||||
|
||||
|
||||
def get_balance_keyboard() -> InlineKeyboardBuilder:
|
||||
"""
|
||||
Создает клавиатуру для раздела баланса.
|
||||
|
||||
Returns:
|
||||
InlineKeyboardBuilder: Построитель клавиатуры с кнопками для баланса.
|
||||
"""
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder.button(text="📊 История баланса", callback_data="balance_history")
|
||||
builder.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
builder.adjust(1)
|
||||
|
||||
return builder
|
||||
|
||||
|
||||
def get_invite_keyboard(chat_id: int, referral_link: str) -> InlineKeyboardBuilder:
|
||||
"""
|
||||
Создает клавиатуру для приглашения друзей.
|
||||
|
||||
Args:
|
||||
chat_id: ID чата пользователя.
|
||||
referral_link: Реферальная ссылка.
|
||||
|
||||
Returns:
|
||||
InlineKeyboardBuilder: Построитель клавиатуры с кнопками для приглашений.
|
||||
"""
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
if INLINE_MODE:
|
||||
builder.button(text="👥 Пригласить друга", switch_inline_query="invite")
|
||||
else:
|
||||
invite_text = f"\nПриглашаю тебя пользоваться действительно быстрым VPN вместе:\n\n{referral_link}"
|
||||
builder.button(text="👥 Пригласить друга", switch_inline_query=invite_text)
|
||||
|
||||
builder.button(text="👤 Личный кабинет", callback_data="profile")
|
||||
builder.adjust(1)
|
||||
|
||||
return builder
|
||||
@@ -6,6 +6,7 @@ from aiogram.dispatcher.middlewares.base import BaseMiddleware
|
||||
|
||||
from .admin import AdminMiddleware
|
||||
from .loggings import LoggingMiddleware
|
||||
from .message_handler import MessageHandlerMiddleware
|
||||
from .session import SessionMiddleware
|
||||
from .throttling import ThrottlingMiddleware
|
||||
from .user import UserMiddleware
|
||||
@@ -34,6 +35,7 @@ def register_middleware(
|
||||
"logging": LoggingMiddleware(),
|
||||
"throttling": ThrottlingMiddleware(),
|
||||
"user": UserMiddleware(),
|
||||
"message_handler": MessageHandlerMiddleware(),
|
||||
}
|
||||
|
||||
# Фильтруем middleware по списку исключений
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
from aiogram import BaseMiddleware
|
||||
from aiogram.types import CallbackQuery, Message, TelegramObject
|
||||
|
||||
|
||||
class MessageHandlerMiddleware(BaseMiddleware):
|
||||
"""
|
||||
Middleware для обработки сообщений и callback-запросов.
|
||||
|
||||
Добавляет в контекст обработчика следующие данные:
|
||||
- chat_id: ID чата или пользователя
|
||||
- target_message: Объект сообщения для ответа или редактирования
|
||||
- is_callback: Флаг, указывающий, является ли запрос callback-запросом
|
||||
"""
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
|
||||
event: Message | CallbackQuery,
|
||||
data: dict[str, Any],
|
||||
) -> Any:
|
||||
"""
|
||||
Обрабатывает входящее событие и добавляет в контекст необходимые данные.
|
||||
|
||||
Args:
|
||||
handler: Обработчик события.
|
||||
event: Событие (сообщение или callback-запрос).
|
||||
data: Словарь с данными контекста.
|
||||
|
||||
Returns:
|
||||
Any: Результат выполнения обработчика.
|
||||
"""
|
||||
# Определяем тип события и извлекаем нужные данные
|
||||
if isinstance(event, CallbackQuery):
|
||||
data["chat_id"] = event.from_user.id
|
||||
data["target_message"] = event.message
|
||||
data["is_callback"] = True
|
||||
else:
|
||||
data["chat_id"] = event.chat.id
|
||||
data["target_message"] = event
|
||||
data["is_callback"] = False
|
||||
|
||||
# Вызываем следующий обработчик
|
||||
return await handler(event, data)
|
||||
Reference in New Issue
Block a user