import os, json, requests, time, re, base64, tempfile, pathlib from html import escape from fastapi import FastAPI, Request, Response app = FastAPI(title="Zalo Proxy Space") BOT_TOKEN = "__TOKEN__" TARGET_API = "https://bot-api.zaloplatforms.com" PROXY_NAME = "__SENDER_DISPLAY__" HF_TOKEN = os.getenv("HF_TOKEN", "") DATASET_ID = "__DATASET_ID__" MAIN_SPACE_URL = "__MAIN_SPACE_URL__" _logs = [] def _send(cid, text): headers = {"Content-Type": "application/json"} url = f"{TARGET_API}/bot{BOT_TOKEN}/sendMessage" return requests.post(url, json={"chat_id": cid, "text": text}, headers=headers) def _safe_name(name): return re.sub(r'[^a-zA-Z0-9]', '_', str(name))[:30] def _save_to_dataset(image_url, image_data_b64, description, price, category, sender_id, sender_name): """Save product data to HF Dataset.""" if not HF_TOKEN or not DATASET_ID: _log("dataset_skip", sender_id, "N/A", "HF_TOKEN or DATASET_ID missing") return None try: from huggingface_hub import HfApi api = HfApi(token=HF_TOKEN) ts = time.strftime("%Y%m%d_%H%M%S") safe_sender = _safe_name(sender_id) or "unknown" img_filename = f"images/{ts}_{safe_sender}.jpg" meta_filename = f"data/{ts}_{safe_sender}.json" img_bytes = None if image_data_b64: try: img_bytes = base64.b64decode(image_data_b64) except Exception: img_bytes = None elif image_url: try: r = requests.get(image_url, timeout=15) img_bytes = r.content except Exception as e: _log("image_download_fail", sender_id, "N/A", str(e)) img_bytes = None if img_bytes: with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp: tmp.write(img_bytes) tmp_path = tmp.name try: api.upload_file( path_or_fileobj=tmp_path, path_in_repo=img_filename, repo_id=DATASET_ID, repo_type="dataset", token=HF_TOKEN, commit_message=f"Add product image from {sender_name}", ) except Exception as e: _log("image_upload_fail", sender_id, "N/A", str(e)) finally: pathlib.Path(tmp_path).unlink(missing_ok=True) record = { "image": img_filename if img_bytes else None, "description": str(description)[:500] if description else "", "price": str(price) if price else "", "category": str(category) if category else "", "sender_id": str(sender_id), "sender_name": str(sender_name), "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), } with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp: json.dump(record, tmp, indent=2, ensure_ascii=False) tmp_path = tmp.name try: api.upload_file( path_or_fileobj=tmp_path, path_in_repo=meta_filename, repo_id=DATASET_ID, repo_type="dataset", token=HF_TOKEN, commit_message=f"Add product metadata from {sender_name}", ) finally: pathlib.Path(tmp_path).unlink(missing_ok=True) _log("dataset_saved", sender_id, "N/A", f"Saved to {DATASET_ID}") return DATASET_ID except Exception as e: _log("dataset_error", sender_id, "N/A", str(e)) return None @app.get("/") async def root(): return {"status": "ok"} @app.get("/health") async def health(): return {"status": "ok", "proxy_target": "Zalo Bot API", "bot_name": PROXY_NAME, "dataset": DATASET_ID} @app.get("/webhooks") async def webhooks_get(): return Response(content=json.dumps({"message": "Success"}), media_type="application/json", status_code=200) @app.post("/webhooks") async def webhooks(request: Request): body = await request.body() body_str = body.decode("utf-8") if body else "" try: data = json.loads(body_str) except Exception: _log("parse_error", "N/A", "N/A", "Bad JSON") return Response(content=json.dumps({"message": "Bad JSON"}), media_type="application/json", status_code=400) result = data.get("result", data) event = result.get("event_name", "unknown") msg = result.get("message", {}) sender = msg.get("from", {}) chat = msg.get("chat", {}) text = msg.get("text", "") sender_id = str(sender.get("id", "")) sender_name = sender.get("display_name") or sender.get("name") or sender_id chat_id = str(chat.get("id", "")) chat_type = str(chat.get("chat_type", "")) attachments = msg.get("attachment", {}) image_url = "" image_data_b64 = "" if attachments: payload = attachments.get("payload", {}) if isinstance(payload, str): try: payload = json.loads(payload) except Exception: payload = {} image_url = payload.get("url", "") image_data_b64 = payload.get("data", "") or msg.get("image", "") _log(event, sender_id, chat_id, text, sender_name, chat_type) if event == "message.text.received" and chat_id: description = "" price = "" category = "" desc_match = re.search(r"(?:mo ta|description|desc)[:\s]*([^|]+)", text, re.IGNORECASE) price_match = re.search(r"(?:gia|price)[:\s]*([\d,.]+)", text, re.IGNORECASE) cat_match = re.search(r"(?:chuyen muc|category|danh muc)[:\s]*([^|]+?)(?:$|\n)", text, re.IGNORECASE) if desc_match: description = desc_match.group(1).strip() if price_match: price = price_match.group(1).strip() if cat_match: category = cat_match.group(1).strip() if image_url or image_data_b64 or description or price or category: dataset_id = _save_to_dataset( image_url=image_url, image_data_b64=image_data_b64, description=description or text[:200], price=price, category=category, sender_id=sender_id, sender_name=sender_name, ) if dataset_id: reply = ( "🎉 **Bot Zalo của bạn đã được AUTOMATION SALE thiết lập thành công!**\n\n" "✅ Mọi cấu hình đã tự động hoàn tất.\n\n" "👉 Bạn có thể vào https://zalo.me/s/botcreator để quản lý và cấu hình bot của mình.\n\n" f"💾 Dữ liệu sản phẩm đã được lưu vào dataset:\n" f"https://huggingface.co/datasets/{DATASET_ID}\n\n" f"🔗 Webhook URL: https://{MAIN_SPACE_URL}/webhooks\n" f"📊 Logs: https://{MAIN_SPACE_URL}/logs\n" f"🗂️ Quản lý proxy: https://{MAIN_SPACE_URL}/proxy-spaces\n\n" "⚙️ Bot của bạn sẽ tự động trả lời khi có người nhắn tin." ) else: reply = ( "🎉 **Bot Zalo của bạn đã được AUTOMATION SALE thiết lập thành công!**\n\n" "✅ Mọi cấu hình đã tự động hoàn tất.\n\n" "👉 Bạn có thể vào https://zalo.me/s/botcreator để quản lý và cấu hình bot của mình." ) else: reply = ( "🎉 **Bot Zalo của bạn đã được AUTOMATION SALE thiết lập thành công!**\n\n" "✅ Mọi cấu hình đã tự động hoàn tất.\n\n" "👉 Bạn có thể vào https://zalo.me/s/botcreator để quản lý và cấu hình bot của mình.\n\n" f"🔗 Webhook URL: https://{MAIN_SPACE_URL}/webhooks\n" f"📊 Logs: https://{MAIN_SPACE_URL}/logs\n" f"🗂️ Quản lý proxy: https://{MAIN_SPACE_URL}/proxy-spaces\n\n" "⚙️ Bot của bạn sẽ tự động trả lời khi có người nhắn tin." ) try: _send(chat_id, reply) except Exception as e: pass return Response(content=json.dumps({"message": "Success"}), media_type="application/json", status_code=200) @app.get("/proxy-spaces") async def proxy_spaces_get(): """Trang quản lý của proxy space này.""" html = [ 'Quản lý Proxy', '', '', '

📊 Quản lý Proxy

', f'

Webhook URL: https://{MAIN_SPACE_URL}/webhooks

', f'

Logs: https://{MAIN_SPACE_URL}/logs

', ] if DATASET_ID: html.append(f'

Dataset: {DATASET_ID}

') html.append('

💡 Gửi ảnh kèm mô tả/giá/chuyên mục để lưu sản phẩm vào dataset.

') html.append('') return Response(content="".join(html), media_type="text/html") @app.get("/logs") async def proxy_logs(): rows = "" for log in reversed(_logs[-50:]): rows += ( f"
" f"[{log['event']}] 👤{escape(log['sender_name'])} " f"🆔{escape(log['sender_id'])} " f"💬{escape(log['chat_id'])} " f"[{escape(log['chat_type'])}]
" f"" f"{escape(log['text'][:200])}
" f"⏰ {log['time']}
" ) empty_msg = "

Chưa có sự kiện

" content = rows if rows else empty_msg return Response(content=f"Proxy Logs

📊 Proxy Logs — {escape(PROXY_NAME)}

Webhook proxy cho: {escape(PROXY_NAME)}

{content}
", media_type="text/html") def _log(event, sender_id, chat_id, text, sender_name="", chat_type=""): _logs.append({ "event": str(event), "sender_id": str(sender_id), "sender_name": str(sender_name), "chat_id": str(chat_id), "chat_type": str(chat_type), "text": str(text)[:200], "time": time.strftime("%Y-%m-%d %H:%M:%S"), }) if len(_logs) > 100: del _logs[:50]