mirror of
https://github.com/UrloMythus/UnHided.git
synced 2026-04-09 02:40:47 +00:00
Update to newest version
This commit is contained in:
@@ -30,46 +30,70 @@ class DLHDExtractor(BaseExtractor):
|
||||
try:
|
||||
# Channel URL is required and serves as the referer
|
||||
channel_url = url
|
||||
player_origin = self._get_origin(channel_url)
|
||||
channel_origin = self._get_origin(channel_url) # Channel page origin
|
||||
|
||||
# Check for direct parameters
|
||||
player_url = kwargs.get("player_url")
|
||||
stream_url = kwargs.get("stream_url")
|
||||
auth_url_base = kwargs.get("auth_url_base")
|
||||
player_url_from_arg = kwargs.get("player_url")
|
||||
stream_url_from_arg = kwargs.get("stream_url")
|
||||
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 not player_url:
|
||||
if not player_url_from_arg:
|
||||
# Get the channel page to extract the player iframe URL
|
||||
channel_headers = {
|
||||
"referer": player_origin + "/",
|
||||
"origin": player_origin,
|
||||
"referer": channel_origin + "/",
|
||||
"origin": channel_origin,
|
||||
"user-agent": self.base_headers["user-agent"],
|
||||
}
|
||||
|
||||
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")
|
||||
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):
|
||||
iframe_player_url = await self._handle_playnow(player_url, player_origin)
|
||||
player_origin = self._get_origin(player_url)
|
||||
player_url = iframe_player_url
|
||||
|
||||
# Attempt 1: _handle_vecloud with current_player_url_for_processing
|
||||
# The referer for _handle_vecloud is the origin of the channel page (channel_origin)
|
||||
# or the origin of the player itself if it is a /stream/ URL.
|
||||
try:
|
||||
return await self._handle_vecloud(player_url, player_origin + "/")
|
||||
except Exception as e:
|
||||
pass
|
||||
referer_for_vecloud = channel_origin + "/"
|
||||
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
|
||||
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
|
||||
player_headers = {
|
||||
"referer": player_origin + "/",
|
||||
"origin": player_origin,
|
||||
"referer": player_origin_for_auth + "/",
|
||||
"origin": player_origin_for_auth,
|
||||
"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
|
||||
|
||||
# Extract authentication details from script tag
|
||||
@@ -78,62 +102,63 @@ class DLHDExtractor(BaseExtractor):
|
||||
raise ExtractorError("Failed to extract authentication data from player")
|
||||
|
||||
# Extract auth URL base if not provided
|
||||
if not auth_url_base:
|
||||
auth_url_base = self._extract_auth_url_base(player_content)
|
||||
final_auth_url_base = auth_url_base_from_arg
|
||||
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 not auth_url_base:
|
||||
if stream_url:
|
||||
auth_url_base = self._get_origin(stream_url)
|
||||
if not final_auth_url_base:
|
||||
if stream_url_from_arg:
|
||||
final_auth_url_base = self._get_origin(stream_url_from_arg)
|
||||
else:
|
||||
# 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
|
||||
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")
|
||||
|
||||
# Construct 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"&sig={quote(auth_data['auth_sig'])}"
|
||||
)
|
||||
|
||||
# Make auth request
|
||||
player_origin = self._get_origin(player_url)
|
||||
auth_headers = {
|
||||
"referer": player_origin + "/",
|
||||
"origin": player_origin,
|
||||
auth_req_headers = {
|
||||
"referer": player_origin_for_auth + "/",
|
||||
"origin": player_origin_for_auth,
|
||||
"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
|
||||
if auth_response.json().get("status") != "ok":
|
||||
raise ExtractorError("Authentication failed")
|
||||
|
||||
# If no stream URL provided, look up the server and generate the stream URL
|
||||
if not stream_url:
|
||||
stream_url = await self._lookup_server(
|
||||
lookup_url_base=player_origin,
|
||||
auth_url_base=auth_url_base,
|
||||
final_stream_url = stream_url_from_arg
|
||||
if not final_stream_url:
|
||||
final_stream_url = await self._lookup_server(
|
||||
lookup_url_base=player_origin_for_auth,
|
||||
auth_url_base=final_auth_url_base,
|
||||
auth_data=auth_data,
|
||||
headers=auth_headers,
|
||||
headers=auth_req_headers,
|
||||
)
|
||||
|
||||
# Set up the final stream headers
|
||||
stream_headers = {
|
||||
"referer": player_url,
|
||||
"origin": player_origin,
|
||||
"referer": player_url_for_auth,
|
||||
"origin": player_origin_for_auth,
|
||||
"user-agent": self.base_headers["user-agent"],
|
||||
}
|
||||
|
||||
# Return the stream URL with headers
|
||||
return {
|
||||
"destination_url": stream_url,
|
||||
"destination_url": final_stream_url,
|
||||
"request_headers": stream_headers,
|
||||
"mediaflow_endpoint": self.mediaflow_endpoint,
|
||||
}
|
||||
|
||||
@@ -56,8 +56,6 @@ class VixCloudExtractor(BaseExtractor):
|
||||
script = soup.find("body").find("script").text
|
||||
token = re.search(r"'token':\s*'(\w+)'", 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)
|
||||
if "?b=1" in server_url:
|
||||
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})
|
||||
|
||||
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)
|
||||
# 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", [""])[
|
||||
0
|
||||
] in ["m3u", "m3u8", "m3u_plus"]:
|
||||
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
|
||||
@@ -100,7 +106,7 @@ async def handle_hls_stream_proxy(
|
||||
|
||||
if "mpegurl" in response_headers.get("content-type", "").lower():
|
||||
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(
|
||||
@@ -190,7 +196,7 @@ async def proxy_stream(method: str, destination: str, proxy_headers: ProxyReques
|
||||
|
||||
|
||||
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.
|
||||
@@ -201,6 +207,7 @@ async def fetch_and_process_m3u8(
|
||||
proxy_headers (ProxyRequestHeaders): The headers to include in the request.
|
||||
request (Request): The incoming HTTP request.
|
||||
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:
|
||||
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)
|
||||
|
||||
# Initialize processor and response headers
|
||||
processor = M3U8Processor(request, key_url)
|
||||
processor = M3U8Processor(request, key_url, force_playlist_proxy)
|
||||
response_headers = {
|
||||
"content-disposition": "inline",
|
||||
"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
|
||||
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(
|
||||
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)
|
||||
|
||||
|
||||
@@ -64,6 +64,10 @@ class HLSManifestParams(GenericParams):
|
||||
None,
|
||||
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):
|
||||
|
||||
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:
|
||||
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.
|
||||
|
||||
Args:
|
||||
request (Request): The incoming HTTP request.
|
||||
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.key_url = parse.urlparse(key_url) if key_url else None
|
||||
self.force_playlist_proxy = force_playlist_proxy
|
||||
self.mediaflow_proxy_url = str(
|
||||
request.url_for("hls_manifest_proxy").replace(scheme=get_original_scheme(request))
|
||||
)
|
||||
@@ -146,8 +148,15 @@ class M3U8Processor:
|
||||
# Determine routing strategy based on configuration
|
||||
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
|
||||
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)
|
||||
|
||||
# Route non-playlist content URLs based on strategy
|
||||
@@ -191,6 +200,8 @@ class M3U8Processor:
|
||||
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
|
||||
[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(
|
||||
self.mediaflow_proxy_url,
|
||||
|
||||
Reference in New Issue
Block a user