mirror of
https://github.com/UrloMythus/UnHided.git
synced 2026-04-11 11:50:51 +00:00
updated to lastest version
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
from typing import Annotated
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import quote, unquote
|
||||
import re
|
||||
import logging
|
||||
|
||||
from fastapi import Request, Depends, APIRouter, Query, HTTPException
|
||||
from fastapi.responses import Response, RedirectResponse
|
||||
|
||||
from mediaflow_proxy.handlers import (
|
||||
handle_hls_stream_proxy,
|
||||
handle_stream_request,
|
||||
proxy_stream,
|
||||
get_manifest,
|
||||
get_playlist,
|
||||
@@ -18,10 +22,144 @@ from mediaflow_proxy.schemas import (
|
||||
MPDManifestParams,
|
||||
)
|
||||
from mediaflow_proxy.utils.http_utils import get_proxy_headers, ProxyRequestHeaders
|
||||
from mediaflow_proxy.utils.base64_utils import process_potential_base64_url
|
||||
|
||||
proxy_router = APIRouter()
|
||||
|
||||
|
||||
def sanitize_url(url: str) -> str:
|
||||
"""
|
||||
Sanitize URL to fix common encoding issues and handle base64 encoded URLs.
|
||||
|
||||
Args:
|
||||
url (str): The URL to sanitize.
|
||||
|
||||
Returns:
|
||||
str: The sanitized URL.
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
original_url = url
|
||||
|
||||
# First, try to process potential base64 encoded URLs
|
||||
url = process_potential_base64_url(url)
|
||||
|
||||
# Fix malformed URLs where https%22// should be https://
|
||||
url = re.sub(r'https%22//', 'https://', url)
|
||||
url = re.sub(r'http%22//', 'http://', url)
|
||||
|
||||
# Fix malformed URLs where https%3A%22// should be https://
|
||||
url = re.sub(r'https%3A%22//', 'https://', url)
|
||||
url = re.sub(r'http%3A%22//', 'http://', url)
|
||||
|
||||
# Fix malformed URLs where https:"// should be https:// (after partial decoding)
|
||||
url = re.sub(r'https:"//', 'https://', url)
|
||||
url = re.sub(r'http:"//', 'http://', url)
|
||||
|
||||
# Fix URLs where key_id and key parameters are incorrectly appended to the base URL
|
||||
# This happens when the URL contains &key_id= and &key= which should be handled as proxy parameters
|
||||
if '&key_id=' in url and '&key=' in url:
|
||||
# Split the URL at the first occurrence of &key_id= to separate the base URL from the incorrectly appended parameters
|
||||
base_url = url.split('&key_id=')[0]
|
||||
logger.info(f"Removed incorrectly appended key parameters from URL: '{url}' -> '{base_url}'")
|
||||
url = base_url
|
||||
|
||||
# Log if URL was changed
|
||||
if url != original_url:
|
||||
logger.info(f"URL sanitized: '{original_url}' -> '{url}'")
|
||||
|
||||
# Also try URL decoding to see what we get
|
||||
try:
|
||||
decoded_url = unquote(url)
|
||||
if decoded_url != url:
|
||||
logger.info(f"URL after decoding: '{decoded_url}'")
|
||||
# If after decoding we still have malformed protocol, fix it
|
||||
if ':"/' in decoded_url:
|
||||
# Fix https:"// or http:"// patterns
|
||||
fixed_decoded = re.sub(r'([a-z]+):"//', r'\1://', decoded_url)
|
||||
logger.info(f"Fixed decoded URL: '{fixed_decoded}'")
|
||||
return fixed_decoded
|
||||
except Exception as e:
|
||||
logger.warning(f"Error decoding URL '{url}': {e}")
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def extract_drm_params_from_url(url: str) -> tuple[str, str, str]:
|
||||
"""
|
||||
Extract DRM parameters (key_id and key) from a URL if they are incorrectly appended.
|
||||
|
||||
Args:
|
||||
url (str): The URL that may contain appended DRM parameters.
|
||||
|
||||
Returns:
|
||||
tuple: (clean_url, key_id, key) where clean_url has the parameters removed,
|
||||
and key_id/key are the extracted values (or None if not found).
|
||||
"""
|
||||
logger = logging.getLogger(__name__)
|
||||
key_id = None
|
||||
key = None
|
||||
clean_url = url
|
||||
|
||||
# Check if URL contains incorrectly appended key_id and key parameters
|
||||
if '&key_id=' in url and '&key=' in url:
|
||||
# Extract key_id
|
||||
key_id_match = re.search(r'&key_id=([^&]+)', url)
|
||||
if key_id_match:
|
||||
key_id = key_id_match.group(1)
|
||||
|
||||
# Extract key
|
||||
key_match = re.search(r'&key=([^&]+)', url)
|
||||
if key_match:
|
||||
key = key_match.group(1)
|
||||
|
||||
# Remove the parameters from the URL
|
||||
clean_url = re.sub(r'&key_id=[^&]*', '', url)
|
||||
clean_url = re.sub(r'&key=[^&]*', '', clean_url)
|
||||
|
||||
logger.info(f"Extracted DRM parameters from URL: key_id={key_id}, key={key}")
|
||||
logger.info(f"Cleaned URL: '{url}' -> '{clean_url}'")
|
||||
|
||||
return clean_url, key_id, key
|
||||
|
||||
|
||||
def _check_and_redirect_dlhd_stream(request: Request, destination: str) -> RedirectResponse | None:
|
||||
"""
|
||||
Check if destination contains stream-{numero} pattern and redirect to extractor if needed.
|
||||
|
||||
Args:
|
||||
request (Request): The incoming HTTP request.
|
||||
destination (str): The destination URL to check.
|
||||
|
||||
Returns:
|
||||
RedirectResponse | None: RedirectResponse if redirect is needed, None otherwise.
|
||||
"""
|
||||
import re
|
||||
|
||||
# Check for stream-{numero} pattern (e.g., stream-1, stream-123, etc.)
|
||||
if re.search(r'stream-\d+', destination):
|
||||
from urllib.parse import urlencode
|
||||
|
||||
# Build redirect URL to extractor
|
||||
redirect_params = {
|
||||
"host": "DLHD",
|
||||
"redirect_stream": "true",
|
||||
"d": destination
|
||||
}
|
||||
|
||||
# Preserve api_password if present
|
||||
if "api_password" in request.query_params:
|
||||
redirect_params["api_password"] = request.query_params["api_password"]
|
||||
|
||||
# Build the redirect URL
|
||||
base_url = str(request.url_for("extract_url"))
|
||||
redirect_url = f"{base_url}?{urlencode(redirect_params)}"
|
||||
|
||||
return RedirectResponse(url=redirect_url, status_code=302)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@proxy_router.head("/hls/manifest.m3u8")
|
||||
@proxy_router.head("/hls/manifest.m3u8")
|
||||
@proxy_router.get("/hls/manifest.m3u8")
|
||||
async def hls_manifest_proxy(
|
||||
@@ -40,9 +178,111 @@ async def hls_manifest_proxy(
|
||||
Returns:
|
||||
Response: The HTTP response with the processed m3u8 playlist or streamed content.
|
||||
"""
|
||||
# Sanitize destination URL to fix common encoding issues
|
||||
hls_params.destination = sanitize_url(hls_params.destination)
|
||||
|
||||
# Check if destination contains stream-{numero} pattern and redirect to extractor
|
||||
redirect_response = _check_and_redirect_dlhd_stream(request, hls_params.destination)
|
||||
if redirect_response:
|
||||
return redirect_response
|
||||
|
||||
return await handle_hls_stream_proxy(request, hls_params, proxy_headers)
|
||||
|
||||
|
||||
@proxy_router.get("/hls/segment")
|
||||
async def hls_segment_proxy(
|
||||
request: Request,
|
||||
proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
|
||||
segment_url: str = Query(..., description="URL of the HLS segment"),
|
||||
):
|
||||
"""
|
||||
Proxy HLS segments with optional pre-buffering support.
|
||||
|
||||
Args:
|
||||
request (Request): The incoming HTTP request.
|
||||
segment_url (str): URL of the HLS segment to proxy.
|
||||
proxy_headers (ProxyRequestHeaders): The headers to include in the request.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the segment content.
|
||||
"""
|
||||
from mediaflow_proxy.utils.hls_prebuffer import hls_prebuffer
|
||||
from mediaflow_proxy.configs import settings
|
||||
|
||||
# Sanitize segment URL to fix common encoding issues
|
||||
segment_url = sanitize_url(segment_url)
|
||||
|
||||
# Extract headers for pre-buffering
|
||||
headers = {}
|
||||
for key, value in request.query_params.items():
|
||||
if key.startswith("h_"):
|
||||
headers[key[2:]] = value
|
||||
|
||||
# Try to get segment from pre-buffer cache first
|
||||
if settings.enable_hls_prebuffer:
|
||||
cached_segment = await hls_prebuffer.get_segment(segment_url, headers)
|
||||
if cached_segment:
|
||||
return Response(
|
||||
content=cached_segment,
|
||||
media_type="video/mp2t",
|
||||
headers={
|
||||
"Content-Type": "video/mp2t",
|
||||
"Cache-Control": "public, max-age=3600",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
)
|
||||
|
||||
# Fallback to direct streaming if not in cache
|
||||
return await handle_stream_request("GET", segment_url, proxy_headers)
|
||||
|
||||
|
||||
@proxy_router.get("/dash/segment")
|
||||
async def dash_segment_proxy(
|
||||
request: Request,
|
||||
proxy_headers: Annotated[ProxyRequestHeaders, Depends(get_proxy_headers)],
|
||||
segment_url: str = Query(..., description="URL of the DASH segment"),
|
||||
):
|
||||
"""
|
||||
Proxy DASH segments with optional pre-buffering support.
|
||||
|
||||
Args:
|
||||
request (Request): The incoming HTTP request.
|
||||
segment_url (str): URL of the DASH segment to proxy.
|
||||
proxy_headers (ProxyRequestHeaders): The headers to include in the request.
|
||||
|
||||
Returns:
|
||||
Response: The HTTP response with the segment content.
|
||||
"""
|
||||
from mediaflow_proxy.utils.dash_prebuffer import dash_prebuffer
|
||||
from mediaflow_proxy.configs import settings
|
||||
|
||||
# Sanitize segment URL to fix common encoding issues
|
||||
segment_url = sanitize_url(segment_url)
|
||||
|
||||
# Extract headers for pre-buffering
|
||||
headers = {}
|
||||
for key, value in request.query_params.items():
|
||||
if key.startswith("h_"):
|
||||
headers[key[2:]] = value
|
||||
|
||||
# Try to get segment from pre-buffer cache first
|
||||
if settings.enable_dash_prebuffer:
|
||||
cached_segment = await dash_prebuffer.get_segment(segment_url, headers)
|
||||
if cached_segment:
|
||||
return Response(
|
||||
content=cached_segment,
|
||||
media_type="video/mp4",
|
||||
headers={
|
||||
"Content-Type": "video/mp4",
|
||||
"Cache-Control": "public, max-age=3600",
|
||||
"Access-Control-Allow-Origin": "*"
|
||||
}
|
||||
)
|
||||
|
||||
# Fallback to direct streaming if not in cache
|
||||
return await handle_stream_request("GET", segment_url, proxy_headers)
|
||||
|
||||
|
||||
@proxy_router.head("/stream")
|
||||
@proxy_router.get("/stream")
|
||||
@proxy_router.head("/stream/{filename:path}")
|
||||
@@ -65,6 +305,14 @@ async def proxy_stream_endpoint(
|
||||
Returns:
|
||||
Response: The HTTP response with the streamed content.
|
||||
"""
|
||||
# Sanitize destination URL to fix common encoding issues
|
||||
destination = sanitize_url(destination)
|
||||
|
||||
# Check if destination contains stream-{numero} pattern and redirect to extractor
|
||||
redirect_response = _check_and_redirect_dlhd_stream(request, destination)
|
||||
if redirect_response:
|
||||
return redirect_response
|
||||
|
||||
content_range = proxy_headers.request.get("range", "bytes=0-")
|
||||
if "nan" in content_range.casefold():
|
||||
# Handle invalid range requests "bytes=NaN-NaN"
|
||||
@@ -103,6 +351,21 @@ async def mpd_manifest_proxy(
|
||||
Returns:
|
||||
Response: The HTTP response with the HLS manifest.
|
||||
"""
|
||||
# Extract DRM parameters from destination URL if they are incorrectly appended
|
||||
clean_url, extracted_key_id, extracted_key = extract_drm_params_from_url(manifest_params.destination)
|
||||
|
||||
# Update the destination with the cleaned URL
|
||||
manifest_params.destination = clean_url
|
||||
|
||||
# Use extracted parameters if they exist and the manifest params don't already have them
|
||||
if extracted_key_id and not manifest_params.key_id:
|
||||
manifest_params.key_id = extracted_key_id
|
||||
if extracted_key and not manifest_params.key:
|
||||
manifest_params.key = extracted_key
|
||||
|
||||
# Sanitize destination URL to fix common encoding issues
|
||||
manifest_params.destination = sanitize_url(manifest_params.destination)
|
||||
|
||||
return await get_manifest(request, manifest_params, proxy_headers)
|
||||
|
||||
|
||||
@@ -123,6 +386,21 @@ async def playlist_endpoint(
|
||||
Returns:
|
||||
Response: The HTTP response with the HLS playlist.
|
||||
"""
|
||||
# Extract DRM parameters from destination URL if they are incorrectly appended
|
||||
clean_url, extracted_key_id, extracted_key = extract_drm_params_from_url(playlist_params.destination)
|
||||
|
||||
# Update the destination with the cleaned URL
|
||||
playlist_params.destination = clean_url
|
||||
|
||||
# Use extracted parameters if they exist and the playlist params don't already have them
|
||||
if extracted_key_id and not playlist_params.key_id:
|
||||
playlist_params.key_id = extracted_key_id
|
||||
if extracted_key and not playlist_params.key:
|
||||
playlist_params.key = extracted_key
|
||||
|
||||
# Sanitize destination URL to fix common encoding issues
|
||||
playlist_params.destination = sanitize_url(playlist_params.destination)
|
||||
|
||||
return await get_playlist(request, playlist_params, proxy_headers)
|
||||
|
||||
|
||||
@@ -152,4 +430,4 @@ async def get_mediaflow_proxy_public_ip():
|
||||
Returns:
|
||||
Response: The HTTP response with the public IP address in the form of a JSON object. {"ip": "xxx.xxx.xxx.xxx"}
|
||||
"""
|
||||
return await get_public_ip()
|
||||
return await get_public_ip()
|
||||
Reference in New Issue
Block a user