Mr-Help commited on
Commit
c53df32
ยท
verified ยท
1 Parent(s): 4d429ea

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +102 -43
main.py CHANGED
@@ -5,39 +5,45 @@ 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", "access_token_242cc8a47d7a5a1f1102228867c1ad4724919f09") # ู…ู† /api/auth/token
17
- SESSION_ID = os.getenv("ODOO_SESSION_ID", "51a026396c3db05dc39fd4a0b7827e11924148eb") # ู…ู† /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()
@@ -45,81 +51,134 @@ def _get_cloudflare_cookie_header_sync(base_url: str, wait_ms: int = 2000) -> st
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
- # ---------- ู†ู‡ุงูŠุฉ ุงู„ู…ู„ู ----------
 
5
  from fastapi import FastAPI, Request, HTTPException
6
  from starlette.concurrency import run_in_threadpool
7
  from playwright.sync_api import sync_playwright
 
8
 
9
+ # ------------ ุฅุนุฏุงุฏุงุช ุซุงุจุชุฉ/ุจูŠุฆุฉ ------------
10
  ODOO_BASE = os.getenv("ODOO_BASE", "https://odoo.binrushd.care")
11
  ODOO_API = f"{ODOO_BASE}/api/crm.lead"
12
+ AUTH_URL = f"{ODOO_BASE}/api/auth/token"
13
+ SESSION_AUTH_URL = f"{ODOO_BASE}/web/session/authenticate"
14
 
15
  CF_ID = os.getenv("CF_ACCESS_CLIENT_ID", "0491b36d7dcabce5b04f1a53f347bb4e.access")
16
  CF_SECRET = os.getenv("CF_ACCESS_CLIENT_SECRET", "22152cb41b62393e159daaff7dce433006c3744c5850e6adc15fa3544bb5eb09")
17
+
18
+ LOGIN = os.getenv("ODOO_LOGIN", "binrushd.automation@gmail.com")
19
+ PASSWORD = os.getenv("ODOO_PASSWORD", "BR2025")
20
+ DB_NAME = os.getenv("ODOO_DB", "BR_EMR_16.0_202401")
21
 
22
  USER_AGENT = os.getenv("USER_AGENT",
23
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
24
  "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
25
 
 
26
  REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "15"))
27
 
28
  app = FastAPI(title="HF Proxy -> Odoo (with Cloudflare cookie bootstrap)")
29
 
30
+ # ---------- Utilities ----------
31
+
32
+ def _mask(s: str, keep: int = 6) -> str:
33
+ """ุฃุธู‡ุฑ ุฃูˆู„/ุขุฎุฑ ุฃุญุฑู ู‚ู„ูŠู„ุฉ ู„ู„ู‘ูˆุฌ ูู‚ุท"""
34
+ if not s: return ""
35
+ if len(s) <= keep*2: return s
36
+ return f"{s[:keep]}โ€ฆ{s[-keep:]}"
37
+
38
  def _get_cloudflare_cookie_header_sync(base_url: str, wait_ms: int = 2000) -> str:
39
  """
40
+ ูŠูุชุญ ุงู„ุตูุญุฉ ุงู„ุฑุฆูŠุณูŠุฉ ุจุงู„ุฏูˆู…ูŠู†ุŒ ูŠู†ูุฐ JS (Cloudflare challenge) ูˆูŠุฌู…ุน ุงู„ูƒูˆูƒูŠุฒ ูƒู€ Cookie header.
 
41
  """
42
  with sync_playwright() as p:
43
  browser = p.chromium.launch(headless=True)
44
  context = browser.new_context(user_agent=USER_AGENT, locale="en-US")
45
  page = context.new_page()
 
46
  page.goto(base_url, timeout=30000)
 
47
  page.wait_for_timeout(wait_ms)
48
  cookies = context.cookies()
49
  browser.close()
 
51
  return ""
52
  return "; ".join(f"{c['name']}={c['value']}" for c in cookies)
53
 
