Mr-Help commited on
Commit
2393dc1
·
verified ·
1 Parent(s): f39d106

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +249 -12
main.py CHANGED
@@ -1,23 +1,255 @@
 
 
 
1
  import os
 
 
2
  import requests
3
- from fastapi import FastAPI, Request
4
  from datetime import datetime, timezone, timedelta
 
 
5
 
6
  app = FastAPI()
7
 
8
- # Load Supabase environment variables
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  SUPABASE_URL = os.getenv("SUPABASE_URL")
10
  SUPABASE_API_KEY = os.getenv("SUPABASE_API_KEY")
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  @app.post("/webhook")
13
  async def receive_webhook(request: Request):
14
  payload = await request.json()
15
-
16
  print("📥 Webhook received:")
17
 
18
  gmt_plus_3 = timezone(timedelta(hours=3))
19
 
20
- # Top-level fields
21
  print(f"Request ID: {payload.get('request_id')}")
22
  print(f"Object: {payload.get('object')}")
23
  print(f"Time (Unix): {payload.get('time')}")
@@ -28,7 +260,6 @@ async def receive_webhook(request: Request):
28
  print("Time (Formatted): Invalid timestamp")
29
  print("----")
30
 
31
- # Loop through entries
32
  for entry in payload.get('entry', []):
33
  lead_id = entry.get('id')
34
  campaign_id = entry.get('campaign_id')
@@ -54,17 +285,17 @@ async def receive_webhook(request: Request):
54
  formatted_create_time = None
55
  print("Create Time: Invalid")
56
 
57
- # Extract lead fields
58
- field_map = {}
59
  for change in entry.get('changes', []):
60
- field = change.get('field').lower()
61
  value = change.get('value')
62
  field_map[field] = value
63
  print(f"{field}: {value}")
64
 
65
  print("----")
66
 
67
- # Prepare data for Supabase insertion
68
  insert_data = {
69
  "platform": "tiktok",
70
  "campaign_id": campaign_id,
@@ -87,11 +318,10 @@ async def receive_webhook(request: Request):
87
  }
88
 
89
  supa_resp = requests.post(
90
- f"{SUPABASE_URL}/rest/v1/binrushdleads",
91
  headers=headers,
92
  json=insert_data
93
  )
94
-
95
  print("💾 Supabase insert status:", supa_resp.status_code)
96
  if supa_resp.status_code == 201:
97
  print("✅ Lead inserted successfully.")
@@ -101,4 +331,11 @@ async def receive_webhook(request: Request):
101
  except Exception:
102
  print("❌ Supabase Response:", supa_resp.text)
103
 
104
- return {"status": "ok"}
 
 
 
 
 
 
 
 
1
+ # file: app.py
2
+ # pip install fastapi uvicorn requests supabase==2.5.1 brotli
3
+
4
  import os
5
+ import time
6
+ import json
7
  import requests
8
+ from typing import Tuple, Optional, Dict, Any
9
  from datetime import datetime, timezone, timedelta
10
+ from fastapi import FastAPI, Request
11
+ from supabase import create_client, Client
12
 
13
  app = FastAPI()
14
 
15
+ # ==========[ ODOO / Cloudflare – ثوابت للاختبار (زي ما اتفقنا) ]==========
16
+ BASE = "https://odoo-demo.binrushd.care"
17
+
18
+ CF_ID = "0491b36d7dcabce5b04f1a53f347bb4e.access"
19
+ CF_SECRET = "22152cb41b62393e159daaff7dce433006c3744c5850e6adc15fa3544bb5eb09"
20
+
21
+ LOGIN = "binrushd.automation@gmail.com"
22
+ PASSWORD = "BR2025"
23
+ DB = "Live_August_25"
24
+
25
+ # Defaults للّيد في Odoo (عدّل الارقام حسب بيئتك)
26
+ ODOO_DEFAULT_USER_ID = 2
27
+ ODOO_DEFAULT_STAGE_ID = 1
28
+ # لو عندك مصدر "TikTok" في Odoo وحابب تربطه ID مباشر، حطّ الـ ID هنا
29
+ ODOO_SOURCE_ID_TIKTOK = None # مثال: 4
30
+
31
+ # ==========[ Supabase – من env زي كودك الأصلي ]==========
32
  SUPABASE_URL = os.getenv("SUPABASE_URL")
