Перенос конфигов в отдельный файл
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},
|
||||||
|
}
|
||||||
280
main.py
280
main.py
@@ -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"])
|
||||||
|
|||||||
Reference in New Issue
Block a user