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()