372 lines
12 KiB
Python
372 lines
12 KiB
Python
import logging
|
|
import time
|
|
from typing import Dict, List, Optional
|
|
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
from fastapi.responses import JSONResponse, Response
|
|
from pydantic import BaseModel
|
|
from scrapling.fetchers import StealthySession
|
|
|
|
# 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",
|
|
},
|
|
}
|
|
|
|
# Global StealthySession instance - will be initialized on startup
|
|
stealthy_session: Optional[StealthySession] = None
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def startup_event():
|
|
"""Initialize the StealthySession when the app starts"""
|
|
global stealthy_session
|
|
logger.info("Initializing StealthySession...")
|
|
stealthy_session = StealthySession(
|
|
headless=True,
|
|
solve_cloudflare=True,
|
|
max_pages=10, # Allow up to 10 concurrent requests
|
|
google_search=False, # Skip Google search simulation for faster startup
|
|
)
|
|
logger.info("StealthySession initialized successfully")
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown_event():
|
|
"""Close the StealthySession when the app shuts down"""
|
|
global stealthy_session
|
|
if stealthy_session:
|
|
logger.info("Closing StealthySession...")
|
|
await stealthy_session.close()
|
|
logger.info("StealthySession closed successfully")
|
|
|
|
|
|
# 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 build_headers(
|
|
authorization: Optional[str] = None, extra_headers: Optional[Dict[str, str]] = None
|
|
) -> Dict[str, str]:
|
|
"""Build headers dict for requests"""
|
|
headers = CONSTANTS["SCRAP_API_BASE_HEADERS"].copy()
|
|
|
|
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 = build_headers(authorization=authorization)
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/v1/user/get-balance",
|
|
params={"userId": userId},
|
|
headers=headers,
|
|
)
|
|
logger.info(f"[GET /v1/user/get-balance] Response: {page.status}")
|
|
return JSONResponse(content=page.response.json(), status_code=page.status)
|
|
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 = build_headers(extra_headers={"Content-Type": "application/json"})
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/v1/auth/login",
|
|
method="POST",
|
|
data=payload.model_dump(),
|
|
headers=headers,
|
|
)
|
|
logger.info(f"[POST /v1/auth/login] Response: {page.status}")
|
|
return JSONResponse(content=page.response.json(), status_code=page.status)
|
|
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 = build_headers(
|
|
extra_headers={
|
|
"Accept": "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
|
|
}
|
|
)
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/verify/image",
|
|
params={"uuid": uuid},
|
|
headers=headers,
|
|
)
|
|
|
|
if page.status == 403:
|
|
logger.error(
|
|
"[GET /verify/image] 403 Forbidden - Cloudflare blocked the request"
|
|
)
|
|
logger.error(
|
|
f"[GET /verify/image] Response headers: {dict(page.response.headers)}"
|
|
)
|
|
logger.error(f"[GET /verify/image] Response text: {page.response.text}")
|
|
|
|
logger.info(
|
|
f"[GET /verify/image] Response: {page.status}, size={len(page.response.content)} bytes"
|
|
)
|
|
return Response(
|
|
content=page.response.content,
|
|
media_type="image/png",
|
|
status_code=page.status,
|
|
)
|
|
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 = build_headers(
|
|
authorization=authorization,
|
|
extra_headers={"Content-Type": "application/json"},
|
|
)
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/v1/user/dealer-list",
|
|
method="POST",
|
|
data=payload.model_dump(),
|
|
headers=headers,
|
|
)
|
|
logger.info(f"[POST /v1/user/dealer-list] Response: {page.status}")
|
|
return JSONResponse(content=page.response.json(), status_code=page.status)
|
|
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 = build_headers(
|
|
authorization=authorization,
|
|
extra_headers={"Content-Type": "application/json"},
|
|
)
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/v1/user/distributor-list",
|
|
method="POST",
|
|
data=payload.model_dump(),
|
|
headers=headers,
|
|
)
|
|
logger.info(f"[POST /v1/user/distributor-list] Response: {page.status}")
|
|
return JSONResponse(content=page.response.json(), status_code=page.status)
|
|
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 = build_headers(
|
|
authorization=authorization,
|
|
extra_headers={"Content-Type": "application/json"},
|
|
)
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/v1/draw/list-my",
|
|
params={"userId": userId},
|
|
headers=headers,
|
|
)
|
|
logger.info(f"[GET /v1/draw/list-my] Response: {page.status}")
|
|
return JSONResponse(content=page.response.json(), status_code=page.status)
|
|
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 = build_headers(
|
|
authorization=authorization,
|
|
extra_headers={"Content-Type": "application/json"},
|
|
)
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/v1/book/list2",
|
|
method="POST",
|
|
data=payload.model_dump(),
|
|
headers=headers,
|
|
)
|
|
logger.info(f"[POST /v1/book/list2] Response: {page.status}")
|
|
return JSONResponse(content=page.response.json(), status_code=page.status)
|
|
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 = build_headers(
|
|
authorization=authorization,
|
|
extra_headers={"Content-Type": "application/json;charset=UTF-8"},
|
|
)
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/v1/book/add-multiple",
|
|
method="POST",
|
|
data=payload.model_dump(),
|
|
headers=headers,
|
|
)
|
|
logger.info(f"[POST /v1/book/add-multiple] Response: {page.status}")
|
|
return JSONResponse(content=page.response.json(), status_code=page.status)
|
|
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 = build_headers(
|
|
authorization=authorization,
|
|
extra_headers={"Content-Type": "application/json;charset=UTF-8"},
|
|
)
|
|
page = stealthy_session.fetch(
|
|
f"{CONSTANTS['SCRAP_API_URL']}/v1/book/delete-multiple",
|
|
method="POST",
|
|
data=payload.model_dump(),
|
|
headers=headers,
|
|
)
|
|
logger.info(f"[POST /v1/book/delete-multiple] Response: {page.status}")
|
|
return JSONResponse(content=page.response.json(), status_code=page.status)
|
|
except Exception as e:
|
|
logger.error(f"[POST /v1/book/delete-multiple] Error: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|