54
+ def _get_token_and_session() -> tuple[str, str]:
55
+ """
56
+ ูŠุณุชุฎุฑุฌ access_token + session_id ููŠ ูƒู„ ู…ุฑุฉ:
57
+ 1) GET /api/auth/token ู…ุน CF-Access + (login/password/db) ูƒู‡ูŠุฏุฑุฒ
58
+ 2) ู„ูˆ session_id ุบูŠุฑ ู…ูˆุฌูˆุฏ: POST /web/session/authenticate (JSON-RPC)
59
+ """
60
+ s = requests.Session()
61
+ s.headers.update({
62
+ "Accept": "*/*",
63
+ "Content-Type": "application/json",
64
+ "Accept-Encoding": "gzip, deflate", # ุจุฏูˆู† br
65
+ "Connection": "keep-alive",
66
+ "CF-Access-Client-Id": CF_ID,
67
+ "CF-Access-Client-Secret": CF_SECRET,
68
+ })
69
+
70
+ # (1) token
71
+ auth_headers = {
72
+ "CF-Access-Client-Id": CF_ID,
73
+ "CF-Access-Client-Secret": CF_SECRET,
74
+ "login": LOGIN,
75
+ "password": PASSWORD,
76
+ "db": DB_NAME,
77
+ }
78
+ r = s.get(AUTH_URL, headers=auth_headers, timeout=(5, 30))
79
+ r.raise_for_status()
80
+
81
+ # Cloudflare ุบุงู„ุจู‹ุง ูŠุฑุฌู‘ุน JSONุ› requests ูŠููƒ gzip ุชู„ู‚ุงุฆูŠู‹ุง
82
+ data = r.json()
83
+ token = data.get("access_token") or (data.get("data") or {}).get("access_token")
84
+ if not token:
85
+ raise RuntimeError(f"No access_token in response: {data}")
86
+
87
+ # (2) session_id
88
+ session_id = s.cookies.get("session_id") or r.cookies.get("session_id")
89
+ if not session_id:
90
+ payload = {
91
+ "jsonrpc": "2.0",
92
+ "params": {"db": DB_NAME, "login": LOGIN, "password": PASSWORD}
93
+ }
94
+ r2 = s.post(SESSION_AUTH_URL, json=payload, timeout=(5, 30))
95
+ r2.raise_for_status()
96
+ session_id = s.cookies.get("session_id") or r2.cookies.get("session_id")
97
+ if not session_id:
98
+ raise RuntimeError("Could not obtain session_id cookie.")
99
+
100
+ # ุงุทุจุน ู„ู„ู‘ูˆุฌ (ู…ุฎูู‘ุถ)
101
+ print(f"[AUTH] token={_mask(token)} session_id={_mask(session_id)}")
102
+ return token, session_id
103
+
104
+ # ---------- Endpoints ----------
105
+
106
+ @app.get("/health")
107
+ def health():
108
+ return {"ok": True, "note": "ready to bootstrap cookies and forward requests"}
109
+
110
+ @app.get("/ping")
111
+ def ping():
112
+ return {"pong": True}
113
+
114
  @app.post("/forward")
115
  async def forward_lead(request: Request):
116
  """
117
+ - ูŠุณุชู‚ุจู„ JSON payload
118
+ - ูŠุณุชุฎุฑุฌ access_token + session_id ููŠ ู†ูุณ ุงู„ู„ุญุธุฉ ูˆูŠุทุจุนู‡ู…ุง ููŠ ุงู„ู„ูˆุฌ
119
+ - ูŠุฃุฎุฐ ูƒูˆูƒูŠุฒ Cloudflare ุจู€ Playwright
120
+ - ูŠุฑุณู„ ุงู„ุทู„ุจ ุฅู„ู‰ Odoo ู…ุน ูƒู„ ุงู„ู‡ูŠุฏุฑุฒ/ุงู„ูƒูˆูƒูŠุฒ ุงู„ู…ุทู„ูˆุจุฉ
121
  """
122
+ # 0) payload
123
  try:
124
  payload = await request.json()
125
  except Exception:
 
126
  body_text = await request.body()
127
  try:
128
  payload = json.loads(body_text.decode("utf-8") or "{}")
129
  except Exception:
130
  payload = {}
131
+ # Debug (ุงุฎุชูŠุงุฑูŠ)
132
+ print(f"[FORWARD] incoming payload keys={list(payload.keys())}")
133
+
134
+ # 1) token + session_id
135
+ try:
136
+ access_token, session_id = await run_in_threadpool(_get_token_and_session)
137
+ except Exception as e:
138
+ raise HTTPException(status_code=502, detail=f"Auth failure: {str(e)}")
139
 
140
+ # 2) Cloudflare cookies via Playwright
141
  try:
142
  cookie_header = await run_in_threadpool(_get_cloudflare_cookie_header_sync, ODOO_BASE, 2000)
143
  except Exception as e:
 
144
  cookie_header = ""
145
+ print("Playwright error (continue without cookies):", str(e))
 
 
 
146
 
147
+ # 3) headers
148
  headers = {
149
  "Accept": "application/json",
150
  "Content-Type": "application/json",
151
  "User-Agent": USER_AGENT,
152
  "Accept-Encoding": "gzip, deflate",
153
  "Connection": "keep-alive",
 
154
  "CF-Access-Client-Id": CF_ID,
155
  "CF-Access-Client-Secret": CF_SECRET,
156
+ "access-token": access_token,
157
  }
 
 
 
 
158
  cookie_list = []
159
  if cookie_header:
160
  cookie_list.append(cookie_header)
161
+ if session_id:
162
+ cookie_list.append(f"session_id={session_id}")
163
  if cookie_list:
164
  headers["Cookie"] = "; ".join(cookie_list)
165
 
166
+ # 4) POST -> Odoo
167
  try:
168
+ resp = requests.post(ODOO_API, headers=headers, json=payload,
169
+ timeout=REQUEST_TIMEOUT, allow_redirects=False)
170
  except requests.RequestException as e:
 
171
  raise HTTPException(status_code=502, detail=f"Error connecting to Odoo: {str(e)}")
172
 
 
173
  status = resp.status_code
174
  ctype = resp.headers.get("Content-Type", "")
175
+ # ุงุทุจุน ู…ู„ุฎุต
176
+ print(f"[FORWARD] Odoo status={status} content-type={ctype}")
177
 
 
178
  if "application/json" in ctype:
179
  try:
180
  return resp.json()
181
  except Exception:
182
+ return {"status": status, "body": resp.text}
183
  else:
184
+ return {"status": status, "body": resp.text}