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

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +24 -326
main.py CHANGED
@@ -1,341 +1,39 @@
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')}")
256
  try:
257
- formatted_time = datetime.fromtimestamp(payload.get('time'), gmt_plus_3).strftime('%Y-%m-%d %H:%M:%S GMT+3')
258
- print(f"Time (Formatted): {formatted_time}")
259
- except Exception:
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')
266
- campaign_name = entry.get('campaign_name')
267
- adset_id = entry.get('adgroup_id')
268
- adset_name = entry.get('adgroup_name')
269
- ad_id = entry.get('ad_id')
270
- ad_name = entry.get('ad_name')
271
- advertiser_id = entry.get('advertiser_id')
272
- create_ts = entry.get('create_time')
273
-
274
- print(f"Lead ID: {lead_id}")
275
- print(f"Campaign ID: {campaign_id}")
276
- print(f"Campaign Name: {campaign_name}")
277
- print(f"Ad Group ID: {adset_id}")
278
- print(f"Ad Group Name: {adset_name}")
279
- print(f"Ad ID: {ad_id}")
280
- print(f"Ad Name: {ad_name}")
281
- try:
282
- formatted_create_time = datetime.fromtimestamp(create_ts, gmt_plus_3).isoformat()
283
- print(f"Create Time: {formatted_create_time}")
284
- except Exception:
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,
302
- "campaign_name": campaign_name,
303
- "adset_id": adset_id,
304
- "adset_name": adset_name,
305
- "ad_id": ad_id,
306
- "lead_id": lead_id,
307
- "full_name": field_map.get("name"),
308
- "email": field_map.get("email"),
309
- "phone": field_map.get("phone_number"),
310
- "city": field_map.get("city"),
311
- "lead_timestamp": formatted_create_time
312
- }
313
-
314
- headers = {
315
- "apikey": SUPABASE_API_KEY,
316
- "Authorization": f"Bearer {SUPABASE_API_KEY}",
317
- "Content-Type": "application/json"
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.")
328
- else:
329
- try:
330
- print("❌ Supabase Error:", supa_resp.json())
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"}
 
1
+ # file: simple_token_api.py
2
+ # pip install fastapi uvicorn requests
3
 
 
 
 
4
  import requests
5
+ from fastapi import FastAPI
 
 
 
6
 
7
  app = FastAPI()
8
 
 
9
  BASE = "https://odoo-demo.binrushd.care"
10
+ CF_ID = "0491b36d7dcabce5b04f1a53f347bb4e.access"
 
11
  CF_SECRET = "22152cb41b62393e159daaff7dce433006c3744c5850e6adc15fa3544bb5eb09"
12
+ LOGIN = "binrushd.automation@gmail.com"
 
13
  PASSWORD = "BR2025"
14
+ DB = "Live_August_25"
 
 
 
 
 
 
 
 
 
 
15
 
 
 
16
 
17
+ @app.post("/test_token")
18
+ def test_token():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  headers = {
20
+ "CF-Access-Client-Id": CF_ID,
21
+ "CF-Access-Client-Secret": CF_SECRET,
22
+ "login": LOGIN,
23
  "password": PASSWORD,
24
+ "db": DB,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
+ print("🔹 Sending request to get new token...")
 
 
 
 
 
 
 
 
 
 
28
  try:
29
+ r = requests.get(f"{BASE}/api/auth/token", headers=headers, timeout=30)
30
+ print("Status:", r.status_code)
31
+ print("Headers:", r.headers)
32
+ print("Response text:", r.text[:500])
33
+ r.raise_for_status()
34
+ data = r.json()
35
+ print("✅ Token response:", data)
36
+ return {"status": "ok", "data": data}
37
+ except Exception as e:
38
+ print("❌ Error while getting token:", e)
39
+ return {"status": "error", "message": str(e)}