Skip to content

Commit

Permalink
Implement LiveTVExtractor and improve error handling
Browse files Browse the repository at this point in the history
Added LiveTVExtractor for extracting M3U8 and MPD streams. Enhanced error handling by introducing ExtractorError and updated existing extractors to use this new error class. Removed support for unused request headers and integrated caching for extractor results.
  • Loading branch information
mhdzumair committed Nov 17, 2024
1 parent 787cdaa commit 95843cc
Show file tree
Hide file tree
Showing 17 changed files with 603 additions and 90 deletions.
1 change: 1 addition & 0 deletions mediaflow_proxy/configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Config:

class Settings(BaseSettings):
api_password: str # The password for accessing the API endpoints.
log_level: str = "INFO" # The logging level to use.
transport_config: TransportConfig = Field(default_factory=TransportConfig) # Configuration for httpx transport.
enable_streaming_progress: bool = False # Whether to enable streaming progress tracking.

Expand Down
7 changes: 0 additions & 7 deletions mediaflow_proxy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,6 @@
]

SUPPORTED_REQUEST_HEADERS = [
"accept",
"accept-encoding",
"accept-language",
"connection",
"range",
"if-range",
"user-agent",
"referer",
"origin",
]
23 changes: 16 additions & 7 deletions mediaflow_proxy/extractors/base.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,50 @@
from abc import ABC, abstractmethod
from typing import Dict, Tuple, Optional
from typing import Dict, Optional, Any

import httpx

from mediaflow_proxy.configs import settings
from mediaflow_proxy.utils.http_utils import create_httpx_client


class ExtractorError(Exception):
"""Base exception for all extractors."""

pass


class BaseExtractor(ABC):
"""Base class for all URL extractors."""

def __init__(self, request_headers: dict):
self.base_headers = {
"user-agent": settings.user_agent,
"accept-language": "en-US,en;q=0.5",
}
self.mediaflow_endpoint = "proxy_stream_endpoint"
self.base_headers.update(request_headers)

async def _make_request(self, url: str, headers: Optional[Dict] = None, **kwargs) -> httpx.Response:
async def _make_request(
self, url: str, method: str = "GET", headers: Optional[Dict] = None, **kwargs
) -> httpx.Response:
"""Make HTTP request with error handling."""
try:
async with create_httpx_client() as client:
request_headers = self.base_headers
request_headers.update(headers or {})
response = await client.get(
response = await client.request(
method,
url,
headers=request_headers,
**kwargs,
)
response.raise_for_status()
return response
except httpx.HTTPError as e:
raise ValueError(f"HTTP request failed: {str(e)}")
raise ExtractorError(f"HTTP request failed: {str(e)}")
except Exception as e:
raise ValueError(f"Request failed: {str(e)}")
raise ExtractorError(f"Request failed: {str(e)}")

@abstractmethod
async def extract(self, url: str) -> Tuple[str, Dict[str, str]]:
async def extract(self, url: str, **kwargs) -> Dict[str, Any]:
"""Extract final URL and required headers."""
pass
25 changes: 15 additions & 10 deletions mediaflow_proxy/extractors/doodstream.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,39 @@
import re
import time
from typing import Tuple, Dict
from typing import Dict

from mediaflow_proxy.extractors.base import BaseExtractor
from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError


class DoodStreamExtractor(BaseExtractor):
"""DoodStream URL extractor."""

def __init__(self, proxy_enabled: bool, request_headers: dict):
super().__init__(proxy_enabled, request_headers)
def __init__(self, request_headers: dict):
super().__init__(request_headers)
self.base_url = "https://d000d.com"

async def extract(self, url: str) -> Tuple[str, Dict[str, str]]:
async def extract(self, url: str, **kwargs) -> Dict[str, str]:
"""Extract DoodStream URL."""
response = await self._make_request(url)

# Extract URL pattern
pattern = r"(\/pass_md5\/.*?)'.*(\?token=.*?expiry=)"
match = re.search(pattern, response.text, re.DOTALL)
if not match:
raise ValueError("Failed to extract URL pattern")
raise ExtractorError("Failed to extract URL pattern")

# Build final URL
pass_url = f"{self.base_url}{match[1]}"
referer = f"{self.base_url}/"
headers = {"range": "bytes=0-", "referer": referer}

rebobo_response = await self._make_request(pass_url, headers=headers)
response = await self._make_request(pass_url, headers=headers)
timestamp = str(int(time.time()))
final_url = f"{rebobo_response.text}123456789{match[2]}{timestamp}"

return final_url, {"Referer": referer}
final_url = f"{response.text}123456789{match[2]}{timestamp}"

self.base_headers["referer"] = referer
return {
"destination_url": final_url,
"request_headers": self.base_headers,
"mediaflow_endpoint": self.mediaflow_endpoint,
}
6 changes: 4 additions & 2 deletions mediaflow_proxy/extractors/factory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from typing import Dict, Type

from mediaflow_proxy.extractors.base import BaseExtractor
from mediaflow_proxy.extractors.base import BaseExtractor, ExtractorError
from mediaflow_proxy.extractors.doodstream import DoodStreamExtractor
from mediaflow_proxy.extractors.livetv import LiveTVExtractor
from mediaflow_proxy.extractors.mixdrop import MixdropExtractor
from mediaflow_proxy.extractors.uqload import UqloadExtractor

Expand All @@ -13,12 +14,13 @@ class ExtractorFactory:
"Doodstream": DoodStreamExtractor,
"Uqload": UqloadExtractor,
"Mixdrop": MixdropExtractor,
"LiveTV": LiveTVExtractor,
}

@classmethod
def get_extractor(cls, host: str, request_headers: dict) -> BaseExtractor:
"""Get appropriate extractor instance for the given host."""
extractor_class = cls._extractors.get(host)
if not extractor_class:
raise ValueError(f"Unsupported host: {host}")
raise ExtractorError(f"Unsupported host: {host}")
return extractor_class(request_headers)
Loading

0 comments on commit 95843cc

Please sign in to comment.