Initial commit
Dieser Commit ist enthalten in:
435
domain/entities/method_rotation.py
Normale Datei
435
domain/entities/method_rotation.py
Normale Datei
@ -0,0 +1,435 @@
|
||||
"""
|
||||
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()
|
||||
}
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren