Spaces:
Paused
Paused
Add support for obfuscating parameters by encrypting & support ip, exp time restriction for generated url
Browse files- README.md +38 -3
- mediaflow_proxy/main.py +23 -0
- mediaflow_proxy/mpd_processor.py +5 -0
- mediaflow_proxy/schemas.py +17 -0
- mediaflow_proxy/utils/crypto_utils.py +99 -0
- mediaflow_proxy/utils/http_utils.py +15 -3
- mediaflow_proxy/utils/m3u8_processor.py +4 -0
README.md
CHANGED
|
@@ -30,7 +30,10 @@ MediaFlow Proxy is a powerful and flexible solution for proxifying various types
|
|
| 30 |
- Retrieve public IP address of the MediaFlow Proxy server for use with Debrid services
|
| 31 |
- Support for HTTP/HTTPS/SOCKS5 proxy forwarding
|
| 32 |
- Protect against unauthorized access and network bandwidth abuses
|
| 33 |
-
- Support for play expired or self-signed SSL certificates server streams
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
## Configuration
|
| 36 |
|
|
@@ -151,10 +154,10 @@ Once the server is running, for more details on the available endpoints and thei
|
|
| 151 |
|
| 152 |
### Examples
|
| 153 |
|
| 154 |
-
#### Proxy HTTPS Stream
|
| 155 |
|
| 156 |
```bash
|
| 157 |
-
mpv "http://localhost:8888/proxy/stream?d=https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/sample-mp4-file.mp4&api_password=your_password"
|
| 158 |
```
|
| 159 |
|
| 160 |
#### Proxy HTTPS self-signed certificate Stream
|
|
@@ -217,6 +220,38 @@ This will output a properly encoded URL that can be used with players like VLC.
|
|
| 217 |
vlc "http://127.0.0.1:8888/proxy/mpd/manifest?key_id=nrQFDeRLSAKTLifXUIPiZg&key=FmY0xnWCPCNaSpRG-tUuTQ&api_password=dedsec&d=https%3A%2F%2Fmedia.axprod.net%2FTestVectors%2Fv7-MultiDRM-SingleKey%2FManifest_1080p_ClearKey.mpd"
|
| 218 |
```
|
| 219 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
### Using MediaFlow Proxy with Debrid Services and Stremio Addons
|
| 221 |
|
| 222 |
MediaFlow Proxy can be particularly useful when working with Debrid services (like Real-Debrid, AllDebrid) and Stremio addons. The `/proxy/ip` endpoint allows you to retrieve the public IP address of the MediaFlow Proxy server, which is crucial for routing Debrid streams correctly.
|
|
|
|
| 30 |
- Retrieve public IP address of the MediaFlow Proxy server for use with Debrid services
|
| 31 |
- Support for HTTP/HTTPS/SOCKS5 proxy forwarding
|
| 32 |
- Protect against unauthorized access and network bandwidth abuses
|
| 33 |
+
- Support for play expired or self-signed SSL certificates server streams `(verify_ssl=false)` default is `false`
|
| 34 |
+
- Flexible request proxy usage control per request `(use_request_proxy=true/false)` default is `true`
|
| 35 |
+
- Obfuscating endpoint parameters by encrypting them to hide sensitive information from third-party.
|
| 36 |
+
- Optional IP-based access control restriction & expiration for encrypted URLs to prevent unauthorized access
|
| 37 |
|
| 38 |
## Configuration
|
| 39 |
|
|
|
|
| 154 |
|
| 155 |
### Examples
|
| 156 |
|
| 157 |
+
#### Proxy HTTPS Stream (without using configured proxy)
|
| 158 |
|
| 159 |
```bash
|
| 160 |
+
mpv "http://localhost:8888/proxy/stream?d=https://jsoncompare.org/LearningContainer/SampleFiles/Video/MP4/sample-mp4-file.mp4&api_password=your_password&use_request_proxy=false"
|
| 161 |
```
|
| 162 |
|
| 163 |
#### Proxy HTTPS self-signed certificate Stream
|
|
|
|
| 220 |
vlc "http://127.0.0.1:8888/proxy/mpd/manifest?key_id=nrQFDeRLSAKTLifXUIPiZg&key=FmY0xnWCPCNaSpRG-tUuTQ&api_password=dedsec&d=https%3A%2F%2Fmedia.axprod.net%2FTestVectors%2Fv7-MultiDRM-SingleKey%2FManifest_1080p_ClearKey.mpd"
|
| 221 |
```
|
| 222 |
|
| 223 |
+
### Generating Encrypted URLs
|
| 224 |
+
|
| 225 |
+
To generate an encrypted URL with optional IP restriction and expiration, Use the `/generate_encrypted_or_encoded_url` endpoint via swagger UI or programmatically as shown below:
|
| 226 |
+
```python
|
| 227 |
+
import requests
|
| 228 |
+
|
| 229 |
+
url = "http://localhost:8888/generate_encrypted_or_encoded_url"
|
| 230 |
+
data = {
|
| 231 |
+
"mediaflow_proxy_url": "http://localhost:8888",
|
| 232 |
+
"endpoint": "/proxy/mpd/manifest",
|
| 233 |
+
"destination_url": "https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p_ClearKey.mpd",
|
| 234 |
+
"query_params": {
|
| 235 |
+
"key_id": "nrQFDeRLSAKTLifXUIPiZg",
|
| 236 |
+
"key": "FmY0xnWCPCNaSpRG-tUuTQ"
|
| 237 |
+
},
|
| 238 |
+
"request_headers": {
|
| 239 |
+
"referer": "https://media.axprod.net/",
|
| 240 |
+
"origin": "https://media.axprod.net",
|
| 241 |
+
},
|
| 242 |
+
"expiration": 3600, # URL will expire in 1 hour
|
| 243 |
+
"ip": "123.123.123.123", # Optional: Restrict access to this IP
|
| 244 |
+
"api_password": "your_password"
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
response = requests.post(url, json=data)
|
| 248 |
+
encrypted_url = response.json()["encoded_url"]
|
| 249 |
+
print(encrypted_url)
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
You can then use the `encoded_url` in your player or application to access the media stream.
|
| 253 |
+
|
| 254 |
+
|
| 255 |
### Using MediaFlow Proxy with Debrid Services and Stremio Addons
|
| 256 |
|
| 257 |
MediaFlow Proxy can be particularly useful when working with Debrid services (like Real-Debrid, AllDebrid) and Stremio addons. The `/proxy/ip` endpoint allows you to retrieve the public IP address of the MediaFlow Proxy server, which is crucial for routing Debrid streams correctly.
|
mediaflow_proxy/main.py
CHANGED
|
@@ -9,6 +9,9 @@ from starlette.staticfiles import StaticFiles
|
|
| 9 |
|
| 10 |
from mediaflow_proxy.configs import settings
|
| 11 |
from mediaflow_proxy.routes import proxy_router
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
| 14 |
app = FastAPI()
|
|
@@ -21,6 +24,7 @@ app.add_middleware(
|
|
| 21 |
allow_methods=["*"],
|
| 22 |
allow_headers=["*"],
|
| 23 |
)
|
|
|
|
| 24 |
|
| 25 |
|
| 26 |
async def verify_api_key(api_key: str = Security(api_password_query), api_key_alt: str = Security(api_password_header)):
|
|
@@ -50,6 +54,25 @@ async def get_favicon():
|
|
| 50 |
return RedirectResponse(url="/logo.png")
|
| 51 |
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
app.include_router(proxy_router, prefix="/proxy", tags=["proxy"], dependencies=[Depends(verify_api_key)])
|
| 54 |
|
| 55 |
static_path = resources.files("mediaflow_proxy").joinpath("static")
|
|
|
|
| 9 |
|
| 10 |
from mediaflow_proxy.configs import settings
|
| 11 |
from mediaflow_proxy.routes import proxy_router
|
| 12 |
+
from mediaflow_proxy.schemas import GenerateUrlRequest
|
| 13 |
+
from mediaflow_proxy.utils.crypto_utils import EncryptionHandler, EncryptionMiddleware
|
| 14 |
+
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url
|
| 15 |
|
| 16 |
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
| 17 |
app = FastAPI()
|
|
|
|
| 24 |
allow_methods=["*"],
|
| 25 |
allow_headers=["*"],
|
| 26 |
)
|
| 27 |
+
app.add_middleware(EncryptionMiddleware)
|
| 28 |
|
| 29 |
|
| 30 |
async def verify_api_key(api_key: str = Security(api_password_query), api_key_alt: str = Security(api_password_header)):
|
|
|
|
| 54 |
return RedirectResponse(url="/logo.png")
|
| 55 |
|
| 56 |
|
| 57 |
+
@app.post("/generate_encrypted_or_encoded_url")
|
| 58 |
+
async def generate_encrypted_or_encoded_url(request: GenerateUrlRequest):
|
| 59 |
+
if "api_password" not in request.query_params:
|
| 60 |
+
request.query_params["api_password"] = request.api_password
|
| 61 |
+
|
| 62 |
+
encoded_url = encode_mediaflow_proxy_url(
|
| 63 |
+
request.mediaflow_proxy_url,
|
| 64 |
+
request.endpoint,
|
| 65 |
+
request.destination_url,
|
| 66 |
+
request.query_params,
|
| 67 |
+
request.request_headers,
|
| 68 |
+
request.response_headers,
|
| 69 |
+
EncryptionHandler(request.api_password) if request.api_password else None,
|
| 70 |
+
request.expiration,
|
| 71 |
+
str(request.ip) if request.ip else None,
|
| 72 |
+
)
|
| 73 |
+
return {"encoded_url": encoded_url}
|
| 74 |
+
|
| 75 |
+
|
| 76 |
app.include_router(proxy_router, prefix="/proxy", tags=["proxy"], dependencies=[Depends(verify_api_key)])
|
| 77 |
|
| 78 |
static_path = resources.files("mediaflow_proxy").joinpath("static")
|
mediaflow_proxy/mpd_processor.py
CHANGED
|
@@ -7,6 +7,7 @@ from fastapi import Request, Response, HTTPException
|
|
| 7 |
|
| 8 |
from mediaflow_proxy.configs import settings
|
| 9 |
from mediaflow_proxy.drm.decrypter import decrypt_segment
|
|
|
|
| 10 |
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url, get_original_scheme, ProxyRequestHeaders
|
| 11 |
|
| 12 |
logger = logging.getLogger(__name__)
|
|
@@ -107,6 +108,7 @@ def build_hls(mpd_dict: dict, request: Request, key_id: str = None, key: str = N
|
|
| 107 |
"""
|
| 108 |
hls = ["#EXTM3U", "#EXT-X-VERSION:6"]
|
| 109 |
query_params = dict(request.query_params)
|
|
|
|
| 110 |
|
| 111 |
video_profiles = {}
|
| 112 |
audio_profiles = {}
|
|
@@ -120,6 +122,7 @@ def build_hls(mpd_dict: dict, request: Request, key_id: str = None, key: str = N
|
|
| 120 |
playlist_url = encode_mediaflow_proxy_url(
|
| 121 |
proxy_url,
|
| 122 |
query_params=query_params,
|
|
|
|
| 123 |
)
|
| 124 |
|
| 125 |
if "video" in profile["mimeType"]:
|
|
@@ -193,6 +196,7 @@ def build_hls_playlist(mpd_dict: dict, profiles: list[dict], request: Request) -
|
|
| 193 |
query_params = dict(request.query_params)
|
| 194 |
query_params.pop("profile_id", None)
|
| 195 |
query_params.pop("d", None)
|
|
|
|
| 196 |
|
| 197 |
for segment in segments:
|
| 198 |
if mpd_dict["isLive"]:
|
|
@@ -207,6 +211,7 @@ def build_hls_playlist(mpd_dict: dict, profiles: list[dict], request: Request) -
|
|
| 207 |
encode_mediaflow_proxy_url(
|
| 208 |
proxy_url,
|
| 209 |
query_params=query_params,
|
|
|
|
| 210 |
)
|
| 211 |
)
|
| 212 |
added_segments += 1
|
|
|
|
| 7 |
|
| 8 |
from mediaflow_proxy.configs import settings
|
| 9 |
from mediaflow_proxy.drm.decrypter import decrypt_segment
|
| 10 |
+
from mediaflow_proxy.utils.crypto_utils import encryption_handler
|
| 11 |
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url, get_original_scheme, ProxyRequestHeaders
|
| 12 |
|
| 13 |
logger = logging.getLogger(__name__)
|
|
|
|
| 108 |
"""
|
| 109 |
hls = ["#EXTM3U", "#EXT-X-VERSION:6"]
|
| 110 |
query_params = dict(request.query_params)
|
| 111 |
+
has_encrypted = query_params.pop("has_encrypted", False)
|
| 112 |
|
| 113 |
video_profiles = {}
|
| 114 |
audio_profiles = {}
|
|
|
|
| 122 |
playlist_url = encode_mediaflow_proxy_url(
|
| 123 |
proxy_url,
|
| 124 |
query_params=query_params,
|
| 125 |
+
encryption_handler=encryption_handler if has_encrypted else None,
|
| 126 |
)
|
| 127 |
|
| 128 |
if "video" in profile["mimeType"]:
|
|
|
|
| 196 |
query_params = dict(request.query_params)
|
| 197 |
query_params.pop("profile_id", None)
|
| 198 |
query_params.pop("d", None)
|
| 199 |
+
has_encrypted = query_params.pop("has_encrypted", False)
|
| 200 |
|
| 201 |
for segment in segments:
|
| 202 |
if mpd_dict["isLive"]:
|
|
|
|
| 211 |
encode_mediaflow_proxy_url(
|
| 212 |
proxy_url,
|
| 213 |
query_params=query_params,
|
| 214 |
+
encryption_handler=encryption_handler if has_encrypted else None,
|
| 215 |
)
|
| 216 |
)
|
| 217 |
added_segments += 1
|
mediaflow_proxy/schemas.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field, IPvAnyAddress
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class GenerateUrlRequest(BaseModel):
|
| 5 |
+
mediaflow_proxy_url: str = Field(..., description="The base URL for the mediaflow proxy.")
|
| 6 |
+
endpoint: str | None = Field(None, description="The specific endpoint to be appended to the base URL.")
|
| 7 |
+
destination_url: str | None = Field(None, description="The destination URL to which the request will be proxied.")
|
| 8 |
+
query_params: dict | None = Field(None, description="Query parameters to be included in the request.")
|
| 9 |
+
request_headers: dict | None = Field(None, description="Headers to be included in the request.")
|
| 10 |
+
response_headers: dict | None = Field(None, description="Headers to be included in the response.")
|
| 11 |
+
expiration: int | None = Field(
|
| 12 |
+
None, description="Expiration time for the URL in seconds. If not provided, the URL will not expire."
|
| 13 |
+
)
|
| 14 |
+
api_password: str | None = Field(
|
| 15 |
+
None, description="API password for encryption. If not provided, the URL will only be encoded."
|
| 16 |
+
)
|
| 17 |
+
ip: IPvAnyAddress | None = Field(None, description="The IP address to restrict the URL to.")
|
mediaflow_proxy/utils/crypto_utils.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import base64
|
| 2 |
+
import json
|
| 3 |
+
import time
|
| 4 |
+
from urllib.parse import urlencode
|
| 5 |
+
|
| 6 |
+
from Crypto.Cipher import AES
|
| 7 |
+
from Crypto.Random import get_random_bytes
|
| 8 |
+
from Crypto.Util.Padding import pad, unpad
|
| 9 |
+
from fastapi import HTTPException, Request
|
| 10 |
+
from starlette.middleware.base import BaseHTTPMiddleware
|
| 11 |
+
from starlette.responses import JSONResponse
|
| 12 |
+
|
| 13 |
+
from mediaflow_proxy.configs import settings
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class EncryptionHandler:
|
| 17 |
+
def __init__(self, secret_key: str):
|
| 18 |
+
self.secret_key = secret_key.encode("utf-8").ljust(32)[:32]
|
| 19 |
+
|
| 20 |
+
def encrypt_data(self, data: dict, expiration: int = None, ip: str = None) -> str:
|
| 21 |
+
if expiration:
|
| 22 |
+
data["exp"] = int(time.time()) + expiration
|
| 23 |
+
if ip:
|
| 24 |
+
data["ip"] = ip
|
| 25 |
+
json_data = json.dumps(data).encode("utf-8")
|
| 26 |
+
iv = get_random_bytes(16)
|
| 27 |
+
cipher = AES.new(self.secret_key, AES.MODE_CBC, iv)
|
| 28 |
+
encrypted_data = cipher.encrypt(pad(json_data, AES.block_size))
|
| 29 |
+
return base64.urlsafe_b64encode(iv + encrypted_data).decode("utf-8")
|
| 30 |
+
|
| 31 |
+
def decrypt_data(self, token: str, client_ip: str) -> dict:
|
| 32 |
+
try:
|
| 33 |
+
encrypted_data = base64.urlsafe_b64decode(token.encode("utf-8"))
|
| 34 |
+
iv = encrypted_data[:16]
|
| 35 |
+
cipher = AES.new(self.secret_key, AES.MODE_CBC, iv)
|
| 36 |
+
decrypted_data = unpad(cipher.decrypt(encrypted_data[16:]), AES.block_size)
|
| 37 |
+
data = json.loads(decrypted_data)
|
| 38 |
+
|
| 39 |
+
if "exp" in data:
|
| 40 |
+
if data["exp"] < time.time():
|
| 41 |
+
raise HTTPException(status_code=401, detail="Token has expired")
|
| 42 |
+
del data["exp"] # Remove expiration from the data
|
| 43 |
+
|
| 44 |
+
if "ip" in data:
|
| 45 |
+
if data["ip"] != client_ip:
|
| 46 |
+
raise HTTPException(status_code=403, detail="IP address mismatch")
|
| 47 |
+
del data["ip"] # Remove IP from the data
|
| 48 |
+
|
| 49 |
+
return data
|
| 50 |
+
except Exception as e:
|
| 51 |
+
raise HTTPException(status_code=401, detail="Invalid or expired token")
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class EncryptionMiddleware(BaseHTTPMiddleware):
|
| 55 |
+
def __init__(self, app):
|
| 56 |
+
super().__init__(app)
|
| 57 |
+
self.encryption_handler = encryption_handler
|
| 58 |
+
|
| 59 |
+
async def dispatch(self, request: Request, call_next):
|
| 60 |
+
encrypted_token = request.query_params.get("token")
|
| 61 |
+
if encrypted_token:
|
| 62 |
+
try:
|
| 63 |
+
client_ip = self.get_client_ip(request)
|
| 64 |
+
decrypted_data = self.encryption_handler.decrypt_data(encrypted_token, client_ip)
|
| 65 |
+
# Modify request query parameters with decrypted data
|
| 66 |
+
query_params = dict(request.query_params)
|
| 67 |
+
query_params.pop("token") # Remove the encrypted token from query params
|
| 68 |
+
query_params.update(decrypted_data) # Add decrypted data to query params
|
| 69 |
+
query_params["has_encrypted"] = True
|
| 70 |
+
|
| 71 |
+
# Create a new request scope with updated query parameters
|
| 72 |
+
new_query_string = urlencode(query_params)
|
| 73 |
+
request.scope["query_string"] = new_query_string.encode()
|
| 74 |
+
request._query_params = query_params
|
| 75 |
+
except HTTPException as e:
|
| 76 |
+
return JSONResponse(content={"error": str(e.detail)}, status_code=e.status_code)
|
| 77 |
+
|
| 78 |
+
response = await call_next(request)
|
| 79 |
+
return response
|
| 80 |
+
|
| 81 |
+
@staticmethod
|
| 82 |
+
def get_client_ip(request: Request) -> str | None:
|
| 83 |
+
"""
|
| 84 |
+
Extract the client's real IP address from the request headers or fallback to the client host.
|
| 85 |
+
"""
|
| 86 |
+
x_forwarded_for = request.headers.get("X-Forwarded-For")
|
| 87 |
+
if x_forwarded_for:
|
| 88 |
+
# In some cases, this header can contain multiple IPs
|
| 89 |
+
# separated by commas.
|
| 90 |
+
# The first one is the original client's IP.
|
| 91 |
+
return x_forwarded_for.split(",")[0].strip()
|
| 92 |
+
# Fallback to X-Real-IP if X-Forwarded-For is not available
|
| 93 |
+
x_real_ip = request.headers.get("X-Real-IP")
|
| 94 |
+
if x_real_ip:
|
| 95 |
+
return x_real_ip
|
| 96 |
+
return request.client.host if request.client else "127.0.0.1"
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
encryption_handler = EncryptionHandler(settings.api_password)
|
mediaflow_proxy/utils/http_utils.py
CHANGED
|
@@ -3,6 +3,7 @@ import typing
|
|
| 3 |
from dataclasses import dataclass
|
| 4 |
from functools import partial
|
| 5 |
from urllib import parse
|
|
|
|
| 6 |
|
| 7 |
import anyio
|
| 8 |
import httpx
|
|
@@ -16,6 +17,7 @@ from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_excep
|
|
| 16 |
|
| 17 |
from mediaflow_proxy.configs import settings
|
| 18 |
from mediaflow_proxy.const import SUPPORTED_REQUEST_HEADERS
|
|
|
|
| 19 |
|
| 20 |
logger = logging.getLogger(__name__)
|
| 21 |
|
|
@@ -215,9 +217,12 @@ def encode_mediaflow_proxy_url(
|
|
| 215 |
query_params: dict | None = None,
|
| 216 |
request_headers: dict | None = None,
|
| 217 |
response_headers: dict | None = None,
|
|
|
|
|
|
|
|
|
|
| 218 |
) -> str:
|
| 219 |
"""
|
| 220 |
-
Encodes a MediaFlow proxy URL with query parameters and headers.
|
| 221 |
|
| 222 |
Args:
|
| 223 |
mediaflow_proxy_url (str): The base MediaFlow proxy URL.
|
|
@@ -226,6 +231,9 @@ def encode_mediaflow_proxy_url(
|
|
| 226 |
query_params (dict, optional): Additional query parameters to include. Defaults to None.
|
| 227 |
request_headers (dict, optional): Headers to include as query parameters. Defaults to None.
|
| 228 |
response_headers (dict, optional): Headers to include as query parameters. Defaults to None.
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
Returns:
|
| 231 |
str: The encoded MediaFlow proxy URL.
|
|
@@ -243,8 +251,12 @@ def encode_mediaflow_proxy_url(
|
|
| 243 |
query_params.update(
|
| 244 |
{key if key.startswith("r_") else f"r_{key}": value for key, value in response_headers.items()}
|
| 245 |
)
|
| 246 |
-
|
| 247 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
# Construct the full URL
|
| 250 |
if endpoint is None:
|
|
|
|
| 3 |
from dataclasses import dataclass
|
| 4 |
from functools import partial
|
| 5 |
from urllib import parse
|
| 6 |
+
from urllib.parse import urlencode
|
| 7 |
|
| 8 |
import anyio
|
| 9 |
import httpx
|
|
|
|
| 17 |
|
| 18 |
from mediaflow_proxy.configs import settings
|
| 19 |
from mediaflow_proxy.const import SUPPORTED_REQUEST_HEADERS
|
| 20 |
+
from mediaflow_proxy.utils.crypto_utils import EncryptionHandler
|
| 21 |
|
| 22 |
logger = logging.getLogger(__name__)
|
| 23 |
|
|
|
|
| 217 |
query_params: dict | None = None,
|
| 218 |
request_headers: dict | None = None,
|
| 219 |
response_headers: dict | None = None,
|
| 220 |
+
encryption_handler: EncryptionHandler = None,
|
| 221 |
+
expiration: int = None,
|
| 222 |
+
ip: str = None,
|
| 223 |
) -> str:
|
| 224 |
"""
|
| 225 |
+
Encodes & Encrypt (Optional) a MediaFlow proxy URL with query parameters and headers.
|
| 226 |
|
| 227 |
Args:
|
| 228 |
mediaflow_proxy_url (str): The base MediaFlow proxy URL.
|
|
|
|
| 231 |
query_params (dict, optional): Additional query parameters to include. Defaults to None.
|
| 232 |
request_headers (dict, optional): Headers to include as query parameters. Defaults to None.
|
| 233 |
response_headers (dict, optional): Headers to include as query parameters. Defaults to None.
|
| 234 |
+
encryption_handler (EncryptionHandler, optional): The encryption handler to use. Defaults to None.
|
| 235 |
+
expiration (int, optional): The expiration time for the encrypted token. Defaults to None.
|
| 236 |
+
ip (str, optional): The public IP address to include in the query parameters. Defaults to None.
|
| 237 |
|
| 238 |
Returns:
|
| 239 |
str: The encoded MediaFlow proxy URL.
|
|
|
|
| 251 |
query_params.update(
|
| 252 |
{key if key.startswith("r_") else f"r_{key}": value for key, value in response_headers.items()}
|
| 253 |
)
|
| 254 |
+
|
| 255 |
+
if encryption_handler:
|
| 256 |
+
encrypted_token = encryption_handler.encrypt_data(query_params, expiration, ip)
|
| 257 |
+
encoded_params = urlencode({"token": encrypted_token})
|
| 258 |
+
else:
|
| 259 |
+
encoded_params = urlencode(query_params)
|
| 260 |
|
| 261 |
# Construct the full URL
|
| 262 |
if endpoint is None:
|
mediaflow_proxy/utils/m3u8_processor.py
CHANGED
|
@@ -3,6 +3,7 @@ from urllib import parse
|
|
| 3 |
|
| 4 |
from pydantic import HttpUrl
|
| 5 |
|
|
|
|
| 6 |
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url, get_original_scheme
|
| 7 |
|
| 8 |
|
|
@@ -74,10 +75,13 @@ class M3U8Processor:
|
|
| 74 |
str: The proxied URL.
|
| 75 |
"""
|
| 76 |
full_url = parse.urljoin(base_url, url)
|
|
|
|
|
|
|
| 77 |
|
| 78 |
return encode_mediaflow_proxy_url(
|
| 79 |
self.mediaflow_proxy_url,
|
| 80 |
"",
|
| 81 |
full_url,
|
| 82 |
query_params=dict(self.request.query_params),
|
|
|
|
| 83 |
)
|
|
|
|
| 3 |
|
| 4 |
from pydantic import HttpUrl
|
| 5 |
|
| 6 |
+
from mediaflow_proxy.utils.crypto_utils import encryption_handler
|
| 7 |
from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url, get_original_scheme
|
| 8 |
|
| 9 |
|
|
|
|
| 75 |
str: The proxied URL.
|
| 76 |
"""
|
| 77 |
full_url = parse.urljoin(base_url, url)
|
| 78 |
+
query_params = dict(self.request.query_params)
|
| 79 |
+
has_encrypted = query_params.pop("has_encrypted", False)
|
| 80 |
|
| 81 |
return encode_mediaflow_proxy_url(
|
| 82 |
self.mediaflow_proxy_url,
|
| 83 |
"",
|
| 84 |
full_url,
|
| 85 |
query_params=dict(self.request.query_params),
|
| 86 |
+
encryption_handler=encryption_handler if has_encrypted else None,
|
| 87 |
)
|