""" Domain entities for method rotation system. These entities represent the core business logic and rules for method rotation. """ from dataclasses import dataclass, field from datetime import datetime, timedelta from enum import Enum from typing import Dict, List, Optional, Any import uuid import json class RiskLevel(Enum): """Risk levels for method strategies""" LOW = "LOW" MEDIUM = "MEDIUM" HIGH = "HIGH" class RotationEventType(Enum): """Types of rotation events""" SUCCESS = "SUCCESS" FAILURE = "FAILURE" ROTATION = "ROTATION" COOLDOWN = "COOLDOWN" CONFIG_CHANGE = "CONFIG_CHANGE" EMERGENCY_MODE = "EMERGENCY_MODE" class RotationStrategy(Enum): """Rotation strategy types""" SEQUENTIAL = "sequential" # Try methods in order RANDOM = "random" # Random method selection ADAPTIVE = "adaptive" # Learn from success patterns SMART = "smart" # AI-driven method selection @dataclass class MethodStrategy: """ Represents a registration/login method strategy for a platform. Contains configuration, performance metrics, and business rules. """ strategy_id: str platform: str method_name: str priority: int = 5 # 1-10, higher = preferred success_rate: float = 0.0 failure_rate: float = 0.0 last_success: Optional[datetime] = None last_failure: Optional[datetime] = None cooldown_period: int = 0 # seconds max_daily_attempts: int = 10 risk_level: RiskLevel = RiskLevel.MEDIUM is_active: bool = True configuration: Dict[str, Any] = field(default_factory=dict) tags: List[str] = field(default_factory=list) created_at: datetime = field(default_factory=datetime.now) updated_at: datetime = field(default_factory=datetime.now) def __post_init__(self): """Validate and normalize data after initialization""" if not self.strategy_id: self.strategy_id = f"{self.platform}_{self.method_name}_{uuid.uuid4().hex[:8]}" # Ensure priority is within valid range self.priority = max(1, min(10, self.priority)) # Ensure rates are valid percentages self.success_rate = max(0.0, min(1.0, self.success_rate)) self.failure_rate = max(0.0, min(1.0, self.failure_rate)) @property def is_on_cooldown(self) -> bool: """Check if method is currently on cooldown""" if not self.last_failure or self.cooldown_period == 0: return False cooldown_until = self.last_failure + timedelta(seconds=self.cooldown_period) return datetime.now() < cooldown_until @property def cooldown_remaining_seconds(self) -> int: """Get remaining cooldown time in seconds""" if not self.is_on_cooldown: return 0 cooldown_until = self.last_failure + timedelta(seconds=self.cooldown_period) remaining = cooldown_until - datetime.now() return max(0, int(remaining.total_seconds())) @property def effectiveness_score(self) -> float: """Calculate overall effectiveness score for method selection""" base_score = self.priority / 10.0 # Adjust for success rate if self.success_rate > 0: base_score *= (1 + self.success_rate) # Penalize for high failure rate if self.failure_rate > 0.5: base_score *= (1 - self.failure_rate * 0.5) # Penalize for high risk risk_penalties = { RiskLevel.LOW: 0.0, RiskLevel.MEDIUM: 0.1, RiskLevel.HIGH: 0.3 } base_score *= (1 - risk_penalties.get(self.risk_level, 0.1)) # Penalize if on cooldown if self.is_on_cooldown: base_score *= 0.1 # Penalize if inactive if not self.is_active: base_score = 0.0 return max(0.0, min(1.0, base_score)) def update_performance(self, success: bool, execution_time: float = 0.0): """Update performance metrics based on execution result""" self.updated_at = datetime.now() if success: self.last_success = datetime.now() # Update success rate with exponential moving average self.success_rate = 0.8 * self.success_rate + 0.2 * 1.0 self.failure_rate = 0.8 * self.failure_rate + 0.2 * 0.0 else: self.last_failure = datetime.now() # Update failure rate with exponential moving average self.success_rate = 0.8 * self.success_rate + 0.2 * 0.0 self.failure_rate = 0.8 * self.failure_rate + 0.2 * 1.0 def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization""" return { 'strategy_id': self.strategy_id, 'platform': self.platform, 'method_name': self.method_name, 'priority': self.priority, 'success_rate': self.success_rate, 'failure_rate': self.failure_rate, 'last_success': self.last_success.isoformat() if self.last_success else None, 'last_failure': self.last_failure.isoformat() if self.last_failure else None, 'cooldown_period': self.cooldown_period, 'max_daily_attempts': self.max_daily_attempts, 'risk_level': self.risk_level.value, 'is_active': self.is_active, 'configuration': self.configuration, 'tags': self.tags, 'created_at': self.created_at.isoformat(), 'updated_at': self.updated_at.isoformat() } @dataclass class RotationSession: """ Represents an active rotation session for account creation/login. Tracks the current state and history of method attempts. """ session_id: str platform: str account_id: Optional[str] = None current_method: str = "" attempted_methods: List[str] = field(default_factory=list) session_start: datetime = field(default_factory=datetime.now) last_rotation: Optional[datetime] = None rotation_count: int = 0 success_count: int = 0 failure_count: int = 0 is_active: bool = True rotation_reason: Optional[str] = None fingerprint_id: Optional[str] = None session_metadata: Dict[str, Any] = field(default_factory=dict) def __post_init__(self): """Validate and normalize data after initialization""" if not self.session_id: self.session_id = f"session_{uuid.uuid4().hex}" @property def session_duration(self) -> timedelta: """Get total session duration""" return datetime.now() - self.session_start @property def success_rate(self) -> float: """Calculate session success rate""" total_attempts = self.success_count + self.failure_count if total_attempts == 0: return 0.0 return self.success_count / total_attempts @property def should_rotate(self) -> bool: """Determine if rotation should occur based on failure patterns""" # Rotate after 2 consecutive failures if self.failure_count >= 2 and self.success_count == 0: return True # Rotate if failure rate is high and we have alternatives if len(self.attempted_methods) < 3 and self.success_rate < 0.3: return True return False def add_attempt(self, method_name: str, success: bool, error_message: Optional[str] = None): """Record a method attempt""" if method_name not in self.attempted_methods: self.attempted_methods.append(method_name) self.current_method = method_name if success: self.success_count += 1 else: self.failure_count += 1 # Add to metadata attempt_data = { 'method': method_name, 'success': success, 'timestamp': datetime.now().isoformat(), 'error': error_message } if 'attempts' not in self.session_metadata: self.session_metadata['attempts'] = [] self.session_metadata['attempts'].append(attempt_data) def rotate_to_method(self, new_method: str, reason: str): """Rotate to a new method""" self.current_method = new_method self.last_rotation = datetime.now() self.rotation_count += 1 self.rotation_reason = reason def complete_session(self, success: bool): """Mark session as completed""" self.is_active = False self.session_metadata['completed_at'] = datetime.now().isoformat() self.session_metadata['final_success'] = success def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization""" return { 'session_id': self.session_id, 'platform': self.platform, 'account_id': self.account_id, 'current_method': self.current_method, 'attempted_methods': self.attempted_methods, 'session_start': self.session_start.isoformat(), 'last_rotation': self.last_rotation.isoformat() if self.last_rotation else None, 'rotation_count': self.rotation_count, 'success_count': self.success_count, 'failure_count': self.failure_count, 'is_active': self.is_active, 'rotation_reason': self.rotation_reason, 'fingerprint_id': self.fingerprint_id, 'session_metadata': self.session_metadata } @dataclass class RotationEvent: """ Represents a specific event in the rotation system. Used for detailed logging and analytics. """ event_id: str session_id: str method_name: str event_type: RotationEventType timestamp: datetime = field(default_factory=datetime.now) details: Dict[str, Any] = field(default_factory=dict) error_message: Optional[str] = None performance_metrics: Dict[str, float] = field(default_factory=dict) correlation_id: Optional[str] = None def __post_init__(self): """Validate and normalize data after initialization""" if not self.event_id: self.event_id = f"event_{uuid.uuid4().hex}" @classmethod def create_success_event(cls, session_id: str, method_name: str, execution_time: float = 0.0, **kwargs) -> 'RotationEvent': """Create a success event""" return cls( event_id=f"success_{uuid.uuid4().hex[:8]}", session_id=session_id, method_name=method_name, event_type=RotationEventType.SUCCESS, performance_metrics={'execution_time': execution_time}, details=kwargs ) @classmethod def create_failure_event(cls, session_id: str, method_name: str, error_message: str, **kwargs) -> 'RotationEvent': """Create a failure event""" return cls( event_id=f"failure_{uuid.uuid4().hex[:8]}", session_id=session_id, method_name=method_name, event_type=RotationEventType.FAILURE, error_message=error_message, details=kwargs ) @classmethod def create_rotation_event(cls, session_id: str, from_method: str, to_method: str, reason: str, **kwargs) -> 'RotationEvent': """Create a rotation event""" return cls( event_id=f"rotation_{uuid.uuid4().hex[:8]}", session_id=session_id, method_name=to_method, event_type=RotationEventType.ROTATION, details={ 'from_method': from_method, 'to_method': to_method, 'reason': reason, **kwargs } ) def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization""" return { 'event_id': self.event_id, 'session_id': self.session_id, 'method_name': self.method_name, 'event_type': self.event_type.value, 'timestamp': self.timestamp.isoformat(), 'details': self.details, 'error_message': self.error_message, 'performance_metrics': self.performance_metrics, 'correlation_id': self.correlation_id } @dataclass class PlatformMethodState: """ Represents the rotation state for a specific platform. Tracks preferences, blocks, and daily limits. """ platform: str last_successful_method: Optional[str] = None last_successful_at: Optional[datetime] = None preferred_methods: List[str] = field(default_factory=list) blocked_methods: List[str] = field(default_factory=list) daily_attempt_counts: Dict[str, int] = field(default_factory=dict) reset_date: datetime = field(default_factory=lambda: datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)) rotation_strategy: RotationStrategy = RotationStrategy.ADAPTIVE emergency_mode: bool = False metadata: Dict[str, Any] = field(default_factory=dict) updated_at: datetime = field(default_factory=datetime.now) def is_method_available(self, method_name: str, max_daily_attempts: int) -> bool: """Check if a method is available for use""" # Check if method is blocked if method_name in self.blocked_methods: return False # Check daily limits current_attempts = self.daily_attempt_counts.get(method_name, 0) if current_attempts >= max_daily_attempts: return False return True def increment_daily_attempts(self, method_name: str): """Increment daily attempt count for a method""" # Reset counts if it's a new day today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) if today > self.reset_date: self.daily_attempt_counts = {} self.reset_date = today self.daily_attempt_counts[method_name] = self.daily_attempt_counts.get(method_name, 0) + 1 self.updated_at = datetime.now() def record_success(self, method_name: str): """Record a successful method execution""" self.last_successful_method = method_name self.last_successful_at = datetime.now() self.updated_at = datetime.now() # Move successful method to front of preferred list if method_name in self.preferred_methods: self.preferred_methods.remove(method_name) self.preferred_methods.insert(0, method_name) def block_method(self, method_name: str, reason: str): """Temporarily block a method""" if method_name not in self.blocked_methods: self.blocked_methods.append(method_name) self.metadata[f'block_reason_{method_name}'] = reason self.metadata[f'blocked_at_{method_name}'] = datetime.now().isoformat() self.updated_at = datetime.now() def unblock_method(self, method_name: str): """Remove method from blocked list""" if method_name in self.blocked_methods: self.blocked_methods.remove(method_name) # Clean up metadata self.metadata.pop(f'block_reason_{method_name}', None) self.metadata.pop(f'blocked_at_{method_name}', None) self.updated_at = datetime.now() def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization""" return { 'platform': self.platform, 'last_successful_method': self.last_successful_method, 'last_successful_at': self.last_successful_at.isoformat() if self.last_successful_at else None, 'preferred_methods': self.preferred_methods, 'blocked_methods': self.blocked_methods, 'daily_attempt_counts': self.daily_attempt_counts, 'reset_date': self.reset_date.isoformat(), 'rotation_strategy': self.rotation_strategy.value, 'emergency_mode': self.emergency_mode, 'metadata': self.metadata, 'updated_at': self.updated_at.isoformat() }