Перенос конфигов в отдельный файл
This commit is contained in:
8
.env
Normal file
8
.env
Normal 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
7
.env.example
Normal 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
20
config.py
Normal 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},
|
||||
}
|
||||
278
main.py
278
main.py
@@ -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)
|
||||
user = await self.get_user(user_id)
|
||||
|
||||
await conn.execute(
|
||||
"UPDATE users SET subscription_until = $1, data_limit = $2 WHERE user_id = $3",
|
||||
new_date, data_limit, 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"])
|
||||
|
||||
Reference in New Issue
Block a user