File size: 11,293 Bytes
b44f9a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# alphattspro-main/soti.py

import os
import asyncio
import ssl
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Request
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse, FileResponse
import httpx
import websockets

router = APIRouter()

# تعریف آدرس‌های مربوط به هر دو اسپیس مستقل هوش مصنوعی صوتی
SOTI_PRO_HTTP = "https://ezmarynoori-sotipro.hf.space"
SOTI_PRO_WS = "wss://ezmarynoori-sotipro.hf.space/ws"

SOTI_FREE_HTTP = "https://ezmarynoori-sotifree.hf.space"
SOTI_FREE_WS = "wss://ezmarynoori-sotifree.hf.space/ws"

# هدرهای امنیتی جهت اعطای رسمی دسترسی دوربین و میکروفون به مرورگر و وب‌ویو موبایل
PERMISSION_HEADERS = {
    "Permissions-Policy": "camera=*, microphone=*, geolocation=*, autoplay=*",
    "Feature-Policy": "camera *; microphone *; autoplay *",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
    "Access-Control-Allow-Headers": "*"
}

# غیرفعال کردن بررسی سخت‌گیرانه SSL برای جلوگیری از خطای تایید گواهینامه در داکر رانفلر
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE

def get_target_urls(request_or_ws) -> tuple[str, str]:
    """
    مسیریابی هوشمند و پویا بر اساس هدر Referer مرورگر کاربر.
    اگر کاربر در صفحه sotifree باشد به سرور رایگان و در غیر این صورت به سرور پرو متصل می‌شود.
    """
    referer = request_or_ws.headers.get("referer", "").lower()
    if "sotifree" in referer:
        return SOTI_FREE_HTTP, SOTI_FREE_WS
    else:
        return SOTI_PRO_HTTP, SOTI_PRO_WS

@router.websocket("/ws")
async def websocket_proxy(client_ws: WebSocket):
    """
    پروکسی هوشمند وب‌سوکت با مسیریابی پویا بین پرو و رایگان بر اساس ارجاع دهنده (Referer)
    """
    await client_ws.accept()
    
    # تشخیص خودکار مقصد بر اساس آدرس صفحه‌ای که کاربر در آن قرار دارد
    target_http, target_ws = get_target_urls(client_ws)
    
    # هدرهای شبیه‌سازی شده برای عبور از سد امنیتی هاگینگ‌فیس
    headers = {
        "Host": target_http.replace("https://", ""),
        "Origin": target_http,
        "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"
    }
    
    # تشخیص پویا و هوشمند نسخه کتابخانه websockets رانفلر جهت ارسال پارامتر صحیح دست‌تکانی
    ws_kwargs = {}
    try:
        ws_version = getattr(websockets, "__version__", "10.0")
        try:
            major_version = int(ws_version.split('.')[0])
        except ValueError:
            major_version = 10
            
        if major_version >= 14:
            ws_kwargs["additional_headers"] = headers
        else:
            ws_kwargs["extra_headers"] = headers
    except Exception:
        ws_kwargs["extra_headers"] = headers

    try:
        # اتصال امن به وب‌سوکت هدف با غیرفعال کردن چک کردن SSL جهت تضمین پایداری ارتباط
        async with websockets.connect(
            target_ws, 
            ssl=ssl_context,
            **ws_kwargs
        ) as server_ws:
            
            async def client_to_server():
                try:
                    while True:
                        data = await client_ws.receive()
                        if "text" in data and data["text"] is not None:
                            await server_ws.send(data["text"])
                        elif "bytes" in data and data["bytes"] is not None:
                            await server_ws.send(data["bytes"])
                except Exception:
                    pass

            async def server_to_client():
                try:
                    async for message in server_ws:
                        if isinstance(message, str):
                            await client_ws.send_text(message)
                        else:
                            await client_ws.send_bytes(message)
                except Exception:
                    pass

            await asyncio.gather(client_to_server(), server_to_client())
            
    except WebSocketDisconnect:
        pass
    except Exception as e:
        print(f"Soti WS Proxy Error: {e}")
    finally:
        try:
            await client_ws.close()
        except:
            pass

@router.get("/api/instructions")
async def proxy_instructions(request: Request):
    """
    دریافت هوشمند و پروکسی دستورالعمل‌های شخصیت‌ها بر اساس نسخه کاربر (پرو یا رایگان)
    """
    target_http, _ = get_target_urls(request)
    async with httpx.AsyncClient(verify=False) as client:
        try:
            resp = await client.get(f"{target_http}/api/instructions", timeout=10.0)
            return JSONResponse(content=resp.json(), status_code=resp.status_code, headers=PERMISSION_HEADERS)
        except Exception as e:
            return JSONResponse(content={"error": str(e)}, status_code=500, headers=PERMISSION_HEADERS)

