Update main.py
Browse files
main.py
CHANGED
|
@@ -1,85 +1,125 @@
|
|
| 1 |
-
|
| 2 |
-
import
|
| 3 |
import json
|
| 4 |
-
import
|
| 5 |
-
from
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
-
|
|
|
|
|
|
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
out.append({
|
| 14 |
-
"name": c.name,
|
| 15 |
-
"value": c.value,
|
| 16 |
-
"domain": c.domain,
|
| 17 |
-
"path": c.path,
|
| 18 |
-
"secure": c.secure,
|
| 19 |
-
"expires": c.expires,
|
| 20 |
-
})
|
| 21 |
-
return out
|
| 22 |
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
PREFLIGHT_URL = f"{BASE}/" # ูุฌูุจ ู
ูู ุงูููููุฒ (ุฌูุณุฉ/ุชูููุถ CF)
|
| 27 |
-
TOKEN_URL = f"{BASE}/api/auth/token"
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
"CF-Access-Client-Id": "0491b36d7dcabce5b04f1a53f347bb4e.access",
|
| 32 |
-
"CF-Access-Client-Secret": "22152cb41b62393e159daaff7dce433006c3744c5850e6adc15fa3544bb5eb09",
|
| 33 |
-
# ูู ุงูู API ูุนูุงู ุจุชูุฑุฃ login/password/db ู
ู ุงูููุฏุฑุฒ ุฎูููููููู
ู
ูุฌูุฏูู:
|
| 34 |
-
"login": "binrushd.automation@gmail.com",
|
| 35 |
-
"password": "BR2025",
|
| 36 |
-
"db": "BR_EMR_16.0_202401",
|
| 37 |
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
|
|
|
| 49 |
try:
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
try:
|
| 62 |
-
|
| 63 |
except Exception:
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
"status_code": resp.status_code,
|
| 68 |
-
"reason": resp.reason,
|
| 69 |
-
"headers": dict(resp.headers),
|
| 70 |
-
"body_preview": body_preview
|
| 71 |
-
}
|
| 72 |
-
print("[TOKEN RESPONSE]", json.dumps(log_obj, ensure_ascii=False), file=sys.stderr)
|
| 73 |
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
content=resp.text,
|
| 78 |
-
status_code=resp.status_code,
|
| 79 |
-
media_type=content_type
|
| 80 |
-
)
|
| 81 |
|
| 82 |
-
|
| 83 |
-
err_txt = f"Network error: {type(e).__name__} - {str(e)}"
|
| 84 |
-
print("[TOKEN ERROR]", err_txt, file=sys.stderr)
|
| 85 |
-
return Response(content=err_txt, status_code=502, media_type="text/plain")
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
import os
|
| 3 |
import json
|
| 4 |
+
import requests
|
| 5 |
+
from fastapi import FastAPI, Request, HTTPException
|
| 6 |
+
from starlette.concurrency import run_in_threadpool
|
| 7 |
+
from playwright.sync_api import sync_playwright
|
| 8 |
+
from typing import Optional
|
| 9 |
|
| 10 |
+
# ------------ ุฅุนุฏุงุฏ ุงูู
ุชุบูุฑุงุช ุนุจุฑ ุงูุจูุฆุฉ ------------
|
| 11 |
+
ODOO_BASE = os.getenv("ODOO_BASE", "https://odoo.binrushd.care")
|
| 12 |
+
ODOO_API = f"{ODOO_BASE}/api/crm.lead"
|
| 13 |
|
| 14 |
+
CF_ID = os.getenv("CF_ACCESS_CLIENT_ID", "0491b36d7dcabce5b04f1a53f347bb4e.access")
|
| 15 |
+
CF_SECRET = os.getenv("CF_ACCESS_CLIENT_SECRET", "22152cb41b62393e159daaff7dce433006c3744c5850e6adc15fa3544bb5eb09")
|
| 16 |
+
ACCESS_TOKEN= os.getenv("ODOO_ACCESS_TOKEN", "") # ู
ู /api/auth/token
|
| 17 |
+
SESSION_ID = os.getenv("ODOO_SESSION_ID", "") # ู
ู /web/session/authenticate (ุงุฎุชูุงุฑู)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
USER_AGENT = os.getenv("USER_AGENT",
|
| 20 |
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
| 21 |
+
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
# ุถุจุท ููุช ุงูุงูุชุธุงุฑ ุนูุฏ ุทูุจ Odoo
|
| 24 |
+
REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "15"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
app = FastAPI(title="HF Proxy -> Odoo (with Cloudflare cookie bootstrap)")
|
| 27 |
+
|
| 28 |
+
# ---- ุฏุงูุฉ ู
ุฒูุฏุฉ: ุชุดุบูู Playwright ู
ุชุฒุงู
ููุง ูุชุฑุฌุน Cookie header ----
|
| 29 |
+
def _get_cloudflare_cookie_header_sync(base_url: str, wait_ms: int = 2000) -> str:
|
| 30 |
+
"""
|
| 31 |
+
ููุชุญ ุงูุตูุญุฉ ุงูุฑุฆูุณูุฉ ุจุงูุฏูู
ููุ ูุชุฑู ุงูู JS ูุชู
ุชูููุฐูุ ููุฌู
ุน ุงูููููุฒ ูู ุตูุบุฉ Cookie header.
|
| 32 |
+
ูุนุงุฏ string ู
ุซู: "cf_clearance=xxx; __cf_bm=yyy"
|
| 33 |
+
"""
|
| 34 |
+
with sync_playwright() as p:
|
| 35 |
+
browser = p.chromium.launch(headless=True)
|
| 36 |
+
context = browser.new_context(user_agent=USER_AGENT, locale="en-US")
|
| 37 |
+
page = context.new_page()
|
| 38 |
+
# ุงูุชุญ ุงูุตูุญุฉ ุงูุฑุฆูุณูุฉ (ุณุชูููุฐ ุฃู JS challenge ู
ุซู Cloudflare)
|
| 39 |
+
page.goto(base_url, timeout=30000)
|
| 40 |
+
# ุงูุชุธุฑ ms ุจุณูุทุฉ ูุชูู
ูู ุงูู JS (ูู
ูู ุฑูุนูุง ูู ูุงุฒู
)
|
| 41 |
+
page.wait_for_timeout(wait_ms)
|
| 42 |
+
cookies = context.cookies()
|
| 43 |
+
browser.close()
|
| 44 |
+
if not cookies:
|
| 45 |
+
return ""
|
| 46 |
+
return "; ".join(f"{c['name']}={c['value']}" for c in cookies)
|
| 47 |
|
| 48 |
+
# ---- endpoint ุฑุฆูุณู: ูุณุชูุจู ุฃู JSON payload ุซู
ูุฑุณูู ูู Odoo ุจุนุฏ ุงูุญุตูู ุนูู ุงูููููุฒ ----
|
| 49 |
+
@app.post("/forward")
|
| 50 |
+
async def forward_lead(request: Request):
|
| 51 |
+
"""
|
| 52 |
+
ุงุณุชูุจู ุฃู JSON body ู
ู ุงูุนู
ููุ ุดุบูู Playwright ููุญุตูู ุนูู ููููุฒ Cloudflareุ
|
| 53 |
+
ุซู
ุฃุฑุณู ููุณ ุงูู body ุงูู Odoo ู
ุน ุงูููุฏุฑุฒ ุงูู
ุทููุจุฉ.
|
| 54 |
+
"""
|
| 55 |
+
try:
|
| 56 |
+
payload = await request.json()
|
| 57 |
+
except Exception:
|
| 58 |
+
# ูู ุงูุฌุณู
ู
ุด JSON ููุฑุฃู ูุณุทุฑ ุฎุงู
|
| 59 |
+
body_text = await request.body()
|
| 60 |
+
try:
|
| 61 |
+
payload = json.loads(body_text.decode("utf-8") or "{}")
|
| 62 |
+
except Exception:
|
| 63 |
+
payload = {}
|
| 64 |
|
| 65 |
+
# 1) ุงุฌูุจ ููููุฒ Cloudflare (ุชุดุบูู Playwright ูู threadpool ุญุชู ูุง ูุนุฑูู event loop)
|
| 66 |
try:
|
| 67 |
+
cookie_header = await run_in_threadpool(_get_cloudflare_cookie_header_sync, ODOO_BASE, 2000)
|
| 68 |
+
except Exception as e:
|
| 69 |
+
# ูู Playwright ูุดูุ ูุณุชู
ุฑ ุจุฏูู ููููุฒ (ุฑุจู
ุง CF ูุง ูุทูุจ JS). ุฃุฑุฌุน ุฎุทุฃ ููู ูุง ุชูุทุน ุงูุชูููุฐ ููุงุฆููุง.
|
| 70 |
+
cookie_header = ""
|
| 71 |
+
app.logger = getattr(app, "logger", None)
|
| 72 |
+
# ูุง ูุฑู
ู ุงุณุชุซูุงุก ูุฃู ุฃุญูุงู ูุซูุฑุฉ ู
ู
ูู ููุฌุญ ุจุฏูู JS
|
| 73 |
+
# ูููู ููุณุฌู ุงูุณุจุจ ูู ุญุงุจุจ ุชูุญุต ุงูู logs
|
| 74 |
+
print("Playwright error (will continue without cookies):", str(e))
|
| 75 |
|
| 76 |
+
# 2) ุฌููุฒ ุงูููุฏุฑุฒ (ููุณ ุงูุชู ููุช ุชุณุชุฎุฏู
ูุง ู
ุญูููุง)
|
| 77 |
+
headers = {
|
| 78 |
+
"Accept": "application/json",
|
| 79 |
+
"Content-Type": "application/json",
|
| 80 |
+
"User-Agent": USER_AGENT,
|
| 81 |
+
"Accept-Encoding": "gzip, deflate",
|
| 82 |
+
"Connection": "keep-alive",
|
| 83 |
+
# Cloudflare Access service token headers (ูู ู
ุณุชุฎุฏู
ุฉ)
|
| 84 |
+
"CF-Access-Client-Id": CF_ID,
|
| 85 |
+
"CF-Access-Client-Secret": CF_SECRET,
|
| 86 |
+
# Odoo access token ูู ู
ุชููุฑ
|
| 87 |
+
}
|
| 88 |
+
if ACCESS_TOKEN:
|
| 89 |
+
headers["access-token"] = ACCESS_TOKEN
|
| 90 |
+
|
| 91 |
+
# ุฃุถู ููููุฒ ุงูุตูุญุฉ ุงูุชู ุญุตููุง ุนูููุง (ุฅู ููุฌุฏุช)
|
| 92 |
+
cookie_list = []
|
| 93 |
+
if cookie_header:
|
| 94 |
+
cookie_list.append(cookie_header)
|
| 95 |
+
if SESSION_ID:
|
| 96 |
+
cookie_list.append(f"session_id={SESSION_ID}")
|
| 97 |
+
if cookie_list:
|
| 98 |
+
headers["Cookie"] = "; ".join(cookie_list)
|
| 99 |
|
| 100 |
+
# 3) ูููุฐ ุทูุจ POST ุฅูู Odoo
|
| 101 |
+
try:
|
| 102 |
+
resp = requests.post(ODOO_API, headers=headers, json=payload, timeout=REQUEST_TIMEOUT, allow_redirects=False)
|
| 103 |
+
except requests.RequestException as e:
|
| 104 |
+
# ูู ูุดู ุงูุงุชุตุงู ุฎุงุฑุฌููุงุ ูุนูุฏ 502 ู
ุน ุฑุณุงูุฉ ุงูุฎุทุฃ
|
| 105 |
+
raise HTTPException(status_code=502, detail=f"Error connecting to Odoo: {str(e)}")
|
| 106 |
|
| 107 |
+
# 4) ุญุงูู ูุฑุฌุน ุงูุฑุฏ ูู
ุง ูู (JSON ุฃู ูุต)
|
| 108 |
+
status = resp.status_code
|
| 109 |
+
ctype = resp.headers.get("Content-Type", "")
|
| 110 |
+
text = resp.text
|
| 111 |
+
|
| 112 |
+
# ุฅุฐุง ูุงู JSON ูุนูุฏู ู
ููููุงุ ูุฅูุง ูุนูุฏ ุงููุต ุงูุฎุงู
|
| 113 |
+
if "application/json" in ctype:
|
| 114 |
try:
|
| 115 |
+
return resp.json()
|
| 116 |
except Exception:
|
| 117 |
+
return {"status": status, "body": text}
|
| 118 |
+
else:
|
| 119 |
+
return {"status": status, "body": text}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
+
@app.get("/health")
|
| 122 |
+
def health():
|
| 123 |
+
return {"ok": True, "note": "ready to bootstrap cookies and forward requests"}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
+
# ---------- ููุงูุฉ ุงูู
ูู ----------
|
|
|
|
|
|
|
|
|