""" Detect Rate Limit Use Case - Erkennt Rate Limits und reagiert entsprechend """ import logging import time from typing import Any, Dict, Optional, Tuple from datetime import datetime from domain.services.rate_limit_service import IRateLimitService from domain.value_objects.action_timing import ActionTiming, ActionType from domain.entities.error_event import ErrorEvent, ErrorType, ErrorContext from domain.entities.rate_limit_policy import RateLimitPolicy logger = logging.getLogger("detect_rate_limit_use_case") class DetectRateLimitUseCase: """ Use Case für Rate Limit Erkennung und Reaktion. Analysiert Responses, erkennt Rate Limits und implementiert Backoff-Strategien. """ def __init__(self, rate_limit_service: IRateLimitService): self.rate_limit_service = rate_limit_service self.detection_patterns = { 'instagram': [ "Bitte warte einige Minuten", "Please wait a few minutes", "Try again later", "Versuche es später erneut", "too many requests", "zu viele Anfragen", "We're sorry, but something went wrong", "temporarily blocked", "vorübergehend gesperrt", "Wir haben deine Anfrage eingeschränkt" ], 'tiktok': [ "Too many attempts", "Zu viele Versuche", "Please slow down", "rate limited", "Try again in" ], 'general': [ "429", "rate limit", "throttled", "quota exceeded" ] } def execute(self, response: Any, context: Optional[Dict[str, Any]] = None) -> Tuple[bool, Optional[ErrorEvent]]: """ Analysiert eine Response auf Rate Limiting. Args: response: HTTP Response, Page Content oder Error Message context: Zusätzlicher Kontext (platform, action_type, etc.) Returns: Tuple aus (is_rate_limited, error_event) """ # Erkenne Rate Limit is_rate_limited = self._detect_rate_limit(response, context) if not is_rate_limited: return False, None # Erstelle Error Event error_event = self._create_error_event(response, context) # Handle Rate Limit self._handle_rate_limit(error_event, context) return True, error_event def _detect_rate_limit(self, response: Any, context: Optional[Dict[str, Any]] = None) -> bool: """Erkennt ob Response auf Rate Limiting hindeutet""" # Nutze Service für Basis-Detection if self.rate_limit_service.detect_rate_limit(response): return True # Erweiterte Detection basierend auf Platform platform = context.get('platform', 'general') if context else 'general' patterns = self.detection_patterns.get(platform, []) + self.detection_patterns['general'] # String-basierte Erkennung response_text = self._extract_text(response) if response_text: response_lower = response_text.lower() for pattern in patterns: if pattern.lower() in response_lower: logger.info(f"Rate limit detected: '{pattern}' found in response") return True # Status Code Erkennung status = self._extract_status(response) if status in [429, 420, 503]: # Common rate limit codes logger.info(f"Rate limit detected: HTTP {status}") return True # Timing-basierte Erkennung if context and 'timing' in context: timing = context['timing'] if isinstance(timing, ActionTiming): # Sehr schnelle Fehler können auf Rate Limits hindeuten if not timing.success and timing.duration < 0.5: logger.warning("Possible rate limit: Fast failure detected") return True return False def _extract_text(self, response: Any) -> Optional[str]: """Extrahiert Text aus verschiedenen Response-Typen""" if isinstance(response, str): return response elif hasattr(response, 'text'): try: return response.text except: pass elif hasattr(response, 'content'): try: if callable(response.content): return response.content() return str(response.content) except: pass elif hasattr(response, 'message'): return str(response.message) return str(response) if response else None def _extract_status(self, response: Any) -> Optional[int]: """Extrahiert Status Code aus Response""" if hasattr(response, 'status'): return response.status elif hasattr(response, 'status_code'): return response.status_code elif hasattr(response, 'code'): try: return int(response.code) except: pass return None def _create_error_event(self, response: Any, context: Optional[Dict[str, Any]] = None) -> ErrorEvent: """Erstellt Error Event für Rate Limit""" error_context = ErrorContext( url=context.get('url') if context else None, action=context.get('action_type').value if context and 'action_type' in context else None, step_name=context.get('step_name') if context else None, screenshot_path=context.get('screenshot_path') if context else None, additional_data={ 'platform': context.get('platform') if context else None, 'response_text': self._extract_text(response)[:500] if self._extract_text(response) else None, 'status_code': self._extract_status(response), 'timestamp': datetime.now().isoformat() } ) return ErrorEvent( error_type=ErrorType.RATE_LIMIT, error_message="Rate limit detected", context=error_context, platform=context.get('platform') if context else None, session_id=context.get('session_id') if context else None, correlation_id=context.get('correlation_id') if context else None ) def _handle_rate_limit(self, error_event: ErrorEvent, context: Optional[Dict[str, Any]] = None) -> None: """Behandelt erkanntes Rate Limit""" # Extrahiere Wait-Zeit aus Response wenn möglich wait_time = self._extract_wait_time(error_event.context.additional_data.get('response_text', '')) if not wait_time: # Verwende exponentielles Backoff retry_count = context.get('retry_count', 0) if context else 0 wait_time = self._calculate_backoff(retry_count) logger.warning(f"Rate limit detected - waiting {wait_time}s before retry") # Update Rate Limit Policy für zukünftige Requests if context and 'action_type' in context: action_type = context['action_type'] current_policy = self.rate_limit_service.get_policy(action_type) # Erhöhe Delays temporär updated_policy = RateLimitPolicy( min_delay=min(current_policy.min_delay * 1.5, 10.0), max_delay=min(current_policy.max_delay * 2, 60.0), adaptive=current_policy.adaptive, backoff_multiplier=min(current_policy.backoff_multiplier * 1.2, 3.0), max_retries=current_policy.max_retries ) self.rate_limit_service.update_policy(action_type, updated_policy) def _extract_wait_time(self, response_text: str) -> Optional[float]: """Versucht Wait-Zeit aus Response zu extrahieren""" if not response_text: return None import re # Patterns für Zeitangaben patterns = [ r'wait (\d+) seconds', r'warte (\d+) Sekunden', r'try again in (\d+)s', r'retry after (\d+)', r'(\d+) Minuten warten', r'wait (\d+) minutes' ] for pattern in patterns: match = re.search(pattern, response_text.lower()) if match: value = int(match.group(1)) # Konvertiere Minuten zu Sekunden wenn nötig if 'minute' in pattern or 'minuten' in pattern: value *= 60 return float(min(value, 300)) # Max 5 Minuten return None def _calculate_backoff(self, retry_count: int) -> float: """Berechnet exponentielles Backoff""" base_wait = 5.0 # 5 Sekunden Basis max_wait = 300.0 # Max 5 Minuten # Exponentielles Backoff mit Jitter wait_time = min(base_wait * (2 ** retry_count), max_wait) # Füge Jitter hinzu (±20%) import random jitter = wait_time * 0.2 * (random.random() - 0.5) return wait_time + jitter def analyze_patterns(self, platform: str, timeframe_hours: int = 24) -> Dict[str, Any]: """Analysiert Rate Limit Muster für eine Plattform""" # Diese Methode würde mit einem Analytics Repository arbeiten # um Muster in Rate Limits zu erkennen analysis = { 'platform': platform, 'timeframe_hours': timeframe_hours, 'peak_times': [], 'safe_times': [], 'recommended_delays': {}, 'incidents': 0 } # TODO: Implementiere mit Analytics Repository return analysis