Улучшение

This commit is contained in:
2026-01-08 19:36:50 +03:00
parent 14f975d4a0
commit 2d95a32005
2 changed files with 298 additions and 108 deletions

View File

@@ -11,6 +11,7 @@ CONFIG = {
"DATABASE_URL": os.getenv("DATABASE_URL"), "DATABASE_URL": os.getenv("DATABASE_URL"),
"ADMIN_IDS": [int(i.strip()) for i in os.getenv("ADMIN_IDS", "").split(",") if i.strip()], "ADMIN_IDS": [int(i.strip()) for i in os.getenv("ADMIN_IDS", "").split(",") if i.strip()],
"PROVIDER_TOKEN": os.getenv("PROVIDER_TOKEN", ""), "PROVIDER_TOKEN": os.getenv("PROVIDER_TOKEN", ""),
"BASE_URL": os.getenv("BASE_URL"), # Внешний домен (например, https://vpn.example.com)
} }
PLANS = { PLANS = {

405
main.py
View File

@@ -5,6 +5,8 @@ from typing import Optional, Dict, Any
import json import json
import random import random
import string import string
from qrcode import QRCode
import io
from aiogram import Bot, Dispatcher, Router, F from aiogram import Bot, Dispatcher, Router, F
from aiogram.filters import Command, StateFilter 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.fsm.storage.memory import MemoryStorage
from aiogram.types import ( from aiogram.types import (
Message, CallbackQuery, InlineKeyboardMarkup, Message, CallbackQuery, InlineKeyboardMarkup,
InlineKeyboardButton, LabeledPrice, PreCheckoutQuery InlineKeyboardButton, LabeledPrice, PreCheckoutQuery,
BufferedInputFile
) )
import aiohttp import aiohttp
import aiosqlite import aiosqlite
@@ -67,26 +70,36 @@ class MarzbanAPI:
await self.login() await self.login()
headers = {"Authorization": f"Bearer {self.token}"} 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( async with self.session.request(
method, f"{self.url}/api{endpoint}", headers=headers, **kwargs method, url, headers=headers, **kwargs
) as resp: ) as resp:
data = await resp.json()
logger.info(f"Marzban Response [{resp.status}]: {data}")
if resp.status == 401: if resp.status == 401:
await self.login() await self.login()
headers = {"Authorization": f"Bearer {self.token}"} headers = {"Authorization": f"Bearer {self.token}"}
async with self.session.request( async with self.session.request(
method, f"{self.url}/api{endpoint}", headers=headers, **kwargs method, url, headers=headers, **kwargs
) as retry_resp: ) as retry_resp:
return await retry_resp.json() retry_data = await retry_resp.json()
return await 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): async def create_user(self, username: str, data_limit: int, expire_days: int):
expire_timestamp = int((datetime.now() + timedelta(days=expire_days)).timestamp()) expire_timestamp = int((datetime.now() + timedelta(days=expire_days)).timestamp())
payload = { payload = {
"username": username, "username": username,
"proxies": { "proxies": {
"vless": {}, "vless": {}
"vmess": {} },
}, "inbounds": {}, # Разрешить все входящие
"excluded_inbounds": {}, # Ничего не исключать
"data_limit": data_limit * 1024 * 1024 * 1024, # GB to bytes "data_limit": data_limit * 1024 * 1024 * 1024, # GB to bytes
"expire": expire_timestamp, "expire": expire_timestamp,
"status": "active" "status": "active"
@@ -101,6 +114,7 @@ class MarzbanAPI:
payload = { payload = {
"data_limit": data_limit * 1024 * 1024 * 1024, "data_limit": data_limit * 1024 * 1024 * 1024,
"expire": expire_timestamp, "expire": expire_timestamp,
"excluded_inbounds": {}, # Снимаем ограничения при продлении
"status": "active" "status": "active"
} }
return await self._request("PUT", f"/user/{username}", json=payload) return await self._request("PUT", f"/user/{username}", json=payload)
@@ -267,18 +281,17 @@ class Database:
code, discount, uses, created_by code, discount, uses, created_by
) )
async def use_promo_code(self, code: str): async def get_promo_code(self, code: str):
promo = await self.fetchrow( return await self.fetchrow(
"SELECT * FROM promo_codes WHERE code = $1 AND uses_left > 0", "SELECT * FROM promo_codes WHERE code = $1 AND uses_left > 0",
code code
) )
if not promo:
return None async def decrement_promo_usage(self, code: str):
await self.execute( await self.execute(
"UPDATE promo_codes SET uses_left = uses_left - 1 WHERE code = $1", "UPDATE promo_codes SET uses_left = uses_left - 1 WHERE code = $1",
code code
) )
return promo['discount']
async def add_payment(self, user_id: int, plan: str, amount: int, promo_code: str = None): async def add_payment(self, user_id: int, plan: str, amount: int, promo_code: str = None):
await self.execute( await self.execute(
@@ -346,6 +359,13 @@ async def cmd_start(message: Message, state: FSMContext):
user_id = message.from_user.id user_id = message.from_user.id
user = await db.get_user(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: if user:
is_admin = user_id in CONFIG["ADMIN_IDS"] is_admin = user_id in CONFIG["ADMIN_IDS"]
await message.answer( await message.answer(
@@ -385,7 +405,15 @@ async def process_invite_code(message: Message, state: FSMContext):
async def show_subscription(callback: CallbackQuery): async def show_subscription(callback: CallbackQuery):
user = await db.get_user(callback.from_user.id) 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( await callback.message.edit_text(
"У вас нет активной подписки.\n\n" "У вас нет активной подписки.\n\n"
"Приобретите подписку, чтобы начать пользоваться VPN.", "Приобретите подписку, чтобы начать пользоваться VPN.",
@@ -395,56 +423,121 @@ async def show_subscription(callback: CallbackQuery):
try: try:
marzban_user = await marzban.get_user(user['marzban_username']) 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 = ( info_text = (
f"📊 Ваша подписка:\n\n" 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"📦 Лимит трафика: {user['data_limit']} ГБ\n"
f"📊 Использовано: {used_traffic:.2f} ГБ\n\n" f"📊 Использовано: {used_traffic:.2f} ГБ\n\n"
f"🔗 Конфигурация:\n" f"🎫 Ссылка на подписку (рекомендуется):\n"
f"`{marzban_user.get('subscription_url', 'Генерируется...')}`" f"`{sub_url}`"
) )
except Exception as e: except Exception as e:
logger.error(f"Error getting user info: {e}") logger.error(f"Error getting user info: {e}")
info_text = "⚠️ Ошибка получения данных. Попробуйте позже." info_text = "⚠️ Ошибка получения данных. Попробуйте позже."
await callback.message.edit_text( try:
info_text, # Генерация QR-кода
reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"]), qr = QRCode(version=1, box_size=10, border=5)
parse_mode="Markdown" 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") @router.callback_query(F.data == "buy_subscription")
async def show_plans(callback: CallbackQuery): async def show_plans(callback: CallbackQuery):
await callback.message.edit_text( text = (
"💎 Выберите тарифный план:\n\n" "💎 Выберите тарифный план:\n\n"
"Все планы включают:\n" "Все планы включают:\n"
"• Безлимитная скорость\n" "• Безлимитная скорость\n"
"• Поддержка всех устройств\n" "• Поддержка всех устройств\n"
"• Техподдержка 24/7", "• Техподдержка 24/7"
reply_markup=plans_keyboard()
) )
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_")) @router.callback_query(F.data.startswith("plan_"))
async def process_plan_selection(callback: CallbackQuery, state: FSMContext): 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] plan = PLANS[plan_id]
await state.update_data(selected_plan=plan_id) await state.update_data(selected_plan=plan_id)
data = await state.get_data()
await callback.message.edit_text( # Если промокод уже активирован
f"Вы выбрали: {plan['name']}\n" if 'promo_code' in data and 'discount' in data:
f"Стоимость: {plan['price']}\n\n" discount = data['discount']
f"📦 Трафик: {plan['data_limit']} ГБ\n" new_price = int(plan['price'] * (100 - discount) / 100)
f"⏰ Период: {plan['days']} дней\n\n" await state.update_data(final_price=new_price)
"Есть промокод на скидку?",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[ await callback.message.edit_text(
[InlineKeyboardButton(text="✅ Ввести промокод", callback_data="enter_promo")], f"Вы выбрали: {plan['name']}\n"
[InlineKeyboardButton(text="💳 Оплатить", callback_data="pay_now")], f"Цена без скидки: {plan['price']}\n"
[InlineKeyboardButton(text="◀️ Назад", callback_data="buy_subscription")], 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") @router.callback_query(F.data == "enter_promo")
async def ask_promo(callback: CallbackQuery, state: FSMContext): 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) @router.message(PromoStates.waiting_for_promo)
async def process_promo(message: Message, state: FSMContext): async def process_promo(message: Message, state: FSMContext):
promo_code = message.text.strip().upper() 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() if promo:
plan_id = data['selected_plan'] discount = promo['discount']
plan = PLANS[plan_id] await state.update_data(promo_code=promo_code, discount=discount)
if discount: data = await state.get_data()
new_price = int(plan['price'] * (100 - discount) / 100)
await state.update_data(promo_code=promo_code, final_price=new_price) # Если план уже выбран (переход из покупки)
await message.answer( if 'selected_plan' in data:
f"✅ Промокод применен! Скидка: {discount}%\n" plan_id = data['selected_plan']
f"Новая цена: {new_price}", plan = PLANS[plan_id]
reply_markup=InlineKeyboardMarkup(inline_keyboard=[ new_price = int(plan['price'] * (100 - discount) / 100)
[InlineKeyboardButton(text="💳 Оплатить", callback_data="pay_now")], await state.update_data(final_price=new_price)
[InlineKeyboardButton(text="◀️ Назад", callback_data="buy_subscription")],
]) 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: else:
await message.answer( await message.answer(
"❌ Промокод недействителен или исчерпан.", "❌ Промокод недействителен или исчерпан.",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[ reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="🔄 Попробовать снова", callback_data="enter_promo")], [InlineKeyboardButton(text="🔄 Попробовать снова", callback_data="enter_promo")],
[InlineKeyboardButton(text="💳 Оплатить без промокода", callback_data="pay_now")], [InlineKeyboardButton(text="◀️ Отмена", callback_data="back_to_main")],
]) ])
) )
await state.set_state(None) 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") @router.callback_query(F.data == "pay_now")
async def process_payment(callback: CallbackQuery, state: FSMContext): async def process_payment(callback: CallbackQuery, state: FSMContext):
data = await state.get_data() 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] 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 # Создаем инвойс для Telegram Stars
await callback.message.answer_invoice( await callback.message.answer_invoice(
@@ -508,36 +702,14 @@ async def pre_checkout_handler(pre_checkout_query: PreCheckoutQuery):
async def successful_payment(message: Message): async def successful_payment(message: Message):
payment = message.successful_payment payment = message.successful_payment
plan_id, promo_code = payment.invoice_payload.split(":") 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)
plan = await grant_subscription(
# Обновляем или создаем пользователя в 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(
message.from_user.id, message.from_user.id,
plan_id, plan_id,
payment.total_amount, promo_code,
promo_code if promo_code else None payment.total_amount
) )
await message.answer( await message.answer(
@@ -555,10 +727,14 @@ async def admin_panel(callback: CallbackQuery):
await callback.answer("❌ Доступ запрещен", show_alert=True) await callback.answer("❌ Доступ запрещен", show_alert=True)
return return
await callback.message.edit_text( text = "👑 Панель администратора"
"👑 Панель администратора", kb = admin_keyboard()
reply_markup=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") @router.callback_query(F.data == "admin_stats")
async def admin_stats(callback: CallbackQuery): 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") @router.callback_query(F.data == "back_to_main")
async def back_to_main(callback: CallbackQuery): async def back_to_main(callback: CallbackQuery):
is_admin = callback.from_user.id in CONFIG["ADMIN_IDS"] is_admin = callback.from_user.id in CONFIG["ADMIN_IDS"]
await callback.message.edit_text( text = "Главное меню:"
"Главное меню:", kb = main_keyboard(is_admin)
reply_markup=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") @router.callback_query(F.data == "use_promo")
async def use_promo_callback(callback: CallbackQuery): async def use_promo_callback(callback: CallbackQuery, state: FSMContext):
await callback.message.edit_text( text = "Введите промокод для активации скидки:"
"Эта функция доступна при покупке подписки.\n" kb = InlineKeyboardMarkup(inline_keyboard=[
"Сначала выберите тариф, затем можно будет ввести промокод.", [InlineKeyboardButton(text="◀️ Отмена", callback_data="back_to_main")]
reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"]) ])
)
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") @router.callback_query(F.data == "help")
async def help_handler(callback: CallbackQuery): async def help_handler(callback: CallbackQuery):
@@ -706,14 +892,17 @@ async def help_handler(callback: CallbackQuery):
"• Windows: v2rayN, Nekoray\n" "• Windows: v2rayN, Nekoray\n"
"• macOS: V2rayU, ClashX\n\n" "• macOS: V2rayU, ClashX\n\n"
"❓ Возникли проблемы?\n" "❓ Возникли проблемы?\n"
"Напишите администратору: @your_admin" "Напишите администратору: @hoshimach1"
)
await callback.message.edit_text(
help_text,
reply_markup=InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="◀️ Назад", callback_data="back_to_main")]
])
) )
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") @router.callback_query(F.data == "admin_broadcast")
async def admin_broadcast_start(callback: CallbackQuery, state: FSMContext): 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() await state.clear()
# Команда для получения своего ID # Команда для получения своего ID
@router.message(Command("myid")) @router.message(Command("myid"), StateFilter("*"))
async def cmd_myid(message: Message): async def cmd_myid(message: Message):
await message.answer(f"Ваш Telegram ID: `{message.from_user.id}`", parse_mode="Markdown") await message.answer(f"Ваш Telegram ID: `{message.from_user.id}`", parse_mode="Markdown")