mirror of
https://github.com/UrloMythus/UnHided.git
synced 2026-04-11 11:50:51 +00:00
205 lines
6.2 KiB
Python
205 lines
6.2 KiB
Python
"""
|
|
Rate limit handlers for host-specific rate limiting strategies.
|
|
|
|
This module provides handler classes that implement specific rate limiting
|
|
logic for different streaming hosts (e.g., Vidoza's aggressive 509 rate limiting).
|
|
|
|
Similar pattern to stream_transformers.py but for rate limiting behavior.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional
|
|
from urllib.parse import urlparse
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class RateLimitHandler:
|
|
"""
|
|
Base class for rate limit handlers.
|
|
|
|
Subclasses should override properties to customize rate limiting behavior.
|
|
"""
|
|
|
|
@property
|
|
def cooldown_seconds(self) -> int:
|
|
"""
|
|
Duration in seconds to wait between upstream connections.
|
|
Default: 0 (no cooldown, allow immediate requests)
|
|
"""
|
|
return 0
|
|
|
|
@property
|
|
def use_head_cache(self) -> bool:
|
|
"""
|
|
Whether to cache HEAD responses to avoid upstream calls.
|
|
Default: False
|
|
"""
|
|
return False
|
|
|
|
@property
|
|
def use_stream_gate(self) -> bool:
|
|
"""
|
|
Whether to use distributed locking to serialize requests.
|
|
Default: False
|
|
"""
|
|
return False
|
|
|
|
@property
|
|
def exclusive_stream(self) -> bool:
|
|
"""
|
|
If True, the stream gate is held for the ENTIRE duration of the stream.
|
|
This prevents any concurrent connections to the same URL.
|
|
Required for hosts that 509 on ANY concurrent streams.
|
|
Default: False (gate released after headers received)
|
|
"""
|
|
return False
|
|
|
|
@property
|
|
def retry_after_seconds(self) -> int:
|
|
"""
|
|
Value for Retry-After header when returning 503.
|
|
Default: 2
|
|
"""
|
|
return 2
|
|
|
|
|
|
class VidozaRateLimitHandler(RateLimitHandler):
|
|
"""
|
|
Rate limit handler for Vidoza CDN.
|
|
|
|
Vidoza aggressively rate-limits (509) if ANY concurrent connections exist
|
|
to the same URL from the same IP. This handler:
|
|
- Uses EXCLUSIVE stream gate: only ONE stream at a time (gate held during entire stream)
|
|
- Caches HEAD responses to serve repeated probes without connections
|
|
- ExoPlayer/clients must wait for the current stream to finish before starting a new one
|
|
|
|
WARNING: This means only one client can actively stream at a time. Other clients will
|
|
wait (up to timeout) and eventually get 503 if the current stream is too long.
|
|
"""
|
|
|
|
@property
|
|
def cooldown_seconds(self) -> int:
|
|
return 0 # No cooldown needed - we use exclusive streaming instead
|
|
|
|
@property
|
|
def use_head_cache(self) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def use_stream_gate(self) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def exclusive_stream(self) -> bool:
|
|
"""
|
|
If True, the stream gate is held for the ENTIRE duration of the stream,
|
|
not just at the start. This prevents any concurrent connections.
|
|
Required for hosts like Vidoza that 509 on ANY concurrent connections.
|
|
"""
|
|
return True
|
|
|
|
@property
|
|
def retry_after_seconds(self) -> int:
|
|
return 5
|
|
|
|
|
|
class AggressiveRateLimitHandler(RateLimitHandler):
|
|
"""
|
|
Generic aggressive rate limit handler for hosts with strict rate limiting.
|
|
|
|
Use this for hosts that show similar behavior to Vidoza but may have
|
|
different thresholds.
|
|
"""
|
|
|
|
@property
|
|
def cooldown_seconds(self) -> int:
|
|
return 3
|
|
|
|
@property
|
|
def use_head_cache(self) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def use_stream_gate(self) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def retry_after_seconds(self) -> int:
|
|
return 2
|
|
|
|
|
|
# Registry of available rate limit handlers by ID
|
|
RATE_LIMIT_HANDLER_REGISTRY: dict[str, type[RateLimitHandler]] = {
|
|
"vidoza": VidozaRateLimitHandler,
|
|
"aggressive": AggressiveRateLimitHandler,
|
|
}
|
|
|
|
# Auto-detection: hostname patterns to handler IDs
|
|
# These patterns are checked against the video URL hostname
|
|
#
|
|
# NOTE: Vidoza CDN DOES rate limit concurrent connections from the same IP.
|
|
# When multiple clients request through the proxy, all requests come from
|
|
# the proxy's IP, triggering Vidoza's rate limit (509 errors).
|
|
# Stream-level rate limiting serializes requests to avoid this.
|
|
#
|
|
HOST_PATTERN_TO_HANDLER: dict[str, str] = {
|
|
"vidoza.net": "vidoza",
|
|
"vidoza.org": "vidoza",
|
|
# Add more patterns as needed for hosts that rate-limit CDN streaming:
|
|
# "example-cdn.com": "aggressive",
|
|
}
|
|
|
|
|
|
def get_rate_limit_handler(
|
|
handler_id: Optional[str] = None,
|
|
video_url: Optional[str] = None,
|
|
) -> RateLimitHandler:
|
|
"""
|
|
Get a rate limit handler instance.
|
|
|
|
Priority:
|
|
1. Explicit handler_id if provided
|
|
2. Auto-detect from video_url hostname
|
|
3. Default (no rate limiting)
|
|
|
|
Args:
|
|
handler_id: Explicit handler identifier (e.g., "vidoza", "aggressive")
|
|
video_url: Video URL for auto-detection based on hostname
|
|
|
|
Returns:
|
|
A rate limit handler instance. Returns base RateLimitHandler (no-op) if
|
|
no handler specified and no auto-detection match.
|
|
"""
|
|
# 1. Explicit handler ID
|
|
if handler_id:
|
|
handler_class = RATE_LIMIT_HANDLER_REGISTRY.get(handler_id)
|
|
if handler_class:
|
|
logger.debug(f"Using explicit rate limit handler: {handler_id}")
|
|
return handler_class()
|
|
else:
|
|
logger.warning(f"Unknown rate limit handler ID: {handler_id}")
|
|
|
|
# 2. Auto-detect from URL hostname
|
|
if video_url:
|
|
try:
|
|
hostname = urlparse(video_url).hostname or ""
|
|
# Check each pattern
|
|
for pattern, detected_handler_id in HOST_PATTERN_TO_HANDLER.items():
|
|
if pattern in hostname:
|
|
handler_class = RATE_LIMIT_HANDLER_REGISTRY.get(detected_handler_id)
|
|
if handler_class:
|
|
logger.info(f"[RateLimit] Auto-detected handler '{detected_handler_id}' for host: {hostname}")
|
|
return handler_class()
|
|
logger.debug(f"[RateLimit] No handler matched for hostname: {hostname}")
|
|
except Exception as e:
|
|
logger.warning(f"[RateLimit] Error during auto-detection: {e}")
|
|
|
|
# 3. Default: no rate limiting
|
|
return RateLimitHandler()
|
|
|
|
|
|
def get_available_handlers() -> list[str]:
|
|
"""Get list of available rate limit handler IDs."""
|
|
return list(RATE_LIMIT_HANDLER_REGISTRY.keys())
|