Opera8 commited on
Commit
ce62daa
·
verified ·
1 Parent(s): dd0de60

Delete templates/app.py

Browse files
Files changed (1) hide show
  1. templates/app.py +0 -861
templates/app.py DELETED
@@ -1,861 +0,0 @@
1
- import os
2
- import asyncio
3
- import requests
4
- import httpx
5
- import json
6
- import uuid
7
- import time
8
- from datetime import date
9
- from fastapi import FastAPI, Request, BackgroundTasks
10
- from fastapi.responses import HTMLResponse, FileResponse, JSONResponse, RedirectResponse, StreamingResponse
11
-
12
- # 🟢 ایمپورت و فراخوانی ماژول مستقل روبیکا بات
13
- from rubikabot import router as rubikabot_router
14
- from soti import router as soti_router
15
-
16
- app = FastAPI()
17
- app.include_router(rubikabot_router) # 🟢 ثبت تمام آدرس‌های آپلود و دانلودد روبیکا به صورت یکپارچه
18
- app.include_router(soti_router)
19
-
20
- # ایجاد پوشه‌ههای مشترک برای وضعیت‌ها، فایل‌های صوتی و دیتای مصرف کاربران
21
- JOBS_DIR = "./tts_jobs"
22
- USAGE_DIR = "./tts_usage"
23
- os.makedirs(JOBS_DIR, exist_ok=True)
24
- os.makedirs(USAGE_DIR, exist_ok=True)
25
-
26
- # 🔒 متغیرها و دیتابیس حافظه موقت رانفلر برای قفل دوگانه (IP + Fingerprint)
27
- CLONE_USAGE_LIMIT = 1
28
- EDIT_USAGE_LIMIT = 5
29
-
30
- def get_client_ip(request: Request) -> str:
31
- """استخراج دقیق آی‌پی واقعی کاربر از پشت پروکسی‌های Cloudflare و هاست رانفلر"""
32
- cf_ip = request.headers.get("CF-Connecting-IP")
33
- if cf_ip:
34
- return cf_ip.strip()
35
- x_forwarded_for = request.headers.get("X-Forwarded-For")
36
- if x_forwarded_for:
37
- return x_forwarded_for.split(",")[0].strip()
38
- if request.client and request.client.host:
39
- return request.client.host
40
- return "0.0.0.0"
41
-
42
- # --- بخش مدیریت دائمی اعتبار کلون صدا (ذخیره روی دیسک رانفلر) ---
43
- def get_local_clone_usage(key: str) -> dict:
44
- today_str = date.today().isoformat()
45
- # تولید آدرس فایل ذخیره بر اساس اثر انگشت یا آی‌پی کاربر
46
- safe_key = "".join([c for c in key if c.isalnum() or c in ["-", "_", "."]])
47
- file_path = os.path.join(USAGE_DIR, f"clone_{safe_key}.json")
48
- if os.path.exists(file_path):
49
- try:
50
- with open(file_path, "r", encoding="utf-8") as f:
51
- data = json.load(f)
52
- if data.get("date") == today_str:
53
- return data
54
- except: pass
55
- return {"count": 0, "date": today_str}
56
-
57
- def save_local_clone_usage(key: str, data: dict):
58
- safe_key = "".join([c for c in key if c.isalnum() or c in ["-", "_", "."]])
59
- file_path = os.path.join(USAGE_DIR, f"clone_{safe_key}.json")
60
- try:
61
- with open(file_path, "w", encoding="utf-8") as f:
62
- json.dump(data, f)
63
- except: pass
64
-
65
- def check_local_clone_limit(fingerprint: str, ip: str) -> bool:
66
- """بررسی محدودیت کلون صدا: اگر اثر انگشت یا آی‌پی سهمیه‌اش پر باشد، بلاک می‌کند"""
67
- for key in [fingerprint, ip]:
68
- if not key: continue
69
- data = get_local_clone_usage(key)
70
- if data["count"] >= CLONE_USAGE_LIMIT:
71
- return True
72
- return False
73
-
74
- def use_local_clone(fingerprint: str, ip: str):
75
- """ثبت مصرف سهمیه کلون صدا روی هارد دیسک رانفلر"""
76
- for key in [fingerprint, ip]:
77
- if not key: continue
78
- data = get_local_clone_usage(key)
79
- data["count"] += 1
80
- save_local_clone_usage(key, data)
81
-
82
- # --- بخش مدیریت دائمی اعتبار ویرایش تصاویر (ذخیره روی دیسک رانفلر) ---
83
- def get_local_edit_usage(key: str) -> dict:
84
- current_week = date.today().isocalendar()[1]
85
- safe_key = "".join([c for c in key if c.isalnum() or c in ["-", "_", "."]])
86
- file_path = os.path.join(USAGE_DIR, f"edit_{safe_key}.json")
87
- if os.path.exists(file_path):
88
- try:
89
- with open(file_path, "r", encoding="utf-8") as f:
90
- data = json.load(f)
91
- if data.get("week") == current_week:
92
- return data
93
- except: pass
94
- return {"count": 0, "week": current_week}
95
-
96
- def save_local_edit_usage(key: str, data: dict):
97
- safe_key = "".join([c for c in key if c.isalnum() or c in ["-", "_", "."]])
98
- file_path = os.path.join(USAGE_DIR, f"edit_{safe_key}.json")
99
- try:
100
- with open(file_path, "w", encoding="utf-8") as f:
101
- json.dump(data, f)
102
- except: pass
103
-
104
- def check_local_edit_limit(fingerprint: str, ip: str) -> bool:
105
- """بررسی محدودیت ادیت عکس: اگر اثر انگشت یا آی‌پی سهمیه هفتگی‌اش پر باشد، بلاک می‌کند"""
106
- for key in [fingerprint, ip]:
107
- if not key: continue
108
- data = get_local_edit_usage(key)
109
- if data["count"] >= EDIT_USAGE_LIMIT:
110
- return True
111
- return False
112
-
113
- def use_local_edit(fingerprint: str, ip: str):
114
- """ثبت مصرف سهمیه ویرایش عکس روی هارد دیسک رانفلر"""
115
- for key in [fingerprint, ip]:
116
- if not key: continue
117
- data = get_local_edit_usage(key)
118
- data["count"] += 1
119
- save_local_edit_usage(key, data)
120
-
121
-
122
- def clean_old_files():
123
- """پاکسازی خودکار فایل‌های قدیمی برای جلوگیری از پر شدن حافظه سرور"""
124
- now = time.time()
125
-
126
- # فایل‌های صوتی و جاب‌ها بعد از ۳۰ دقیقه (۱۸۰۰ ثانیه) حذف می‌شوند
127
- try:
128
- for f in os.listdir(JOBS_DIR):
129
- fpath = os.path.join(JOBS_DIR, f)
130
- if os.path.isfile(fpath) and os.stat(fpath).st_mtime < now - 1800:
131
- os.remove(fpath)
132
- except: pass
133
-
134
- # لاگ‌های اعتبار روزانه کاربران بعد از ۴۸ ساعت (۱۷۲۸۰۰ ثانیه) حذف می‌شوند
135
- try:
136
- for f in os.listdir(USAGE_DIR):
137
- fpath = os.path.join(USAGE_DIR, f)
138
- if os.path.isfile(fpath) and os.stat(fpath).st_mtime < now - 172800:
139
- os.remove(fpath)
140
- except: pass
141
-
142
- def save_job_state(job_id: str, status: str, audio_data: bytes = None, error_message: str = None):
143
- """ذخیره وضعیت کار در فایل متنی مشترک دیسک"""
144
- clean_old_files()
145
-
146
- state = {
147
- "status": status,
148
- "error_message": error_message,
149
- "has_audio": audio_data is not None
150
- }
151
- state_file = os.path.join(JOBS_DIR, f"{job_id}.json")
152
- with open(state_file, "w", encoding="utf-8") as f:
153
- json.dump(state, f, ensure_ascii=False)
154
-
155
- if audio_data:
156
- audio_file = os.path.join(JOBS_DIR, f"{job_id}.mp3")
157
- with open(audio_file, "wb") as f:
158
- f.write(audio_data)
159
-
160
- def get_job_state(job_id: str):
161
- """خواندن وضعیت کار از فایل متنی مشترک دیسک"""
162
- state_file = os.path.join(JOBS_DIR, f"{job_id}.json")
163
- if not os.path.exists(state_file):
164
- return None
165
- try:
166
- with open(state_file, "r", encoding="utf-8") as f:
167
- return json.load(f)
168
- except:
169
- return None
170
-
171
- def get_job_audio(job_id: str):
172
- """خواندن دیتای باینری صوتی ذخیره شده روی دیسک"""
173
- audio_file = os.path.join(JOBS_DIR, f"{job_id}.mp3")
174
- if os.path.exists(audio_file):
175
- try:
176
- with open(audio_file, "rb") as f:
177
- return f.read()
178
- except:
179
- return None
180
- return None
181
-
182
- async def run_tts_background(job_id: str, payload: dict, headers: dict):
183
- try:
184
- # برای ارتباط با sada8888-tts2 فقط متن، گوینده و خلاقیت فرستاده می‌شود (مشابه پادکست)
185
- target_payload = {
186
- "text": payload.get("text"),
187
- "speaker": payload.get("speaker"),
188
- "temperature": payload.get("temperature", 1.5)
189
- }
190
-
191
- async with httpx.AsyncClient(timeout=180.0) as client:
192
- resp = await client.post(
193
- "https://sada8888-tts2.hf.space/api/generate",
194
- json=target_payload,
195
- headers={"Content-Type": "application/json"}
196
- )
197
- if resp.status_code == 200:
198
- save_job_state(job_id, "completed", audio_data=resp.content)
199
- else:
200
- save_job_state(job_id, "error", error_message=f"خطا از سرور تولید صدا: {resp.text}")
201
- except Exception as e:
202
- save_job_state(job_id, "error", error_message=str(e))
203
-
204
- # =================================================================
205
- # 1. مسیرهای اصلی و بخش متن به صدا (TTS PRO)
206
- # =================================================================
207
- @app.get("/")
208
- async def root_path():
209
- # صفحه اصلی چیزی نشان نمی‌دهد و دایرکت هم نمی‌شود
210
- return HTMLResponse("Alpha API Server is running.", status_code=200)
211
-
212
- @app.get("/tts")
213
- @app.get("/tts/")
214
- async def old_tts_path():
215
- # لینک قدیمی غیرفعال شد
216
- return HTMLResponse("<h1>این مسیر تغییر یافته است. لطفا از ttspro استفاده کنید.</h1>", status_code=404)
217
-
218
- @app.get("/ttspro", response_class=HTMLResponse)
219
- @app.get("/ttspro/", response_class=HTMLResponse)
220
- async def serve_ttspro():
221
- # فقط این آدرس فایل ttspro.html را باز میکند
222
- if os.path.exists("ttspro.html"):
223
- return FileResponse("ttspro.html")
224
- return HTMLResponse("<h1>خطا: فایل ttspro.html پیدا نشد! لطفا آن را آپلود کنید.</h1>", status_code=404)
225
-
226
-
227
- # =================================================================
228
- # 2. آینه چت بات (پروکسی خام و بدون بافر برای سرعت حداکثری)
229
- # =================================================================
230
- CHAT_SPACE_URL = "https://sada8888-ttslive-chat.hf.space"
231
-
232
- @app.get("/chat", response_class=HTMLResponse)
233
- async def serve_chat():
234
- if os.path.exists("chat.html"):
235
- return FileResponse("chat.html")
236
- return HTMLResponse("<h1>خطا: فایل chat.html ��یدا نشد!</h1>", status_code=404)
237
-
238
- @app.get("/audio/{filename}")
239
- async def serve_chat_audio(filename: str):
240
- # اگر فایل صوتی مربوط به درخواست‌های متن به صدای استاندارد ما بود
241
- if filename.startswith("standard_"):
242
- job_id = filename.replace("standard_", "").replace(".mp3", "").replace(".wav", "")
243
- audio_data = get_job_audio(job_id)
244
- if audio_data:
245
- return StreamingResponse(
246
- iter([audio_data]),
247
- media_type="audio/mpeg"
248
- )
249
- return HTMLResponse("<h1>فایل صوتی یافت نشد</h1>", status_code=404)
250
-
251
- # روال عادی برای چت‌پروکسی
252
- async def iterfile():
253
- try:
254
- async with httpx.AsyncClient(timeout=10.0) as client:
255
- async with client.stream("GET", f"{CHAT_SPACE_URL}/audio/{filename}") as r:
256
- r.raise_for_status()
257
- async for chunk in r.aiter_bytes(chunk_size=8192):
258
- if chunk: yield chunk
259
- except Exception:
260
- yield b""
261
- return StreamingResponse(iterfile(), media_type="audio/wav")
262
-
263
- @app.post("/api/chat_proxy")
264
- async def chat_proxy_bridge(request: Request):
265
- try:
266
- body_bytes = await request.body()
267
-
268
- async def stream_generator():
269
- try:
270
- # اتصال مستقیم به هاگینگ فیس با httpx برای جلوگیری از مسدود شدن ورکرها
271
- async with httpx.AsyncClient(timeout=60.0) as client:
272
- async with client.stream(
273
- "POST",
274
- f"{CHAT_SPACE_URL}/api/chat_proxy",
275
- content=body_bytes,
276
- headers={"Content-Type": "application/json"}
277
- ) as resp:
278
- async for line in resp.aiter_lines():
279
- if line:
280
- yield (line + "\n").encode('utf-8')
281
- except Exception as e:
282
- err = {"status": "error", "message": f"خطا در ارتباط رانفلر با سرور اصلی: {str(e)}"}
283
- yield (json.dumps(err) + "\n").encode('utf-8')
284
-
285
- headers = {
286
- "Cache-Control": "no-cache, no-transform",
287
- "X-Accel-Buffering": "no",
288
- "Connection": "keep-alive",
289
- "Transfer-Encoding": "chunked"
290
- }
291
- return StreamingResponse(stream_generator(), media_type="application/json", headers=headers)
292
- except Exception as e:
293
- return JSONResponse({"status": "error", "message": "سرور موقتا در دسترس نیست."}, status_code=500)
294
-
295
-
296
- # =================================================================
297
- # 3. دور زدن فیلترینگ برای API های متن به صدا (TTS Proxy) با کش یکپارچه
298
- # =================================================================
299
- HF_SPACE_BASE_URL_TTS = "https://ezmarynoori-tts.hf.space"
300
- USAGE_LIMIT_GENERATE = 10
301
-
302
- def get_user_usage(fingerprint: str):
303
- """خواندن آمار مصرف کاربر از دیسک مشترک سرور"""
304
- today_str = date.today().isoformat()
305
- usage_file = os.path.join(USAGE_DIR, f"usage_{fingerprint}.json")
306
- if os.path.exists(usage_file):
307
- try:
308
- with open(usage_file, "r", encoding="utf-8") as f:
309
- data = json.load(f)
310
- if data.get("last_reset") == today_str:
311
- return data
312
- except: pass
313
- return {"count": 0, "last_reset": today_str}
314
-
315
- def increment_user_usage(fingerprint: str):
316
- """افزایش آمار مصرف کاربر و ذخیره روی دیسک مشترک"""
317
- data = get_user_usage(fingerprint)
318
- data["count"] += 1
319
- usage_file = os.path.join(USAGE_DIR, f"usage_{fingerprint}.json")
320
- try:
321
- with open(usage_file, "w", encoding="utf-8") as f:
322
- json.dump(data, f)
323
- except: pass
324
-
325
- def check_limit_tts(payload):
326
- if payload.get('subscriptionStatus') != 'paid':
327
- fingerprint = payload.get('fingerprint')
328
- if not fingerprint:
329
- return False
330
-
331
- record = get_user_usage(fingerprint)
332
- if record["count"] >= USAGE_LIMIT_GENERATE:
333
- return False
334
-
335
- increment_user_usage(fingerprint)
336
- return True
337
-
338
- @app.post("/api/check-credit-tts")
339
- async def check_credit_tts(request: Request):
340
- try:
341
- data = await request.json()
342
- fingerprint = data.get('fingerprint')
343
- subscription_status = data.get('subscriptionStatus')
344
-
345
- if not fingerprint:
346
- return JSONResponse({"message": "Fingerprint required."}, status_code=400)
347
-
348
- if subscription_status == 'paid':
349
- return JSONResponse({"credits_remaining": "unlimited", "limit_reached": False})
350
-
351
- record = get_user_usage(fingerprint)
352
- credits = max(0, USAGE_LIMIT_GENERATE - record["count"])
353
- return JSONResponse({"credits_remaining": credits, "limit_reached": credits <= 0})
354
- except:
355
- return JSONResponse({"message": "Server Error"}, status_code=500)
356
-
357
- @app.post("/api/check-clone-credit")
358
- async def check_clone_credit(request: Request):
359
- """بررسی و شارژ خودکار اعتبار شبیه‌سازی صدا بر اساس قفل دوگانه دائمی (IP + Fingerprint)"""
360
- try:
361
- data = await request.json()
362
- fingerprint = data.get('fingerprint')
363
- subscription_status = data.get('subscriptionStatus')
364
- client_ip = get_client_ip(request)
365
-
366
- if not fingerprint:
367
- return JSONResponse({"message": "Fingerprint required."}, status_code=400)
368
-
369
- if subscription_status == 'paid':
370
- return JSONResponse({"credits_remaining": "unlimited", "limit_reached": False})
371
-
372
- # بررسی قفل دوگانه دیسکی
373
- limit_reached = check_local_clone_limit(fingerprint, client_ip)
374
-
375
- today_str = date.today().isoformat()
376
-
377
- # محاسبه اعتبار باقیمانده بر اساس بدترین سناریو دیسک رانفلر
378
- rec = get_local_clone_usage(fingerprint)
379
- rem_fp = max(0, CLONE_USAGE_LIMIT - rec["count"])
380
-
381
- rec = get_local_clone_usage(client_ip)
382
- rem_ip = max(0, CLONE_USAGE_LIMIT - rec["count"])
383
-
384
- credits_remaining = min(rem_fp, rem_ip)
385
-
386
- return JSONResponse({
387
- "credits_remaining": credits_remaining,
388
- "limit_reached": limit_reached or (credits_remaining <= 0)
389
- })
390
- except Exception as e:
391
- return JSONResponse({"message": f"Server Error: {str(e)}"}, status_code=500)
392
-
393
- @app.post("/api/use-clone-credit")
394
- async def use_clone_credit(request: Request):
395
- """ثبت و کسر یک واحد اعتبار شبیه‌سازی صدا در سیستم قفل دوگانه دیسک رانفلر"""
396
- try:
397
- data = await request.json()
398
- fingerprint = data.get('fingerprint')
399
- subscription_status = data.get('subscriptionStatus')
400
- client_ip = get_client_ip(request)
401
-
402
- if not fingerprint or subscription_status == 'paid':
403
- return JSONResponse({"status": "success"})
404
-
405
- use_local_clone(fingerprint, client_ip)
406
- return JSONResponse({"status": "success"})
407
- except Exception as e:
408
- return JSONResponse({"message": f"Server Error: {str(e)}"}, status_code=500)
409
-
410
- def get_hf_headers(request: Request):
411
- headers = {
412
- "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",
413
- "Accept": "application/json"
414
- }
415
- client_ip = request.headers.get("X-Forwarded-For")
416
- if client_ip:
417
- headers["X-Forwarded-For"] = client_ip
418
- elif request.client and request.client.host:
419
- headers["X-Forwarded-For"] = request.client.host
420
- return headers
421
-
422
- HF_VIDEO_SPACE_URL = "https://sada8888-tts3.hf.space"
423
-
424
- @app.post("/api/generate")
425
- async def submit_job(request: Request, background_tasks: BackgroundTasks):
426
- payload = await request.json()
427
- headers = get_hf_headers(request)
428
-
429
- # --- تشخیص هوشمند درخواست تولید عکس (Flux Pro و غیره) ---
430
- if "action_name" in payload and "prompt" in payload:
431
- try:
432
- resp = await asyncio.to_thread(requests.post, f"{HF_VIDEO_SPACE_URL}/api/generate", json=payload, headers=headers, timeout=120)
433
- return JSONResponse(resp.json(), status_code=resp.status_code)
434
- except Exception as e:
435
- return JSONResponse({"status": "error", "message": f"خطای سرور تصویر: {str(e)}"}, status_code=500)
436
-
437
- # --- تشخیص هوشمند درخواست‌های پادکست (متصل به اسپیس جدید) ---
438
- if "fingerprint" not in payload and "subscriptionStatus" not in payload:
439
- try:
440
- # ارسال امن به اسپیس جدید پادکست
441
- resp = await asyncio.to_thread(
442
- requests.post,
443
- "https://sada8888-tts2.hf.space/api/generate",
444
- json=payload,
445
- headers=headers,
446
- timeout=180
447
- )
448
- # اگر خطای سرور ابری دریافت کردیم با فرمت درست به کلاینت ارور می‌دهیم
449
- if resp.status_code != 200:
450
- return JSONResponse({"error": f"خطا از سرور تولید پادکست: {resp.text}"}, status_code=resp.status_code)
451
-
452
- # بازگرداندن فایل صوتی تولید شده به کلاینت
453
- return StreamingResponse(
454
- iter([resp.content]),
455
- media_type="audio/mpeg"
456
- )
457
- except Exception as e:
458
- return JSONResponse({"error": f"قطعی ارتباط با سرور ابری: {str(e)}"}, status_code=500)
459
-
460
- # --- درخواست‌های عادی TTS از صفحه اصلی (متصل به همان اسپیس پادکست جدید) ---
461
- if not check_limit_tts(payload): return JSONResponse({"message": "سقف تکمیل شده"}, status_code=429)
462
- try:
463
- job_id = f"job_{uuid.uuid4().hex}"
464
- save_job_state(job_id, "processing")
465
- background_tasks.add_task(run_tts_background, job_id, payload, headers)
466
- return JSONResponse({"job_id": job_id}, status_code=200)
467
- except Exception as e:
468
- return JSONResponse({"message": f"خطا در شروع فرآیند: {str(e)}"}, status_code=500)
469
-
470
- @app.post("/api/check_status")
471
- async def check_status(request: Request):
472
- try:
473
- payload = await request.json()
474
- job_id = payload.get("job_id")
475
-
476
- # بررسی وضعیت وظیفه از دیسک مشترک (سازگار با چند ورکر همزمان)
477
- job_state = get_job_state(job_id) if job_id else None
478
-
479
- if job_state:
480
- if job_state["status"] == "completed":
481
- return JSONResponse({
482
- "status": "completed",
483
- "proxy_url": f"/audio/standard_{job_id}.mp3"
484
- })
485
- elif job_state["status"] == "error":
486
- return JSONResponse({
487
- "status": "error",
488
- "result": job_state.get("error_message", "خطا در تولید صدا")
489
- })
490
- else:
491
- return JSONResponse({"status": "processing"})
492
-
493
- # در غیر این صورت (سایر کارهای مربوط به فضاهای دیگر)
494
- resp = await asyncio.to_thread(requests.post, f"{HF_SPACE_BASE_URL_TTS}/api/check_status", json=payload, timeout=30)
495
- return JSONResponse(resp.json(), status_code=resp.status_code)
496
- except: return JSONResponse({"status": "error"}, status_code=500)
497
-
498
- @app.post("/api/generate_podcast")
499
- async def generate_podcast_proxy(request: Request):
500
- payload = await request.json()
501
- if not check_limit_tts(payload): return JSONResponse({"message": "سقف تکمیل شده"}, status_code=429)
502
- try:
503
- resp = await asyncio.to_thread(requests.post, f"{HF_SPACE_BASE_URL_TTS}/api/generate_podcast", json=payload, timeout=30)
504
- return JSONResponse(resp.json(), status_code=resp.status_code)
505
- except: return JSONResponse({"message": "خطا"}, status_code=500)
506
-
507
- @app.post("/api/podcast_status")
508
- async def podcast_status_proxy(request: Request):
509
- try:
510
- payload = await request.json()
511
- resp = await asyncio.to_thread(requests.post, f"{HF_SPACE_BASE_URL_TTS}/api/podcast_status", json=payload, timeout=30)
512
- return JSONResponse(resp.json(), status_code=resp.status_code)
513
- except: return JSONResponse({"status": "error"}, status_code=500)
514
-
515
-
516
- # =================================================================
517
- # 4. سرویس استودیوی پادکست و مسیریابی هوشمند دیتابیس‌ها
518
- # =================================================================
519
- # لینک اسپیس جدید پادکست
520
- HF_PODCAST_SPACE_URL = "https://sada8888-tts2.hf.space"
521
-
522
- @app.get("/podcast", response_class=HTMLResponse)
523
- @app.get("/podcast/", response_class=HTMLResponse)
524
- async def serve_podcast():
525
- if os.path.exists("podcast.html"):
526
- return FileResponse("podcast.html")
527
- return HTMLResponse("<h1>خطا: فایل podcast.html پیدا نشد!</h1>", status_code=404)
528
-
529
- @app.post("/api/check-credit")
530
- async def smart_check_credit(request: Request):
531
- try:
532
- payload = await request.json()
533
- headers = get_hf_headers(request)
534
- referer = request.headers.get("referer", "").lower()
535
-
536
- if "podcast" in referer:
537
- url = f"{HF_PODCAST_SPACE_URL}/api/check-credit"
538
- else:
539
- url = f"{HF_VIDEO_SPACE_URL}/api/check-credit"
540
-
541
- resp = await asyncio.to_thread(requests.post, url, json=payload, headers=headers, timeout=30)
542
- return JSONResponse(resp.json(), status_code=resp.status_code)
543
- except Exception as e:
544
- return JSONResponse({"status": "error", "message": f"خطا در ارتباط با سرور: {str(e)}"}, status_code=500)
545
-
546
- @app.post("/api/use-credit")
547
- async def smart_use_credit(request: Request):
548
- try:
549
- payload = await request.json()
550
- headers = get_hf_headers(request)
551
- referer = request.headers.get("referer", "").lower()
552
-
553
- if "podcast" in referer:
554
- url = f"{HF_PODCAST_SPACE_URL}/api/use-credit"
555
- else:
556
- url = f"{HF_VIDEO_SPACE_URL}/api/use-credit"
557
-
558
- resp = await asyncio.to_thread(requests.post, url, json=payload, headers=headers, timeout=30)
559
- return JSONResponse(resp.json(), status_code=resp.status_code)
560
- except: return JSONResponse({"status": "error"}, status_code=500)
561
-
562
- @app.post("/api/create-full-podcast")
563
- async def podgen_create_full(request: Request):
564
- try:
565
- payload = await request.json()
566
- headers = get_hf_headers(request)
567
- resp = await asyncio.to_thread(requests.post, f"{HF_PODCAST_SPACE_URL}/api/create-full-podcast", json=payload, headers=headers, timeout=60)
568
- return JSONResponse(resp.json(), status_code=resp.status_code)
569
- except: return JSONResponse({"error": "ارتباط با سرور قطع شد"}, status_code=500)
570
-
571
- @app.get("/api/podcast-status/{task_id}")
572
- async def podgen_status(request: Request, task_id: str):
573
- try:
574
- headers = get_hf_headers(request)
575
- resp = await asyncio.to_thread(requests.get, f"{HF_PODCAST_SPACE_URL}/api/podcast-status/{task_id}", headers=headers, timeout=30)
576
- return JSONResponse(resp.json(), status_code=resp.status_code)
577
- except: return JSONResponse({"status": "error"}, status_code=500)
578
-
579
- @app.post("/api/auto-podcast")
580
- async def podgen_auto(request: Request):
581
- try:
582
- payload = await request.json()
583
- headers = get_hf_headers(request)
584
- resp = await asyncio.to_thread(requests.post, f"{HF_PODCAST_SPACE_URL}/api/auto-podcast", json=payload, headers=headers, timeout=60)
585
- return JSONResponse(resp.json(), status_code=resp.status_code)
586
- except: return JSONResponse({"error": "ارتباط قطع شد"}, status_code=500)
587
-
588
- @app.get("/api/auto-podcast-status/{task_id}")
589
- async def podgen_auto_status(request: Request, task_id: str):
590
- try:
591
- headers = get_hf_headers(request)
592
- resp = await asyncio.to_thread(requests.get, f"{HF_PODCAST_SPACE_URL}/api/auto-podcast-status/{task_id}", headers=headers, timeout=30)
593
- return JSONResponse(resp.json(), status_code=resp.status_code)
594
- except: return JSONResponse({"status": "error"}, status_code=500)
595
-
596
- @app.get("/api/download-podcast/{filename}")
597
- async def podgen_download(request: Request, filename: str):
598
- async def iterfile():
599
- try:
600
- headers = get_hf_headers(request)
601
- async with httpx.AsyncClient(timeout=120.0) as client:
602
- async with client.stream("GET", f"{HF_PODCAST_SPACE_URL}/api/download-podcast/{filename}", headers=headers) as r:
603
- r.raise_for_status()
604
- async for chunk in r.aiter_bytes(chunk_size=65536):
605
- if chunk: yield chunk
606
- except Exception:
607
- yield b""
608
- return StreamingResponse(iterfile(), media_type="audio/mpeg")
609
-
610
-
611
- # =================================================================
612
- # 5. سرویس‌های ساخت ویدیو، تصویر و ویرایش تصویر (اتصال مستقیم خام)
613
- # =================================================================
614
- @app.get("/video", response_class=HTMLResponse)
615
- @app.get("/video/", response_class=HTMLResponse)
616
- async def serve_video_studio():
617
- if os.path.exists("video.html"):
618
- return FileResponse("video.html")
619
- return HTMLResponse("<h1>خطا: فایل video.html پیدا نشد!</h1>", status_code=404)
620
-
621
- @app.get("/flux", response_class=HTMLResponse)
622
- @app.get("/flux/", response_class=HTMLResponse)
623
- async def serve_flux_studio():
624
- if os.path.exists("flux.html"):
625
- return FileResponse("flux.html")
626
- return HTMLResponse("<h1>خطا: فایل flux.html پیدا نشد!</h1>", status_code=404)
627
-
628
- @app.get("/image", response_class=HTMLResponse)
629
- @app.get("/image/", response_class=HTMLResponse)
630
- async def serve_image_studio():
631
- if os.path.exists("image.html"):
632
- return FileResponse("image.html")
633
- elif os.path.exists("flux.html"):
634
- return FileResponse("flux.html")
635
- return HTMLResponse("<h1>خطا: فایل image.html پیدا نشد!</h1>", status_code=404)
636
-
637
- @app.get("/edit", response_class=HTMLResponse)
638
- @app.get("/edit/", response_class=HTMLResponse)
639
- async def serve_edit_studio():
640
- if os.path.exists("edit.html"):
641
- return FileResponse("edit.html")
642
- return HTMLResponse("<h1>خطا: فایل edit.html پیدا نشد!</h1>", status_code=404)
643
-
644
- # --- مسیرهای اعتبار اختصاصی تصاویر ---
645
- @app.post("/api/check-image-credit")
646
- async def proxy_check_image_credit(request: Request):
647
- try:
648
- body = await request.body()
649
- headers = get_hf_headers(request)
650
- if "content-type" in request.headers:
651
- headers["Content-Type"] = request.headers["content-type"]
652
- resp = await asyncio.to_thread(requests.post, f"{HF_VIDEO_SPACE_URL}/api/check-image-credit", data=body, headers=headers, timeout=30)
653
- return JSONResponse(resp.json(), status_code=resp.status_code)
654
- except Exception as e:
655
- return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500)
656
-
657
- @app.post("/api/use-image-credit")
658
- async def proxy_use_image_credit(request: Request):
659
- try:
660
- body = await request.body()
661
- headers = get_hf_headers(request)
662
- if "content-type" in request.headers:
663
- headers["Content-Type"] = request.headers["content-type"]
664
- resp = await asyncio.to_thread(requests.post, f"{HF_VIDEO_SPACE_URL}/api/use-image-credit", data=body, headers=headers, timeout=30)
665
- return JSONResponse(resp.json(), status_code=resp.status_code)
666
- except Exception as e:
667
- return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500)
668
-
669
- # --- مسیرهای اعتبار اختصاصی ویرایش تصویر (فتوشاپ هوش مصنوعی) با قفل دوگانه دائمی رانفلر ---
670
- @app.post("/api/check-edit-credit")
671
- async def proxy_check_edit_credit(request: Request):
672
- try:
673
- data = await request.json()
674
- fingerprint = data.get('fingerprint')
675
- subscription_status = data.get('subscriptionStatus')
676
- client_ip = get_client_ip(request)
677
-
678
- if subscription_status == 'paid':
679
- return JSONResponse({"credits_remaining": "unlimited", "limit_reached": False})
680
-
681
- # بررسی دائمی قفل دوگانه (آی‌پی + اثر انگشت) از دیسک رانفلر
682
- limit_reached = check_local_edit_limit(fingerprint, client_ip)
683
-
684
- current_week = date.today().isocalendar()[1]
685
-
686
- # بررسی بدترین سناریوی سهمیه هفتگی دیسکی
687
- rem_fp = 0
688
- if fingerprint:
689
- rec = get_local_edit_usage(fingerprint)
690
- if rec["week"] == current_week:
691
- rem_fp = max(0, EDIT_USAGE_LIMIT - rec["count"])
692
- else:
693
- rem_fp = EDIT_USAGE_LIMIT
694
- else:
695
- rem_fp = EDIT_USAGE_LIMIT
696
-
697
- rec = get_local_edit_usage(client_ip)
698
- if rec["week"] == current_week:
699
- rem_ip = max(0, EDIT_USAGE_LIMIT - rec["count"])
700
- else:
701
- rem_ip = EDIT_USAGE_LIMIT
702
-
703
- credits_remaining = min(rem_fp, rem_ip)
704
-
705
- return JSONResponse({
706
- "credits_remaining": credits_remaining,
707
- "limit_reached": limit_reached or (credits_remaining <= 0)
708
- })
709
- except Exception as e:
710
- return JSONResponse({"status": "error", "message": f"error: {str(e)}"}, status_code=500)
711
-
712
- @app.post("/api/use-edit-credit")
713
- async def proxy_use_edit_credit(request: Request):
714
- try:
715
- data = await request.json()
716
- fingerprint = data.get('fingerprint')
717
- subscription_status = data.get('subscriptionStatus')
718
- client_ip = get_client_ip(request)
719
-
720
- if subscription_status != 'paid':
721
- use_local_edit(fingerprint, client_ip)
722
- return JSONResponse({"status": "success"})
723
- except Exception as e:
724
- return JSONResponse({"status": "error", "message": f"error: {str(e)}"}, status_code=500)
725
-
726
- # --- پروکسی درخواست‌های تولید و ویرایش ---
727
- @app.post("/api/generate-video")
728
- async def proxy_generate_video(request: Request):
729
- try:
730
- body = await request.body()
731
- headers = get_hf_headers(request)
732
-
733
- if "content-type" in request.headers:
734
- headers["Content-Type"] = request.headers["content-type"]
735
-
736
- resp = await asyncio.to_thread(
737
- requests.post,
738
- f"{HF_VIDEO_SPACE_URL}/api/generate-video",
739
- data=body,
740
- headers=headers,
741
- timeout=300
742
- )
743
- try:
744
- return JSONResponse(resp.json(), status_code=resp.status_code)
745
- except:
746
- return JSONResponse({"status": "error", "message": "خطا در سرور ابری", "details": resp.text}, status_code=502)
747
- except Exception as e:
748
- return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500)
749
-
750
- @app.post("/api/merge-videos")
751
- async def proxy_merge_videos(request: Request):
752
- try:
753
- body = await request.body()
754
- headers = get_hf_headers(request)
755
- if "content-type" in request.headers:
756
- headers["Content-Type"] = request.headers["content-type"]
757
-
758
- resp = await asyncio.to_thread(
759
- requests.post,
760
- f"{HF_VIDEO_SPACE_URL}/api/merge-videos",
761
- data=body,
762
- headers=headers,
763
- timeout=400
764
- )
765
-
766
- if resp.status_code == 200:
767
- return StreamingResponse(
768
- iter([resp.content]),
769
- media_type=resp.headers.get("Content-Type", "video/mp4"),
770
- headers={"Content-Disposition": resp.headers.get("Content-Disposition", "attachment; filename=merged_video.mp4")}
771
- )
772
- else:
773
- try:
774
- err_json = resp.json()
775
- return JSONResponse({"status": "error", "message": err_json.get("error", "خطا در پردازش ویدیو")}, status_code=resp.status_code)
776
- except:
777
- return JSONResponse({"status": "error", "message": "خطا در پردازش و میکس ویدیو"}, status_code=resp.status_code)
778
- except Exception as e:
779
- return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500)
780
-
781
- @app.get("/api/status/{run_id}")
782
- async def proxy_video_status(request: Request, run_id: str):
783
- try:
784
- headers = get_hf_headers(request)
785
- resp = await asyncio.to_thread(requests.get, f"{HF_VIDEO_SPACE_URL}/api/status/{run_id}", headers=headers, timeout=30)
786
- return JSONResponse(resp.json(), status_code=resp.status_code)
787
- except Exception as e:
788
- return JSONResponse({"status": "processing"}, status_code=500)
789
-
790
- @app.get("/static/images/{filename}")
791
- async def proxy_static_images(request: Request, filename: str):
792
- headers = get_hf_headers(request)
793
- url = f"{HF_VIDEO_SPACE_URL}/static/images/{filename}"
794
-
795
- async def async_iterfile():
796
- try:
797
- async with httpx.AsyncClient(timeout=120.0) as client:
798
- async with client.stream("GET", url, headers=headers) as response:
799
- response.raise_for_status()
800
- async for chunk in response.aiter_bytes(chunk_size=65536):
801
- if chunk:
802
- yield chunk
803
- except Exception:
804
- yield b""
805
-
806
- media_type = "application/octet-stream"
807
- if filename.endswith(".mp4"): media_type = "video/mp4"
808
- elif filename.endswith(".png"): media_type = "image/png"
809
- elif filename.endswith(".webp"): media_type = "image/webp"
810
- elif filename.endswith(".jpg") or filename.endswith(".jpeg"): media_type = "image/jpeg"
811
- elif filename.endswith(".txt"): media_type = "text/plain"
812
-
813
- response_headers = {
814
- "Cache-Control": "public, max-age=86400"
815
- }
816
-
817
- return StreamingResponse(async_iterfile(), media_type=media_type, headers=response_headers)
818
-
819
- @app.post("/api/edit")
820
- async def proxy_edit_image(request: Request):
821
- try:
822
- body = await request.body()
823
- headers = get_hf_headers(request)
824
- if "content-type" in request.headers:
825
- headers["Content-Type"] = request.headers["content-type"]
826
-
827
- resp = await asyncio.to_thread(requests.post, f"{HF_VIDEO_SPACE_URL}/api/edit", data=body, headers=headers, timeout=120)
828
- try:
829
- return JSONResponse(resp.json(), status_code=resp.status_code)
830
- except:
831
- return JSONResponse({"status": "error", "message": "خطا در سرور ابری", "details": resp.text}, status_code=502)
832
- except Exception as e:
833
- return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500)
834
-
835
- @app.post("/api/chat")
836
- async def proxy_gemma_chat(request: Request):
837
- try:
838
- body = await request.body()
839
- headers = get_hf_headers(request)
840
- if "content-type" in request.headers:
841
- headers["Content-Type"] = request.headers["content-type"]
842
-
843
- resp = await asyncio.to_thread(requests.post, f"{HF_VIDEO_SPACE_URL}/api/chat", data=body, headers=headers, timeout=120)
844
- try:
845
- return JSONResponse(resp.json(), status_code=resp.status_code)
846
- except:
847
- return JSONResponse({"status": "error", "message": "خطا در سرور ابری", "details": resp.text}, status_code=502)
848
- except Exception as e:
849
- return JSONResponse({"status": "error", "message": f"خطا در ارتباط رانفلر: {str(e)}"}, status_code=500)
850
-
851
- # alphattspro-main/app.py -> انتهای فایل
852
-
853
- # =================================================================
854
- # 6. مسیر استاتیک (باید همیشه در انتهای کد باشد)
855
- # =================================================================
856
- @app.get("/{path:path}")
857
- async def serve_static(path: str):
858
- # کلمات "soti" و "sotifree" به استثناها اضافه شدند تا رانفلر آن‌ها را فایل فرض نکند
859
- if os.path.exists(path) and path not in ["chat", "tts", "ws", "podcast", "video", "flux", "image", "edit", "ttspro", "soti", "sotifree"]:
860
- return FileResponse(path)
861
- return HTMLResponse("Not Found", status_code=404)