import asyncio import logging from datetime import datetime, timedelta from typing import Optional, Dict, Any import json import random import string from aiogram import Bot, Dispatcher, Router, F from aiogram.filters import Command, StateFilter from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.storage.memory import MemoryStorage from aiogram.types import ( Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, LabeledPrice, PreCheckoutQuery ) import aiohttp import aiosqlite from config import CONFIG, PLANS logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # FSM States class InviteStates(StatesGroup): waiting_for_code = State() class PromoStates(StatesGroup): waiting_for_promo = State() creating_promo = State() promo_code = State() promo_discount = State() promo_uses = State() class BroadcastStates(StatesGroup): waiting_for_message = State() # Marzban API Client class MarzbanAPI: def __init__(self, url: str, username: str, password: str): self.url = url.rstrip('/') self.username = username self.password = password self.token = None self.session = None async def init_session(self): self.session = aiohttp.ClientSession() async def close_session(self): if self.session: await self.session.close() async def login(self): async with self.session.post( f"{self.url}/api/admin/token", data={"username": self.username, "password": self.password} ) as resp: data = await resp.json() self.token = data["access_token"] return self.token async def _request(self, method: str, endpoint: str, **kwargs): if not self.token: await self.login() headers = {"Authorization": f"Bearer {self.token}"} async with self.session.request( method, f"{self.url}/api{endpoint}", headers=headers, **kwargs ) as resp: 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 ) as retry_resp: return await retry_resp.json() return await resp.json() 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": {} }, "data_limit": data_limit * 1024 * 1024 * 1024, # GB to bytes "expire": expire_timestamp, "status": "active" } return await self._request("POST", "/user", json=payload) async def get_user(self, username: str): return await self._request("GET", f"/user/{username}") async def modify_user(self, username: str, data_limit: int, expire_days: int): expire_timestamp = int((datetime.now() + timedelta(days=expire_days)).timestamp()) payload = { "data_limit": data_limit * 1024 * 1024 * 1024, "expire": expire_timestamp, "status": "active" } return await self._request("PUT", f"/user/{username}", json=payload) async def delete_user(self, username: str): return await self._request("DELETE", f"/user/{username}") async def get_system_stats(self): return await self._request("GET", "/system") async def get_users_stats(self): return await self._request("GET", "/users") # Database Manager class Database: def __init__(self, url: Optional[str]): self.url = url self.is_sqlite = not url or not url.startswith("postgresql://") self.pool = None self.conn = None # For SQLite async def init_pool(self): if self.is_sqlite: db_path = "bot.db" self.conn = await aiosqlite.connect(db_path) self.conn.row_factory = aiosqlite.Row logger.info(f"Using SQLite database: {db_path}") else: self.pool = await asyncpg.create_pool(self.url) logger.info("Using PostgreSQL database") await self.create_tables() async def execute(self, query: str, *args): if self.is_sqlite: query = query.replace("$1", "?").replace("$2", "?").replace("$3", "?").replace("$4", "?") async with self.conn.execute(query, args) as cursor: await self.conn.commit() return cursor else: async with self.pool.acquire() as conn: return await conn.execute(query, *args) async def fetchrow(self, query: str, *args): if self.is_sqlite: query = query.replace("$1", "?").replace("$2", "?").replace("$3", "?").replace("$4", "?") async with self.conn.execute(query, args) as cursor: return await cursor.fetchone() else: async with self.pool.acquire() as conn: return await conn.fetchrow(query, *args) async def fetchval(self, query: str, *args): if self.is_sqlite: query = query.replace("$1", "?").replace("$2", "?").replace("$3", "?").replace("$4", "?") async with self.conn.execute(query, args) as cursor: row = await cursor.fetchone() return row[0] if row else None else: async with self.pool.acquire() as conn: return await conn.fetchval(query, *args) async def fetch(self, query: str, *args): if self.is_sqlite: query = query.replace("$1", "?").replace("$2", "?").replace("$3", "?").replace("$4", "?") async with self.conn.execute(query, args) as cursor: return await cursor.fetchall() else: async with self.pool.acquire() as conn: return await conn.fetch(query, *args) async def create_tables(self): now_default = "CURRENT_TIMESTAMP" if self.is_sqlite else "NOW()" serial_type = "INTEGER PRIMARY KEY AUTOINCREMENT" if self.is_sqlite else "SERIAL PRIMARY KEY" queries = [ f"""CREATE TABLE IF NOT EXISTS users ( user_id BIGINT PRIMARY KEY, username TEXT, marzban_username TEXT UNIQUE, subscription_until TIMESTAMP, data_limit INTEGER, invited_by BIGINT, created_at TIMESTAMP DEFAULT {now_default} )""", f"""CREATE TABLE IF NOT EXISTS invite_codes ( code TEXT PRIMARY KEY, created_by BIGINT, used_by BIGINT, used_at TIMESTAMP, created_at TIMESTAMP DEFAULT {now_default} )""", f"""CREATE TABLE IF NOT EXISTS promo_codes ( code TEXT PRIMARY KEY, discount INTEGER, uses_left INTEGER, created_by BIGINT, created_at TIMESTAMP DEFAULT {now_default} )""", f"""CREATE TABLE IF NOT EXISTS payments ( id {serial_type}, user_id BIGINT, plan TEXT, amount INTEGER, promo_code TEXT, paid_at TIMESTAMP DEFAULT {now_default} )""" ] for q in queries: await self.execute(q) async def get_user(self, user_id: int): return await self.fetchrow("SELECT * FROM users WHERE user_id = $1", user_id) async def create_user(self, user_id: int, username: str, marzban_username: str, invited_by: int = None): await self.execute( "INSERT INTO users (user_id, username, marzban_username, invited_by) VALUES ($1, $2, $3, $4)", user_id, username, marzban_username, invited_by ) async def update_subscription(self, user_id: int, days: int, data_limit: int): user = await self.get_user(user_id) # SQLite returns Row object, datetime handling might need care sub_until = user['subscription_until'] if isinstance(sub_until, str): # SQLite might return it as string sub_until = datetime.strptime(sub_until, '%Y-%m-%d %H:%M:%S') if '.' not in sub_until else datetime.strptime(sub_until, '%Y-%m-%d %H:%M:%S.%f') if user and sub_until and sub_until > datetime.now(): new_date = sub_until + timedelta(days=days) else: new_date = datetime.now() + timedelta(days=days) await self.execute( "UPDATE users SET subscription_until = $1, data_limit = $2 WHERE user_id = $3", new_date, data_limit, user_id ) async def create_invite_code(self, created_by: int): code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8)) await self.execute( "INSERT INTO invite_codes (code, created_by) VALUES ($1, $2)", code, created_by ) return code async def use_invite_code(self, code: str, user_id: int): invite = await self.fetchrow( "SELECT * FROM invite_codes WHERE code = $1 AND used_by IS NULL", code ) if not invite: return False now_val = datetime.now() await self.execute( "UPDATE invite_codes SET used_by = $1, used_at = $2 WHERE code = $3", user_id, now_val, code ) return invite['created_by'] async def create_promo_code(self, code: str, discount: int, uses: int, created_by: int): await self.execute( "INSERT INTO promo_codes (code, discount, uses_left, created_by) VALUES ($1, $2, $3, $4)", code, discount, uses, created_by ) async def use_promo_code(self, code: str): promo = await self.fetchrow( "SELECT * FROM promo_codes WHERE code = $1 AND uses_left > 0", code ) if not promo: return None 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( "INSERT INTO payments (user_id, plan, amount, promo_code) VALUES ($1, $2, $3, $4)", user_id, plan, amount, promo_code ) async def get_all_users(self): return await self.fetch("SELECT user_id FROM users") async def get_stats(self): now_expr = "CURRENT_TIMESTAMP" if self.is_sqlite else "NOW()" total = await self.fetchval("SELECT COUNT(*) FROM users") active = await self.fetchval( f"SELECT COUNT(*) FROM users WHERE subscription_until > {now_expr}" ) revenue = await self.fetchval("SELECT SUM(amount) FROM payments") return {"total": total, "active": active, "revenue": revenue or 0} # Initialize bot = Bot(token=CONFIG["BOT_TOKEN"]) storage = MemoryStorage() dp = Dispatcher(storage=storage) router = Router() dp.include_router(router) marzban = MarzbanAPI(CONFIG["MARZBAN_URL"], CONFIG["MARZBAN_USERNAME"], CONFIG["MARZBAN_PASSWORD"]) db = Database(CONFIG["DATABASE_URL"]) # Keyboards def main_keyboard(is_admin: bool = False): buttons = [ [InlineKeyboardButton(text="📊 Моя подписка", callback_data="my_subscription")], [InlineKeyboardButton(text="💎 Купить подписку", callback_data="buy_subscription")], [InlineKeyboardButton(text="🎟️ Использовать промокод", callback_data="use_promo")], [InlineKeyboardButton(text="ℹ️ Помощь", callback_data="help")], ] if is_admin: buttons.append([InlineKeyboardButton(text="👑 Админ-панель", callback_data="admin_panel")]) return InlineKeyboardMarkup(inline_keyboard=buttons) def admin_keyboard(): return InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="📊 Статистика", callback_data="admin_stats")], [InlineKeyboardButton(text="🎟️ Создать промокод", callback_data="admin_create_promo")], [InlineKeyboardButton(text="👥 Создать инвайт", callback_data="admin_create_invite")], [InlineKeyboardButton(text="📢 Рассылка", callback_data="admin_broadcast")], [InlineKeyboardButton(text="🖥️ Статистика сервера", callback_data="admin_server_stats")], [InlineKeyboardButton(text="◀️ Назад", callback_data="back_to_main")], ]) def plans_keyboard(): buttons = [] for plan_id, plan_data in PLANS.items(): buttons.append([InlineKeyboardButton( text=f"{plan_data['name']} - {plan_data['price']} ⭐", callback_data=f"plan_{plan_id}" )]) buttons.append([InlineKeyboardButton(text="◀️ Назад", callback_data="back_to_main")]) return InlineKeyboardMarkup(inline_keyboard=buttons) # Handlers @router.message(Command("start")) async def cmd_start(message: Message, state: FSMContext): user_id = message.from_user.id user = await db.get_user(user_id) if user: is_admin = user_id in CONFIG["ADMIN_IDS"] await message.answer( f"Привет, {message.from_user.first_name}! 👋\n\n" "Выберите действие:", reply_markup=main_keyboard(is_admin) ) else: await message.answer( "👋 Добро пожаловать!\n\n" "Для использования бота необходим инвайт-код.\n" "Введите ваш инвайт-код:" ) await state.set_state(InviteStates.waiting_for_code) @router.message(InviteStates.waiting_for_code) async def process_invite_code(message: Message, state: FSMContext): code = message.text.strip().upper() invited_by = await db.use_invite_code(code, message.from_user.id) if not invited_by: await message.answer("❌ Неверный или использованный инвайт-код. Попробуйте снова:") return marzban_username = f"user_{message.from_user.id}" await db.create_user(message.from_user.id, message.from_user.username, marzban_username, invited_by) is_admin = message.from_user.id in CONFIG["ADMIN_IDS"] await message.answer( "✅ Регистрация успешна!\n\n" "Теперь вы можете приобрести подписку.", reply_markup=main_keyboard(is_admin) ) await state.clear() @router.callback_query(F.data == "my_subscription") 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(): await callback.message.edit_text( "❌ У вас нет активной подписки.\n\n" "Приобретите подписку, чтобы начать пользоваться VPN.", reply_markup=main_keyboard(callback.from_user.id in CONFIG["ADMIN_IDS"]) ) return try: marzban_user = await marzban.get_user(user['marzban_username']) used_traffic = marzban_user.get('used_traffic', 0) / (1024**3) # bytes to GB info_text = ( f"📊 Ваша подписка:\n\n" f"⏰ Действует до: {user['subscription_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', 'Генерируется...')}`" ) 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" ) @router.callback_query(F.data == "buy_subscription") async def show_plans(callback: CallbackQuery): await callback.message.edit_text( "💎 Выберите тарифный план:\n\n" "Все планы включают:\n" "• Безлимитная скорость\n" "• Поддержка всех устройств\n" "• Техподдержка 24/7", reply_markup=plans_keyboard() ) @router.callback_query(F.data.startswith("plan_")) async def process_plan_selection(callback: CallbackQuery, state: FSMContext): plan_id = callback.data.split("_")[1] plan = PLANS[plan_id] await state.update_data(selected_plan=plan_id) 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): await callback.message.edit_text("Введите промокод:") await state.set_state(PromoStates.waiting_for_promo) @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) data = await state.get_data() plan_id = data['selected_plan'] plan = PLANS[plan_id] 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")], ]) ) else: await message.answer( "❌ Промокод недействителен или исчерпан.", reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="🔄 Попробовать снова", callback_data="enter_promo")], [InlineKeyboardButton(text="💳 Оплатить без промокода", callback_data="pay_now")], ]) ) await state.set_state(None) @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 = PLANS[plan_id] final_price = data.get('final_price', plan['price']) # Создаем инвойс для Telegram Stars await callback.message.answer_invoice( title=f"Подписка VPN - {plan['name']}", description=f"Трафик: {plan['data_limit']} ГБ на {plan['days']} дней", payload=f"{plan_id}:{data.get('promo_code', '')}", currency="XTR", # Telegram Stars prices=[LabeledPrice(label=plan['name'], amount=final_price)], reply_markup=InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="💳 Оплатить", pay=True)] ]) ) @router.pre_checkout_query() async def pre_checkout_handler(pre_checkout_query: PreCheckoutQuery): await pre_checkout_query.answer(ok=True) @router.message(F.successful_payment) async def successful_payment(message: Message): payment = message.successful_payment plan_id, promo_code = payment.invoice_payload.split(":") plan = PLANS[plan_id] 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( message.from_user.id, plan_id, payment.total_amount, promo_code if promo_code else None ) await message.answer( f"✅ Оплата успешна!\n\n" f"Ваша подписка активирована на {plan['days']} дней.\n" f"Трафик: {plan['data_limit']} ГБ\n\n" f"Получите конфигурацию через: 📊 Моя подписка", reply_markup=main_keyboard(message.from_user.id in CONFIG["ADMIN_IDS"]) ) # Admin handlers @router.callback_query(F.data == "admin_panel") async def admin_panel(callback: CallbackQuery): if callback.from_user.id not in CONFIG["ADMIN_IDS"]: await callback.answer("❌ Доступ запрещен", show_alert=True) return await callback.message.edit_text( "👑 Панель администратора", reply_markup=admin_keyboard() ) @router.callback_query(F.data == "admin_stats") async def admin_stats(callback: CallbackQuery): if callback.from_user.id not in CONFIG["ADMIN_IDS"]: await callback.answer("❌ Доступ запрещен", show_alert=True) return stats = await db.get_stats() text = ( "📊 Статистика бота:\n\n" f"👥 Всего пользователей: {stats['total']}\n" f"✅ Активных подписок: {stats['active']}\n" f"💰 Общая выручка: {stats['revenue']} ⭐\n" ) await callback.message.edit_text(text, reply_markup=admin_keyboard()) @router.callback_query(F.data == "admin_server_stats") async def admin_server_stats(callback: CallbackQuery): if callback.from_user.id not in CONFIG["ADMIN_IDS"]: await callback.answer("❌ Доступ запрещен", show_alert=True) return try: system_stats = await marzban.get_system_stats() users_stats = await marzban.get_users_stats() text = ( "🖥️ Статистика сервера:\n\n" f"📊 CPU: {system_stats.get('cpu_usage', 'N/A')}%\n" f"💾 RAM: {system_stats.get('mem_used', 0) / (1024**3):.2f} / " f"{system_stats.get('mem_total', 0) / (1024**3):.2f} ГБ\n" f"💿 Диск: {system_stats.get('disk_used', 0) / (1024**3):.2f} / " f"{system_stats.get('disk_total', 0) / (1024**3):.2f} ГБ\n\n" f"👥 Пользователей в Marzban: {users_stats.get('total', 0)}\n" f"✅ Активных: {users_stats.get('active', 0)}\n" ) except Exception as e: text = f"⚠️ Ошибка получения статистики: {str(e)}" await callback.message.edit_text(text, reply_markup=admin_keyboard()) @router.callback_query(F.data == "admin_create_invite") async def admin_create_invite(callback: CallbackQuery): if callback.from_user.id not in CONFIG["ADMIN_IDS"]: await callback.answer("❌ Доступ запрещен", show_alert=True) return code = await db.create_invite_code(callback.from_user.id) await callback.answer(f"✅ Инвайт-код создан: {code}", show_alert=True) @router.callback_query(F.data == "admin_create_promo") async def admin_create_promo_start(callback: CallbackQuery, state: FSMContext): if callback.from_user.id not in CONFIG["ADMIN_IDS"]: await callback.answer("❌ Доступ запрещен", show_alert=True) return await callback.message.edit_text( "Создание промокода\n\n" "Введите код промокода (или 'авто' для генерации):" ) await state.set_state(PromoStates.promo_code) @router.message(PromoStates.promo_code) async def admin_promo_code(message: Message, state: FSMContext): code = message.text.strip().upper() if code == 'АВТО' or code == 'AUTO': code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8)) await state.update_data(promo_code=code) await message.answer("Введите размер скидки (%):") await state.set_state(PromoStates.promo_discount) @router.message(PromoStates.promo_discount) async def admin_promo_discount(message: Message, state: FSMContext): try: discount = int(message.text.strip()) if discount < 1 or discount > 100: await message.answer("❌ Скидка должна быть от 1 до 100%. Попробуйте снова:") return await state.update_data(discount=discount) await message.answer("Введите количество использований:") await state.set_state(PromoStates.promo_uses) except ValueError: await message.answer("❌ Введите число. Попробуйте снова:") @router.message(PromoStates.promo_uses) async def admin_promo_uses(message: Message, state: FSMContext): try: uses = int(message.text.strip()) if uses < 1: await message.answer("❌ Минимум 1 использование. Попробуйте снова:") return data = await state.get_data() await db.create_promo_code( data['promo_code'], data['discount'], uses, message.from_user.id ) await message.answer( f"✅ Промокод создан!\n\n" f"Код: `{data['promo_code']}`\n" f"Скидка: {data['discount']}%\n" f"Использований: {uses}", reply_markup=admin_keyboard(), parse_mode="Markdown" ) await state.clear() except ValueError: await message.answer("❌ Введите число. Попробуйте снова:") @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) ) @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"]) ) @router.callback_query(F.data == "help") async def help_handler(callback: CallbackQuery): help_text = ( "ℹ️ Помощь по использованию бота:\n\n" "📱 Как подключиться:\n" "1. Купите подписку\n" "2. Получите ссылку конфигурации\n" "3. Скопируйте ссылку\n" "4. Вставьте в VPN-клиент\n\n" "📲 Рекомендуемые клиенты:\n" "• iOS: Shadowrocket, V2Box\n" "• Android: V2rayNG, NekoBox\n" "• 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")] ]) ) @router.callback_query(F.data == "admin_broadcast") async def admin_broadcast_start(callback: CallbackQuery, state: FSMContext): if callback.from_user.id not in CONFIG["ADMIN_IDS"]: await callback.answer("❌ Доступ запрещен", show_alert=True) return await callback.message.edit_text( "📢 Рассылка сообщений\n\n" "Отправьте сообщение, которое хотите разослать всем пользователям:" ) await state.set_state(BroadcastStates.waiting_for_message) @router.message(BroadcastStates.waiting_for_message) async def admin_broadcast_send(message: Message, state: FSMContext): if message.from_user.id not in CONFIG["ADMIN_IDS"]: return users = await db.get_all_users() success_count = 0 fail_count = 0 status_msg = await message.answer("📤 Начинаю рассылку...") for user in users: try: await bot.copy_message( chat_id=user['user_id'], from_chat_id=message.chat.id, message_id=message.message_id ) success_count += 1 await asyncio.sleep(0.05) # Защита от rate limit except Exception as e: fail_count += 1 logger.error(f"Broadcast error for user {user['user_id']}: {e}") await status_msg.edit_text( f"✅ Рассылка завершена!\n\n" f"Успешно: {success_count}\n" f"Ошибок: {fail_count}", reply_markup=admin_keyboard() ) await state.clear() # Команда для получения своего ID @router.message(Command("myid")) async def cmd_myid(message: Message): await message.answer(f"Ваш Telegram ID: `{message.from_user.id}`", parse_mode="Markdown") # Обработка неизвестных команд @router.message() async def unknown_message(message: Message, state: FSMContext): current_state = await state.get_state() if current_state is None: user = await db.get_user(message.from_user.id) if user: is_admin = message.from_user.id in CONFIG["ADMIN_IDS"] await message.answer( "Используйте кнопки меню для навигации:", reply_markup=main_keyboard(is_admin) ) else: await message.answer( "Для использования бота начните с команды /start" ) # Main function async def main(): # Инициализация await db.init_pool() await marzban.init_session() logger.info("Bot started successfully!") try: await dp.start_polling(bot) finally: await marzban.close_session() await bot.session.close() if __name__ == "__main__": asyncio.run(main())