141 lines
5.7 KiB
Python
141 lines
5.7 KiB
Python
from fastapi import FastAPI, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
from automation import ShopeeBot
|
|
from typing import Optional
|
|
import logging
|
|
import os
|
|
import sys
|
|
import urllib.request
|
|
import uuid
|
|
import threading
|
|
from collections import defaultdict
|
|
from facebook_bot import FacebookBot
|
|
|
|
# Quản lý hàng đợi (Lock) cho từng thiết bị
|
|
device_locks = defaultdict(threading.Lock)
|
|
|
|
# Cấu hình logging: ghi log ra cả màn hình (console) và file (shopee_bot.log)
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
handlers=[
|
|
logging.FileHandler("shopee_bot.log", encoding="utf-8"),
|
|
logging.StreamHandler(sys.stdout)
|
|
]
|
|
)
|
|
|
|
app = FastAPI(title="n8n Shopee Bridge API")
|
|
|
|
class CommissionRequest(BaseModel):
|
|
link: str
|
|
# Cho phép truyền IP linh hoạt từ n8n.
|
|
device_ip: Optional[str] = Field(default=None, description="IP:Port của điện thoại, ví dụ 192.168.1.100:5555 (WiFi) hoặc 10.8.0.5:5555 (VPN)")
|
|
|
|
from typing import Optional, Any
|
|
|
|
class FacebookCommentRequest(BaseModel):
|
|
device_ip: Optional[str] = Field(default=None, description="IP:Port của điện thoại (Cấu hình trên n8n)")
|
|
group_id: Any
|
|
content: str
|
|
image_url: Optional[str] = None
|
|
image_base64: Optional[str] = None
|
|
post_index: int = Field(default=1, description="Số thứ tự bài viết (1 là bài đầu tiên)")
|
|
|
|
@app.post("/get_commission")
|
|
def get_commission_endpoint(req: CommissionRequest):
|
|
if not req.link:
|
|
raise HTTPException(status_code=400, detail="Thiếu link sản phẩm")
|
|
|
|
try:
|
|
# Lấy IP theo độ ưu tiên
|
|
raw_ip = req.device_ip or os.environ.get("DEVICE_IP") or ""
|
|
lock_key = raw_ip if raw_ip else "default_device"
|
|
|
|
# Dùng Lock để xếp hàng: Nếu điện thoại đang bận, request sau phải chờ
|
|
with device_locks[lock_key]:
|
|
logging.info(f"Đã khóa thiết bị {lock_key} để chạy tác vụ Shopee.")
|
|
bot = ShopeeBot(device_ip=raw_ip)
|
|
|
|
result = bot.get_commission_rate(req.link)
|
|
|
|
# Nếu đã có status thì gộp thẳng các thuộc tính của result vào JSON trả về
|
|
if isinstance(result, dict) and "status" in result:
|
|
return result
|
|
|
|
return {
|
|
"status": "success",
|
|
"commission": result
|
|
}
|
|
except Exception as e:
|
|
logging.error(f"Lỗi API: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.post("/post_facebook_comment")
|
|
def post_facebook_comment(req: FacebookCommentRequest):
|
|
try:
|
|
raw_ip = req.device_ip or os.environ.get("DEVICE_IP") or ""
|
|
lock_key = raw_ip if raw_ip else "default_device"
|
|
|
|
with device_locks[lock_key]:
|
|
logging.info(f"Đã khóa thiết bị {lock_key} để chạy tác vụ Facebook.")
|
|
bot = FacebookBot(device_ip=raw_ip)
|
|
|
|
local_image_path = None
|
|
if req.image_base64:
|
|
import base64
|
|
# Bỏ phần header data:image/jpeg;base64, nếu có
|
|
b64_data = req.image_base64
|
|
if "," in b64_data:
|
|
b64_data = b64_data.split(",", 1)[1]
|
|
|
|
local_image_path = f"temp_fb_{uuid.uuid4().hex}.jpg"
|
|
with open(local_image_path, "wb") as f:
|
|
f.write(base64.b64decode(b64_data))
|
|
logging.info(f"Đã tạo ảnh từ chuỗi Base64: {local_image_path}")
|
|
|
|
elif req.image_url:
|
|
import re
|
|
url = str(req.image_url)
|
|
|
|
# Kiểm tra xem url có phải là một đường dẫn file nội bộ trên máy hay không
|
|
if os.path.exists(url):
|
|
local_image_path = url
|
|
logging.info(f"Sử dụng file ảnh nội bộ có sẵn: {local_image_path}")
|
|
else:
|
|
# Tự động chuyển link Google Drive sang link tải trực tiếp
|
|
if "drive.google.com" in url:
|
|
file_id_match = re.search(r"/d/([a-zA-Z0-9_-]+)", url)
|
|
if file_id_match:
|
|
file_id = file_id_match.group(1)
|
|
url = f"https://drive.google.com/uc?export=download&id={file_id}"
|
|
logging.info(f"Đã convert link Google Drive thành direct link: {url}")
|
|
|
|
# Tải ảnh về máy chủ Python tạm thời
|
|
local_image_path = f"temp_fb_{uuid.uuid4().hex}.jpg"
|
|
urllib.request.urlretrieve(url, local_image_path)
|
|
logging.info(f"Đã tải ảnh tạm thời từ internet: {local_image_path}")
|
|
|
|
result = bot.post_comment(str(req.group_id), str(req.content), local_image_path, req.post_index)
|
|
|
|
# Dọn dẹp ảnh tạm sau khi xong
|
|
if local_image_path and os.path.exists(local_image_path):
|
|
if "temp_fb_" in local_image_path:
|
|
os.remove(local_image_path)
|
|
logging.info(f"Đã dọn dẹp file tạm: {local_image_path}")
|
|
|
|
if isinstance(result, dict) and "status" in result:
|
|
# Trả về luôn JSON kết quả, n8n sẽ nhận HTTP 200 và tiếp tục vòng lặp
|
|
# Ngay cả khi status == "error", ta vẫn muốn n8n chạy tiếp nhóm khác
|
|
return result
|
|
|
|
return {"status": "success", "message": "Hoàn tất"}
|
|
|
|
except Exception as e:
|
|
logging.error(f"Lỗi API Facebook: {str(e)}")
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
# Chạy server API ở cổng 8000
|
|
uvicorn.run(app, host="0.0.0.0", port=8000)
|