# --- صفحات اصلی هردو نسخه ---
@router.get("/soti")
@router.get("/soti/")
async def serve_soti_pro_page():
    async with httpx.AsyncClient(verify=False) as client:
        try:
            resp = await client.get(f"{SOTI_PRO_HTTP}/", timeout=15.0)
            return HTMLResponse(content=resp.text, status_code=resp.status_code, headers=PERMISSION_HEADERS)
        except Exception as e:
            return HTMLResponse(f"<h1>خطا در بارگذاری نرم‌افزار صوتی پرو: {e}</h1>", status_code=500, headers=PERMISSION_HEADERS)

@router.get("/sotifree")
@router.get("/sotifree/")
async def serve_soti_free_page():
    async with httpx.AsyncClient(verify=False) as client:
        try:
            resp = await client.get(f"{SOTI_FREE_HTTP}/", timeout=15.0)
            return HTMLResponse(content=resp.text, status_code=resp.status_code, headers=PERMISSION_HEADERS)
        except Exception as e:
            return HTMLResponse(f"<h1>خطا در بارگذاری نرم‌افزار صوتی رایگان: {e}</h1>", status_code=500, headers=PERMISSION_HEADERS)


@router.get("/static/{file_path:path}")
async def proxy_static_files(request: Request, file_path: str):
    """
    انتقال زنده استاتیک ری‌اکت با تفکیک هوشمند مسیرها بین پرو، رایگان و دیسک رانفلر
    """
    if file_path.startswith("images/"):
        # 🟢 بهینه‌سازی بسیار مهم: لود مستقیم تصویر و ویدیوهای گالری‌ها از هارد دیسک داخلی رانفلر بدون تاخیر شبکه
        filename = file_path.replace("images/", "")
        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"
            return FileResponse(local_path, media_type=media_type, headers=PERMISSION_HEADERS)
        return HTMLResponse("File not found", status_code=404)
    else:
        # تشخیص هوشمند مبدا فایل استاتیک ری‌اکت صوتی (پرو یا رایگان) بر اساس Referer
        target_http, _ = get_target_urls(request)
        url = f"{target_http}/static/{file_path}"

    async def stream_file():
        async with httpx.AsyncClient(verify=False) as client:
            try:
                async with client.stream("GET", url, timeout=20.0) as resp:
                    async for chunk in resp.aiter_bytes():
                        yield chunk
            except Exception:
                yield b""
    
    media_type = "application/octet-stream"
    if file_path.endswith(".js"):
        media_type = "application/javascript"
    elif file_path.endswith(".css"):
        media_type = "text/css"
    elif file_path.endswith(".png"):
        media_type = "image/png"
    elif file_path.endswith(".jpg") or file_path.endswith(".jpeg"):
        media_type = "image/jpeg"
    elif file_path.endswith(".svg"):
        media_type = "image/svg+xml"
    elif file_path.endswith(".mp4"):
        media_type = "video/mp4"
    elif file_path.endswith(".webp"):
        media_type = "image/webp"
        
    return StreamingResponse(stream_file(), media_type=media_type, headers=PERMISSION_HEADERS)

@router.get("/manifest.json")
async def proxy_manifest(request: Request):
    target_http, _ = get_target_urls(request)
    async with httpx.AsyncClient(verify=False) as client:
        try:
            resp = await client.get(f"{target_http}/manifest.json", timeout=10.0)
            return JSONResponse(content=resp.json(), status_code=resp.status_code, headers=PERMISSION_HEADERS)
        except Exception:
            return JSONResponse(content={}, status_code=404, headers=PERMISSION_HEADERS)

@router.get("/favicon.ico")
async def proxy_favicon(request: Request):
    target_http, _ = get_target_urls(request)
    async def stream_fav():
        async with httpx.AsyncClient(verify=False) as client:
            try:
                async with client.stream("GET", f"{target_http}/favicon.ico", timeout=10.0) as r:
                    async for chunk in r.aiter_bytes():
                        yield chunk
            except Exception:
                yield b""
    return StreamingResponse(stream_fav(), media_type="image/x-icon", headers=PERMISSION_HEADERS)

@router.get("/logo192.png")
async def proxy_logo192(request: Request):
    target_http, _ = get_target_urls(request)
    async def stream_logo():
        async with httpx.AsyncClient(verify=False) as client:
            try:
                async with client.stream("GET", f"{target_http}/logo192.png", timeout=10.0) as r:
                    async for chunk in r.aiter_bytes():
                        yield chunk
            except Exception:
                yield b""
    return StreamingResponse(stream_logo(), media_type="image/png", headers=PERMISSION_HEADERS)

@router.get("/logo512.png")
async def proxy_logo512(request: Request):
    target_http, _ = get_target_urls(request)
    async def stream_logo():
        async with httpx.AsyncClient(verify=False) as client:
            try:
                async with client.stream("GET", f"{target_http}/logo512.png", timeout=10.0) as r:
                    async for chunk in r.aiter_bytes():
                        yield chunk
            except Exception:
                yield b""
    return StreamingResponse(stream_logo(), media_type="image/png", headers=PERMISSION_HEADERS)