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

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

8
.env Normal file
View File

@@ -0,0 +1,8 @@
BOT_TOKEN=8406127231:AAG5m0Ft0UUyTW2KI-jwYniXtIRcbSdlxf8
MARZBAN_URL=http://144.31.66.170:7575/
MARZBAN_USERNAME=admin
MARZBAN_PASSWORD=rY4tU8hX4nqF
# Оставьте пустым для использования SQLite (создаст файл bot.db)
# DATABASE_URL=postgresql://user:password@localhost/vpnbot
ADMIN_IDS=123456789
PROVIDER_TOKEN=

7
.env.example Normal file
View File

@@ -0,0 +1,7 @@
BOT_TOKEN=
MARZBAN_URL=
MARZBAN_USERNAME=
MARZBAN_PASSWORD=
# DATABASE_URL=postgresql://user:password@localhost/vpnbot
ADMIN_IDS=
PROVIDER_TOKEN=

20
config.py Normal file
View File

@@ -0,0 +1,20 @@
import os
from dotenv import load_dotenv
load_dotenv()
CONFIG = {
"BOT_TOKEN": os.getenv("BOT_TOKEN"),
"MARZBAN_URL": os.getenv("MARZBAN_URL"),
"MARZBAN_USERNAME": os.getenv("MARZBAN_USERNAME"),
"MARZBAN_PASSWORD": os.getenv("MARZBAN_PASSWORD"),
"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", ""),
}
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},
}

280
main.py
View File

