| |
| import os |
| import asyncio |
| import requests |
| import httpx |
| import json |
| import uuid |
| import time |
| from datetime import date |
| from fastapi import FastAPI, Request, BackgroundTasks |
| from fastapi.responses import HTMLResponse, FileResponse, JSONResponse, RedirectResponse, StreamingResponse |
| from fastapi.middleware.wsgi import WSGIMiddleware |
|
|
| |
| from rubikabot import router as rubikabot_router |
| from soti import router as soti_router |
|
|
| |
| import action |
|
|
| app = FastAPI() |
| app.include_router(rubikabot_router) |
| app.include_router(soti_router) |
|
|
| |
| JOBS_DIR = "./tts_jobs" |
| USAGE_DIR = "./tts_usage" |
| os.makedirs(JOBS_DIR, exist_ok=True) |
| os.makedirs(USAGE_DIR, exist_ok=True) |
|
|
| |
| CLONE_USAGE_LIMIT = 1 |
| EDIT_USAGE_LIMIT = 5 |
|
|
| |
| @app.middleware("http") |
| async def set_space_url_middleware(request: Request, call_next): |
| if not getattr(action, "SPACE_HOST", None): |
| host = request.headers.get("host", "") |
| if host: |
| action.SPACE_HOST = host |
| action.SPACE_URL = f"https://{host}" |
| print(f"[Runflare Auto-Config] Set SPACE_URL dynamically to: {action.SPACE_URL}", flush=True) |
| return await call_next(request) |
|
|
| def get_client_ip(request: Request) -> str: |
| """استخراج دقیق آیپی واقعی کاربر از پشت پروکسیهای Cloudflare و هاست رانفلر""" |
| cf_ip = request.headers.get("CF-Connecting-IP") |
| if cf_ip: |
| return cf_ip.strip() |
| x_forwarded_for = request.headers.get("X-Forwarded-For") |
| if x_forwarded_for: |
| return x_forwarded_for.split(",")[0].strip() |
| if request.client and request.client.host: |
| return request.client.host |
| return "0.0.0.0" |
|
|
| |
| def get_local_clone_usage(key: str) -> dict: |
| today_str = date.today().isoformat() |
| safe_key = "".join([c for c in key if c.isalnum() or c in ["-", "_", "."]]) |
| file_path = os.path.join(USAGE_DIR, f"clone_{safe_key}.json") |
| if os.path.exists(file_path): |
| try: |
| with open(file_path, "r", encoding="utf-8") as f: |
| data = json.load(f) |
| if data.get("date") == today_str: |
| return data |
| except: pass |
| return {"count": 0, "date": today_str} |
|
|
| def save_local_clone_usage(key: str, data: dict): |
| safe_key = "".join([c for c in key if c.isalnum() or c in ["-", "_", "."]]) |
| file_path = os.path.join(USAGE_DIR, f"clone_{safe_key}.json") |
| try: |
| with open(file_path, "w", encoding="utf-8") as f: |
| json.dump(data, f) |
| except: pass |
|
|
| def check_local_clone_limit(fingerprint: str, ip: str) -> bool: |
| for key in [fingerprint, ip]: |
| if not key: continue |
| data = get_local_clone_usage(key) |
| if data["count"] >= CLONE_USAGE_LIMIT: |
| return True |
| return False |
|
|
| def use_local_clone(fingerprint: str, ip: str): |
| for key in [fingerprint, ip]: |
| if not key: continue |
| data = get_local_clone_usage(key) |
| data["count"] += 1 |
| save_local_clone_usage(key, data) |
|
|
| |
| def get_local_edit_usage(key: str) -> dict: |
| current_week = date.today().isocalendar()[1] |
| safe_key = "".join([c for c in key if c.isalnum() or c in ["-", "_", "."]]) |
| file_path = os.path.join(USAGE_DIR, f"edit_{safe_key}.json") |
| if os.path.exists(file_path): |
| try: |
| with open(file_path, "r", encoding="utf-8") as f: |
| data = json.load(f) |
| if data.get("week") == current_week: |
| return data |
| except: pass |
| return {"count": 0, "week": current_week} |
|
|
| def save_local_edit_usage(key: str, data: dict): |
| safe_key = "".join([c for c in key if c.isalnum() or c in ["-", "_", "."]]) |
| file_path = os.path.join(USAGE_DIR, f"edit_{safe_key}.json") |
| try: |
| with open(file_path, "w", encoding="utf-8") as f: |
| json.dump(data, f) |
| except: pass |
|
|
| def check_local_edit_limit(fingerprint: str, ip: str) -> bool: |
| for key in [fingerprint, ip]: |
| if not key: continue |
| data = get_local_edit_usage(key) |
| if data["count"] >= EDIT_USAGE_LIMIT: |
| return True |
| return False |
|
|
| def use_local_edit(fingerprint: str, ip: str): |
| for key in [fingerprint, ip]: |
| if not key: continue |
| data = get_local_edit_usage(key) |
| data["count"] += 1 |
| save_local_edit_usage(key, data) |
|
|
|
|
| def clean_old_files(): |
| now = time.time() |
| try: |
| for f in os.listdir(JOBS_DIR): |
| fpath = os.path.join(JOBS_DIR, f) |
| if os.path.isfile(fpath) and os.stat(fpath).st_mtime < now - 1800: |
| os.remove(fpath) |
| except: pass |
| try: |
| for f in os.listdir(USAGE_DIR): |
| fpath = os.path.join(USAGE_DIR, f) |
| if os.path.isfile(fpath) and os.stat(fpath).st_mtime < now - 172800: |
| os.remove(fpath) |
| except: pass |
|
|
| def save_job_state(job_id: str, status: str, audio_data: bytes = None, error_message: str = None): |
| clean_old_files() |
| state = { |
| "status": status, |
| "error_message": error_message, |
| "has_audio": audio_data is not None |
| } |
| state_file = os.path.join(JOBS_DIR, f"{job_id}.json") |
| with open(state_file, "w", encoding="utf-8") as f: |
| json.dump(state, f, ensure_ascii=False) |
| |
| if audio_data: |
| audio_file = os.path.join(JOBS_DIR, f"{job_id}.mp3") |
| with open(audio_file, "wb") as f: |
| f.write(audio_data) |
|
|
| def get_job_state(job_id: str): |
| state_file = os.path.join(JOBS_DIR, f"{job_id}.json") |
| if not os.path.exists(state_file): |
| return None |
| try: |
| with open(state_file, "r", encoding="utf-8") as f: |
| return json.load(f) |
| except: |
| return None |
|
|
| def get_job_audio(job_id: str): |
| audio_file = os.path.join(JOBS_DIR, f"{job_id}.mp3") |
| if os.path.exists(audio_file): |
| try: |
| with open(audio_file, "rb") as f: |
| return f.read() |
| except: |
| return None |
| return None |
|
|
| async def run_tts_background(job_id: str, payload: dict, headers: dict): |
| try: |
| target_payload = { |
| "text": payload.get("text"), |
| "speaker": payload.get("speaker"), |
| "temperature": payload.get("temperature", 1.5) |
| } |
| |
| async with httpx.AsyncClient(timeout=180.0) as client: |
| resp = await client.post( |
| "https://sada8888-tts2.hf.space/api/generate", |
| json=target_payload, |
| headers={"Content-Type": "application/json"} |
| ) |
| if resp.status_code == 200: |
| save_job_state(job_id, "completed", audio_data=resp.content) |
| else: |
| save_job_state(job_id, "error", error_message=f"خطا از سرور تولید صدا: {resp.text}") |
| except Exception as e: |
| save_job_state(job_id, "error", error_message=str(e)) |
|
|
| |
| |
| |
| @app.get("/") |
| async def root_path(): |
| return HTMLResponse("error 404", status_code=200) |
|
|
| @app.get("/tts") |
| @app.get("/tts/") |
| async def old_tts_path(): |
| return HTMLResponse("<h1>این مسیر تغییر یافته است. لطفا از ttspro استفاده کنید.</h1>", status_code=404) |
|
|
| @app.get("/ttspro", response_class=HTMLResponse) |
| @app.get("/ttspro/", response_class=HTMLResponse) |
| async def serve_ttspro(): |
| for path in ["ttspro.html", "templates/ttspro.html"]: |
| if os.path.exists(path): |
| return FileResponse(path) |
| return HTMLResponse("<h1>خطا: فایل ttspro.html پیدا نشد!</h1>", status_code=404) |
|
|
|
|
| |
| |
| |
| |
| CHAT_SPACE_URL = "https://opera10-ttslive-chat.hf.space" |
|
|
| @app.get("/chat", response_class=HTMLResponse) |
| @app.get("/chat/", response_class=HTMLResponse) |
| async def serve_chat(): |
| for path in ["chat.html", "templates/chat.html"]: |
| if os.path.exists(path): |
| return FileResponse(path) |
| return HTMLResponse("<h1>خطا: فایل chat.html پیدا نشد!</h1>", status_code=404) |
|
|
| @app.get("/audio/{filename}") |
| async def serve_chat_audio(filename: str): |
| if filename.startswith("standard_"): |
| job_id = filename.replace("standard_", "").replace(".mp3", "").replace(".wav", "") |
| audio_data = get_job_audio(job_id) |
| if audio_data: |
| return StreamingResponse( |
| iter([audio_data]), |
| media_type="audio/mpeg" |
| ) |
| return HTMLResponse("<h1>فایل صوتی یافت نشد</h1>", status_code=404) |
|
|
| |
| media_type = "audio/wav" |
| if filename.endswith(".pdf"): |
| media_type = "application/pdf" |
| elif filename.endswith(".docx"): |
| media_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" |
| elif filename.endswith(".mp3"): |
| media_type = "audio/mpeg" |
| elif filename.endswith(".mp4"): |
| media_type = "video/mp4" |
|
|
| async def iterfile(): |
| try: |
| |
| async with httpx.AsyncClient(timeout=120.0) as client: |
| async with client.stream("GET", f"{CHAT_SPACE_URL}/audio/{filename}") as r: |
| r.raise_for_status() |
| async for chunk in r.aiter_bytes(chunk_size=65536): |
| if chunk: yield chunk |
| except Exception: |
| yield b"" |
| return StreamingResponse(iterfile(), media_type=media_type) |
|
|
| @app.post("/api/chat_proxy") |
| async def chat_proxy_bridge(request: Request): |
| try: |
| body_bytes = await request.body() |
|
|
| async def stream_generator(): |
| try: |
| async with httpx.AsyncClient(timeout=60.0) as client: |
| async with client.stream( |
| "POST", |
| f"{CHAT_SPACE_URL}/api/chat_proxy", |
| content=body_bytes, |
| headers={"Content-Type": "application/json"} |
| ) as resp: |
| async for line in resp.aiter_lines(): |
| if line: |
| yield (line + "\n").encode('utf-8') |
| except Exception as e: |
| err = {"status": "error", "message": f"خطا در ارتباط رانفلر با سرور اصلی: {str(e)}"} |
| yield (json.dumps(err) + "\n").encode('utf-8') |
|
|
| headers = { |
| "Cache-Control": "no-cache, no-transform", |
| "X-Accel-Buffering": "no", |
| "Connection": "keep-alive", |
| "Transfer-Encoding": "chunked" |
| } |
| return StreamingResponse(stream_generator(), media_type="application/json", headers=headers) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": "سرور موقتا در دسترس نیست."}, status_code=500) |
|
|
|
|
| |
| |
| |
| HF_SPACE_BASE_URL_TTS = "https://ezmarynoori-tts.hf.space" |
| USAGE_LIMIT_GENERATE = 10 |
|
|
| def get_user_usage(fingerprint: str): |
| today_str = date.today().isoformat() |
| usage_file = os.path.join(USAGE_DIR, f"usage_{fingerprint}.json") |
| if os.path.exists(usage_file): |
| try: |
| with open(usage_file, "r", encoding="utf-8") as f: |
| data = json.load(f) |
| if data.get("last_reset") == today_str: |
| return data |
| except: pass |
| return {"count": 0, "last_reset": today_str} |
|
|
| def increment_user_usage(fingerprint: str): |
| data = get_user_usage(fingerprint) |
| data["count"] += 1 |
| usage_file = os.path.join(USAGE_DIR, f"usage_{fingerprint}.json") |
| try: |
| with open(usage_file, "w", encoding="utf-8") as f: |
| json.dump(data, f) |
| except: pass |
|
|
| def check_limit_tts(payload): |
| if payload.get('subscriptionStatus') != 'paid': |
| fingerprint = payload.get('fingerprint') |
| if not fingerprint: |
| return False |
| |
| record = get_user_usage(fingerprint) |
| if record["count"] >= USAGE_LIMIT_GENERATE: |
| return False |
| |
| increment_user_usage(fingerprint) |
| return True |
|
|
| @app.post("/api/check-credit-tts") |
| async def check_credit_tts(request: Request): |
| try: |
| data = await request.json() |
| fingerprint = data.get('fingerprint') |
| subscription_status = data.get('subscriptionStatus') |
| |
| if not fingerprint: |
| return JSONResponse({"message": "Fingerprint required."}, status_code=400) |
| |
| if subscription_status == 'paid': |
| return JSONResponse({"credits_remaining": "unlimited", "limit_reached": False}) |
| |
| record = get_user_usage(fingerprint) |
| credits = max(0, USAGE_LIMIT_GENERATE - record["count"]) |
| return JSONResponse({"credits_remaining": credits, "limit_reached": credits <= 0}) |
| except: |
| return JSONResponse({"message": "Server Error"}, status_code=500) |
|
|
| @app.post("/api/check-clone-credit") |
| async def check_clone_credit(request: Request): |
| try: |
| data = await request.json() |
| fingerprint = data.get('fingerprint') |
| subscription_status = data.get('subscriptionStatus') |
| client_ip = get_client_ip(request) |
| |
| if not fingerprint: |
| return JSONResponse({"message": "Fingerprint required."}, status_code=400) |
| |
| if subscription_status == 'paid': |
| return JSONResponse({"credits_remaining": "unlimited", "limit_reached": False}) |
| |
| limit_reached = check_local_clone_limit(fingerprint, client_ip) |
| |
| rec = get_local_clone_usage(fingerprint) |
| rem_fp = max(0, CLONE_USAGE_LIMIT - rec["count"]) |
| |
| rec = get_local_clone_usage(client_ip) |
| rem_ip = max(0, CLONE_USAGE_LIMIT - rec["count"]) |
| |
| credits_remaining = min(rem_fp, rem_ip) |
| |
| return JSONResponse({ |
| "credits_remaining": credits_remaining, |
| "limit_reached": limit_reached or (credits_remaining <= 0) |
| }) |
| except Exception as e: |
| return JSONResponse({"message": f"Server Error: {str(e)}"}, status_code=500) |
|
|
| @app.post("/api/use-clone-credit") |
| async def use_clone_credit(request: Request): |
| try: |
| data = await request.json() |
| fingerprint = data.get('fingerprint') |
| subscription_status = data.get('subscriptionStatus') |
| client_ip = get_client_ip(request) |
| |
| if not fingerprint or subscription_status == 'paid': |
| return JSONResponse({"status": "success"}) |
| |
| use_local_clone(fingerprint, client_ip) |
| return JSONResponse({"status": "success"}) |
| except Exception as e: |
| return JSONResponse({"message": f"Server Error: {str(e)}"}, status_code=500) |
|
|
| def get_hf_headers(request: Request): |
| headers = { |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", |
| "Accept": "application/json" |
| } |
| |
| |
| if "content-type" in request.headers: |
| headers["Content-Type"] = request.headers["content-type"] |
| |
| client_ip = request.headers.get("X-Forwarded-For") |
| if client_ip: |
| headers["X-Forwarded-For"] = client_ip |
| elif request.client and request.client.host: |
| headers["X-Forwarded-For"] = request.client.host |
| return headers |
|
|
| HF_PODCAST_SPACE_URL = "https://sada8888-tts2.hf.space" |
|
|
| @app.post("/api/generate") |
| async def submit_job(request: Request, background_tasks: BackgroundTasks): |
| payload = await request.json() |
| headers = get_hf_headers(request) |
|
|
| |
| if "action_name" in payload and "prompt" in payload: |
| try: |
| |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=120.0) as client: |
| resp = client.post("/api/generate", json=payload, headers=headers) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"خطای سرور تصویر داخلی: {str(e)}"}, status_code=500) |
| |
| |
| if "fingerprint" not in payload and "subscriptionStatus" not in payload: |
| try: |
| resp = await asyncio.to_thread( |
| requests.post, |
| "https://sada8888-tts2.hf.space/api/generate", |
| json=payload, |
| headers=headers, |
| timeout=180 |
| ) |
| if resp.status_code != 200: |
| return JSONResponse({"error": f"خطا از سرور تولید پادکست: {resp.text}"}, status_code=resp.status_code) |
| |
| return StreamingResponse( |
| iter([resp.content]), |
| media_type="audio/mpeg" |
| ) |
| except Exception as e: |
| return JSONResponse({"error": f"قطعی ارتباط با سرور ابری پادکست: {str(e)}"}, status_code=500) |
|
|
| |
| if not check_limit_tts(payload): return JSONResponse({"message": "سقف تکمیل شده"}, status_code=429) |
| try: |
| job_id = f"job_{uuid.uuid4().hex}" |
| save_job_state(job_id, "processing") |
| background_tasks.add_task(run_tts_background, job_id, payload, headers) |
| return JSONResponse({"job_id": job_id}, status_code=200) |
| except Exception as e: |
| return JSONResponse({"message": f"خطا در شروع فرآیند: {str(e)}"}, status_code=500) |
|
|
| @app.post("/api/check_status") |
| async def check_status(request: Request): |
| try: |
| payload = await request.json() |
| job_id = payload.get("job_id") |
| |
| |
| job_state = get_job_state(job_id) if job_id else None |
| |
| if job_state: |
| if job_state["status"] == "completed": |
| return JSONResponse({ |
| "status": "completed", |
| "proxy_url": f"/audio/standard_{job_id}.mp3" |
| }) |
| elif job_state["status"] == "error": |
| return JSONResponse({ |
| "status": "error", |
| "result": job_state.get("error_message", "خطا در تولید صدا") |
| }) |
| else: |
| return JSONResponse({"status": "processing"}) |
| |
| |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=30.0) as client: |
| resp = client.post("/api/check_status", json=payload, headers=get_hf_headers(request)) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": str(e)}, status_code=500) |
|
|
| @app.post("/api/generate_podcast") |
| async def generate_podcast_proxy(request: Request): |
| payload = await request.json() |
| if not check_limit_tts(payload): return JSONResponse({"message": "سقف تکمیل شده"}, status_code=429) |
| try: |
| resp = await asyncio.to_thread(requests.post, f"{HF_SPACE_BASE_URL_TTS}/api/generate_podcast", json=payload, timeout=30) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: return JSONResponse({"message": "خطا"}, status_code=500) |
|
|
| @app.post("/api/podcast_status") |
| async def podcast_status_proxy(request: Request): |
| try: |
| payload = await request.json() |
| resp = await asyncio.to_thread(requests.post, f"{HF_SPACE_BASE_URL_TTS}/api/podcast_status", json=payload, timeout=30) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: return JSONResponse({"status": "error"}, status_code=500) |
|
|
|
|
| |
| |
| |
|
|
| @app.get("/podcast", response_class=HTMLResponse) |
| @app.get("/podcast/", response_class=HTMLResponse) |
| async def serve_podcast(): |
| for path in ["podcast.html", "templates/podcast.html"]: |
| if os.path.exists(path): |
| return FileResponse(path) |
| return HTMLResponse("<h1>خطا: فایل podcast.html پیدا نشد!</h1>", status_code=404) |
|
|
| @app.post("/api/check-credit") |
| async def smart_check_credit(request: Request): |
| try: |
| payload = await request.json() |
| headers = get_hf_headers(request) |
| referer = request.headers.get("referer", "").lower() |
| |
| if "podcast" in referer: |
| url = f"{HF_PODCAST_SPACE_URL}/api/check-credit" |
| async with httpx.AsyncClient(timeout=30.0) as client: |
| resp = await client.post(url, json=payload, headers=headers) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| else: |
| |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=30.0) as client: |
| resp = client.post("/api/check-credit", json=payload, headers=headers) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"خطا در ارتباط با سرور: {str(e)}"}, status_code=500) |
|
|
| @app.post("/api/use-credit") |
| async def smart_use_credit(request: Request): |
| try: |
| payload = await request.json() |
| headers = get_hf_headers(request) |
| referer = request.headers.get("referer", "").lower() |
| |
| if "podcast" in referer: |
| url = f"{HF_PODCAST_SPACE_URL}/api/use-credit" |
| async with httpx.AsyncClient(timeout=30.0) as client: |
| resp = await client.post(url, json=payload, headers=headers) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| else: |
| |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=30.0) as client: |
| resp = client.post("/api/use-credit", json=payload, headers=headers) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": str(e)}, status_code=500) |
|
|
| @app.post("/api/create-full-podcast") |
| async def podgen_create_full(request: Request): |
| try: |
| payload = await request.json() |
| headers = get_hf_headers(request) |
| resp = await asyncio.to_thread(requests.post, f"{HF_PODCAST_SPACE_URL}/api/create-full-podcast", json=payload, headers=headers, timeout=60) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: return JSONResponse({"error": "ارتباط با سرور قطع شد"}, status_code=500) |
|
|
| @app.get("/api/podcast-status/{task_id}") |
| async def podgen_status(request: Request, task_id: str): |
| try: |
| headers = get_hf_headers(request) |
| resp = await asyncio.to_thread(requests.get, f"{HF_PODCAST_SPACE_URL}/api/podcast-status/{task_id}", headers=headers, timeout=30) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: return JSONResponse({"status": "error"}, status_code=500) |
|
|
| @app.post("/api/auto-podcast") |
| async def podgen_auto(request: Request): |
| try: |
| payload = await request.json() |
| headers = get_hf_headers(request) |
| resp = await asyncio.to_thread(requests.post, f"{HF_PODCAST_SPACE_URL}/api/auto-podcast", json=payload, headers=headers, timeout=60) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: return JSONResponse({"error": "ارتباط قطع شد"}, status_code=500) |
|
|
| @app.get("/api/auto-podcast-status/{task_id}") |
| async def podgen_auto_status(request: Request, task_id: str): |
| try: |
| headers = get_hf_headers(request) |
| resp = await asyncio.to_thread(requests.get, f"{HF_PODCAST_SPACE_URL}/api/auto-podcast-status/{task_id}", headers=headers, timeout=30) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: return JSONResponse({"status": "error"}, status_code=500) |
|
|
| @app.get("/api/download-podcast/{filename}") |
| async def podgen_download(request: Request, filename: str): |
| async def iterfile(): |
| try: |
| headers = get_hf_headers(request) |
| async with httpx.AsyncClient(timeout=120.0) as client: |
| async with client.stream("GET", f"{HF_PODCAST_SPACE_URL}/api/download-podcast/{filename}", headers=headers) as r: |
| r.raise_for_status() |
| async for chunk in r.aiter_bytes(chunk_size=65536): |
| if chunk: yield chunk |
| except Exception: |
| yield b"" |
| return StreamingResponse(iterfile(), media_type="audio/mpeg") |
|
|
|
|
| |
| |
| |
| @app.get("/video", response_class=HTMLResponse) |
| @app.get("/video/", response_class=HTMLResponse) |
| async def serve_video_studio(): |
| for path in ["video.html", "templates/video.html"]: |
| if os.path.exists(path): |
| return FileResponse(path) |
| return HTMLResponse("<h1>خطا: فایل video.html پیدا نشد!</h1>", status_code=404) |
|
|
| @app.get("/flux", response_class=HTMLResponse) |
| @app.get("/flux/", response_class=HTMLResponse) |
| async def serve_flux_studio(): |
| for path in ["flux.html", "templates/flux.html"]: |
| if os.path.exists(path): |
| return FileResponse(path) |
| return HTMLResponse("<h1>خطا: فایل flux.html پیدا نشد!</h1>", status_code=404) |
|
|
| @app.get("/image", response_class=HTMLResponse) |
| @app.get("/image/", response_class=HTMLResponse) |
| async def serve_image_studio(): |
| for path in ["image.html", "templates/image.html", "flux.html", "templates/flux.html"]: |
| if os.path.exists(path): |
| return FileResponse(path) |
| return HTMLResponse("<h1>خطا: فایل image.html پیدا نشد!</h1>", status_code=404) |
|
|
| @app.get("/edit", response_class=HTMLResponse) |
| @app.get("/edit/", response_class=HTMLResponse) |
| async def serve_edit_studio(): |
| for path in ["edit.html", "templates/edit.html"]: |
| if os.path.exists(path): |
| return FileResponse(path) |
| return HTMLResponse("<h1>خطا: فایل edit.html پیدا نشد!</h1>", status_code=404) |
|
|
| @app.get("/ttspro", response_class=HTMLResponse) |
| @app.get("/ttspro/", response_class=HTMLResponse) |
| async def serve_ttspro(): |
| for path in ["ttspro.html", "templates/ttspro.html"]: |
| if os.path.exists(path): |
| return FileResponse(path) |
| return HTMLResponse("<h1>خطا: فایل ttspro.html پیدا نشد!</h1>", status_code=404) |
|
|
| |
| @app.post("/api/check-image-credit") |
| async def proxy_check_image_credit(request: Request): |
| try: |
| body = await request.body() |
| headers = get_hf_headers(request) |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=30.0) as client: |
| resp = client.post("/api/check-image-credit", content=body, headers=headers) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500) |
|
|
| @app.post("/api/use-image-credit") |
| async def proxy_use_image_credit(request: Request): |
| try: |
| body = await request.body() |
| headers = get_hf_headers(request) |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=30.0) as client: |
| resp = client.post("/api/use-image-credit", content=body, headers=headers) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500) |
|
|
| |
| @app.post("/api/check-edit-credit") |
| async def proxy_check_edit_credit(request: Request): |
| try: |
| data = await request.json() |
| fingerprint = data.get('fingerprint') |
| subscription_status = data.get('subscriptionStatus') |
| client_ip = get_client_ip(request) |
|
|
| if subscription_status == 'paid': |
| return JSONResponse({"credits_remaining": "unlimited", "limit_reached": False}) |
|
|
| limit_reached = check_local_edit_limit(fingerprint, client_ip) |
| current_week = date.today().isocalendar()[1] |
| |
| rem_fp = 0 |
| if fingerprint: |
| rec = get_local_edit_usage(fingerprint) |
| if rec["week"] == current_week: |
| rem_fp = max(0, EDIT_USAGE_LIMIT - rec["count"]) |
| else: |
| rem_fp = EDIT_USAGE_LIMIT |
| else: |
| rem_fp = EDIT_USAGE_LIMIT |
| |
| rec = get_local_edit_usage(client_ip) |
| if rec["week"] == current_week: |
| rem_ip = max(0, EDIT_USAGE_LIMIT - rec["count"]) |
| else: |
| rem_ip = EDIT_USAGE_LIMIT |
| |
| credits_remaining = min(rem_fp, rem_ip) |
| |
| return JSONResponse({ |
| "credits_remaining": credits_remaining, |
| "limit_reached": limit_reached or (credits_remaining <= 0) |
| }) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"error: {str(e)}"}, status_code=500) |
|
|
| @app.post("/api/use-edit-credit") |
| async def proxy_use_edit_credit(request: Request): |
| try: |
| data = await request.json() |
| fingerprint = data.get('fingerprint') |
| subscription_status = data.get('subscriptionStatus') |
| client_ip = get_client_ip(request) |
|
|
| if subscription_status != 'paid': |
| use_local_edit(fingerprint, client_ip) |
| return JSONResponse({"status": "success"}) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"error: {str(e)}"}, status_code=500) |
|
|
| |
| @app.post("/api/generate-video") |
| async def proxy_generate_video(request: Request): |
| try: |
| body = await request.body() |
| headers = get_hf_headers(request) |
| |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=300.0) as client: |
| resp = client.post("/api/generate-video", content=body, headers=headers) |
| try: |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: |
| return JSONResponse({"status": "error", "message": "خطا در سرور ابری", "details": resp.text}, status_code=502) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500) |
|
|
| @app.post("/api/merge-videos") |
| async def proxy_merge_videos(request: Request): |
| try: |
| body = await request.body() |
| headers = get_hf_headers(request) |
| |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=400.0) as client: |
| resp = client.post("/api/merge-videos", content=body, headers=headers) |
| |
| if resp.status_code == 200: |
| return StreamingResponse( |
| iter([resp.content]), |
| media_type=resp.headers.get("Content-Type", "video/mp4"), |
| headers={"Content-Disposition": resp.headers.get("Content-Disposition", "attachment; filename=merged_video.mp4")} |
| ) |
| else: |
| try: |
| err_json = resp.json() |
| return JSONResponse({"status": "error", "message": err_json.get("error", "خطا در پردازش ویدیو")}, status_code=resp.status_code) |
| except: |
| return JSONResponse({"status": "error", "message": "خطا در پردازش و میکس ویدیو"}, status_code=resp.status_code) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500) |
|
|
| @app.get("/api/status/{run_id}") |
| async def proxy_video_status(request: Request, run_id: str): |
| try: |
| headers = get_hf_headers(request) |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=30.0) as client: |
| resp = client.get(f"/api/status/{run_id}", headers=headers) |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except Exception as e: |
| return JSONResponse({"status": "processing"}, status_code=500) |
|
|
| @app.get("/static/images/{filename}") |
| async def proxy_static_images(request: Request, filename: str): |
| local_path = os.path.join("static/images", filename) |
| if os.path.exists(local_path): |
| media_type = "application/octet-stream" |
| if filename.endswith(".mp4"): media_type = "video/mp4" |
| elif filename.endswith(".png"): media_type = "image/png" |
| elif filename.endswith(".webp"): media_type = "image/webp" |
| elif filename.endswith(".jpg") or filename.endswith(".jpeg"): media_type = "image/jpeg" |
| elif filename.endswith(".txt"): media_type = "text/plain" |
| |
| response_headers = { |
| "Cache-Control": "public, max-age=86400" |
| } |
| return FileResponse(local_path, media_type=media_type, headers=response_headers) |
| return HTMLResponse("<h1>فایل یافت نشد</h1>", status_code=404) |
|
|
| @app.post("/api/edit") |
| async def proxy_edit_image(request: Request): |
| try: |
| body = await request.body() |
| headers = get_hf_headers(request) |
| |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=120.0) as client: |
| resp = client.post("/api/edit", content=body, headers=headers) |
| try: |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: |
| return JSONResponse({"status": "error", "message": "خطا در سرور ابری", "details": resp.text}, status_code=502) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500) |
|
|
| @app.post("/api/chat") |
| async def proxy_gemma_chat(request: Request): |
| try: |
| body = await request.body() |
| headers = get_hf_headers(request) |
| |
| transport = httpx.WSGITransport(app=action.app) |
| with httpx.Client(transport=transport, base_url="http://local", timeout=120.0) as client: |
| resp = client.post("/api/chat", content=body, headers=headers) |
| try: |
| return JSONResponse(resp.json(), status_code=resp.status_code) |
| except: |
| return JSONResponse({"status": "error", "message": "خطا در سرور ابری", "details": resp.text}, status_code=502) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500) |
|
|
|
|
| |
| |
| |
| @app.post("/api/create_file") |
| async def proxy_create_file(request: Request): |
| try: |
| body_bytes = await request.body() |
| headers = get_hf_headers(request) |
| |
| |
| async def stream_generator(): |
| try: |
| async with httpx.AsyncClient(timeout=300.0) as client: |
| async with client.stream( |
| "POST", |
| f"{CHAT_SPACE_URL}/api/create_file", |
| content=body_bytes, |
| headers={"Content-Type": "application/json"} |
| ) as resp: |
| async for chunk in resp.aiter_bytes(): |
| if chunk: |
| yield chunk |
| except Exception as e: |
| yield f"data: {json.dumps({'type': 'error', 'message': f'خطای رانفلر: {str(e)}'})}\n\n".encode('utf-8') |
|
|
| headers_to_return = { |
| "Cache-Control": "no-cache, no-transform", |
| "Connection": "keep-alive", |
| "X-Accel-Buffering": "no", |
| "Content-Type": "text/event-stream" |
| } |
| return StreamingResponse(stream_generator(), media_type="text/event-stream", headers=headers_to_return) |
| except Exception as e: |
| return JSONResponse({"status": "error", "message": str(e)}, status_code=500) |
|
|
|
|
| |
| |
| |
|
|
| app.mount("/", WSGIMiddleware(action.app)) |