File size: 3,140 Bytes
d285f12
cd02463
d285f12
 
 
ac02880
cd02463
 
 
 
 
 
 
 
 
 
ac02880
d285f12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd02463
d285f12
 
 
 
cd02463
d285f12
 
cd02463
d285f12
cd02463
d285f12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cd02463
39b2cfc
cd02463
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import gradio as gr
from fastapi import Body, Query
from fastapi.responses import JSONResponse

from scraper import is_valid_rednote_url, scrape

# Space ZeroGPU bắt buộc phải có ít nhất một hàm @spaces.GPU (app này không dùng GPU thật).
# Nếu chạy ở nơi không có thư viện `spaces` (CPU / máy local) thì bỏ qua.
try:
    import spaces

    @spaces.GPU
    def _gpu_placeholder():
        return None

except ImportError:
    pass

# ---------------------------------------------------------------- core


def process_url(url: str):
    """Trả về (payload, http_status) giống main.rs."""
    url = (url or "").strip()
    if not is_valid_rednote_url(url):
        return {"status": False, "error": "Invalid Xiaohongshu URL"}, 400
    try:
        data = scrape(url)
    except ValueError as e:
        return {"status": False, "error": str(e)}, 400
    data["status"] = True
    return data, 200


# ---------------------------------------------------------------- REST API (giữ nguyên đường dẫn bản Rust)

CORS = {"Access-Control-Allow-Origin": "*"}


def api_get(url: str = Query(..., description="Rednote / xhslink URL")):
    payload, code = process_url(url)
    return JSONResponse(payload, status_code=code, headers=CORS)


def api_post(body: dict = Body(..., examples=[{"url": "http://xhslink.com/o/21DKXV988zp"}])):
    payload, code = process_url(body.get("url", ""))
    return JSONResponse(payload, status_code=code, headers=CORS)


# ---------------------------------------------------------------- Gradio UI


def ui_fn(url: str):
    payload, _ = process_url(url)
    if not payload.get("status"):
        raise gr.Error(payload.get("error", "Unknown error"))
    links = "\n".join(f"- [{d['quality']}]({d['url']})" for d in payload["downloads"]) or "_Không có video_"
    info = (
        f"**{payload['title']}**  \n"
        f"👤 {payload['nickname']} · ❤️ {payload['engagement']['likes']} · "
        f"💬 {payload['engagement']['comments']} · ⭐ {payload['engagement']['collects']}\n\n"
        f"{payload['desc']}\n\n### Video\n{links}"
    )
    return info, payload["images"], payload


with gr.Blocks(title="Rednote Downloader") as demo:
    gr.Markdown("# Rednote (小红书) Downloader\nDán link `xhslink.com` hoặc `xiaohongshu.com`.")
    inp = gr.Textbox(label="URL", placeholder="http://xhslink.com/o/21DKXV988zp")
    btn = gr.Button("Lấy dữ liệu", variant="primary")
    info_md = gr.Markdown()
    gallery = gr.Gallery(label="Ảnh", columns=4)
    raw = gr.JSON(label="JSON")
    btn.click(ui_fn, inp, [info_md, gallery, raw], api_name="rednote")
    inp.submit(ui_fn, inp, [info_md, gallery, raw])

if __name__ == "__main__":
    # Khởi động kiểu Gradio chuẩn (tương thích ZeroGPU), rồi gắn thêm route REST vào app bên dưới
    fastapi_app, _, _ = demo.launch(server_name="0.0.0.0", server_port=7860, prevent_thread_lock=True, ssr_mode=False)
    fastapi_app.add_api_route("/api/rednote", api_get, methods=["GET"])
    fastapi_app.add_api_route("/api/rednote", api_post, methods=["POST"])
    demo.block_thread()