@@ -16,25 +16,9 @@ from aiogram.types import (
InlineKeyboardButton, LabeledPrice, PreCheckoutQuery InlineKeyboardButton, LabeledPrice, PreCheckoutQuery
) )
import aiohttp 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) logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -132,143 +116,187 @@ class MarzbanAPI:
# Database Manager # Database Manager
class Database: class Database:
def __init__(self, url: str): def __init__(self, url: Optional[str]):
self.url = url self.url = url
self.is_sqlite = not url or not url.startswith("postgresql://")
self.pool = None self.pool = None
self.conn = None # For SQLite
async def init_pool(self): 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() 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 def create_tables(self):
async with self.pool.acquire() as conn: now_default = "CURRENT_TIMESTAMP" if self.is_sqlite else "NOW()"
await conn.execute(""" serial_type = "INTEGER PRIMARY KEY AUTOINCREMENT" if self.is_sqlite else "SERIAL PRIMARY KEY"
CREATE TABLE IF NOT EXISTS users (
user_id BIGINT PRIMARY KEY, queries = [
username TEXT, f"""CREATE TABLE IF NOT EXISTS users (
marzban_username TEXT UNIQUE, user_id BIGINT PRIMARY KEY,
subscription_until TIMESTAMP, username TEXT,
data_limit INTEGER, marzban_username TEXT UNIQUE,
invited_by BIGINT, subscription_until TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW() data_limit INTEGER,
) invited_by BIGINT,
""") created_at TIMESTAMP DEFAULT {now_default}
await conn.execute(""" )""",
CREATE TABLE IF NOT EXISTS invite_codes ( f"""CREATE TABLE IF NOT EXISTS invite_codes (
code TEXT PRIMARY KEY, code TEXT PRIMARY KEY,
created_by BIGINT, created_by BIGINT,
used_by BIGINT, used_by BIGINT,
used_at TIMESTAMP, used_at TIMESTAMP,
created_at TIMESTAMP DEFAULT NOW() created_at TIMESTAMP DEFAULT {now_default}
) )""",
""") f"""CREATE TABLE IF NOT EXISTS promo_codes (
await conn.execute(""" code TEXT PRIMARY KEY,
CREATE TABLE IF NOT EXISTS promo_codes ( discount INTEGER,
code TEXT PRIMARY KEY, uses_left INTEGER,
discount INTEGER, created_by BIGINT,
uses_left INTEGER, created_at TIMESTAMP DEFAULT {now_default}
created_by BIGINT, )""",
created_at TIMESTAMP DEFAULT NOW() f"""CREATE TABLE IF NOT EXISTS payments (
) id {serial_type},
""") user_id BIGINT,
await conn.execute(""" plan TEXT,
CREATE TABLE IF NOT EXISTS payments ( amount INTEGER,
id SERIAL PRIMARY KEY, promo_code TEXT,
user_id BIGINT, paid_at TIMESTAMP DEFAULT {now_default}
plan TEXT, )"""
amount INTEGER, ]
promo_code TEXT, for q in queries:
paid_at TIMESTAMP DEFAULT NOW() await self.execute(q)
)
""")
async def get_user(self, user_id: int): async def get_user(self, user_id: int):
async with self.pool.acquire() as conn: return await self.fetchrow("SELECT * FROM users WHERE user_id = $1", user_id)
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 def create_user(self, user_id: int, username: str, marzban_username: str, invited_by: int = None):
async with self.pool.acquire() as conn: await self.execute(
await conn.execute( "INSERT INTO users (user_id, username, marzban_username, invited_by) VALUES ($1, $2, $3, $4)",
"INSERT INTO users (user_id, username, marzban_username, invited_by) VALUES ($1, $2, $3, $4)", user_id, username, marzban_username, invited_by
user_id, username, marzban_username, invited_by )
)
async def update_subscription(self, user_id: int, days: int, data_limit: int): 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)
user = await self.get_user(user_id)
if user and user['subscription_until'] and user['subscription_until'] > datetime.now(): # SQLite returns Row object, datetime handling might need care
new_date = user['subscription_until'] + timedelta(days=days) sub_until = user['subscription_until']
else: if isinstance(sub_until, str): # SQLite might return it as string
new_date = datetime.now() + timedelta(days=days) 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')
await conn.execute( if user and sub_until and sub_until > datetime.now():
"UPDATE users SET subscription_until = $1, data_limit = $2 WHERE user_id = $3", new_date = sub_until + timedelta(days=days)
new_date, data_limit, user_id 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): async def create_invite_code(self, created_by: int):
code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8)) code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8))
async with self.pool.acquire() as conn: await self.execute(
await conn.execute( "INSERT INTO invite_codes (code, created_by) VALUES ($1, $2)",
"INSERT INTO invite_codes (code, created_by) VALUES ($1, $2)", code, created_by
code, created_by )
)
return code return code
async def use_invite_code(self, code: str, user_id: int): async def use_invite_code(self, code: str, user_id: int):
async with self.pool.acquire() as conn: invite = await self.fetchrow(
invite = await conn.fetchrow( "SELECT * FROM invite_codes WHERE code = $1 AND used_by IS NULL",
"SELECT * FROM invite_codes WHERE code = $1 AND used_by IS NULL", code
code )
) if not invite:
if not invite: return False
return False
await conn.execute( now_val = datetime.now()
"UPDATE invite_codes SET used_by = $1, used_at = NOW() WHERE code = $2", await self.execute(
user_id, code "UPDATE invite_codes SET used_by = $1, used_at = $2 WHERE code = $3",
) user_id, now_val, code
return invite['created_by'] )
return invite['created_by']
async def create_promo_code(self, code: str, discount: int, uses: int, created_by: int): async def create_promo_code(self, code: str, discount: int, uses: int, created_by: int):
async with self.pool.acquire() as conn: await self.execute(
await conn.execute( "INSERT INTO promo_codes (code, discount, uses_left, created_by) VALUES ($1, $2, $3, $4)",
"INSERT INTO promo_codes (code, discount, uses_left, created_by) VALUES ($1, $2, $3, $4)", code, discount, uses, created_by
code, discount, uses, created_by )
)
async def use_promo_code(self, code: str): async def use_promo_code(self, code: str):
async with self.pool.acquire() as conn: promo = await self.fetchrow(
promo = await conn.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:
if not promo: return None
return None await self.execute(
await conn.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']
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):
async with self.pool.acquire() as conn: await self.execute(
await conn.execute( "INSERT INTO payments (user_id, plan, amount, promo_code) VALUES ($1, $2, $3, $4)",
"INSERT INTO payments (user_id, plan, amount, promo_code) VALUES ($1, $2, $3, $4)", user_id, plan, amount, promo_code
user_id, plan, amount, promo_code )
)
async def get_all_users(self): async def get_all_users(self):
async with self.pool.acquire() as conn: return await self.fetch("SELECT user_id FROM users")
return await conn.fetch("SELECT user_id FROM users")
async def get_stats(self): async def get_stats(self):
async with self.pool.acquire() as conn: now_expr = "CURRENT_TIMESTAMP" if self.is_sqlite else "NOW()"
total = await conn.fetchval("SELECT COUNT(*) FROM users") total = await self.fetchval("SELECT COUNT(*) FROM users")
active = await conn.fetchval( active = await self.fetchval(
"SELECT COUNT(*) FROM users WHERE subscription_until > NOW()" f"SELECT COUNT(*) FROM users WHERE subscription_until > {now_expr}"
) )
revenue = await conn.fetchval("SELECT SUM(amount) FROM payments") revenue = await self.fetchval("SELECT SUM(amount) FROM payments")
return {"total": total, "active": active, "revenue": revenue or 0} return {"total": total, "active": active, "revenue": revenue or 0}
# Initialize # Initialize
bot = Bot(token=CONFIG["BOT_TOKEN"]) bot = Bot(token=CONFIG["BOT_TOKEN"])