import logging
from aiogram import Router, F, Bot
from aiogram.types import Message, CallbackQuery
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from config import ADMIN_IDS
from database.db import (
get_active_products, get_stock_summary, add_gift_code,
count_users, get_total_stats, get_pending_delivery_orders,
get_available_code, mark_code_used, complete_order,
update_product_price, get_all_user_ids, count_available_codes
)
from keyboards.admin_kb import (
admin_panel_kb, admin_select_product_kb,
admin_back_kb, admin_pending_order_kb, cancel_admin_action_kb
)
logger = logging.getLogger(__name__)
router = Router()
# ─────────────────── FSM STATES ───────────────────
class AdminStates(StatesGroup):
waiting_product_for_code = State() # Qaysi mahsulotga kod qo'shish
waiting_gift_link = State() # Kodni kiritish
waiting_broadcast_message = State() # Broadcast xabari
waiting_product_for_price = State() # Narx o'zgartirish uchun mahsulot
waiting_new_price = State() # Yangi narxni kiritish
# ─────────────────── ADMIN CHECK DECORATOR ───────────────────
def admin_only(func):
"""Faqat adminlar uchun filtr."""
async def wrapper(event, **kwargs):
user_id = event.from_user.id if hasattr(event, 'from_user') else None
if user_id not in ADMIN_IDS:
if isinstance(event, Message):
await event.answer("🚫 Ruxsat yo'q!")
elif isinstance(event, CallbackQuery):
await event.answer("🚫 Faqat adminlar!", show_alert=True)
return
return await func(event, **kwargs)
return wrapper
# ─────────────────── ADMIN PANEL ───────────────────
@router.message(Command("admin"))
async def cmd_admin(message: Message):
"""Admin paneli — faqat adminlar uchun."""
if message.from_user.id not in ADMIN_IDS:
await message.answer("🚫 Bu buyruq faqat adminlar uchun!")
return
await message.answer(
"🛠️ Admin Panel\n\nXohlagan amalni tanlang:",
reply_markup=admin_panel_kb(),
parse_mode="HTML"
)
@router.callback_query(F.data == "admin_panel")
async def show_admin_panel(callback: CallbackQuery, state: FSMContext):
"""Admin panelga qaytish."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
await state.clear()
await callback.message.edit_text(
"🛠️ Admin Panel\n\nXohlagan amalni tanlang:",
reply_markup=admin_panel_kb(),
parse_mode="HTML"
)
await callback.answer()
# ─────────────────── ZAXIRA HOLATI ───────────────────
@router.callback_query(F.data == "admin_stock")
async def show_stock(callback: CallbackQuery):
"""Zaxira holati."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
stock = await get_stock_summary()
lines = ["📦 Zaxira Holati:\n"]
for item in stock:
status = "✅" if item["available"] > 0 else "❌"
lines.append(
f"{status} {item['emoji']} {item['name']}\n"
f" ⭐ Narx: {item['stars_price']} Stars\n"
f" 📌 Mavjud: {item['available']} ta | "
f"Ishlatilgan: {item['used']} ta | Jami: {item['total']} ta\n"
)
await callback.message.edit_text(
"\n".join(lines),
reply_markup=admin_back_kb(),
parse_mode="HTML"
)
await callback.answer()
# ─────────────────── SOVGʻA LINK QO'SHISH ───────────────────
@router.callback_query(F.data == "admin_add_code")
async def admin_add_code_start(callback: CallbackQuery, state: FSMContext):
"""Sovg'a link qo'shish — mahsulot tanlash."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
products = await get_active_products()
await callback.message.edit_text(
"➕ Sovg'a Link Qo'shish\n\nQaysi mahsulot uchun link qo'shmoqchisiz?",
reply_markup=admin_select_product_kb(products, action="addcode"),
parse_mode="HTML"
)
await state.set_state(AdminStates.waiting_product_for_code)
await callback.answer()
@router.callback_query(F.data.startswith("addcode_"), AdminStates.waiting_product_for_code)
async def admin_add_code_product_selected(callback: CallbackQuery, state: FSMContext):
"""Mahsulot tanlandi — link kiritishni kutish."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
product_id = int(callback.data.split("_")[1])
await state.update_data(product_id=product_id)
await state.set_state(AdminStates.waiting_gift_link)
await callback.message.edit_text(
"🔗 Gift Linkni Yuboring\n\n"
"Fragment yoki Telegram Premium sovg'a linkini yuboring.\n"
"Masalan: https://t.me/premium?gift=...\n\n"
"Bir nechta link qo'shmoqchi bo'lsangiz, har birini alohida xabar sifatida yuboring.\n"
"Tugatgach, ❌ Bekor Qilish tugmasini bosing.",
reply_markup=cancel_admin_action_kb(),
parse_mode="HTML"
)
await callback.answer()
@router.message(AdminStates.waiting_gift_link)
async def admin_receive_gift_link(message: Message, state: FSMContext):
"""Admin tomonidan yuborilgan gift linkni saqlash."""
if message.from_user.id not in ADMIN_IDS:
return
data = await state.get_data()
product_id = data.get("product_id")
gift_link = message.text.strip()
# URL tekshirish (oddiy)
if not gift_link.startswith("https://"):
await message.answer(
"⚠️ Noto'g'ri format! Link https:// bilan boshlanishi kerak.\n"
"Qaytadan yuboring:",
parse_mode="HTML",
reply_markup=cancel_admin_action_kb()
)
return
success = await add_gift_code(product_id=product_id, gift_link=gift_link)
available = await count_available_codes(product_id)
if success:
await message.answer(
f"✅ Link muvaffaqiyatli qo'shildi!\n\n"
f"📦 Ushbu mahsulot uchun mavjud linklar soni: {available} ta\n\n"
f"Yana link qo'shmoqchi bo'lsangiz, yuboring:",
parse_mode="HTML",
reply_markup=cancel_admin_action_kb()
)
else:
await message.answer(
f"⚠️ Bu link allaqachon bazada mavjud! Boshqa link yuboring:",
reply_markup=cancel_admin_action_kb()
)
# ─────────────────── STATISTIKA ───────────────────
@router.callback_query(F.data == "admin_stats")
async def show_stats(callback: CallbackQuery):
"""Umumiy statistika."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
stats = await get_total_stats()
user_count = await count_users()
text = (
"📊 Bot Statistikasi\n\n"
f"👥 Jami foydalanuvchilar: {user_count} ta\n\n"
f"✅ Bajarilgan buyurtmalar: {stats['total_completed']} ta\n"
f"⭐ Jami Stars tushumi: {stats['total_stars']:,}\n"
f"⏳ Yetkazilishi kutilayotgan: {stats['pending_delivery']} ta\n\n"
)
# Zaxira holati
stock = await get_stock_summary()
text += "📦 Zaxira:\n"
for item in stock:
icon = "✅" if item["available"] > 0 else "❌"
text += f" {icon} {item['emoji']} {item['name']}: {item['available']} ta\n"
await callback.message.edit_text(
text,
reply_markup=admin_back_kb(),
parse_mode="HTML"
)
await callback.answer()
# ─────────────────── KUTILAYOTGAN BUYURTMALAR ───────────────────
@router.callback_query(F.data == "admin_pending")
async def show_pending_orders(callback: CallbackQuery):
"""Yetkazilishi kutilayotgan buyurtmalar."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
orders = await get_pending_delivery_orders()
if not orders:
await callback.message.edit_text(
"✅ Kutilayotgan buyurtmalar yo'q!\n\nHamma buyurtmalar bajarilgan.",
reply_markup=admin_back_kb(),
parse_mode="HTML"
)
await callback.answer()
return
for order in orders:
username_str = f"@{order['username']}" if order['username'] else "username_yoq"
await callback.message.answer(
f"⏳ Kutilayotgan Buyurtma #{order['id']}\n\n"
f"👤 {order['full_name']} ({username_str})\n"
f"🆔 User ID: {order['user_id']}\n"
f"📦 Mahsulot: {order['product_name']}\n"
f"⭐ To'langan: {order['stars_amount']} Stars\n"
f"📅 Sana: {order['created_at'][:16]}",
reply_markup=admin_pending_order_kb(
order_id=order["id"],
user_id=order["user_id"],
product_id=order["product_id"]
),
parse_mode="HTML"
)
await callback.answer()
@router.callback_query(F.data.startswith("send_pending_"))
async def send_pending_gift(callback: CallbackQuery, bot: Bot):
"""Kutilayotgan buyurtmaga link yuborish."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
# Callback data: send_pending_{order_id}_{user_id}_{product_id}
# parts[0]=send, parts[1]=pending, parts[2]=order_id, parts[3]=user_id, parts[4]=product_id
parts = callback.data.split("_")
order_id = int(parts[2])
user_id = int(parts[3])
product_id = int(parts[4])
code = await get_available_code(product_id)
if not code:
await callback.answer(
"❌ Zaxirada bu mahsulot uchun link yo'q! Avval link qo'shing.",
show_alert=True
)
return
await mark_code_used(code_id=code["id"], user_id=user_id, order_id=order_id)
await complete_order(
order_id=order_id,
gift_code_id=code["id"],
charge_id="manual_delivery"
)
# Foydalanuvchiga link yuborish
try:
await bot.send_message(
user_id,
f"🎉 Sizning Premium Sovg'angiz Tayyor!\n\n"
f"Buyurtma ID: #{order_id}\n\n"
f"🔗 Premium linkingiz:\n{code['gift_link']}\n\n"
f"⚠️ Ushbu link bir marta ishlatiladi!",
parse_mode="HTML",
disable_web_page_preview=True
)
await callback.message.edit_text(
f"✅ Link muvaffaqiyatli yuborildi!\n\n"
f"Buyurtma #{order_id} bajarildi.",
reply_markup=admin_back_kb(),
parse_mode="HTML"
)
await callback.answer("✅ Yuborildi!")
except Exception as e:
logger.error(f"send_pending_gift xatosi: {e}")
await callback.answer(
f"❌ Xabar yuborishda xato: {e}",
show_alert=True
)
# ─────────────────── BROADCAST ───────────────────
@router.callback_query(F.data == "admin_broadcast")
async def start_broadcast(callback: CallbackQuery, state: FSMContext):
"""Broadcast boshlash."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
user_count = await count_users()
await callback.message.edit_text(
f"📢 Xabar Yuborish (Broadcast)\n\n"
f"Jami foydalanuvchilar: {user_count} ta\n\n"
f"Barcha foydalanuvchilarga yuboriladigan xabarni yozing:\n"
f"(HTML formatida yozishingiz mumkin: bold, italic, code)",
reply_markup=cancel_admin_action_kb(),
parse_mode="HTML"
)
await state.set_state(AdminStates.waiting_broadcast_message)
await callback.answer()
@router.message(AdminStates.waiting_broadcast_message)
async def send_broadcast(message: Message, state: FSMContext, bot: Bot):
"""Broadcast xabarini barcha foydalanuvchilarga yuborish."""
if message.from_user.id not in ADMIN_IDS:
return
await state.clear()
user_ids = await get_all_user_ids()
total = len(user_ids)
success_count = 0
fail_count = 0
status_msg = await message.answer(
f"📤 Yuborilmoqda... 0/{total}"
)
for i, uid in enumerate(user_ids, 1):
try:
await bot.send_message(uid, message.text, parse_mode="HTML")
success_count += 1
except Exception:
fail_count += 1
# Har 20 ta foydalanuvchida status yangilash
if i % 20 == 0:
try:
await status_msg.edit_text(f"📤 Yuborilmoqda... {i}/{total}")
except Exception:
pass
await status_msg.edit_text(
f"✅ Broadcast yakunlandi!\n\n"
f"📤 Yuborildi: {success_count} ta\n"
f"❌ Xato: {fail_count} ta\n"
f"📊 Jami: {total} ta",
parse_mode="HTML",
reply_markup=admin_back_kb()
)
# ─────────────────── NARX O'ZGARTIRISH ───────────────────
@router.callback_query(F.data == "admin_prices")
async def admin_prices_start(callback: CallbackQuery, state: FSMContext):
"""Narxlarni o'zgartirish — mahsulot tanlash."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
products = await get_active_products()
await callback.message.edit_text(
"💰 Narxni O'zgartirish\n\nQaysi mahsulot narxini o'zgartirmoqchisiz?",
reply_markup=admin_select_product_kb(products, action="setprice"),
parse_mode="HTML"
)
await state.set_state(AdminStates.waiting_product_for_price)
await callback.answer()
@router.callback_query(F.data.startswith("setprice_"), AdminStates.waiting_product_for_price)
async def admin_price_product_selected(callback: CallbackQuery, state: FSMContext):
"""Narx o'zgartirish uchun mahsulot tanlandi."""
if callback.from_user.id not in ADMIN_IDS:
await callback.answer("🚫 Ruxsat yo'q!", show_alert=True)
return
product_id = int(callback.data.split("_")[1])
await state.update_data(product_id=product_id)
await state.set_state(AdminStates.waiting_new_price)
products = await get_active_products()
product = next((p for p in products if p["id"] == product_id), None)
if not product:
await callback.answer("❌ Mahsulot topilmadi!", show_alert=True)
await state.clear()
return
await callback.message.edit_text(
f"💰 {product['name']}\n\n"
f"Joriy narx: {product['stars_price']} ⭐ Stars\n\n"
f"Yangi narxni (faqat raqam) yuboring:",
reply_markup=cancel_admin_action_kb(),
parse_mode="HTML"
)
await callback.answer()
@router.message(AdminStates.waiting_new_price)
async def admin_set_new_price(message: Message, state: FSMContext):
"""Yangi narxni saqlash."""
if message.from_user.id not in ADMIN_IDS:
return
if not message.text.isdigit():
await message.answer(
"⚠️ Faqat raqam kiriting! Masalan: 750",
parse_mode="HTML",
reply_markup=cancel_admin_action_kb()
)
return
new_price = int(message.text)
if new_price < 1:
await message.answer("⚠️ Narx 1 dan katta bo'lishi kerak!")
return
data = await state.get_data()
product_id = data.get("product_id")
await update_product_price(product_id, new_price)
await state.clear()
await message.answer(
f"✅ Narx muvaffaqiyatli o'zgartirildi!\n\n"
f"Yangi narx: {new_price} ⭐ Stars",
parse_mode="HTML",
reply_markup=admin_back_kb()
)