Files
marzban_tg_bot/server.py
2026-01-09 22:21:26 +03:00

167 lines
5.1 KiB
Python

from fastapi import FastAPI, HTTPException, Request
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import uvicorn
from datetime import datetime
import logging
import json
from database import db
from config import PLANS, CONFIG
from marzban import marzban
# Setup logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("server")
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
@app.on_event("startup")
async def startup():
await db.connect()
logger.info("Database connected")
@app.get("/api/plans")
async def get_plans():
plans_list = []
for pid, p in PLANS.items():
plans_list.append({
"id": pid,
**p
})
return plans_list
from aiogram.types import LabeledPrice
from pydantic import BaseModel
class BuyPlanRequest(BaseModel):
user_id: int
plan_id: str
promo_code: str = None
class PromoCheckRequest(BaseModel):
code: str
@app.post("/api/check-promo")
async def check_promo_code(req: PromoCheckRequest):
promo = await db.get_promo_code(req.code)
if not promo:
return JSONResponse(status_code=404, content={"error": "Invalid or expired promo code"})
return {
"code": promo["code"],
"discount": promo["discount"],
"bonus_days": promo["bonus_days"],
"is_unlimited": promo["is_unlimited"],
"description": f"Discount {promo['discount']}%" + (f" + {promo['bonus_days']} Days" if promo['bonus_days'] else "")
}
@app.post("/api/create-invoice")
async def create_invoice(req: BuyPlanRequest, request: Request):
bot = getattr(request.app.state, "bot", None)
if not bot:
return JSONResponse(status_code=500, content={"error": "Bot instance not initialized"})
plan = PLANS.get(req.plan_id)
if not plan:
return JSONResponse(status_code=404, content={"error": "Plan not found"})
price = plan['price']
# Validating Promo
discount = 0
if req.promo_code:
promo = await db.get_promo_code(req.promo_code)
if promo:
discount = promo['discount']
final_price = int(price * (100 - discount) / 100)
if final_price < 1: final_price = 1
try:
invoice_link = await bot.create_invoice_link(
title=f"Sub: {plan['name']}",
description=f"{plan['data_limit']}GB / {plan['days']} days",
payload=f"{req.plan_id}:{req.promo_code or ''}",
provider_token="", # Empty for Stars
currency="XTR",
prices=[LabeledPrice(label=plan['name'], amount=final_price)]
)
return {"invoice_link": invoice_link}
except Exception as e:
logger.error(f"Error generating invoice: {e}")
return JSONResponse(status_code=500, content={"error": str(e)})
@app.get("/api/user/{user_id}")
async def get_user_stats(user_id: int):
user = await db.get_user(user_id)
if not user:
return JSONResponse(status_code=404, content={"error": "User not found"})
sub_until = user['subscription_until']
days_left = 0
status = "Inactive"
expire_str = "No active subscription"
if sub_until:
if isinstance(sub_until, str):
try:
sub_until = datetime.fromisoformat(sub_until)
except:
pass
if isinstance(sub_until, datetime):
expire_str = sub_until.strftime("%d.%m.%Y")
if sub_until > datetime.now():
delta = sub_until - datetime.now()
days_left = delta.days
status = "Active"
else:
status = "Expired"
# Fetch detailed stats from Marzban
sub_url = ""
used_traffic = 0
if user['marzban_username']:
try:
m_user = await marzban.get_user(user['marzban_username'])
# Check for error in response
if isinstance(m_user, dict) and not m_user.get('detail'):
used_traffic = m_user.get('used_traffic', 0)
sub_url = m_user.get('subscription_url', "")
# Fix relative URL
if sub_url and sub_url.startswith('/'):
base = CONFIG.get('BASE_URL') or CONFIG['MARZBAN_URL']
sub_url = f"{base.rstrip('/')}{sub_url}"
except Exception as e:
logger.error(f"Marzban fetch error: {e}")
return {
"status": status,
"days_left": days_left,
"expire_date": expire_str,
"data_limit_gb": user['data_limit'] or 0,
"used_traffic_gb": round(used_traffic / (1024**3), 2),
"plan": "Premium",
"subscription_url": sub_url,
"username": user['username'],
"marzban_username": user['marzban_username']
}
# Serve Static Files (must be last)
app.mount("/", StaticFiles(directory="web_app/static", html=True), name="static")
if __name__ == "__main__":
from config import CONFIG
uvicorn.run(app, host="0.0.0.0", port=CONFIG["WEB_APP_PORT"])