# main.py from fastapi import FastAPI, Request, UploadFile from typing import Any, Dict, Tuple from datetime import datetime import json, os, asyncio import httpx # pip install httpx app = FastAPI() GAS_WEBAPP_URL = os.getenv("GAS_WEBAPP_URL") def print_human_friendly(payload: Dict[str, Any]) -> None: order = [ ("client","๐Ÿ‘ค Client"),("source","๐ŸŒ Source"),("event_name","๐ŸŽฏ Event Name"), ("event_id","๐Ÿงพ Event ID"),("value","๐Ÿ’ฐ Value"),("currency","๐Ÿ’ฑ Currency"), ("transaction_id","๐Ÿ’ณ Transaction ID"),("content_id","๐Ÿ†” Content ID"), ("content_type","๐Ÿ“ฆ Content Type"),("fbp","๐Ÿ…ต fbp"),("fbc","๐Ÿ…ต fbc"), ("ttclid","๐Ÿ•ธ๏ธ ttclid"),("ttp","๐Ÿช ttp"),("scid","๐ŸŸช scid"), ("x_email","๐Ÿ“ง x_email (hashed)"),("x_phone","๐Ÿ“ฑ x_phone (hashed)"), ("x_fn","๐Ÿง‘ First Name"),("x_ln","๐Ÿง‘ Last Name"), ("timestamp","โฑ๏ธ Timestamp"),("client_ua","๐Ÿ–ฅ๏ธ Client UA"), ("user_info","๐Ÿ‘ค User Info"), ("cart_data","๐Ÿ›’ Cart Data"), ] print("\n" + "-"*18 + " ๐Ÿ‘€ Human-friendly view " + "-"*18) label_pad = 20 for key, label in order: if isinstance(payload, dict) and key in payload and payload.get(key) not in (None, ""): print(f"{label:<{label_pad}}: {payload.get(key)}") contents = payload.get("contents") if isinstance(payload, dict) else None if isinstance(contents, list): print("\n๐Ÿงบ Contents:") for idx, item in enumerate(contents, start=1): if isinstance(item, dict): cid = item.get("content_id","-") name = item.get("content_name","-") price = item.get("price","-") qty = item.get("quantity","-") print(f" #{idx} โ€ข ID: {cid} | Name: {name} | Price: {price} | Qty: {qty}") else: print(f" #{idx} โ€ข {item}") elif contents is not None: print("\n๐Ÿงบ Contents (raw):") try: parsed = contents if isinstance(contents,(dict,list)) else json.loads(str(contents)) print(json.dumps(parsed, ensure_ascii=False, indent=2)) except Exception: print(str(contents)) # ๐Ÿ‘ค User Info ui = payload.get("user_info") if isinstance(payload, dict) else None if ui is not None: print("\n๐Ÿ‘ค User Info:") try: print(json.dumps(ui, ensure_ascii=False, indent=2)) except Exception: print(str(ui)) # ๐Ÿ›’ Cart Data cd = payload.get("cart_data") if isinstance(payload, dict) else None if cd is not None: print("\n๐Ÿ›’ Cart Data:") try: print(json.dumps(cd, ensure_ascii=False, indent=2)) except Exception: print(str(cd)) print("-"*60 + "\n") async def post_to_gas_raw(raw_body: str, content_type: str) -> Tuple[int, str]: if not GAS_WEBAPP_URL or GAS_WEBAPP_URL.startswith("YOUR_"): return (0, "GAS_WEBAPP_URL is not configured") headers = {"Content-Type": content_type or "application/json"} timeout = httpx.Timeout(10.0, connect=5.0) async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: for attempt in range(1, 3): try: r = await client.post(GAS_WEBAPP_URL, content=raw_body.encode("utf-8"), headers=headers) # ู„ูˆ ูƒุงู† 302/303 ูˆุงุชุจุนุช ุงู„ุชุญูˆูŠู„ุŒ ู‡ูŠุจู‚ู‰ status 200 ููŠ ุงู„ุขุฎุฑ # ุจุณ ู„ูˆ ู„ุณู‡ 302 ู„ุฃูŠ ุณุจุจุŒ ุงุนุชุจุฑู‡ ู†ุฌุงุญ ูˆุณุฌู‘ู„ Location if r.status_code in (301, 302, 303, 307, 308): loc = r.headers.get("Location", "") return (r.status_code, f"Redirected to: {loc}") return (r.status_code, r.text[:5000]) except Exception as e: if attempt == 2: return (0, f"Error posting to GAS: {e}") await asyncio.sleep(0.5) @app.get("/ping") async def ping(): return {"message": "pong"} @app.post("/echo") async def echo(request: Request) -> Dict[str, Any]: body_bytes = await request.body() now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") print("\n" + "="*30 + f" ๐Ÿ†• NEW REQUEST ({now}) " + "="*30) # ุงุทุจุน ุงู„ุจูˆุฏูŠ ุงู„ุฎุงู… ูƒู…ุง ู‡ูˆ raw_text = body_bytes.decode("utf-8", errors="ignore") # print("\n๐Ÿ“ Request body:") # print(raw_text) # print("="*98) # ุญุงูˆู„ ู†ุทุจุนู‡ ุจุดูƒู„ ูˆุฏู‘ูŠ ูู‚ุท ู„ูˆ JSON object (ู„ู„ุนุฑุถ ูู‚ุทุŒ ุจุฏูˆู† ุชุนุฏูŠู„ ุงู„ุจูŠุงู†ุงุช) parsed: Any try: parsed = await request.json() except Exception: try: parsed = json.loads(raw_text) except Exception: parsed = None if isinstance(parsed, dict): print_human_friendly(parsed) else: print("\n(โ„น๏ธ Human-friendly view skipped: payload is not a JSON object)\n") # ๐Ÿš€ ุงุจุนุช ู„ู„ู€ GAS ูƒู…ุง ู‡ูˆ (RAW) ุจู†ูุณ content-type ุงู„ู„ูŠ ุฌู‡ ct = request.headers.get("content-type", "application/json") gas_status, gas_body = await post_to_gas_raw(raw_text, ct) print(f"๐Ÿ“ค GAS post status: {gas_status}") # if gas_body: # print(f"๐Ÿ”Ž GAS response: {gas_body[:1000]}") return { "received": parsed if isinstance(parsed, dict) else raw_text, "forwarded_to_gas": True, "gas_status": gas_status, "info": { "method": request.method, "url": str(request.url), "headers": dict(request.headers), }, }