mirror of
https://github.com/UrloMythus/UnHided.git
synced 2026-04-11 03:40:54 +00:00
Update to newest version
This commit is contained in:
@@ -30,46 +30,70 @@ class DLHDExtractor(BaseExtractor):
|
|||||||
try:
|
try:
|
||||||
# Channel URL is required and serves as the referer
|
# Channel URL is required and serves as the referer
|
||||||
channel_url = url
|
channel_url = url
|
||||||
player_origin = self._get_origin(channel_url)
|
channel_origin = self._get_origin(channel_url) # Channel page origin
|
||||||
|
|
||||||
# Check for direct parameters
|
# Check for direct parameters
|
||||||
player_url = kwargs.get("player_url")
|
player_url_from_arg = kwargs.get("player_url")
|
||||||
stream_url = kwargs.get("stream_url")
|
stream_url_from_arg = kwargs.get("stream_url")
|
||||||
auth_url_base = kwargs.get("auth_url_base")
|
auth_url_base_from_arg = kwargs.get("auth_url_base")
|
||||||
|
|
||||||
|
current_player_url_for_processing: str
|
||||||
|
|
||||||
# If player URL not provided, extract it from channel page
|
# If player URL not provided, extract it from channel page
|
||||||
if not player_url:
|
if not player_url_from_arg:
|
||||||
# Get the channel page to extract the player iframe URL
|
# Get the channel page to extract the player iframe URL
|
||||||
channel_headers = {
|
channel_headers = {
|
||||||
"referer": player_origin + "/",
|
"referer": channel_origin + "/",
|
||||||
"origin": player_origin,
|
"origin": channel_origin,
|
||||||
"user-agent": self.base_headers["user-agent"],
|
"user-agent": self.base_headers["user-agent"],
|
||||||
}
|
}
|
||||||
|
|
||||||
channel_response = await self._make_request(channel_url, headers=channel_headers)
|
channel_response = await self._make_request(channel_url, headers=channel_headers)
|
||||||
player_url = self._extract_player_url(channel_response.text)
|
extracted_iframe_url = self._extract_player_url(channel_response.text)
|
||||||
|
|
||||||
if not player_url:
|
if not extracted_iframe_url:
|
||||||
raise ExtractorError("Could not extract player URL from channel page")
|
raise ExtractorError("Could not extract player URL from channel page")
|
||||||
|
current_player_url_for_processing = extracted_iframe_url
|
||||||
|
else:
|
||||||
|
current_player_url_for_processing = player_url_from_arg
|
||||||
|
|
||||||
if not re.search(r"/stream/([a-zA-Z0-9-]+)", player_url):
|
# Attempt 1: _handle_vecloud with current_player_url_for_processing
|
||||||
iframe_player_url = await self._handle_playnow(player_url, player_origin)
|
# The referer for _handle_vecloud is the origin of the channel page (channel_origin)
|
||||||
player_origin = self._get_origin(player_url)
|
# or the origin of the player itself if it is a /stream/ URL.
|
||||||
player_url = iframe_player_url
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return await self._handle_vecloud(player_url, player_origin + "/")
|
referer_for_vecloud = channel_origin + "/"
|
||||||
except Exception as e:
|
if re.search(r"/stream/([a-zA-Z0-9-]+)", current_player_url_for_processing):
|
||||||
|
referer_for_vecloud = self._get_origin(current_player_url_for_processing) + "/"
|
||||||
|
return await self._handle_vecloud(current_player_url_for_processing, referer_for_vecloud)
|
||||||
|
except Exception:
|
||||||
|
pass # Fail, Continue
|
||||||
|
|
||||||
|
# Attempt 2: If _handle_vecloud fail and the URL is not /stream/, try _handle_playnow
|
||||||
|
# and then _handle_vecloud again with the URL resulting from playnow.
|
||||||
|
if not re.search(r"/stream/([a-zA-Z0-9-]+)", current_player_url_for_processing):
|
||||||
|
try:
|
||||||
|
playnow_derived_player_url = await self._handle_playnow(current_player_url_for_processing, channel_origin + "/")
|
||||||
|
if re.search(r"/stream/([a-zA-Z0-9-]+)", playnow_derived_player_url):
|
||||||
|
try:
|
||||||
|
referer_for_vecloud_after_playnow = self._get_origin(playnow_derived_player_url) + "/"
|
||||||
|
return await self._handle_vecloud(playnow_derived_player_url, referer_for_vecloud_after_playnow)
|
||||||
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# If all previous attempts have failed, proceed with standard authentication.
|
||||||
|
player_url_for_auth = current_player_url_for_processing
|
||||||
|
player_origin_for_auth = self._get_origin(player_url_for_auth)
|
||||||
|
|
||||||
# Get player page to extract authentication information
|
# Get player page to extract authentication information
|
||||||
player_headers = {
|
player_headers = {
|
||||||
"referer": player_origin + "/",
|
"referer": player_origin_for_auth + "/",
|
||||||
"origin": player_origin,
|
"origin": player_origin_for_auth,
|
||||||
"user-agent": self.base_headers["user-agent"],
|
"user-agent": self.base_headers["user-agent"],
|
||||||
}
|
}
|
||||||
|
|
||||||
player_response = await self._make_request(player_url, headers=player_headers)
|
player_response = await self._make_request(player_url_for_auth, headers=player_headers)
|
||||||
player_content = player_response.text
|
player_content = player_response.text
|
||||||
|
|
||||||
# Extract authentication details from script tag
|
# Extract authentication details from script tag
|
||||||
@@ -78,62 +102,63 @@ class DLHDExtractor(BaseExtractor):
|
|||||||
raise ExtractorError("Failed to extract authentication data from player")
|
raise ExtractorError("Failed to extract authentication data from player")
|
||||||
|
|
||||||
# Extract auth URL base if not provided
|
# Extract auth URL base if not provided
|
||||||
if not auth_url_base:
|
final_auth_url_base = auth_url_base_from_arg
|
||||||
auth_url_base = self._extract_auth_url_base(player_content)
|
if not final_auth_url_base:
|
||||||
|
final_auth_url_base = self._extract_auth_url_base(player_content)
|
||||||
|
|
||||||
# If still no auth URL base, try to derive from stream URL or player URL
|
# If still no auth URL base, try to derive from stream URL or player URL
|
||||||
if not auth_url_base:
|
if not final_auth_url_base:
|
||||||
if stream_url:
|
if stream_url_from_arg:
|
||||||
auth_url_base = self._get_origin(stream_url)
|
final_auth_url_base = self._get_origin(stream_url_from_arg)
|
||||||
else:
|
else:
|
||||||
# Try to extract from player URL structure
|
# Try to extract from player URL structure
|
||||||
player_domain = self._get_origin(player_url)
|
player_domain_for_auth_derive = self._get_origin(player_url_for_auth)
|
||||||
# Attempt to construct a standard auth domain
|
# Attempt to construct a standard auth domain
|
||||||
auth_url_base = self._derive_auth_url_base(player_domain)
|
final_auth_url_base = self._derive_auth_url_base(player_domain_for_auth_derive)
|
||||||
|
|
||||||
if not auth_url_base:
|
if not final_auth_url_base:
|
||||||
raise ExtractorError("Could not determine auth URL base")
|
raise ExtractorError("Could not determine auth URL base")
|
||||||
|
|
||||||
# Construct auth URL
|
# Construct auth URL
|
||||||
auth_url = (
|
auth_url = (
|
||||||
f"{auth_url_base}/auth.php?channel_id={auth_data['channel_key']}"
|
f"{final_auth_url_base}/auth.php?channel_id={auth_data['channel_key']}"
|
||||||
f"&ts={auth_data['auth_ts']}&rnd={auth_data['auth_rnd']}"
|
f"&ts={auth_data['auth_ts']}&rnd={auth_data['auth_rnd']}"
|
||||||
f"&sig={quote(auth_data['auth_sig'])}"
|
f"&sig={quote(auth_data['auth_sig'])}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Make auth request
|
# Make auth request
|
||||||
player_origin = self._get_origin(player_url)
|
auth_req_headers = {
|
||||||
auth_headers = {
|
"referer": player_origin_for_auth + "/",
|
||||||
"referer": player_origin + "/",
|
"origin": player_origin_for_auth,
|
||||||
"origin": player_origin,
|
|
||||||
"user-agent": self.base_headers["user-agent"],
|
"user-agent": self.base_headers["user-agent"],
|
||||||
}
|
}
|
||||||
|
|
||||||
auth_response = await self._make_request(auth_url, headers=auth_headers)
|
auth_response = await self._make_request(auth_url, headers=auth_req_headers)
|
||||||
|
|
||||||
# Check if authentication succeeded
|
# Check if authentication succeeded
|
||||||
if auth_response.json().get("status") != "ok":
|
if auth_response.json().get("status") != "ok":
|
||||||
raise ExtractorError("Authentication failed")
|
raise ExtractorError("Authentication failed")
|
||||||
|
|
||||||
# If no stream URL provided, look up the server and generate the stream URL
|
# If no stream URL provided, look up the server and generate the stream URL
|
||||||
if not stream_url:
|
final_stream_url = stream_url_from_arg
|
||||||
stream_url = await self._lookup_server(
|
if not final_stream_url:
|
||||||
lookup_url_base=player_origin,
|
final_stream_url = await self._lookup_server(
|
||||||
auth_url_base=auth_url_base,
|
lookup_url_base=player_origin_for_auth,
|
||||||
|
auth_url_base=final_auth_url_base,
|
||||||
auth_data=auth_data,
|
auth_data=auth_data,
|
||||||
headers=auth_headers,
|
headers=auth_req_headers,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set up the final stream headers
|
# Set up the final stream headers
|
||||||
stream_headers = {
|
stream_headers = {
|
||||||
"referer": player_url,
|
"referer": player_url_for_auth,
|
||||||
"origin": player_origin,
|
"origin": player_origin_for_auth,
|
||||||
"user-agent": self.base_headers["user-agent"],
|
"user-agent": self.base_headers["user-agent"],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Return the stream URL with headers
|
# Return the stream URL with headers
|
||||||
return {
|
return {
|
||||||
"destination_url": stream_url,
|
"destination_url": final_stream_url,
|
||||||
"request_headers": stream_headers,
|
"request_headers": stream_headers,
|
||||||
"mediaflow_endpoint": self.mediaflow_endpoint,
|
"mediaflow_endpoint": self.mediaflow_endpoint,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,6 @@ class VixCloudExtractor(BaseExtractor):
|
|||||||
script = soup.find("body").find("script").text
|
script = soup.find("body").find("script").text
|
||||||
token = re.search(r"'token':\s*'(\w+)'", script).group(1)
|
token = re.search(r"'token':\s*'(\w+)'", script).group(1)
|
||||||
expires = re.search(r"'expires':\s*'(\d+)'", script).group(1)
|
expires = re.search(r"'expires':\s*'(\d+)'", script).group(1)
|
||||||
canPlayFHD = re.search(r"window\.canPlayFHD\s*=\s*(\w+)", script).group(1)
|
|
||||||
print(script,"A")
|
|
||||||
server_url = re.search(r"url:\s*'([^']+)'", script).group(1)
|
server_url = re.search(r"url:\s*'([^']+)'", script).group(1)
|
||||||
if "?b=1" in server_url:
|
if "?b=1" in server_url:
|
||||||
final_url = f'{server_url}&token={token}&expires={expires}'
|
final_url = f'{server_url}&token={token}&expires={expires}'
|
||||||
|
|||||||
@@ -85,13 +85,19 @@ async def handle_hls_stream_proxy(
|
|||||||
proxy_headers.request.update({"range": content_range})
|
proxy_headers.request.update({"range": content_range})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# If force_playlist_proxy is enabled, skip detection and directly process as m3u8
|
||||||
|
if hls_params.force_playlist_proxy:
|
||||||
|
return await fetch_and_process_m3u8(
|
||||||
|
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url, hls_params.force_playlist_proxy
|
||||||
|
)
|
||||||
|
|
||||||
parsed_url = urlparse(hls_params.destination)
|
parsed_url = urlparse(hls_params.destination)
|
||||||
# Check if the URL is a valid m3u8 playlist or m3u file
|
# Check if the URL is a valid m3u8 playlist or m3u file
|
||||||
if parsed_url.path.endswith((".m3u", ".m3u8", ".m3u_plus")) or parse_qs(parsed_url.query).get("type", [""])[
|
if parsed_url.path.endswith((".m3u", ".m3u8", ".m3u_plus")) or parse_qs(parsed_url.query).get("type", [""])[
|
||||||
0
|
0
|
||||||
] in ["m3u", "m3u8", "m3u_plus"]:
|
] in ["m3u", "m3u8", "m3u_plus"]:
|
||||||
return await fetch_and_process_m3u8(
|
return await fetch_and_process_m3u8(
|
||||||
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url
|
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url, hls_params.force_playlist_proxy
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create initial streaming response to check content type
|
# Create initial streaming response to check content type
|
||||||
@@ -100,7 +106,7 @@ async def handle_hls_stream_proxy(
|
|||||||
|
|
||||||
if "mpegurl" in response_headers.get("content-type", "").lower():
|
if "mpegurl" in response_headers.get("content-type", "").lower():
|
||||||
return await fetch_and_process_m3u8(
|
return await fetch_and_process_m3u8(
|
||||||
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url
|
streamer, hls_params.destination, proxy_headers, request, hls_params.key_url, hls_params.force_playlist_proxy
|
||||||
)
|
)
|
||||||
|
|
||||||
return EnhancedStreamingResponse(
|
return EnhancedStreamingResponse(
|
||||||
@@ -190,7 +196,7 @@ async def proxy_stream(method: str, destination: str, proxy_headers: ProxyReques
|
|||||||
|
|
||||||
|
|
||||||
async def fetch_and_process_m3u8(
|
async def fetch_and_process_m3u8(
|
||||||
streamer: Streamer, url: str, proxy_headers: ProxyRequestHeaders, request: Request, key_url: str = None
|
streamer: Streamer, url: str, proxy_headers: ProxyRequestHeaders, request: Request, key_url: str = None, force_playlist_proxy: bool = None
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Fetches and processes the m3u8 playlist on-the-fly, converting it to an HLS playlist.
|
Fetches and processes the m3u8 playlist on-the-fly, converting it to an HLS playlist.
|
||||||
@@ -201,6 +207,7 @@ async def fetch_and_process_m3u8(
|
|||||||
proxy_headers (ProxyRequestHeaders): The headers to include in the request.
|
proxy_headers (ProxyRequestHeaders): The headers to include in the request.
|
||||||
request (Request): The incoming HTTP request.
|
request (Request): The incoming HTTP request.
|
||||||
key_url (str, optional): The HLS Key URL to replace the original key URL. Defaults to None.
|
key_url (str, optional): The HLS Key URL to replace the original key URL. Defaults to None.
|
||||||
|
force_playlist_proxy (bool, optional): Force all playlist URLs to be proxied through MediaFlow. Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Response: The HTTP response with the processed m3u8 playlist.
|
Response: The HTTP response with the processed m3u8 playlist.
|
||||||
@@ -211,7 +218,7 @@ async def fetch_and_process_m3u8(
|
|||||||
await streamer.create_streaming_response(url, proxy_headers.request)
|
await streamer.create_streaming_response(url, proxy_headers.request)
|
||||||
|
|
||||||
# Initialize processor and response headers
|
# Initialize processor and response headers
|
||||||
processor = M3U8Processor(request, key_url)
|
processor = M3U8Processor(request, key_url, force_playlist_proxy)
|
||||||
response_headers = {
|
response_headers = {
|
||||||
"content-disposition": "inline",
|
"content-disposition": "inline",
|
||||||
"accept-ranges": "none",
|
"accept-ranges": "none",
|
||||||
|
|||||||
@@ -137,8 +137,10 @@ def build_hls(mpd_dict: dict, request: Request, key_id: str = None, key: str = N
|
|||||||
|
|
||||||
# Add video streams
|
# Add video streams
|
||||||
for profile, playlist_url in video_profiles.values():
|
for profile, playlist_url in video_profiles.values():
|
||||||
|
# Only add AUDIO attribute if there are audio profiles available
|
||||||
|
audio_attr = ',AUDIO="audio"' if audio_profiles else ""
|
||||||
hls.append(
|
hls.append(
|
||||||
f'#EXT-X-STREAM-INF:BANDWIDTH={profile["bandwidth"]},RESOLUTION={profile["width"]}x{profile["height"]},CODECS="{profile["codecs"]}",FRAME-RATE={profile["frameRate"]},AUDIO="audio"'
|
f'#EXT-X-STREAM-INF:BANDWIDTH={profile["bandwidth"]},RESOLUTION={profile["width"]}x{profile["height"]},CODECS="{profile["codecs"]}",FRAME-RATE={profile["frameRate"]}{audio_attr}'
|
||||||
)
|
)
|
||||||
hls.append(playlist_url)
|
hls.append(playlist_url)
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ class HLSManifestParams(GenericParams):
|
|||||||
None,
|
None,
|
||||||
description="The HLS Key URL to replace the original key URL. Defaults to None. (Useful for bypassing some sneaky protection)",
|
description="The HLS Key URL to replace the original key URL. Defaults to None. (Useful for bypassing some sneaky protection)",
|
||||||
)
|
)
|
||||||
|
force_playlist_proxy: Optional[bool] = Field(
|
||||||
|
None,
|
||||||
|
description="Force all playlist URLs to be proxied through MediaFlow regardless of m3u8_content_routing setting. Useful for IPTV m3u/m3u_plus formats that don't have clear URL indicators.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MPDManifestParams(GenericParams):
|
class MPDManifestParams(GenericParams):
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,16 +9,18 @@ from mediaflow_proxy.utils.http_utils import encode_mediaflow_proxy_url, encode_
|
|||||||
|
|
||||||
|
|
||||||
class M3U8Processor:
|
class M3U8Processor:
|
||||||
def __init__(self, request, key_url: str = None):
|
def __init__(self, request, key_url: str = None, force_playlist_proxy: bool = None):
|
||||||
"""
|
"""
|
||||||
Initializes the M3U8Processor with the request and URL prefix.
|
Initializes the M3U8Processor with the request and URL prefix.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
request (Request): The incoming HTTP request.
|
request (Request): The incoming HTTP request.
|
||||||
key_url (HttpUrl, optional): The URL of the key server. Defaults to None.
|
key_url (HttpUrl, optional): The URL of the key server. Defaults to None.
|
||||||
|
force_playlist_proxy (bool, optional): Force all playlist URLs to be proxied through MediaFlow. Defaults to None.
|
||||||
"""
|
"""
|
||||||
self.request = request
|
self.request = request
|
||||||
self.key_url = parse.urlparse(key_url) if key_url else None
|
self.key_url = parse.urlparse(key_url) if key_url else None
|
||||||
|
self.force_playlist_proxy = force_playlist_proxy
|
||||||
self.mediaflow_proxy_url = str(
|
self.mediaflow_proxy_url = str(
|
||||||
request.url_for("hls_manifest_proxy").replace(scheme=get_original_scheme(request))
|
request.url_for("hls_manifest_proxy").replace(scheme=get_original_scheme(request))
|
||||||
)
|
)
|
||||||
@@ -146,8 +148,15 @@ class M3U8Processor:
|
|||||||
# Determine routing strategy based on configuration
|
# Determine routing strategy based on configuration
|
||||||
routing_strategy = settings.m3u8_content_routing
|
routing_strategy = settings.m3u8_content_routing
|
||||||
|
|
||||||
|
# Check if we should force MediaFlow proxy for all playlist URLs
|
||||||
|
if self.force_playlist_proxy:
|
||||||
|
return await self.proxy_url(full_url, base_url, use_full_url=True)
|
||||||
|
|
||||||
# For playlist URLs, always use MediaFlow proxy regardless of strategy
|
# For playlist URLs, always use MediaFlow proxy regardless of strategy
|
||||||
if ".m3u" in full_url:
|
# Check for actual playlist file extensions, not just substring matches
|
||||||
|
parsed_url = parse.urlparse(full_url)
|
||||||
|
if (parsed_url.path.endswith((".m3u", ".m3u8", ".m3u_plus")) or
|
||||||
|
parse.parse_qs(parsed_url.query).get("type", [""])[0] in ["m3u", "m3u8", "m3u_plus"]):
|
||||||
return await self.proxy_url(full_url, base_url, use_full_url=True)
|
return await self.proxy_url(full_url, base_url, use_full_url=True)
|
||||||
|
|
||||||
# Route non-playlist content URLs based on strategy
|
# Route non-playlist content URLs based on strategy
|
||||||
@@ -191,6 +200,8 @@ class M3U8Processor:
|
|||||||
has_encrypted = query_params.pop("has_encrypted", False)
|
has_encrypted = query_params.pop("has_encrypted", False)
|
||||||
# Remove the response headers from the query params to avoid it being added to the consecutive requests
|
# Remove the response headers from the query params to avoid it being added to the consecutive requests
|
||||||
[query_params.pop(key, None) for key in list(query_params.keys()) if key.startswith("r_")]
|
[query_params.pop(key, None) for key in list(query_params.keys()) if key.startswith("r_")]
|
||||||
|
# Remove force_playlist_proxy to avoid it being added to subsequent requests
|
||||||
|
query_params.pop("force_playlist_proxy", None)
|
||||||
|
|
||||||
return encode_mediaflow_proxy_url(
|
return encode_mediaflow_proxy_url(
|
||||||
self.mediaflow_proxy_url,
|
self.mediaflow_proxy_url,
|
||||||
|
|||||||
Reference in New Issue
Block a user