Files
rdv/pyapi/main.py
2026-01-01 20:00:23 +02:00

343 lines
11 KiB
Python

import logging
import time
from typing import Dict, List, Optional
import cloudscraper
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse, Response
from pydantic import BaseModel
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger(__name__)
app = FastAPI()
logger.info("FastAPI Proxy Server initialized")
CONSTANTS = {
"SESSION_KEY_NAME": "SID",
"SESSION_EXPIRE_TIME_MS": 6 * 60 * 60 * 1000,
"POST_SESSION_KEY": "postsession",
"LAST_FETCHED_KEY": "LAST_FETCHED",
"SCRAP_API_URL": "https://gamebooking24.com/lottery-api",
"SCRAP_API_SESSION_KEY": "SRAJWT",
"SCRAP_API_BASE_HEADERS": {
"Host": "gamebooking24.com",
"Sec-Ch-Ua": '"Not/A)Brand";v="8", "Chromium";v="126"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Fetch-Site": "cross-site",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Dest": "image",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en-US,en;q=0.9",
"Access-Control-Allow-Origin": "*",
"Accept": "application/json, text/plain, */*",
"Origin": "https://gamebooking24.com",
"Referer": "https://gamebooking24.com/",
"Priority": "u=1, i",
},
}
# Create a single global cloudscraper instance to maintain session/cookies
# This solves the Cloudflare challenge once and reuses the session
scraper = cloudscraper.create_scraper()
scraper.headers.update(CONSTANTS["SCRAP_API_BASE_HEADERS"])
logger.info("Cloudscraper instance created")
# Middleware for logging all requests
@app.middleware("http")
async def log_requests(request: Request, call_next):
start_time = time.time()
# Log incoming request
logger.info(f"{request.method} {request.url.path}")
if request.query_params:
logger.debug(f" Query params: {dict(request.query_params)}")
# Process request
response = await call_next(request)
# Log response
duration = (time.time() - start_time) * 1000
logger.info(
f"{request.method} {request.url.path} [{response.status_code}] ({duration:.2f}ms)"
)
return response
def update_scraper_headers(
authorization: Optional[str] = None, extra_headers: Optional[Dict[str, str]] = None
) -> Dict[str, str]:
"""Build headers dict to update the global scraper with"""
headers = {}
if authorization:
headers["Authorization"] = authorization
if extra_headers:
headers.update(extra_headers)
return headers
# Pydantic models for request bodies
class LoginPayload(BaseModel):
userId: str
password: str
verifyToken: str
code: str
userType: int
class DealerListPayload(BaseModel):
page: int
pageSize: int
parentDistributor: int
class DistributorListPayload(BaseModel):
page: int
pageSize: int
parentDistributor: int
class BookListPayload(BaseModel):
userType: int
userIds: List[int]
drawId: int
startDate: str
endDate: str
beAdmin: bool
containImported: bool
keyword: str
class AddMultiplePayload(BaseModel):
dealerId: int
drawId: int
closeTime: str
date: str
changedBalance: int
insertData: str
class DeleteMultiplePayload(BaseModel):
bookIds: List[str]
closeTime: str
dealerId: int
drawId: int
@app.get("/ping")
def ping():
logger.info("Ping request received")
return {"status": "pong"}
@app.get("/v1/user/get-balance")
async def get_balance(userId: int, authorization: str):
logger.info(f"[GET /v1/user/get-balance] userId={userId}")
try:
headers = update_scraper_headers(authorization=authorization)
res = scraper.get(
f"{CONSTANTS['SCRAP_API_URL']}/v1/user/get-balance",
params={"userId": userId},
headers=headers,
)
logger.info(f"[GET /v1/user/get-balance] Response: {res.status_code}")
return JSONResponse(content=res.json(), status_code=res.status_code)
except Exception as e:
logger.error(f"[GET /v1/user/get-balance] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/v1/auth/login")
async def login(payload: LoginPayload):
logger.info(f"[POST /v1/auth/login] - payload={payload.model_dump()}")
try:
headers = update_scraper_headers(
extra_headers={"Content-Type": "application/json"}
)
res = scraper.post(
f"{CONSTANTS['SCRAP_API_URL']}/v1/auth/login",
json=payload.model_dump(),
headers=headers,
)
logger.info(f"[POST /v1/auth/login] Response: {res.status_code}")
return JSONResponse(content=res.json(), status_code=res.status_code)
except Exception as e:
logger.error(f"[POST /v1/auth/login] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/verify/image")
async def get_captcha(uuid: str):
logger.info(f"[GET /verify/image] uuid={uuid}")
try:
headers = update_scraper_headers(
extra_headers={
"Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
}
)
res = scraper.get(
f"{CONSTANTS['SCRAP_API_URL']}/verify/image",
params={"uuid": uuid},
headers=headers,
)
if res.status_code == 403:
logger.error(
"[GET /verify/image] 403 Forbidden - Cloudflare blocked the request"
)
logger.error(f"[GET /verify/image] Response headers: {dict(res.headers)}")
logger.error(f"[GET /verify/image] Response text: {res.text}")
logger.info(
f"[GET /verify/image] Response: {res.status_code}, size={len(res.content)} bytes"
)
return Response(
content=res.content, media_type="image/png", status_code=res.status_code
)
except Exception as e:
logger.error(f"[GET /verify/image] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/v1/user/dealer-list")
async def dealer_list(payload: DealerListPayload, authorization: str):
logger.info(
f"[POST /v1/user/dealer-list] parentDistributor={payload.parentDistributor}, page={payload.page}, pageSize={payload.pageSize}"
)
try:
headers = update_scraper_headers(
authorization=authorization,
extra_headers={"Content-Type": "application/json"},
)
res = scraper.post(
f"{CONSTANTS['SCRAP_API_URL']}/v1/user/dealer-list",
json=payload.dict(),
headers=headers,
)
logger.info(f"[POST /v1/user/dealer-list] Response: {res.status_code}")
return JSONResponse(content=res.json(), status_code=res.status_code)
except Exception as e:
logger.error(f"[POST /v1/user/dealer-list] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/v1/user/distributor-list")
async def distributor_list(payload: DistributorListPayload, authorization: str):
logger.info(
f"[POST /v1/user/distributor-list] parentDistributor={payload.parentDistributor}, page={payload.page}, pageSize={payload.pageSize}"
)
try:
headers = update_scraper_headers(
authorization=authorization,
extra_headers={"Content-Type": "application/json"},
)
res = scraper.post(
f"{CONSTANTS['SCRAP_API_URL']}/v1/user/distributor-list",
json=payload.dict(),
headers=headers,
)
logger.info(f"[POST /v1/user/distributor-list] Response: {res.status_code}")
return JSONResponse(content=res.json(), status_code=res.status_code)
except Exception as e:
logger.error(f"[POST /v1/user/distributor-list] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/v1/draw/list-my")
async def list_draws(userId: int, authorization: str):
logger.info(f"[GET /v1/draw/list-my] userId={userId}")
try:
headers = update_scraper_headers(
authorization=authorization,
extra_headers={"Content-Type": "application/json"},
)
res = scraper.get(
f"{CONSTANTS['SCRAP_API_URL']}/v1/draw/list-my",
params={"userId": userId},
headers=headers,
)
logger.info(f"[GET /v1/draw/list-my] Response: {res.status_code}")
return JSONResponse(content=res.json(), status_code=res.status_code)
except Exception as e:
logger.error(f"[GET /v1/draw/list-my] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/v1/book/list2")
async def book_list(payload: BookListPayload, authorization: str):
logger.info(
f"[POST /v1/book/list2] drawId={payload.drawId}, userIds={len(payload.userIds)}, date={payload.startDate}"
)
try:
headers = update_scraper_headers(
authorization=authorization,
extra_headers={"Content-Type": "application/json"},
)
res = scraper.post(
f"{CONSTANTS['SCRAP_API_URL']}/v1/book/list2",
json=payload.dict(),
headers=headers,
)
logger.info(f"[POST /v1/book/list2] Response: {res.status_code}")
return JSONResponse(content=res.json(), status_code=res.status_code)
except Exception as e:
logger.error(f"[POST /v1/book/list2] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/v1/book/add-multiple")
async def add_multiple(payload: AddMultiplePayload, authorization: str):
entries_count = len(payload.insertData.split(";")) if payload.insertData else 0
logger.info(
f"[POST /v1/book/add-multiple] dealerId={payload.dealerId}, drawId={payload.drawId}, entries={entries_count}, balance={payload.changedBalance}"
)
try:
headers = update_scraper_headers(
authorization=authorization,
extra_headers={"Content-Type": "application/json;charset=UTF-8"},
)
res = scraper.post(
f"{CONSTANTS['SCRAP_API_URL']}/v1/book/add-multiple",
json=payload.dict(),
headers=headers,
)
logger.info(f"[POST /v1/book/add-multiple] Response: {res.status_code}")
return JSONResponse(content=res.json(), status_code=res.status_code)
except Exception as e:
logger.error(f"[POST /v1/book/add-multiple] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/v1/book/delete-multiple")
async def delete_multiple(payload: DeleteMultiplePayload, authorization: str):
logger.info(
f"[POST /v1/book/delete-multiple] dealerId={payload.dealerId}, drawId={payload.drawId}, bookIds={len(payload.bookIds)}"
)
try:
headers = update_scraper_headers(
authorization=authorization,
extra_headers={"Content-Type": "application/json;charset=UTF-8"},
)
res = scraper.post(
f"{CONSTANTS['SCRAP_API_URL']}/v1/book/delete-multiple",
json=payload.dict(),
headers=headers,
)
logger.info(f"[POST /v1/book/delete-multiple] Response: {res.status_code}")
return JSONResponse(content=res.json(), status_code=res.status_code)
except Exception as e:
logger.error(f"[POST /v1/book/delete-multiple] Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))