import html from datetime import datetime from typing import Any import pytz from aiogram import F, Router from aiogram.enums import ParseMode from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.types import ( CallbackQuery, InlineQuery, InlineQueryResultArticle, InputTextMessageContent, Message, ) from aiogram.utils.keyboard import InlineKeyboardBuilder from config import INLINE_MODE, USERNAME_BOT from database import ( create_coupon, delete_coupon, get_all_coupons, ) from filters.admin import IsAdminFilter from handlers.buttons import BACK from handlers.utils import format_days from logger import logger from ..panel.keyboard import AdminPanelCallback, build_admin_back_kb from .keyboard import AdminCouponDeleteCallback, build_coupons_kb, build_coupons_list_kb, format_coupons_list router = Router() class AdminCouponsState(StatesGroup): waiting_for_coupon_type = State() waiting_for_balance_data = State() waiting_for_days_data = State() @router.callback_query( AdminPanelCallback.filter(F.action == "coupons"), IsAdminFilter(), ) async def handle_coupons(callback_query: CallbackQuery): await callback_query.message.edit_text(text="πŸ›  МСню управлСния ΠΊΡƒΠΏΠΎΠ½Π°ΠΌΠΈ:", reply_markup=build_coupons_kb()) @router.callback_query( AdminPanelCallback.filter(F.action == "coupons_create"), IsAdminFilter(), ) async def handle_coupons_create(callback_query: CallbackQuery, state: FSMContext): text = "🎫 Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ Ρ‚ΠΈΠΏ ΠΊΡƒΠΏΠΎΠ½Π°:" kb = InlineKeyboardBuilder() kb.button(text="πŸ’° Баланс", callback_data="coupon_type_balance") kb.button(text="⏳ ВрСмя", callback_data="coupon_type_days") kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) kb.adjust(1) await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup()) await state.set_state(AdminCouponsState.waiting_for_coupon_type) @router.callback_query(F.data == "coupon_type_balance") async def handle_balance_coupon_selection(callback_query: CallbackQuery, state: FSMContext): text = ( "🎫 Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ для создания ΠΊΡƒΠΏΠΎΠ½Π° Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅:\n\n" "πŸ“ ΠΊΠΎΠ΄ πŸ’° сумма πŸ”’ Π»ΠΈΠΌΠΈΡ‚\n\n" "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 'COUPON1 50 5' πŸ‘ˆ\n\n" ) kb = InlineKeyboardBuilder() kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup()) await state.set_state(AdminCouponsState.waiting_for_balance_data) @router.callback_query(F.data == "coupon_type_days") async def handle_days_coupon_selection(callback_query: CallbackQuery, state: FSMContext): text = ( "🎫 Π’Π²Π΅Π΄ΠΈΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ для создания ΠΊΡƒΠΏΠΎΠ½Π° Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅:\n\n" "πŸ“ ΠΊΠΎΠ΄ ⏳ Π΄Π½ΠΈ πŸ”’ Π»ΠΈΠΌΠΈΡ‚\n\n" "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 'DAYS10 10 50' πŸ‘ˆ\n\n" ) kb = InlineKeyboardBuilder() kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) await callback_query.message.edit_text(text=text, reply_markup=kb.as_markup()) await state.set_state(AdminCouponsState.waiting_for_days_data) @router.message(AdminCouponsState.waiting_for_balance_data, IsAdminFilter()) async def handle_balance_coupon_input(message: Message, state: FSMContext, session: Any): text = message.text.strip() parts = text.split() kb = InlineKeyboardBuilder() kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) if len(parts) != 3: text = ( "❌ НСкоррСктный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚! πŸ“ ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Π²Π΅Π΄ΠΈΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅:\n" "🏷️ ΠΊΠΎΠ΄ πŸ’° сумма πŸ”’ Π»ΠΈΠΌΠΈΡ‚\n" "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 'COUPON1 50 5' πŸ‘ˆ" ) await message.answer(text=text, reply_markup=kb.as_markup()) return try: coupon_code = parts[0] coupon_amount = int(parts[1]) usage_limit = int(parts[2]) if coupon_amount <= 0: raise ValueError("Π‘ΡƒΠΌΠΌΠ° Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ большС 0") except ValueError: text = "⚠️ ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΡΡ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ…!\nπŸ’± Π‘ΡƒΠΌΠΌΠ° Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ числом, Π° Π»ΠΈΠΌΠΈΡ‚ β€” Ρ†Π΅Π»Ρ‹ΠΌ числом." await message.answer(text=text, reply_markup=kb.as_markup()) return try: await create_coupon(coupon_code, coupon_amount, usage_limit, session, days=None) coupon_link = f"https://t.me/{USERNAME_BOT}?start=coupons_{coupon_code}" text = ( f"βœ… ΠšΡƒΠΏΠΎΠ½ с ΠΊΠΎΠ΄ΠΎΠΌ {coupon_code} ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ создан!\n" f"πŸ’° Π‘ΡƒΠΌΠΌΠ°: {coupon_amount} Ρ€ΡƒΠ±Π»Π΅ΠΉ\n" f"πŸ”’ Π›ΠΈΠΌΠΈΡ‚ использования: {usage_limit} Ρ€Π°Π·\n" f"πŸ”— Бсылка: {coupon_link}\n" ) kb = InlineKeyboardBuilder() if INLINE_MODE: kb.button(text="πŸ“€ ΠŸΠΎΠ΄Π΅Π»ΠΈΡ‚ΡŒΡΡ", switch_inline_query=f"coupon_{coupon_code}") kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) kb.adjust(1) await message.answer(text=text, reply_markup=kb.as_markup()) await state.clear() except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ создании ΠΊΡƒΠΏΠΎΠ½Π°: {e}") await message.answer("❌ ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ создании ΠΊΡƒΠΏΠΎΠ½Π°.", reply_markup=kb.as_markup()) @router.message(AdminCouponsState.waiting_for_days_data, IsAdminFilter()) async def handle_days_coupon_input(message: Message, state: FSMContext, session: Any): text = message.text.strip() parts = text.split() kb = InlineKeyboardBuilder() kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) if len(parts) != 3: text = ( "❌ НСкоррСктный Ρ„ΠΎΡ€ΠΌΠ°Ρ‚! πŸ“ ΠŸΠΎΠΆΠ°Π»ΡƒΠΉΡΡ‚Π°, Π²Π²Π΅Π΄ΠΈΡ‚Π΅ Π΄Π°Π½Π½Ρ‹Π΅ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅:\n" "🏷️ ΠΊΠΎΠ΄ ⏳ Π΄Π½ΠΈ πŸ”’ Π»ΠΈΠΌΠΈΡ‚\n" "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: 'DAYS10 10 50' πŸ‘ˆ" ) await message.answer(text=text, reply_markup=kb.as_markup()) return try: coupon_code = parts[0] days = int(parts[1]) usage_limit = int(parts[2]) if days <= 0: raise ValueError("ΠšΠΎΠ»ΠΈΡ‡Π΅ΡΡ‚Π²ΠΎ Π΄Π½Π΅ΠΉ Π΄ΠΎΠ»ΠΆΠ½ΠΎ Π±Ρ‹Ρ‚ΡŒ большС 0") except ValueError: text = "⚠️ ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡŒΡ‚Π΅ ΠΏΡ€Π°Π²ΠΈΠ»ΡŒΠ½ΠΎΡΡ‚ΡŒ Π²Π²Π΅Π΄Π΅Π½Π½Ρ‹Ρ… Π΄Π°Π½Π½Ρ‹Ρ…!\nπŸ’± Π”Π½ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ числом, Π° Π»ΠΈΠΌΠΈΡ‚ β€” Ρ†Π΅Π»Ρ‹ΠΌ числом." await message.answer(text=text, reply_markup=kb.as_markup()) return try: await create_coupon(coupon_code, 0, usage_limit, session, days=days) coupon_link = f"https://t.me/{USERNAME_BOT}?start=coupons_{coupon_code}" text = ( f"βœ… ΠšΡƒΠΏΠΎΠ½ с ΠΊΠΎΠ΄ΠΎΠΌ {coupon_code} ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ создан!\n" f"⏳ {format_days(days)}\n" f"πŸ”’ Π›ΠΈΠΌΠΈΡ‚ использования: {usage_limit} Ρ€Π°Π·\n" f"πŸ”— Бсылка: {coupon_link}\n" ) kb = InlineKeyboardBuilder() if INLINE_MODE: kb.button(text="πŸ“€ ΠŸΠΎΠ΄Π΅Π»ΠΈΡ‚ΡŒΡΡ", switch_inline_query=f"coupon_{coupon_code}") kb.button(text=BACK, callback_data=AdminPanelCallback(action="coupons").pack()) kb.adjust(1) await message.answer(text=text, reply_markup=kb.as_markup()) await state.clear() except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ создании ΠΊΡƒΠΏΠΎΠ½Π°: {e}") await message.answer("❌ ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ создании ΠΊΡƒΠΏΠΎΠ½Π°.", reply_markup=kb.as_markup()) @router.callback_query( AdminPanelCallback.filter(F.action == "coupons_list"), IsAdminFilter(), ) async def handle_coupons_list(callback_query: CallbackQuery, session: Any): try: data = AdminPanelCallback.unpack(callback_query.data) page = data.page if data.page is not None else 1 await update_coupons_list(callback_query.message, session, page) except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ списка ΠΊΡƒΠΏΠΎΠ½ΠΎΠ²: {e}") await callback_query.message.edit_text("ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ списка ΠΊΡƒΠΏΠΎΠ½ΠΎΠ².") @router.callback_query(AdminCouponDeleteCallback.filter(F.confirm.is_(None)), IsAdminFilter()) async def handle_coupon_delete(callback_query: CallbackQuery, callback_data: AdminCouponDeleteCallback, session: Any): coupon_code = callback_data.coupon_code kb = InlineKeyboardBuilder() kb.button( text="βœ… Π”Π°, ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ", callback_data=AdminCouponDeleteCallback(coupon_code=coupon_code, confirm=True).pack() ) kb.button( text="❌ НСт, ΠΎΡ‚ΠΌΠ΅Π½ΠΈΡ‚ΡŒ", callback_data=AdminCouponDeleteCallback(coupon_code=coupon_code, confirm=False).pack() ) kb.adjust(1) await callback_query.message.edit_text( f"Π’Ρ‹ ΡƒΠ²Π΅Ρ€Π΅Π½Ρ‹, Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ‚ΠΈΡ‚Π΅ ΡƒΠ΄Π°Π»ΠΈΡ‚ΡŒ ΠΊΡƒΠΏΠΎΠ½ {coupon_code}?", reply_markup=kb.as_markup() ) @router.callback_query(AdminCouponDeleteCallback.filter(F.confirm.is_not(None)), IsAdminFilter()) async def confirm_coupon_delete(callback_query: CallbackQuery, callback_data: AdminCouponDeleteCallback, session: Any): coupon_code = callback_data.coupon_code confirm = callback_data.confirm if confirm: try: result = await delete_coupon(coupon_code, session) if not result: await callback_query.message.edit_text( f"❌ ΠšΡƒΠΏΠΎΠ½ с ΠΊΠΎΠ΄ΠΎΠΌ {coupon_code} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½.", reply_markup=build_admin_back_kb("coupons") ) return except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ ΠΊΡƒΠΏΠΎΠ½Π°: {e}") await callback_query.message.edit_text( "ΠŸΡ€ΠΎΠΈΠ·ΠΎΡˆΠ»Π° ошибка ΠΏΡ€ΠΈ ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΈ ΠΊΡƒΠΏΠΎΠ½Π°.", reply_markup=build_admin_back_kb("coupons") ) return await update_coupons_list(callback_query.message, session) async def update_coupons_list(message, session: Any, page: int = 1): per_page = 10 result = await get_all_coupons(session, page, per_page) coupons = result["coupons"] if not coupons: await message.edit_text( text="❌ На Π΄Π°Π½Π½Ρ‹ΠΉ ΠΌΠΎΠΌΠ΅Π½Ρ‚ Π½Π΅Ρ‚ доступных ΠΊΡƒΠΏΠΎΠ½ΠΎΠ²!", reply_markup=build_admin_back_kb("coupons"), ) return kb = build_coupons_list_kb(coupons, result["current_page"], result["pages"]) text = format_coupons_list(coupons, USERNAME_BOT) await message.edit_text(text=text, reply_markup=kb) @router.inline_query(F.query.startswith("coupon_")) async def inline_coupon_handler(inline_query: InlineQuery, session: Any): if not INLINE_MODE: return coupon_code = inline_query.query.split("coupon_")[1] coupon_link = f"https://t.me/{USERNAME_BOT}?start=coupons_{coupon_code}" coupons = await get_all_coupons(session, page=1, per_page=10) coupon = next((c for c in coupons["coupons"] if c["code"] == coupon_code), None) if not coupon: await inline_query.answer( results=[], switch_pm_text="ΠšΡƒΠΏΠΎΠ½ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½", switch_pm_parameter="coupons", cache_time=1, ) return title = f"ΠšΡƒΠΏΠΎΠ½ {coupon['code']}" description = ( f"ΠŸΠΎΠ»ΡƒΡ‡ΠΈ {coupon['amount']} Ρ€ΡƒΠ±Π»Π΅ΠΉ!" if coupon["amount"] > 0 else f"ΠŸΡ€ΠΎΠ΄Π»ΠΈ подписку Π½Π° {format_days(coupon['days'])}!" ) message_text = ( f"🎫 ΠšΡƒΠΏΠΎΠ½: {coupon['code']}\n" f"{'πŸ’° Бонус: ' + str(coupon['amount']) + ' Ρ€ΡƒΠ±Π»Π΅ΠΉ' if coupon['amount'] > 0 else '⏳ ΠŸΡ€ΠΎΠ΄Π»Π΅Π½ΠΈΠ΅: ' + format_days(coupon['days'])}\n" f"πŸ‘‡ НаТми, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ!" ) builder = InlineKeyboardBuilder() builder.button(text="ΠΠΊΡ‚ΠΈΠ²ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΊΡƒΠΏΠΎΠ½", url=coupon_link) result = InlineQueryResultArticle( id=coupon_code, title=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=[result], cache_time=86400, is_personal=True)