| |
| import os |
| import json |
| import requests |
| from fastapi import FastAPI, Request, HTTPException |
| from starlette.concurrency import run_in_threadpool |
| from playwright.sync_api import sync_playwright |
| from typing import Optional |
|
|
| |
| ODOO_BASE = os.getenv("ODOO_BASE", "https://odoo.binrushd.care") |
| ODOO_API = f"{ODOO_BASE}/api/crm.lead" |
|
|
| CF_ID = os.getenv("CF_ACCESS_CLIENT_ID", "0491b36d7dcabce5b04f1a53f347bb4e.access") |
| CF_SECRET = os.getenv("CF_ACCESS_CLIENT_SECRET", "22152cb41b62393e159daaff7dce433006c3744c5850e6adc15fa3544bb5eb09") |
| ACCESS_TOKEN= os.getenv("ODOO_ACCESS_TOKEN", "") |
| SESSION_ID = os.getenv("ODOO_SESSION_ID", "") |
|
|
| USER_AGENT = os.getenv("USER_AGENT", |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " |
| "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") |
|
|
| |
| REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "15")) |
|
|
| app = FastAPI(title="HF Proxy -> Odoo (with Cloudflare cookie bootstrap)") |
|
|
| |
| def _get_cloudflare_cookie_header_sync(base_url: str, wait_ms: int = 2000) -> str: |
| """ |
| يفتح الصفحة الرئيسية بالدومين، يترك الـ JS يتم تنفيذه، ويجمع الكوكيز في صيغة Cookie header. |
| يعاد string مثل: "cf_clearance=xxx; __cf_bm=yyy" |
| """ |
| with sync_playwright() as p: |
| browser = p.chromium.launch(headless=True) |
| context = browser.new_context(user_agent=USER_AGENT, locale="en-US") |
| page = context.new_page() |
| |
| page.goto(base_url, timeout=30000) |
| |
| page.wait_for_timeout(wait_ms) |
| cookies = context.cookies() |
| browser.close() |
| if not cookies: |
| return "" |
| return "; ".join(f"{c['name']}={c['value']}" for c in cookies) |
|
|
| |
| @app.post("/forward") |
| async def forward_lead(request: Request): |
| """ |
| استقبل أي JSON body من العميل، شغّل Playwright للحصول على كوكيز Cloudflare، |
| ثم أرسل نفس الـ body الى Odoo مع الهيدرز المطلوبة. |
| """ |
| try: |
| payload = await request.json() |
| except Exception: |
| |
| body_text = await request.body() |
| try: |
| payload = json.loads(body_text.decode("utf-8") or "{}") |
| except Exception: |
| payload = {} |
|
|
| |
| try: |
| cookie_header = await run_in_threadpool(_get_cloudflare_cookie_header_sync, ODOO_BASE, 2000) |
| except Exception as e: |
| |
| cookie_header = "" |
| app.logger = getattr(app, "logger", None) |
| |
| |
| print("Playwright error (will continue without cookies):", str(e)) |
|
|
| |
| headers = { |
| "Accept": "application/json", |
| "Content-Type": "application/json", |
| "User-Agent": USER_AGENT, |
| "Accept-Encoding": "gzip, deflate", |
| "Connection": "keep-alive", |
| |
| "CF-Access-Client-Id": CF_ID, |
| "CF-Access-Client-Secret": CF_SECRET, |
| |
| } |
| if ACCESS_TOKEN: |
| headers["access-token"] = ACCESS_TOKEN |
|
|
| |
| cookie_list = [] |
| if cookie_header: |
| cookie_list.append(cookie_header) |
| if SESSION_ID: |
| cookie_list.append(f"session_id={SESSION_ID}") |
| if cookie_list: |
| headers["Cookie"] = "; ".join(cookie_list) |
|
|
| |
| try: |
| resp = requests.post(ODOO_API, headers=headers, json=payload, timeout=REQUEST_TIMEOUT, allow_redirects=False) |
| except requests.RequestException as e: |
| |
| raise HTTPException(status_code=502, detail=f"Error connecting to Odoo: {str(e)}") |
|
|
| |
| status = resp.status_code |
| ctype = resp.headers.get("Content-Type", "") |
| text = resp.text |
|
|
| |
| if "application/json" in ctype: |
| try: |
| return resp.json() |
| except Exception: |
| return {"status": status, "body": text} |
| else: |
| return {"status": status, "body": text} |
|
|
| @app.get("/health") |
| def health(): |
| return {"ok": True, "note": "ready to bootstrap cookies and forward requests"} |
|
|
| |
|
|