Update WebApp
This commit is contained in:
@@ -148,42 +148,47 @@ async def admin_promos(callback: CallbackQuery):
|
||||
if callback.from_user.id not in CONFIG["ADMIN_IDS"]: return
|
||||
promos = await db.get_active_promos() # Only valid ones
|
||||
|
||||
text = "🏷 <b>Активные промокоды:</b>\n\n"
|
||||
if not promos:
|
||||
text = "Нет активных промокодов."
|
||||
|
||||
text = "🏷 <b>Управление промокодами:</b>\n\n"
|
||||
kb_buttons = []
|
||||
now = datetime.now()
|
||||
for p in promos:
|
||||
# Обработка даты (SQLite возвращает строку)
|
||||
exp_val = p['expires_at']
|
||||
exp_dt = None
|
||||
if exp_val:
|
||||
if isinstance(exp_val, str):
|
||||
try:
|
||||
exp_dt = datetime.strptime(exp_val, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
try:
|
||||
exp_dt = datetime.strptime(exp_val, '%Y-%m-%d %H:%M:%S.%f')
|
||||
except:
|
||||
pass
|
||||
try: exp_dt = datetime.strptime(exp_val, '%Y-%m-%d %H:%M:%S')
|
||||
except:
|
||||
try: exp_dt = datetime.strptime(exp_val, '%Y-%m-%d %H:%M:%S.%f')
|
||||
except: pass
|
||||
elif isinstance(exp_val, datetime):
|
||||
exp_dt = exp_val
|
||||
|
||||
# Filter expired
|
||||
if exp_dt and exp_dt < now:
|
||||
continue
|
||||
|
||||
exp_str = exp_dt.strftime('%d.%m.%Y') if exp_dt else "∞"
|
||||
|
||||
# Получаем значения по ключам (не через get)
|
||||
is_unl = p['is_unlimited']
|
||||
type_str = " (VIP ∞)" if is_unl else f" (-{p['discount']}%)"
|
||||
type_str = " (VIP)" if is_unl else f" (-{p['discount']}%)"
|
||||
|
||||
text += (
|
||||
f"🔹 <code>{p['code']}</code>{type_str}\n"
|
||||
f" Осталось: {p['uses_left']} | До: {exp_str}\n"
|
||||
f" Осталось: {p['uses_left']} | До: {exp_str}\n\n"
|
||||
)
|
||||
# Add delete button for each promo
|
||||
kb_buttons.append([InlineKeyboardButton(text=f"❌ Удалить {p['code']}", callback_data=f"admin_promo_del_{p['code']}")])
|
||||
|
||||
kb = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="➕ Создать промокод", callback_data="admin_create_promo")],
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="admin_panel")]
|
||||
])
|
||||
await callback.message.edit_text(text, reply_markup=kb, parse_mode="HTML")
|
||||
kb_buttons.append([InlineKeyboardButton(text="➕ Создать промокод", callback_data="admin_create_promo")])
|
||||
kb_buttons.append([InlineKeyboardButton(text="◀️ Назад", callback_data="admin_panel")])
|
||||
|
||||
await callback.message.edit_text(text, reply_markup=InlineKeyboardMarkup(inline_keyboard=kb_buttons), parse_mode="HTML")
|
||||
|
||||
@router.callback_query(F.data.startswith("admin_promo_del_"))
|
||||
async def admin_delete_promo_bot(callback: CallbackQuery):
|
||||
code = callback.data.replace("admin_promo_del_", "")
|
||||
await db.delete_promo_code(code)
|
||||
await callback.answer(f"✅ Промокод {code} удален")
|
||||
await admin_promos(callback)
|
||||
|
||||
@router.callback_query(F.data == "admin_create_promo")
|
||||
async def start_create_promo(callback: CallbackQuery, state: FSMContext):
|
||||
@@ -510,16 +515,18 @@ async def show_user_panel(message_or_call, user_id):
|
||||
|
||||
rows = [
|
||||
[InlineKeyboardButton(text="➕ Продлить", callback_data=f"adm_usr_add_{user_id}"),
|
||||
InlineKeyboardButton(text="✏️ Лимит", callback_data=f"adm_usr_gb_{user_id}")],
|
||||
[status_btn,
|
||||
InlineKeyboardButton(text="🔄 Сброс трафика", callback_data=f"adm_usr_reset_{user_id}")]
|
||||
InlineKeyboardButton(text="⏳ Уст. срок", callback_data=f"adm_usr_set_{user_id}")],
|
||||
[InlineKeyboardButton(text="✏️ Лимит ГБ", callback_data=f"adm_usr_gb_{user_id}"),
|
||||
InlineKeyboardButton(text="🔄 Сброс", callback_data=f"adm_usr_reset_{user_id}")],
|
||||
[InlineKeyboardButton(text="📋 План (Admin)", callback_data=f"adm_usr_plan_{user_id}"),
|
||||
InlineKeyboardButton(text="✉️ Написать", callback_data=f"adm_usr_msg_{user_id}")],
|
||||
[status_btn]
|
||||
]
|
||||
|
||||
# Только если есть активная дата подписки
|
||||
if sub_until and isinstance(sub_until, datetime):
|
||||
rows.append([InlineKeyboardButton(text="❌ Удалить подписку", callback_data=f"adm_usr_delsub_{user_id}")])
|
||||
|
||||
rows.append([InlineKeyboardButton(text="✉️ Сообщение", callback_data=f"adm_usr_msg_{user_id}")])
|
||||
rows.append([InlineKeyboardButton(text="◀️ Назад", callback_data="admin_users_list")])
|
||||
|
||||
kb = InlineKeyboardMarkup(inline_keyboard=rows)
|
||||
@@ -590,6 +597,109 @@ async def adm_usr_add_process(message: Message, state: FSMContext):
|
||||
except ValueError:
|
||||
await message.answer("Ошибка. Введите целое число.")
|
||||
|
||||
# Set Fixed Expiry
|
||||
@router.callback_query(F.data.startswith("adm_usr_set_"))
|
||||
async def adm_usr_set_start(callback: CallbackQuery, state: FSMContext):
|
||||
user_id = int(callback.data.split("_")[3])
|
||||
await state.update_data(target_user_id=user_id)
|
||||
await callback.message.edit_text(
|
||||
"Введите общее количество дней подписки от ТЕКУЩЕГО момента:\n"
|
||||
"0 - Истечет сразу\n"
|
||||
"36500 - Вечная подписка",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Отмена", callback_data=f"adm_sel_{user_id}")]])
|
||||
)
|
||||
await state.set_state(AdminUserStates.waiting_for_fixed_days)
|
||||
|
||||
@router.message(AdminUserStates.waiting_for_fixed_days)
|
||||
async def adm_usr_set_process(message: Message, state: FSMContext):
|
||||
try:
|
||||
days = int(message.text)
|
||||
data = await state.get_data()
|
||||
user_id = data['target_user_id']
|
||||
|
||||
if days > 10000:
|
||||
new_date = datetime(2099, 12, 31)
|
||||
else:
|
||||
new_date = datetime.now() + timedelta(days=days)
|
||||
|
||||
await db.execute("UPDATE users SET subscription_until = $1 WHERE user_id = $2", new_date, user_id)
|
||||
|
||||
# Sync to Marzban
|
||||
user = await db.get_user(user_id)
|
||||
marz_user = await marzban.get_user(user['marzban_username'])
|
||||
|
||||
# Marzban treats 0 or negative as no limit or infinity?
|
||||
# Actually in our server.py we used:
|
||||
days_left = (new_date - datetime.now()).days + 1 if days > 0 else 0
|
||||
marz_days = days_left if days < 10000 else 0
|
||||
|
||||
await marzban.modify_user(
|
||||
user['marzban_username'],
|
||||
(user['data_limit'] / (1024**3)),
|
||||
expire_timestamp=marz_days if days > 0 else 1 # 1 sec if expired
|
||||
)
|
||||
|
||||
await message.answer(f"✅ Срок установлен: {days} дней")
|
||||
await show_user_panel(message, user_id)
|
||||
await state.clear()
|
||||
except Exception as e:
|
||||
await message.answer(f"❌ Ошибка: {e}")
|
||||
|
||||
# Set Plan (All plans visible to admin)
|
||||
@router.callback_query(F.data.startswith("adm_usr_plan_"))
|
||||
async def adm_usr_plan_list(callback: CallbackQuery):
|
||||
user_id = int(callback.data.split("_")[3])
|
||||
|
||||
kb_btns = []
|
||||
# Show ALL plans from CONFIG to admin
|
||||
for pid, p in PLANS.items():
|
||||
kb_btns.append([InlineKeyboardButton(text=f"{p['name']} ({p['days']}d / {p['data_limit'] or '∞'}GB)",
|
||||
callback_data=f"adm_setplan_{user_id}_{pid}")])
|
||||
|
||||
kb_btns.append([InlineKeyboardButton(text="◀️ Отмена", callback_data=f"adm_sel_{user_id}")])
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"Выберите тарифный план для применения пользователю {user_id}:",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=kb_btns)
|
||||
)
|
||||
|
||||
@router.callback_query(F.data.startswith("adm_setplan_"))
|
||||
async def adm_usr_plan_process(callback: CallbackQuery):
|
||||
parts = callback.data.split("_")
|
||||
user_id = int(parts[2])
|
||||
plan_id = parts[3]
|
||||
|
||||
plan = PLANS.get(plan_id)
|
||||
if not plan:
|
||||
await callback.answer("Ошибка: Тариф не найден", show_alert=True)
|
||||
return
|
||||
|
||||
user = await db.get_user(user_id)
|
||||
if not user: return
|
||||
|
||||
total_days = plan['days']
|
||||
data_limit_gb = plan['data_limit']
|
||||
limit_bytes = int(data_limit_gb * (1024**3)) if data_limit_gb > 0 else 999999 * (1024**3)
|
||||
|
||||
# Update DB
|
||||
await db.execute("UPDATE users SET data_limit = $1 WHERE user_id = $2", limit_bytes, user_id)
|
||||
new_date = datetime.now() + timedelta(days=total_days)
|
||||
await db.execute("UPDATE users SET subscription_until = $1 WHERE user_id = $2", new_date, user_id)
|
||||
|
||||
# Sync to Marzban
|
||||
marz_days = total_days if total_days > 0 else 0
|
||||
marz_limit = data_limit_gb if data_limit_gb > 0 else 0
|
||||
|
||||
try:
|
||||
await marzban.modify_user(user['marzban_username'],
|
||||
marz_limit,
|
||||
marz_days if marz_days > 0 else 1)
|
||||
await callback.answer(f"✅ План {plan['name']} успешно применен!", show_alert=True)
|
||||
except Exception as e:
|
||||
await callback.answer(f"⚠️ БД обновлена, но Marzban вернул ошибку: {e}", show_alert=True)
|
||||
|
||||
await show_user_panel(callback, user_id)
|
||||
|
||||
# Reset Traffic
|
||||
@router.callback_query(F.data.startswith("adm_usr_reset_"))
|
||||
async def adm_usr_reset(callback: CallbackQuery):
|
||||
@@ -673,14 +783,18 @@ async def adm_usr_limit_process(message: Message, state: FSMContext):
|
||||
|
||||
marz_user = await marzban.get_user(user['marzban_username'])
|
||||
expire_ts = marz_user.get('expire')
|
||||
|
||||
current_status = marz_user.get('status', 'active')
|
||||
await marzban.modify_user(user['marzban_username'], limit_gb, status=current_status, expire_timestamp=expire_ts)
|
||||
|
||||
limit_bytes = int(limit_gb * 1024 * 1024 * 1024)
|
||||
# 0 in our logic means unlimited
|
||||
marz_limit = limit_gb if limit_gb > 0 else 0
|
||||
|
||||
await marzban.modify_user(user['marzban_username'], marz_limit, status=current_status, expire_timestamp=expire_ts)
|
||||
|
||||
# Store in DB
|
||||
limit_bytes = int(limit_gb * (1024**3)) if limit_gb > 0 else 999999 * (1024**3)
|
||||
await db.execute("UPDATE users SET data_limit = $1 WHERE user_id = $2", limit_bytes, user_id)
|
||||
|
||||
await message.answer(f"✅ Лимит изменен на {limit_gb} GB")
|
||||
await message.answer(f"✅ Лимит изменен на {limit_gb if limit_gb > 0 else '∞'} GB")
|
||||
await show_user_panel(message, user_id)
|
||||
await state.clear()
|
||||
except ValueError:
|
||||
|
||||
Reference in New Issue
Block a user