33
  SUPABASE_API_KEY = os.getenv("SUPABASE_API_KEY")
34
 
35
+ # جدول الليدز اللي انت بتكتب فيه من TikTok
36
+ SUPABASE_LEADS_TABLE = "binrushdleads"
37
+
38
+ # جدول حفظ التوكن والسيشن
39
+ TOKENS_TABLE = "api_tokens"
40
+ TOKEN_ROW_ID = "odoo_demo"
41
+
42
+ # =========[ Supabase Client ]=========
43
+ supabase: Client = create_client(SUPABASE_URL, SUPABASE_API_KEY)
44
+
45
+ # =========[ Helpers (مش بنغيّر فيها) ]=========
46
+ def _std_headers() -> Dict[str, str]:
47
+ return {
48
+ "User-Agent": "PostmanRuntime/7.48.0",
49
+ "Accept": "application/json",
50
+ "Accept-Encoding": "gzip, deflate",
51
+ "Connection": "keep-alive",
52
+ }
53
+
54
+ def _api_headers() -> Dict[str, str]:
55
+ return {
56
+ "CF-Access-Client-Id": CF_ID,
57
+ "CF-Access-Client-Secret": CF_SECRET,
58
+ }
59
+
60
+ def _save_tokens_to_supabase(access_token: str, session_id: Optional[str]) -> None:
61
+ payload = {
62
+ "id": TOKEN_ROW_ID,
63
+ "access_token": access_token,
64
+ "session_id": session_id or "",
65
+ "updated_at": int(time.time()),
66
+ }
67
+ try:
68
+ res = supabase.table(TOKENS_TABLE).upsert(payload, on_conflict="id").execute()
69
+ print("📝 upsert attempted:", getattr(res, "data", None))
70
+ except Exception as e:
71
+ print("❌ upsert failed:", e)
72
+
73
+ # اقرأه تاني فورًا للتأكد
74
+ try:
75
+ check = (
76
+ supabase.table(TOKENS_TABLE)
77
+ .select("access_token, session_id, updated_at")
78
+ .eq("id", TOKEN_ROW_ID)
79
+ .execute()
80
+ )
81
+ print("🔎 readback:", getattr(check, "data", None))
82
+ except Exception as e:
83
+ print("⚠️ readback failed:", e)
84
+
85
+ def _load_tokens_from_supabase() -> Tuple[Optional[str], Optional[str]]:
86
+ try:
87
+ res = (
88
+ supabase.table(TOKENS_TABLE)
89
+ .select("access_token, session_id")
90
+ .eq("id", TOKEN_ROW_ID)
91
+ .execute()
92
+ )
93
+ data = None
94
+ if hasattr(res, "data") and res.data:
95
+ if isinstance(res.data, list) and len(res.data) > 0:
96
+ data = res.data[0]
97
+ elif isinstance(res.data, dict):
98
+ data = res.data
99
+ if not data:
100
+ print("⚠️ No token row found in Supabase.")
101
+ return None, None
102
+ return data.get("access_token"), (data.get("session_id") or None)
103
+ except Exception as e:
104
+ print("⚠️ Error loading tokens from Supabase:", e)
105
+ return None, None
106
+
107
+ def _maybe_parse_brotli(resp: requests.Response) -> Optional[Any]:
108
+ enc = (resp.headers.get("Content-Encoding") or "").lower()
109
+ if "br" in enc:
110
+ try:
111
+ import brotli
112
+ text = brotli.decompress(resp.content).decode("utf-8", errors="replace")
113
+ return json.loads(text)
114
+ except Exception:
115
+ return None
116
+ try:
117
+ return resp.json()
118
+ except ValueError:
119
+ return None
120
+
121
+ # =========[ Odoo Auth ]=========
122
+ def get_token_and_session() -> Tuple[str, Optional[str]]:
123
+ s = requests.Session()
124
+ headers = {
125
+ **_std_headers(),
126
+ **_api_headers(),
127
+ "login": LOGIN,
128
+ "password": PASSWORD,
129
+ "db": DB,
130
+ }
131
+ r = s.get(f"{BASE}/api/auth/token", headers=headers, timeout=30)
132
+ r.raise_for_status()
133
+ data = r.json()
134
+
135
+ access_token = data.get("access_token")
136
+ session_id = r.cookies.get("session_id")
137
+ if not access_token:
138
+ raise RuntimeError(f"No access_token in response: {data}")
139
+
140
+ _save_tokens_to_supabase(access_token, session_id)
141
+ return access_token, session_id
142
+
143
+ def _build_auth_headers(access_token: str, session_id: Optional[str], extra_headers: Dict[str, str]) -> Dict[str, str]:
144
+ hdr = {**_std_headers(), **_api_headers(), **extra_headers}
145
+ hdr["access-token"] = access_token
146
+ if session_id:
147
+ hdr["Cookie"] = f"session_id={session_id}"
148
+ hdr.setdefault("Content-Type", "application/json")
149
+ return hdr
150
+
151
+ def call_endpoint(
152
+ path: str,
153
+ extra_headers: Optional[Dict[str, str]] = None,
154
+ method: str = "GET",
155
+ retry_on_expired: bool = True,
156
+ json_body: Optional[Dict[str, Any]] = None,
157
+ ) -> Optional[requests.Response]:
158
+ """
159
+ يعمل الطلب باستخدام التوكن والسيشن من Supabase.
160
+ لو الرد HTML (login) أو JSON فيه access_token error، نجدد ونكرر مرة واحدة.
161
+ """
162
+ access_token, session_id = _load_tokens_from_supabase()
163
+ if not access_token:
164
+ access_token, session_id = get_token_and_session()
165
+
166
+ url = f"{BASE}{path}"
167
+
168
+ def _do(token, sid):
169
+ headers = _build_auth_headers(token, sid, extra_headers or {})
170
+ try:
171
+ return requests.request(method, url, headers=headers, json=json_body, timeout=30)
172
+ except requests.RequestException as e:
173
+ print(f"⚠️ Network error during request: {e}")
174
+ return None
175
+
176
+ r = _do(access_token, session_id)
177
+ if r is None:
178
+ return None
179
+
180
+ ct = (r.headers.get("Content-Type") or "").lower()
181
+ if ("text/html" in ct) or (r.text.strip().startswith("<!DOCTYPE html")) or ("csrf_token" in r.text):
182
+ print("⚠️ Detected HTML login page — renewing token/session...")
183
+ if retry_on_expired:
184
+ new_token, new_sid = get_token_and_session()
185
+ r = _do(new_token, new_sid)
186
+ return r
187
+
188
+ data = _maybe_parse_brotli(r)
189
+ if isinstance(data, dict):
190
+ msg = (data.get("message") or "").lower()
191
+ if data.get("response") == "access_token" or "token seems to have expired" in msg or "expired" in msg:
192
+ print("⚠️ Detected expired token in JSON — renewing...")
193
+ if retry_on_expired:
194
+ new_token, new_sid = get_token_and_session()
195
+ r = _do(new_token, new_sid)
196
+ return r
197
+
198
+ return r
199
+
200
+ # =========[ بناء ليد Odoo من TikTok ]=========
201
+ def build_odoo_lead_from_tiktok(
202
+ campaign_id: Optional[str],
203
+ campaign_name: Optional[str],
204
+ adset_id: Optional[str],
205
+ adset_name: Optional[str],
206
+ ad_id: Optional[str],
207
+ ad_name: Optional[str],
208
+ field_map: Dict[str, Any],
209
+ formatted_create_time: Optional[str],
210
+ ) -> Dict[str, Any]:
211
+ lead = {
212
+ "name": field_map.get("name") or field_map.get("full_name") or "TikTok Lead",
213
+ "type": "opportunity",
214
+ "kanban_state": "grey",
215
+ "stage_id": ODOO_DEFAULT_STAGE_ID,
216
+ "user_id": ODOO_DEFAULT_USER_ID,
217
+ # بيانات الإعلان/المصدر
218
+ "adgroup_name": adset_name,
219
+ "adset_name": adset_name,
220
+ "ad_name": ad_name,
221
+ "lead_timestamp": formatted_create_time,
222
+ # تواصل
223
+ "email_from": field_map.get("email"),
224
+ "phone": field_map.get("phone_number"),
225
+ "city_ar": field_map.get("city"),
226
+ }
227
+ if ODOO_SOURCE_ID_TIKTOK:
228
+ lead["source_id"] = ODOO_SOURCE_ID_TIKTOK
229
+ return lead
230
+
231
+ def create_crm_lead_on_odoo(lead: Dict[str, Any]) -> None:
232
+ r = call_endpoint("/api/crm.lead", method="POST", json_body=lead)
233
+ if r is None:
234
+ print("❌ Odoo request failed (no response).")
235
+ return
236
+
237
+ print("ODDO Status:", r.status_code, "| Content-Type:", r.headers.get("Content-Type"),
238
+ "| Content-Encoding:", r.headers.get("Content-Encoding"))
239
+ data = _maybe_parse_brotli(r)
240
+ if isinstance(data, (dict, list)):
241
+ print("Odoo JSON:", json.dumps(data, ensure_ascii=False, indent=2))
242
+ else:
243
+ print("Odoo RAW:", (r.text or "")[:400])
244
+
245
+ # =========[ Webhook – نفس منطقك + إضافة إنشاء ليد في Odoo ]=========
246
  @app.post("/webhook")
