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}