Перенос конфигов в отдельный файл

This commit is contained in:
2026-01-08 04:24:15 +03:00
parent ae3123535c
commit 14f975d4a0
4 changed files with 189 additions and 126 deletions

280
main.py
View File

@@ -16,25 +16,9 @@ from aiogram.types import (
InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
)
import aiohttp
import asyncpg
import aiosqlite
from config import CONFIG, PLANS
# Конфигурация
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__)
@@ -132,143 +116,187 @@ class MarzbanAPI:
# Database Manager
class Database:
def __init__(self, url: str):
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):
self.pool = await asyncpg.create_pool(self.url)
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):
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()
)
""")
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):
async with self.pool.acquire() as conn:
return await conn.fetchrow("SELECT * FROM users WHERE user_id = $1", user_id)
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):
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
)
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):
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
)
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))
async with self.pool.acquire() as conn:
await conn.execute(
"INSERT INTO invite_codes (code, created_by) VALUES ($1, $2)",
code, created_by
)
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):
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']
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):
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
)
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):
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']
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):
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
)
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):
async with self.pool.acquire() as conn:
return await conn.fetch("SELECT user_id FROM users")
return await self.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}
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"])