Улучшение
This commit is contained in:
@@ -11,6 +11,7 @@ CONFIG = {
|
||||
"DATABASE_URL": os.getenv("DATABASE_URL"),
|
||||
"ADMIN_IDS": [int(i.strip()) for i in os.getenv("ADMIN_IDS", "").split(",") if i.strip()],
|
||||
"PROVIDER_TOKEN": os.getenv("PROVIDER_TOKEN", ""),
|
||||
"BASE_URL": os.getenv("BASE_URL"), # Внешний домен (например, https://vpn.example.com)
|
||||
}
|
||||
|
||||
PLANS = {
|
||||
|
||||
399
main.py
399
main.py
@@ -5,6 +5,8 @@ from typing import Optional, Dict, Any
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
from qrcode import QRCode
|
||||
import io
|
||||
|
||||
from aiogram import Bot, Dispatcher, Router, F
|
||||
from aiogram.filters import Command, StateFilter
|
||||
@@ -13,7 +15,8 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.fsm.storage.memory import MemoryStorage
|
||||
from aiogram.types import (
|
||||
Message, CallbackQuery, InlineKeyboardMarkup,
|
||||
InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
|
||||
InlineKeyboardButton, LabeledPrice, PreCheckoutQuery,
|
||||
BufferedInputFile
|
||||
)
|
||||
import aiohttp
|
||||
import aiosqlite
|
||||
@@ -67,26 +70,36 @@ class MarzbanAPI:
|
||||
await self.login()
|
||||
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
url = f"{self.url}/api{endpoint}"
|
||||
|
||||
logger.debug(f"Marzban Request: {method} {url} Payload: {kwargs.get('json')}")
|
||||
|
||||
async with self.session.request(
|
||||
method, f"{self.url}/api{endpoint}", headers=headers, **kwargs
|
||||
method, url, headers=headers, **kwargs
|
||||
) as resp:
|
||||
data = await resp.json()
|
||||
logger.info(f"Marzban Response [{resp.status}]: {data}")
|
||||
|
||||
if resp.status == 401:
|
||||
await self.login()
|
||||
headers = {"Authorization": f"Bearer {self.token}"}
|
||||
async with self.session.request(
|
||||
method, f"{self.url}/api{endpoint}", headers=headers, **kwargs
|
||||
method, url, headers=headers, **kwargs
|
||||
) as retry_resp:
|
||||
return await retry_resp.json()
|
||||
return await resp.json()
|
||||
retry_data = await retry_resp.json()
|
||||
logger.info(f"Marzban Retry Response [{retry_resp.status}]: {retry_data}")
|
||||
return retry_data
|
||||
return data
|
||||
|
||||
async def create_user(self, username: str, data_limit: int, expire_days: int):
|
||||
expire_timestamp = int((datetime.now() + timedelta(days=expire_days)).timestamp())
|
||||
payload = {
|
||||
"username": username,
|
||||
"proxies": {
|
||||
"vless": {},
|
||||
"vmess": {}
|
||||
"vless": {}
|
||||
},
|
||||
"inbounds": {}, # Разрешить все входящие
|
||||
"excluded_inbounds": {}, # Ничего не исключать
|
||||
"data_limit": data_limit * 1024 * 1024 * 1024, # GB to bytes
|
||||
"expire": expire_timestamp,
|
||||
"status": "active"
|
||||
@@ -101,6 +114,7 @@ class MarzbanAPI:
|
||||
payload = {
|
||||
"data_limit": data_limit * 1024 * 1024 * 1024,
|
||||
"expire": expire_timestamp,
|
||||
"excluded_inbounds": {}, # Снимаем ограничения при продлении
|
||||
"status": "active"
|
||||
}
|
||||
return await self._request("PUT", f"/user/{username}", json=payload)
|
||||
@@ -267,18 +281,17 @@ class Database:
|
||||
code, discount, uses, created_by
|
||||
)
|
||||
|
||||
async def use_promo_code(self, code: str):
|
||||
promo = await self.fetchrow(
|
||||
async def get_promo_code(self, code: str):
|
||||
return await self.fetchrow(
|
||||
"SELECT * FROM promo_codes WHERE code = $1 AND uses_left > 0",
|
||||
code
|
||||
)
|
||||
if not promo:
|
||||
return None
|
||||
|
||||
async def decrement_promo_usage(self, code: str):
|
||||
await self.execute(
|
||||
"UPDATE promo_codes SET uses_left = uses_left - 1 WHERE code = $1",
|
||||
code
|
||||
)
|
||||
return promo['discount']
|
||||
|
||||
async def add_payment(self, user_id: int, plan: str, amount: int, promo_code: str = None):
|
||||
await self.execute(
|
||||
@@ -346,6 +359,13 @@ async def cmd_start(message: Message, state: FSMContext):
|
||||
user_id = message.from_user.id
|
||||
user = await db.get_user(user_id)
|
||||
|
||||
# Автоматическая регистрация админа
|
||||
if not user and user_id in CONFIG["ADMIN_IDS"]:
|
||||
marzban_username = f"user_{user_id}"
|
||||
await db.create_user(user_id, message.from_user.username, marzban_username, invited_by=None)
|
||||
user = await db.get_user(user_id)
|
||||
await message.answer("✅ Администратор автоматически зарегистрирован.")
|
||||
|
||||
if user:
|
||||
is_admin = user_id in CONFIG["ADMIN_IDS"]
|
||||
await message.answer(
|
||||
@@ -385,7 +405,15 @@ async def process_invite_code(message: Message, state: FSMContext):
|
||||
async def show_subscription(callback: CallbackQuery):
|
||||
user = await db.get_user(callback.from_user.id)
|
||||
|
||||
if not user['subscription_until'] or user['subscription_until'] < datetime.now():
|
||||
sub_until = user['subscription_until']
|
||||
if isinstance(sub_until, str):
|
||||
try:
|
||||
sub_until = datetime.strptime(sub_until, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
# Try with microseconds if previous format fails
|
||||
sub_until = datetime.strptime(sub_until, '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
if not sub_until or sub_until < datetime.now():
|
||||
await callback.message.edit_text(
|
||||
"❌ У вас нет активной подписки.\n\n"
|
||||
"Приобретите подписку, чтобы начать пользоваться VPN.",
|
||||
@@ -395,56 +423,121 @@ async def show_subscription(callback: CallbackQuery):
|
||||
|
||||
try:
|
||||
marzban_user = await marzban.get_user(user['marzban_username'])
|
||||
used_traffic = marzban_user.get('used_traffic', 0) / (1024**3) # bytes to GB
|
||||
|
||||
# Если пользователя нет в панели, но подписка активна - пробуем создать заново
|
||||
if isinstance(marzban_user, dict) and marzban_user.get('detail') == 'User not found':
|
||||
logger.warning(f"User {user['marzban_username']} not found in Marzban, restoring...")
|
||||
await marzban.create_user(
|
||||
user['marzban_username'],
|
||||
user['data_limit'] or 50, # Лимит из базы или 50 по умолчанию
|
||||
30 # Срок не важен, он обновится при следующей оплате
|
||||
)
|
||||
marzban_user = await marzban.get_user(user['marzban_username'])
|
||||
|
||||
used_traffic = marzban_user.get('used_traffic', 0) / (1024**3)
|
||||
|
||||
sub_url = marzban_user.get('subscription_url', 'Генерируется...')
|
||||
if sub_url and sub_url.startswith('/'):
|
||||
# Используем BASE_URL если он есть, иначе MARZBAN_URL
|
||||
base = CONFIG.get('BASE_URL') or CONFIG['MARZBAN_URL']
|
||||
sub_url = f"{base.rstrip('/')}{sub_url}"
|
||||
|
||||
info_text = (
|
||||
f"📊 Ваша подписка:\n\n"
|
||||
f"⏰ Действует до: {user['subscription_until'].strftime('%d.%m.%Y %H:%M')}\n"
|
||||
f"⏰ Действует до: {sub_until.strftime('%d.%m.%Y %H:%M')}\n"
|
||||
f"📦 Лимит трафика: {user['data_limit']} ГБ\n"
|
||||
f"📊 Использовано: {used_traffic:.2f} ГБ\n\n"
|
||||
f"🔗 Конфигурация:\n"
|
||||
f"`{marzban_user.get('subscription_url', 'Генерируется...')}`"
|
||||
f"🎫 Ссылка на подписку (рекомендуется):\n"
|
||||
f"`{sub_url}`"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user info: {e}")
|
||||
info_text = "⚠️ Ошибка получения данных. Попробуйте позже."
|
||||
|
||||
await callback.message.edit_text(
|
||||
info_text,
|
||||
reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"]),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
try:
|
||||
# Генерация QR-кода
|
||||
qr = QRCode(version=1, box_size=10, border=5)
|
||||
qr.add_data(sub_url)
|
||||
qr.make(fit=True)
|
||||
qr_img = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
# Сохранение в буфер
|
||||
img_buffer = io.BytesIO()
|
||||
qr_img.save(img_buffer)
|
||||
img_buffer.seek(0)
|
||||
qr_file = BufferedInputFile(img_buffer.getvalue(), filename="subscription_qr.png")
|
||||
|
||||
# Если это текстовое сообщение - удаляем и шлем фото
|
||||
# Если это уже фото - пробуем edit_media (но проще удалить и прислать новое для чистоты)
|
||||
await callback.message.delete()
|
||||
await callback.message.answer_photo(
|
||||
photo=qr_file,
|
||||
caption=info_text,
|
||||
reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"]),
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error sending subscription photo: {e}")
|
||||
# В случае ошибки - шлем текст как раньше
|
||||
await callback.message.answer(info_text, reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"]), parse_mode="Markdown")
|
||||
|
||||
@router.callback_query(F.data == "buy_subscription")
|
||||
async def show_plans(callback: CallbackQuery):
|
||||
await callback.message.edit_text(
|
||||
text = (
|
||||
"💎 Выберите тарифный план:\n\n"
|
||||
"Все планы включают:\n"
|
||||
"• Безлимитная скорость\n"
|
||||
"• Поддержка всех устройств\n"
|
||||
"• Техподдержка 24/7",
|
||||
reply_markup=plans_keyboard()
|
||||
"• Техподдержка 24/7"
|
||||
)
|
||||
kb = plans_keyboard()
|
||||
|
||||
if callback.message.photo:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(text, reply_markup=kb)
|
||||
else:
|
||||
await callback.message.edit_text(text, reply_markup=kb)
|
||||
|
||||
@router.callback_query(F.data.startswith("plan_"))
|
||||
async def process_plan_selection(callback: CallbackQuery, state: FSMContext):
|
||||
plan_id = callback.data.split("_")[1]
|
||||
plan_id = callback.data.replace("plan_", "", 1)
|
||||
plan = PLANS[plan_id]
|
||||
|
||||
await state.update_data(selected_plan=plan_id)
|
||||
data = await state.get_data()
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"Вы выбрали: {plan['name']}\n"
|
||||
f"Стоимость: {plan['price']} ⭐\n\n"
|
||||
f"📦 Трафик: {plan['data_limit']} ГБ\n"
|
||||
f"⏰ Период: {plan['days']} дней\n\n"
|
||||
"Есть промокод на скидку?",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="✅ Ввести промокод", callback_data="enter_promo")],
|
||||
[InlineKeyboardButton(text="💳 Оплатить", callback_data="pay_now")],
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="buy_subscription")],
|
||||
])
|
||||
)
|
||||
# Если промокод уже активирован
|
||||
if 'promo_code' in data and 'discount' in data:
|
||||
discount = data['discount']
|
||||
new_price = int(plan['price'] * (100 - discount) / 100)
|
||||
await state.update_data(final_price=new_price)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"Вы выбрали: {plan['name']}\n"
|
||||
f"Цена без скидки: {plan['price']} ⭐\n"
|
||||
f"✅ Применен промокод: {discount}%\n"
|
||||
f"Итого к оплате: {new_price} ⭐\n\n"
|
||||
f"📦 Трафик: {plan['data_limit']} ГБ\n"
|
||||
f"⏰ Период: {plan['days']} дней",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="💳 Оплатить", callback_data="pay_now")],
|
||||
[InlineKeyboardButton(text="❌ Сбросить промокод", callback_data="reset_promo")],
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="buy_subscription")],
|
||||
])
|
||||
)
|
||||
else:
|
||||
await callback.message.edit_text(
|
||||
f"Вы выбрали: {plan['name']}\n"
|
||||
f"Стоимость: {plan['price']} ⭐\n\n"
|
||||
f"📦 Трафик: {plan['data_limit']} ГБ\n"
|
||||
f"⏰ Период: {plan['days']} дней\n\n"
|
||||
"Есть промокод на скидку?",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="✅ Ввести промокод", callback_data="enter_promo")],
|
||||
[InlineKeyboardButton(text="💳 Оплатить", callback_data="pay_now")],
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="buy_subscription")],
|
||||
])
|
||||
)
|
||||
|
||||
@router.callback_query(F.data == "enter_promo")
|
||||
async def ask_promo(callback: CallbackQuery, state: FSMContext):
|
||||
@@ -454,39 +547,140 @@ async def ask_promo(callback: CallbackQuery, state: FSMContext):
|
||||
@router.message(PromoStates.waiting_for_promo)
|
||||
async def process_promo(message: Message, state: FSMContext):
|
||||
promo_code = message.text.strip().upper()
|
||||
discount = await db.use_promo_code(promo_code)
|
||||
promo = await db.get_promo_code(promo_code)
|
||||
|
||||
data = await state.get_data()
|
||||
plan_id = data['selected_plan']
|
||||
plan = PLANS[plan_id]
|
||||
if promo:
|
||||
discount = promo['discount']
|
||||
await state.update_data(promo_code=promo_code, discount=discount)
|
||||
|
||||
if discount:
|
||||
new_price = int(plan['price'] * (100 - discount) / 100)
|
||||
await state.update_data(promo_code=promo_code, final_price=new_price)
|
||||
await message.answer(
|
||||
f"✅ Промокод применен! Скидка: {discount}%\n"
|
||||
f"Новая цена: {new_price} ⭐",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="💳 Оплатить", callback_data="pay_now")],
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="buy_subscription")],
|
||||
])
|
||||
)
|
||||
data = await state.get_data()
|
||||
|
||||
# Если план уже выбран (переход из покупки)
|
||||
if 'selected_plan' in data:
|
||||
plan_id = data['selected_plan']
|
||||
plan = PLANS[plan_id]
|
||||
new_price = int(plan['price'] * (100 - discount) / 100)
|
||||
await state.update_data(final_price=new_price)
|
||||
|
||||
await message.answer(
|
||||
f"✅ Промокод применен! Скидка: {discount}%\n"
|
||||
f"Новая цена: {new_price} ⭐",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="💳 Оплатить", callback_data="pay_now")],
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="buy_subscription")],
|
||||
])
|
||||
)
|
||||
# Если план не выбран (переход из главного меню)
|
||||
else:
|
||||
await message.answer(
|
||||
f"✅ Промокод активирован! Скидка: {discount}%\n"
|
||||
"Теперь выберите тариф:",
|
||||
reply_markup=plans_keyboard()
|
||||
)
|
||||
else:
|
||||
await message.answer(
|
||||
"❌ Промокод недействителен или исчерпан.",
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="🔄 Попробовать снова", callback_data="enter_promo")],
|
||||
[InlineKeyboardButton(text="💳 Оплатить без промокода", callback_data="pay_now")],
|
||||
[InlineKeyboardButton(text="◀️ Отмена", callback_data="back_to_main")],
|
||||
])
|
||||
)
|
||||
await state.set_state(None)
|
||||
|
||||
@router.callback_query(F.data == "reset_promo")
|
||||
async def reset_promo(callback: CallbackQuery, state: FSMContext):
|
||||
await state.update_data(promo_code=None, discount=None, final_price=None)
|
||||
# Перезагружаем выбор плана
|
||||
data = await state.get_data()
|
||||
if 'selected_plan' in data:
|
||||
# Имитируем повторный выбор плана для обновления текста
|
||||
callback.data = f"plan_{data['selected_plan']}"
|
||||
await process_plan_selection(callback, state)
|
||||
else:
|
||||
await back_to_main(callback)
|
||||
|
||||
async def grant_subscription(user_id: int, plan_id: str, promo_code: str, amount: int):
|
||||
plan = PLANS[plan_id]
|
||||
user = await db.get_user(user_id)
|
||||
|
||||
sub_until = user['subscription_until']
|
||||
if isinstance(sub_until, str):
|
||||
try:
|
||||
sub_until = datetime.strptime(sub_until, '%Y-%m-%d %H:%M:%S')
|
||||
except ValueError:
|
||||
sub_until = datetime.strptime(sub_until, '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
# Marzban
|
||||
try:
|
||||
marzban_username = user['marzban_username']
|
||||
resp = None
|
||||
|
||||
if sub_until and sub_until > datetime.now():
|
||||
logger.info(f"Attempting to modify existing user: {marzban_username}")
|
||||
resp = await marzban.modify_user(
|
||||
marzban_username,
|
||||
plan['data_limit'],
|
||||
plan['days']
|
||||
)
|
||||
|
||||
# Если пользователь не найден в Marzban (хотя в БД бота он есть)
|
||||
if isinstance(resp, dict) and resp.get('detail') == 'User not found':
|
||||
logger.info(f"User {marzban_username} missing in Marzban, re-creating...")
|
||||
resp = await marzban.create_user(
|
||||
marzban_username,
|
||||
plan['data_limit'],
|
||||
plan['days']
|
||||
)
|
||||
else:
|
||||
logger.info(f"Creating/Reactivating user: {marzban_username}")
|
||||
resp = await marzban.create_user(
|
||||
marzban_username,
|
||||
plan['data_limit'],
|
||||
plan['days']
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Marzban error in grant_subscription: {e}")
|
||||
|
||||
# DB
|
||||
await db.update_subscription(user_id, plan['days'], plan['data_limit'])
|
||||
await db.add_payment(
|
||||
user_id,
|
||||
plan_id,
|
||||
amount,
|
||||
promo_code
|
||||
)
|
||||
|
||||
if promo_code:
|
||||
await db.decrement_promo_usage(promo_code)
|
||||
|
||||
return plan
|
||||
|
||||
@router.callback_query(F.data == "pay_now")
|
||||
async def process_payment(callback: CallbackQuery, state: FSMContext):
|
||||
data = await state.get_data()
|
||||
plan_id = data['selected_plan']
|
||||
plan_id = data.get('selected_plan')
|
||||
|
||||
if not plan_id:
|
||||
await callback.answer("❌ Сессия истекла. Пожалуйста, выберите тариф снова.", show_alert=True)
|
||||
await show_plans(callback)
|
||||
return
|
||||
|
||||
plan = PLANS[plan_id]
|
||||
final_price = data.get('final_price', plan['price'])
|
||||
final_price = int(data.get('final_price', plan['price']))
|
||||
promo_code = data.get('promo_code')
|
||||
|
||||
if final_price <= 0:
|
||||
await grant_subscription(callback.from_user.id, plan_id, promo_code, 0)
|
||||
await callback.message.edit_text(
|
||||
f"✅ Подписка активирована бесплатно!\n\n"
|
||||
f"План: {plan['name']}\n"
|
||||
f"Срок: {plan['days']} дней\n"
|
||||
f"Трафик: {plan['data_limit']} ГБ\n\n"
|
||||
f"Настройте подключение в меню: 📊 Моя подписка",
|
||||
reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"])
|
||||
)
|
||||
await state.clear()
|
||||
return
|
||||
|
||||
# Создаем инвойс для Telegram Stars
|
||||
await callback.message.answer_invoice(
|
||||
@@ -508,36 +702,14 @@ async def pre_checkout_handler(pre_checkout_query: PreCheckoutQuery):
|
||||
async def successful_payment(message: Message):
|
||||
payment = message.successful_payment
|
||||
plan_id, promo_code = payment.invoice_payload.split(":")
|
||||
plan = PLANS[plan_id]
|
||||
if not promo_code:
|
||||
promo_code = None
|
||||
|
||||
user = await db.get_user(message.from_user.id)
|
||||
|
||||
# Обновляем или создаем пользователя в Marzban
|
||||
try:
|
||||
if user['subscription_until'] and user['subscription_until'] > datetime.now():
|
||||
# Продлеваем существующую подписку
|
||||
await marzban.modify_user(
|
||||
user['marzban_username'],
|
||||
plan['data_limit'],
|
||||
plan['days']
|
||||
)
|
||||
else:
|
||||
# Создаем новую подписку
|
||||
await marzban.create_user(
|
||||
user['marzban_username'],
|
||||
plan['data_limit'],
|
||||
plan['days']
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Marzban error: {e}")
|
||||
|
||||
# Обновляем БД
|
||||
await db.update_subscription(message.from_user.id, plan['days'], plan['data_limit'])
|
||||
await db.add_payment(
|
||||
plan = await grant_subscription(
|
||||
message.from_user.id,
|
||||
plan_id,
|
||||
payment.total_amount,
|
||||
promo_code if promo_code else None
|
||||
promo_code,
|
||||
payment.total_amount
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
@@ -555,10 +727,14 @@ async def admin_panel(callback: CallbackQuery):
|
||||
await callback.answer("❌ Доступ запрещен", show_alert=True)
|
||||
return
|
||||
|
||||
await callback.message.edit_text(
|
||||
"👑 Панель администратора",
|
||||
reply_markup=admin_keyboard()
|
||||
)
|
||||
text = "👑 Панель администратора"
|
||||
kb = admin_keyboard()
|
||||
|
||||
if callback.message.photo:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(text, reply_markup=kb)
|
||||
else:
|
||||
await callback.message.edit_text(text, reply_markup=kb)
|
||||
|
||||
@router.callback_query(F.data == "admin_stats")
|
||||
async def admin_stats(callback: CallbackQuery):
|
||||
@@ -678,18 +854,28 @@ async def admin_promo_uses(message: Message, state: FSMContext):
|
||||
@router.callback_query(F.data == "back_to_main")
|
||||
async def back_to_main(callback: CallbackQuery):
|
||||
is_admin = callback.from_user.id in CONFIG["ADMIN_IDS"]
|
||||
await callback.message.edit_text(
|
||||
"Главное меню:",
|
||||
reply_markup=main_keyboard(is_admin)
|
||||
)
|
||||
text = "Главное меню:"
|
||||
kb = main_keyboard(is_admin)
|
||||
|
||||
if callback.message.photo:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(text, reply_markup=kb)
|
||||
else:
|
||||
await callback.message.edit_text(text, reply_markup=kb)
|
||||
|
||||
@router.callback_query(F.data == "use_promo")
|
||||
async def use_promo_callback(callback: CallbackQuery):
|
||||
await callback.message.edit_text(
|
||||
"Эта функция доступна при покупке подписки.\n"
|
||||
"Сначала выберите тариф, затем можно будет ввести промокод.",
|
||||
reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"])
|
||||
)
|
||||
async def use_promo_callback(callback: CallbackQuery, state: FSMContext):
|
||||
text = "Введите промокод для активации скидки:"
|
||||
kb = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="◀️ Отмена", callback_data="back_to_main")]
|
||||
])
|
||||
|
||||
if callback.message.photo:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(text, reply_markup=kb)
|
||||
else:
|
||||
await callback.message.edit_text(text, reply_markup=kb)
|
||||
await state.set_state(PromoStates.waiting_for_promo)
|
||||
|
||||
@router.callback_query(F.data == "help")
|
||||
async def help_handler(callback: CallbackQuery):
|
||||
@@ -706,14 +892,17 @@ async def help_handler(callback: CallbackQuery):
|
||||
"• Windows: v2rayN, Nekoray\n"
|
||||
"• macOS: V2rayU, ClashX\n\n"
|
||||
"❓ Возникли проблемы?\n"
|
||||
"Напишите администратору: @your_admin"
|
||||
)
|
||||
await callback.message.edit_text(
|
||||
help_text,
|
||||
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="back_to_main")]
|
||||
])
|
||||
"Напишите администратору: @hoshimach1"
|
||||
)
|
||||
kb = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="◀️ Назад", callback_data="back_to_main")]
|
||||
])
|
||||
|
||||
if callback.message.photo:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(help_text, reply_markup=kb)
|
||||
else:
|
||||
await callback.message.edit_text(help_text, reply_markup=kb)
|
||||
|
||||
@router.callback_query(F.data == "admin_broadcast")
|
||||
async def admin_broadcast_start(callback: CallbackQuery, state: FSMContext):
|
||||
@@ -760,7 +949,7 @@ async def admin_broadcast_send(message: Message, state: FSMContext):
|
||||
await state.clear()
|
||||
|
||||
# Команда для получения своего ID
|
||||
@router.message(Command("myid"))
|
||||
@router.message(Command("myid"), StateFilter("*"))
|
||||
async def cmd_myid(message: Message):
|
||||
await message.answer(f"Ваш Telegram ID: `{message.from_user.id}`", parse_mode="Markdown")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user