feat: Add main application file

This commit is contained in:
2026-01-08 03:09:23 +03:00
commit ae3123535c

771
main.py Normal file
View File

@@ -0,0 +1,771 @@
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 asyncpg
# Конфигурация
CONFIG = {
"BOT_TOKEN": "YOUR_BOT_TOKEN",
"MARZBAN_URL": "https://your-panel.com",
"MARZBAN_USERNAME": "admin",
"MARZBAN_PASSWORD": "your_password",
"DATABASE_URL": "postgresql://user:password@localhost/vpnbot",
"ADMIN_IDS": [123456789], # ID администраторов
"PROVIDER_TOKEN": "", # Для Telegram Stars оставляем пустым
}
# Тарифные планы
PLANS = {
"month_1": {"name": "1 месяц", "days": 30, "price": 100, "data_limit": 50},
"month_3": {"name": "3 месяца", "days": 90, "price": 270, "data_limit": 150},
"month_6": {"name": "6 месяцев", "days": 180, "price": 500, "data_limit": 300},
}
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: str):
self.url = url
self.pool = None
async def init_pool(self):
self.pool = await asyncpg.create_pool(self.url)
await self.create_tables()
async def create_tables(self):
async with self.pool.acquire() as conn:
await conn.execute("""
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()
)
""")
await conn.execute("""
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()
)
""")
await conn.execute("""
CREATE TABLE IF NOT EXISTS promo_codes (
code TEXT PRIMARY KEY,
discount INTEGER,
uses_left INTEGER,
created_by BIGINT,
created_at TIMESTAMP DEFAULT NOW()
)
""")
await conn.execute("""
CREATE TABLE IF NOT EXISTS payments (
id SERIAL PRIMARY KEY,
user_id BIGINT,
plan TEXT,
amount INTEGER,
promo_code TEXT,
paid_at TIMESTAMP DEFAULT NOW()
)
""")
async def get_user(self, user_id: int):
async with self.pool.acquire() as conn:
return await conn.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):
async with self.pool.acquire() as conn:
await conn.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):
async with self.pool.acquire() as conn:
user = await self.get_user(user_id)
if user and user['subscription_until'] and user['subscription_until'] > datetime.now():
new_date = user['subscription_until'] + timedelta(days=days)
else:
new_date = datetime.now() + timedelta(days=days)
await conn.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))
async with self.pool.acquire() as conn:
await conn.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):
async with self.pool.acquire() as conn:
invite = await conn.fetchrow(
"SELECT * FROM invite_codes WHERE code = $1 AND used_by IS NULL",
code
)
if not invite:
return False
await conn.execute(
"UPDATE invite_codes SET used_by = $1, used_at = NOW() WHERE code = $2",
user_id, code
)
return invite['created_by']
async def create_promo_code(self, code: str, discount: int, uses: int, created_by: int):
async with self.pool.acquire() as conn:
await conn.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):
async with self.pool.acquire() as conn:
promo = await conn.fetchrow(
"SELECT * FROM promo_codes WHERE code = $1 AND uses_left > 0",
code
)
if not promo:
return None
await conn.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):
async with self.pool.acquire() as conn:
await conn.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):
async with self.pool.acquire() as conn:
return await conn.fetch("SELECT user_id FROM users")
async def get_stats(self):
async with self.pool.acquire() as conn:
total = await conn.fetchval("SELECT COUNT(*) FROM users")
active = await conn.fetchval(
"SELECT COUNT(*) FROM users WHERE subscription_until > NOW()"
)
revenue = await conn.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())