247
  async def receive_webhook(request: Request):
248
  payload = await request.json()
 
249
  print("📥 Webhook received:")
250
 
251
  gmt_plus_3 = timezone(timedelta(hours=3))
252
 
 
253
  print(f"Request ID: {payload.get('request_id')}")
254
  print(f"Object: {payload.get('object')}")
255
  print(f"Time (Unix): {payload.get('time')}")
 
260
  print("Time (Formatted): Invalid timestamp")
261
  print("----")
262
 
 
263
  for entry in payload.get('entry', []):
264
  lead_id = entry.get('id')
265
  campaign_id = entry.get('campaign_id')
 
285
  formatted_create_time = None
286
  print("Create Time: Invalid")
287
 
288
+ # fields من TikTok
289
+ field_map: Dict[str, Any] = {}
290
  for change in entry.get('changes', []):
291
+ field = (change.get('field') or "").lower()
292
  value = change.get('value')
293
  field_map[field] = value
294
  print(f"{field}: {value}")
295
 
296
  print("----")
297
 
298
+ # 1) Supabase insert (زي كودك)
299
  insert_data = {
300
  "platform": "tiktok",
301
  "campaign_id": campaign_id,
 
318
  }
319
 
320
  supa_resp = requests.post(
321
+ f"{SUPABASE_URL}/rest/v1/{SUPABASE_LEADS_TABLE}",
322
  headers=headers,
323
  json=insert_data
324
  )
 
325
  print("💾 Supabase insert status:", supa_resp.status_code)
326
  if supa_resp.status_code == 201:
327
  print("✅ Lead inserted successfully.")
 
331
  except Exception:
332
  print("❌ Supabase Response:", supa_resp.text)
333
 
334
+ # 2) بناء وإرسال الليد لـ Odoo
335
+ odoo_lead = build_odoo_lead_from_tiktok(
336
+ campaign_id, campaign_name, adset_id, adset_name, ad_id, ad_name,
337
+ field_map, formatted_create_time
338
+ )
339
+ create_crm_lead_on_odoo(odoo_lead)
340
+
341
+ return {"status": "ok"}