Initial commit
Dieser Commit ist enthalten in:
15
domain/entities/__init__.py
Normale Datei
15
domain/entities/__init__.py
Normale Datei
@ -0,0 +1,15 @@
|
||||
"""
|
||||
Domain Entities - Geschäftsobjekte mit Identität
|
||||
"""
|
||||
|
||||
from .rate_limit_policy import RateLimitPolicy
|
||||
from .browser_fingerprint import BrowserFingerprint
|
||||
from .account_creation_event import AccountCreationEvent
|
||||
from .error_event import ErrorEvent
|
||||
|
||||
__all__ = [
|
||||
'RateLimitPolicy',
|
||||
'BrowserFingerprint',
|
||||
'AccountCreationEvent',
|
||||
'ErrorEvent'
|
||||
]
|
||||
174
domain/entities/account_creation_event.py
Normale Datei
174
domain/entities/account_creation_event.py
Normale Datei
@ -0,0 +1,174 @@
|
||||
"""
|
||||
Account Creation Event Entity - Event für jede Account-Erstellung
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict, Any, Optional
|
||||
from enum import Enum
|
||||
import uuid
|
||||
|
||||
|
||||
class WorkflowStepStatus(Enum):
|
||||
"""Status eines Workflow-Schritts"""
|
||||
PENDING = "pending"
|
||||
IN_PROGRESS = "in_progress"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
SKIPPED = "skipped"
|
||||
|
||||
|
||||
@dataclass
|
||||
class WorkflowStep:
|
||||
"""Einzelner Schritt im Account-Erstellungsprozess"""
|
||||
step_name: str
|
||||
start_time: datetime
|
||||
end_time: Optional[datetime] = None
|
||||
status: WorkflowStepStatus = WorkflowStepStatus.PENDING
|
||||
retry_count: int = 0
|
||||
error_message: Optional[str] = None
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def duration(self) -> Optional[timedelta]:
|
||||
"""Berechnet die Dauer des Schritts"""
|
||||
if self.start_time and self.end_time:
|
||||
return self.end_time - self.start_time
|
||||
return None
|
||||
|
||||
@property
|
||||
def success(self) -> bool:
|
||||
"""Prüft ob der Schritt erfolgreich war"""
|
||||
return self.status == WorkflowStepStatus.COMPLETED
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Konvertiert zu Dictionary"""
|
||||
return {
|
||||
'step_name': self.step_name,
|
||||
'start_time': self.start_time.isoformat(),
|
||||
'end_time': self.end_time.isoformat() if self.end_time else None,
|
||||
'status': self.status.value,
|
||||
'retry_count': self.retry_count,
|
||||
'error_message': self.error_message,
|
||||
'metadata': self.metadata,
|
||||
'duration_seconds': self.duration.total_seconds() if self.duration else None
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccountData:
|
||||
"""Daten des erstellten Accounts"""
|
||||
platform: str
|
||||
username: str
|
||||
password: str
|
||||
email: str
|
||||
phone: Optional[str] = None
|
||||
full_name: Optional[str] = None
|
||||
birthday: Optional[str] = None
|
||||
profile_image: Optional[str] = None
|
||||
bio: Optional[str] = None
|
||||
verification_status: str = "unverified"
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ErrorDetails:
|
||||
"""Details zu aufgetretenen Fehlern"""
|
||||
error_type: str
|
||||
error_message: str
|
||||
stack_trace: Optional[str] = None
|
||||
screenshot_path: Optional[str] = None
|
||||
recovery_attempted: bool = False
|
||||
recovery_successful: bool = False
|
||||
context: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccountCreationEvent:
|
||||
"""Event für jede Account-Erstellung"""
|
||||
|
||||
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
timestamp: datetime = field(default_factory=datetime.now)
|
||||
account_data: Optional[AccountData] = None
|
||||
session_id: str = ""
|
||||
fingerprint_id: str = ""
|
||||
duration: Optional[timedelta] = None
|
||||
success: bool = False
|
||||
error_details: Optional[ErrorDetails] = None
|
||||
steps_completed: List[WorkflowStep] = field(default_factory=list)
|
||||
|
||||
# Performance-Metriken
|
||||
total_retry_count: int = 0
|
||||
network_requests: int = 0
|
||||
screenshots_taken: int = 0
|
||||
|
||||
# Kontext-Informationen
|
||||
proxy_used: bool = False
|
||||
proxy_type: Optional[str] = None
|
||||
browser_type: str = "chromium"
|
||||
headless: bool = False
|
||||
|
||||
def add_step(self, step: WorkflowStep):
|
||||
"""Fügt einen Workflow-Schritt hinzu"""
|
||||
self.steps_completed.append(step)
|
||||
if step.retry_count > 0:
|
||||
self.total_retry_count += step.retry_count
|
||||
|
||||
def get_step(self, step_name: str) -> Optional[WorkflowStep]:
|
||||
"""Holt einen Schritt nach Name"""
|
||||
for step in self.steps_completed:
|
||||
if step.step_name == step_name:
|
||||
return step
|
||||
return None
|
||||
|
||||
def calculate_duration(self):
|
||||
"""Berechnet die Gesamtdauer der Account-Erstellung"""
|
||||
if self.steps_completed:
|
||||
first_step = min(self.steps_completed, key=lambda s: s.start_time)
|
||||
last_step = max(self.steps_completed, key=lambda s: s.end_time or s.start_time)
|
||||
if last_step.end_time:
|
||||
self.duration = last_step.end_time - first_step.start_time
|
||||
|
||||
def get_success_rate(self) -> float:
|
||||
"""Berechnet die Erfolgsrate der Schritte"""
|
||||
if not self.steps_completed:
|
||||
return 0.0
|
||||
successful_steps = sum(1 for step in self.steps_completed if step.success)
|
||||
return successful_steps / len(self.steps_completed)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Konvertiert Event zu Dictionary für Serialisierung"""
|
||||
return {
|
||||
'event_id': self.event_id,
|
||||
'timestamp': self.timestamp.isoformat(),
|
||||
'account_data': {
|
||||
'platform': self.account_data.platform,
|
||||
'username': self.account_data.username,
|
||||
'email': self.account_data.email,
|
||||
'phone': self.account_data.phone,
|
||||
'full_name': self.account_data.full_name,
|
||||
'birthday': self.account_data.birthday,
|
||||
'verification_status': self.account_data.verification_status,
|
||||
'metadata': self.account_data.metadata
|
||||
} if self.account_data else None,
|
||||
'session_id': self.session_id,
|
||||
'fingerprint_id': self.fingerprint_id,
|
||||
'duration_seconds': self.duration.total_seconds() if self.duration else None,
|
||||
'success': self.success,
|
||||
'error_details': {
|
||||
'error_type': self.error_details.error_type,
|
||||
'error_message': self.error_details.error_message,
|
||||
'recovery_attempted': self.error_details.recovery_attempted,
|
||||
'recovery_successful': self.error_details.recovery_successful,
|
||||
'context': self.error_details.context
|
||||
} if self.error_details else None,
|
||||
'steps_completed': [step.to_dict() for step in self.steps_completed],
|
||||
'total_retry_count': self.total_retry_count,
|
||||
'network_requests': self.network_requests,
|
||||
'screenshots_taken': self.screenshots_taken,
|
||||
'proxy_used': self.proxy_used,
|
||||
'proxy_type': self.proxy_type,
|
||||
'browser_type': self.browser_type,
|
||||
'headless': self.headless,
|
||||
'success_rate': self.get_success_rate()
|
||||
}
|
||||
276
domain/entities/browser_fingerprint.py
Normale Datei
276
domain/entities/browser_fingerprint.py
Normale Datei
@ -0,0 +1,276 @@
|
||||
"""
|
||||
Browser Fingerprint Entity - Repräsentiert einen kompletten Browser-Fingerprint
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any, Optional
|
||||
from enum import Enum
|
||||
import uuid
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class StaticComponents:
|
||||
"""Static fingerprint components that don't change"""
|
||||
device_type: str = "desktop" # desktop/mobile/tablet
|
||||
os_family: str = "windows" # windows/macos/linux/android/ios
|
||||
browser_family: str = "chromium" # chromium/firefox/safari
|
||||
gpu_vendor: str = "Intel Inc."
|
||||
gpu_model: str = "Intel Iris OpenGL Engine"
|
||||
cpu_architecture: str = "x86_64"
|
||||
base_fonts: List[str] = field(default_factory=list)
|
||||
base_resolution: tuple = (1920, 1080)
|
||||
base_timezone: str = "Europe/Berlin"
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
'device_type': self.device_type,
|
||||
'os_family': self.os_family,
|
||||
'browser_family': self.browser_family,
|
||||
'gpu_vendor': self.gpu_vendor,
|
||||
'gpu_model': self.gpu_model,
|
||||
'cpu_architecture': self.cpu_architecture,
|
||||
'base_fonts': self.base_fonts,
|
||||
'base_resolution': self.base_resolution,
|
||||
'base_timezone': self.base_timezone
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class CanvasNoise:
|
||||
"""Canvas Fingerprinting Schutz-Konfiguration"""
|
||||
noise_level: float = 0.02
|
||||
seed: int = 42
|
||||
algorithm: str = "gaussian"
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebRTCConfig:
|
||||
"""WebRTC Konfiguration für IP-Leak Prevention"""
|
||||
enabled: bool = True
|
||||
ice_servers: List[str] = field(default_factory=list)
|
||||
local_ip_mask: str = "10.0.0.x"
|
||||
disable_webrtc: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
class HardwareConfig:
|
||||
"""Hardware-Konfiguration für Fingerprinting"""
|
||||
hardware_concurrency: int = 4
|
||||
device_memory: int = 8
|
||||
max_touch_points: int = 0
|
||||
screen_resolution: tuple = (1920, 1080)
|
||||
color_depth: int = 24
|
||||
pixel_ratio: float = 1.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class NavigatorProperties:
|
||||
"""Navigator-Eigenschaften für Browser-Fingerprint"""
|
||||
platform: str = "Win32"
|
||||
vendor: str = "Google Inc."
|
||||
vendor_sub: str = ""
|
||||
product: str = "Gecko"
|
||||
product_sub: str = "20030107"
|
||||
app_name: str = "Netscape"
|
||||
app_version: str = "5.0"
|
||||
user_agent: str = ""
|
||||
language: str = "de-DE"
|
||||
languages: List[str] = field(default_factory=lambda: ["de-DE", "de", "en-US", "en"])
|
||||
online: bool = True
|
||||
do_not_track: str = "1"
|
||||
|
||||
|
||||
@dataclass
|
||||
class BrowserFingerprint:
|
||||
"""Repräsentiert einen kompletten Browser-Fingerprint"""
|
||||
|
||||
fingerprint_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
canvas_noise: CanvasNoise = field(default_factory=CanvasNoise)
|
||||
webrtc_config: WebRTCConfig = field(default_factory=WebRTCConfig)
|
||||
font_list: List[str] = field(default_factory=list)
|
||||
hardware_config: HardwareConfig = field(default_factory=HardwareConfig)
|
||||
navigator_props: NavigatorProperties = field(default_factory=NavigatorProperties)
|
||||
created_at: datetime = field(default_factory=datetime.now)
|
||||
last_rotated: datetime = None
|
||||
|
||||
# WebGL Parameter
|
||||
webgl_vendor: str = "Intel Inc."
|
||||
webgl_renderer: str = "Intel Iris OpenGL Engine"
|
||||
|
||||
# Audio Context
|
||||
audio_context_base_latency: float = 0.00
|
||||
audio_context_output_latency: float = 0.00
|
||||
audio_context_sample_rate: int = 48000
|
||||
|
||||
# Timezone
|
||||
timezone: str = "Europe/Berlin"
|
||||
timezone_offset: int = -60 # UTC+1
|
||||
|
||||
# Plugins
|
||||
plugins: List[Dict[str, str]] = field(default_factory=list)
|
||||
|
||||
# New fields for account-bound persistence
|
||||
static_components: Optional[StaticComponents] = None
|
||||
rotation_seed: Optional[str] = None
|
||||
account_bound: bool = False
|
||||
platform_specific_config: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Konvertiert Fingerprint zu Dictionary für Serialisierung"""
|
||||
return {
|
||||
'fingerprint_id': self.fingerprint_id,
|
||||
'canvas_noise': {
|
||||
'noise_level': self.canvas_noise.noise_level,
|
||||
'seed': self.canvas_noise.seed,
|
||||
'algorithm': self.canvas_noise.algorithm
|
||||
},
|
||||
'webrtc_config': {
|
||||
'enabled': self.webrtc_config.enabled,
|
||||
'ice_servers': self.webrtc_config.ice_servers,
|
||||
'local_ip_mask': self.webrtc_config.local_ip_mask,
|
||||
'disable_webrtc': self.webrtc_config.disable_webrtc
|
||||
},
|
||||
'font_list': self.font_list,
|
||||
'hardware_config': {
|
||||
'hardware_concurrency': self.hardware_config.hardware_concurrency,
|
||||
'device_memory': self.hardware_config.device_memory,
|
||||
'max_touch_points': self.hardware_config.max_touch_points,
|
||||
'screen_resolution': self.hardware_config.screen_resolution,
|
||||
'color_depth': self.hardware_config.color_depth,
|
||||
'pixel_ratio': self.hardware_config.pixel_ratio
|
||||
},
|
||||
'navigator_props': {
|
||||
'platform': self.navigator_props.platform,
|
||||
'vendor': self.navigator_props.vendor,
|
||||
'vendor_sub': self.navigator_props.vendor_sub,
|
||||
'product': self.navigator_props.product,
|
||||
'product_sub': self.navigator_props.product_sub,
|
||||
'app_name': self.navigator_props.app_name,
|
||||
'app_version': self.navigator_props.app_version,
|
||||
'user_agent': self.navigator_props.user_agent,
|
||||
'language': self.navigator_props.language,
|
||||
'languages': self.navigator_props.languages,
|
||||
'online': self.navigator_props.online,
|
||||
'do_not_track': self.navigator_props.do_not_track
|
||||
},
|
||||
'webgl_vendor': self.webgl_vendor,
|
||||
'webgl_renderer': self.webgl_renderer,
|
||||
'audio_context': {
|
||||
'base_latency': self.audio_context_base_latency,
|
||||
'output_latency': self.audio_context_output_latency,
|
||||
'sample_rate': self.audio_context_sample_rate
|
||||
},
|
||||
'timezone': self.timezone,
|
||||
'timezone_offset': self.timezone_offset,
|
||||
'plugins': self.plugins,
|
||||
'created_at': self.created_at.isoformat(),
|
||||
'last_rotated': self.last_rotated.isoformat() if self.last_rotated else None,
|
||||
'static_components': self.static_components.to_dict() if self.static_components else None,
|
||||
'rotation_seed': self.rotation_seed,
|
||||
'account_bound': self.account_bound,
|
||||
'platform_specific_config': self.platform_specific_config
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'BrowserFingerprint':
|
||||
"""Creates BrowserFingerprint from dictionary"""
|
||||
fingerprint = cls()
|
||||
|
||||
# Basic fields
|
||||
fingerprint.fingerprint_id = data.get('fingerprint_id', str(uuid.uuid4()))
|
||||
fingerprint.webgl_vendor = data.get('webgl_vendor', "Intel Inc.")
|
||||
fingerprint.webgl_renderer = data.get('webgl_renderer', "Intel Iris OpenGL Engine")
|
||||
fingerprint.timezone = data.get('timezone', "Europe/Berlin")
|
||||
fingerprint.timezone_offset = data.get('timezone_offset', -60)
|
||||
fingerprint.plugins = data.get('plugins', [])
|
||||
|
||||
# Canvas noise
|
||||
if 'canvas_noise' in data:
|
||||
cn = data['canvas_noise']
|
||||
fingerprint.canvas_noise = CanvasNoise(
|
||||
noise_level=cn.get('noise_level', 0.02),
|
||||
seed=cn.get('seed', 42),
|
||||
algorithm=cn.get('algorithm', 'gaussian')
|
||||
)
|
||||
|
||||
# WebRTC config
|
||||
if 'webrtc_config' in data:
|
||||
wc = data['webrtc_config']
|
||||
fingerprint.webrtc_config = WebRTCConfig(
|
||||
enabled=wc.get('enabled', True),
|
||||
ice_servers=wc.get('ice_servers', []),
|
||||
local_ip_mask=wc.get('local_ip_mask', "10.0.0.x"),
|
||||
disable_webrtc=wc.get('disable_webrtc', False)
|
||||
)
|
||||
|
||||
# Hardware config
|
||||
if 'hardware_config' in data:
|
||||
hc = data['hardware_config']
|
||||
fingerprint.hardware_config = HardwareConfig(
|
||||
hardware_concurrency=hc.get('hardware_concurrency', 4),
|
||||
device_memory=hc.get('device_memory', 8),
|
||||
max_touch_points=hc.get('max_touch_points', 0),
|
||||
screen_resolution=tuple(hc.get('screen_resolution', [1920, 1080])),
|
||||
color_depth=hc.get('color_depth', 24),
|
||||
pixel_ratio=hc.get('pixel_ratio', 1.0)
|
||||
)
|
||||
|
||||
# Navigator properties
|
||||
if 'navigator_props' in data:
|
||||
np = data['navigator_props']
|
||||
fingerprint.navigator_props = NavigatorProperties(
|
||||
platform=np.get('platform', "Win32"),
|
||||
vendor=np.get('vendor', "Google Inc."),
|
||||
vendor_sub=np.get('vendor_sub', ""),
|
||||
product=np.get('product', "Gecko"),
|
||||
product_sub=np.get('product_sub', "20030107"),
|
||||
app_name=np.get('app_name', "Netscape"),
|
||||
app_version=np.get('app_version', "5.0"),
|
||||
user_agent=np.get('user_agent', ""),
|
||||
language=np.get('language', "de-DE"),
|
||||
languages=np.get('languages', ["de-DE", "de", "en-US", "en"]),
|
||||
online=np.get('online', True),
|
||||
do_not_track=np.get('do_not_track', "1")
|
||||
)
|
||||
|
||||
# Audio context
|
||||
if 'audio_context' in data:
|
||||
ac = data['audio_context']
|
||||
fingerprint.audio_context_base_latency = ac.get('base_latency', 0.00)
|
||||
fingerprint.audio_context_output_latency = ac.get('output_latency', 0.00)
|
||||
fingerprint.audio_context_sample_rate = ac.get('sample_rate', 48000)
|
||||
|
||||
# Font list
|
||||
fingerprint.font_list = data.get('font_list', [])
|
||||
|
||||
# Dates
|
||||
if 'created_at' in data:
|
||||
fingerprint.created_at = datetime.fromisoformat(data['created_at'])
|
||||
if 'last_rotated' in data and data['last_rotated']:
|
||||
fingerprint.last_rotated = datetime.fromisoformat(data['last_rotated'])
|
||||
|
||||
# New persistence fields
|
||||
if 'static_components' in data and data['static_components']:
|
||||
sc = data['static_components']
|
||||
fingerprint.static_components = StaticComponents(
|
||||
device_type=sc.get('device_type', 'desktop'),
|
||||
os_family=sc.get('os_family', 'windows'),
|
||||
browser_family=sc.get('browser_family', 'chromium'),
|
||||
gpu_vendor=sc.get('gpu_vendor', 'Intel Inc.'),
|
||||
gpu_model=sc.get('gpu_model', 'Intel Iris OpenGL Engine'),
|
||||
cpu_architecture=sc.get('cpu_architecture', 'x86_64'),
|
||||
base_fonts=sc.get('base_fonts', []),
|
||||
base_resolution=tuple(sc.get('base_resolution', [1920, 1080])),
|
||||
base_timezone=sc.get('base_timezone', 'Europe/Berlin')
|
||||
)
|
||||
|
||||
fingerprint.rotation_seed = data.get('rotation_seed')
|
||||
fingerprint.account_bound = data.get('account_bound', False)
|
||||
fingerprint.platform_specific_config = data.get('platform_specific_config', {})
|
||||
|
||||
return fingerprint
|
||||
150
domain/entities/error_event.py
Normale Datei
150
domain/entities/error_event.py
Normale Datei
@ -0,0 +1,150 @@
|
||||
"""
|
||||
Error Event Entity - Detailliertes Fehler-Event
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, List
|
||||
from enum import Enum
|
||||
import uuid
|
||||
|
||||
|
||||
class ErrorType(Enum):
|
||||
"""Typen von Fehlern die auftreten können"""
|
||||
RATE_LIMIT = "rate_limit"
|
||||
CAPTCHA = "captcha"
|
||||
NETWORK = "network"
|
||||
VALIDATION = "validation"
|
||||
BROWSER = "browser"
|
||||
PROXY = "proxy"
|
||||
EMAIL = "email"
|
||||
TIMEOUT = "timeout"
|
||||
AUTHENTICATION = "authentication"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
class ErrorSeverity(Enum):
|
||||
"""Schweregrad des Fehlers"""
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
HIGH = "high"
|
||||
CRITICAL = "critical"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ErrorContext:
|
||||
"""Kontext-Informationen zum Fehler"""
|
||||
url: Optional[str] = None
|
||||
action: Optional[str] = None
|
||||
step_name: Optional[str] = None
|
||||
user_input: Optional[Dict[str, Any]] = None
|
||||
browser_state: Optional[Dict[str, Any]] = None
|
||||
network_state: Optional[Dict[str, Any]] = None
|
||||
screenshot_path: Optional[str] = None
|
||||
html_snapshot: Optional[str] = None
|
||||
additional_data: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class RecoveryAttempt:
|
||||
"""Informationen über Wiederherstellungsversuche"""
|
||||
strategy: str
|
||||
timestamp: datetime
|
||||
successful: bool
|
||||
error_message: Optional[str] = None
|
||||
duration_seconds: float = 0.0
|
||||
|
||||
|
||||
@dataclass
|
||||
class ErrorEvent:
|
||||
"""Detailliertes Fehler-Event"""
|
||||
|
||||
error_id: str = field(default_factory=lambda: str(uuid.uuid4()))
|
||||
timestamp: datetime = field(default_factory=datetime.now)
|
||||
error_type: ErrorType = ErrorType.UNKNOWN
|
||||
error_message: str = ""
|
||||
stack_trace: Optional[str] = None
|
||||
context: ErrorContext = field(default_factory=ErrorContext)
|
||||
recovery_attempted: bool = False
|
||||
recovery_successful: bool = False
|
||||
recovery_attempts: List[RecoveryAttempt] = field(default_factory=list)
|
||||
|
||||
# Fehler-Metadaten
|
||||
severity: ErrorSeverity = ErrorSeverity.MEDIUM
|
||||
platform: Optional[str] = None
|
||||
session_id: Optional[str] = None
|
||||
account_id: Optional[str] = None
|
||||
correlation_id: Optional[str] = None
|
||||
|
||||
# Impact-Metriken
|
||||
user_impact: bool = True
|
||||
system_impact: bool = False
|
||||
data_loss: bool = False
|
||||
|
||||
def add_recovery_attempt(self, attempt: RecoveryAttempt):
|
||||
"""Fügt einen Wiederherstellungsversuch hinzu"""
|
||||
self.recovery_attempts.append(attempt)
|
||||
self.recovery_attempted = True
|
||||
if attempt.successful:
|
||||
self.recovery_successful = True
|
||||
|
||||
def get_recovery_success_rate(self) -> float:
|
||||
"""Berechnet die Erfolgsrate der Wiederherstellungsversuche"""
|
||||
if not self.recovery_attempts:
|
||||
return 0.0
|
||||
successful = sum(1 for attempt in self.recovery_attempts if attempt.successful)
|
||||
return successful / len(self.recovery_attempts)
|
||||
|
||||
def is_critical(self) -> bool:
|
||||
"""Prüft ob der Fehler kritisch ist"""
|
||||
return self.severity == ErrorSeverity.CRITICAL or self.data_loss
|
||||
|
||||
def should_retry(self) -> bool:
|
||||
"""Entscheidet ob ein Retry sinnvoll ist"""
|
||||
recoverable_types = [
|
||||
ErrorType.NETWORK,
|
||||
ErrorType.TIMEOUT,
|
||||
ErrorType.RATE_LIMIT,
|
||||
ErrorType.PROXY
|
||||
]
|
||||
return (self.error_type in recoverable_types and
|
||||
len(self.recovery_attempts) < 3 and
|
||||
not self.recovery_successful)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Konvertiert Event zu Dictionary für Serialisierung"""
|
||||
return {
|
||||
'error_id': self.error_id,
|
||||
'timestamp': self.timestamp.isoformat(),
|
||||
'error_type': self.error_type.value,
|
||||
'error_message': self.error_message,
|
||||
'stack_trace': self.stack_trace,
|
||||
'context': {
|
||||
'url': self.context.url,
|
||||
'action': self.context.action,
|
||||
'step_name': self.context.step_name,
|
||||
'screenshot_path': self.context.screenshot_path,
|
||||
'additional_data': self.context.additional_data
|
||||
},
|
||||
'recovery_attempted': self.recovery_attempted,
|
||||
'recovery_successful': self.recovery_successful,
|
||||
'recovery_attempts': [
|
||||
{
|
||||
'strategy': attempt.strategy,
|
||||
'timestamp': attempt.timestamp.isoformat(),
|
||||
'successful': attempt.successful,
|
||||
'error_message': attempt.error_message,
|
||||
'duration_seconds': attempt.duration_seconds
|
||||
}
|
||||
for attempt in self.recovery_attempts
|
||||
],
|
||||
'severity': self.severity.value,
|
||||
'platform': self.platform,
|
||||
'session_id': self.session_id,
|
||||
'account_id': self.account_id,
|
||||
'correlation_id': self.correlation_id,
|
||||
'user_impact': self.user_impact,
|
||||
'system_impact': self.system_impact,
|
||||
'data_loss': self.data_loss,
|
||||
'recovery_success_rate': self.get_recovery_success_rate()
|
||||
}
|
||||
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()
|
||||
}
|
||||
36
domain/entities/rate_limit_policy.py
Normale Datei
36
domain/entities/rate_limit_policy.py
Normale Datei
@ -0,0 +1,36 @@
|
||||
"""
|
||||
Rate Limit Policy Entity - Definiert Geschwindigkeitsregeln für verschiedene Aktionen
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class RateLimitPolicy:
|
||||
"""Definiert Geschwindigkeitsregeln für verschiedene Aktionen"""
|
||||
|
||||
min_delay: float
|
||||
max_delay: float
|
||||
adaptive: bool = True
|
||||
backoff_multiplier: float = 1.5
|
||||
max_retries: int = 3
|
||||
|
||||
def __post_init__(self):
|
||||
"""Validierung der Policy-Parameter"""
|
||||
if self.min_delay < 0:
|
||||
raise ValueError("min_delay muss >= 0 sein")
|
||||
if self.max_delay < self.min_delay:
|
||||
raise ValueError("max_delay muss >= min_delay sein")
|
||||
if self.backoff_multiplier < 1.0:
|
||||
raise ValueError("backoff_multiplier muss >= 1.0 sein")
|
||||
if self.max_retries < 0:
|
||||
raise ValueError("max_retries muss >= 0 sein")
|
||||
|
||||
def calculate_backoff_delay(self, attempt: int) -> float:
|
||||
"""Berechnet Verzögerung basierend auf Versuchsnummer"""
|
||||
if not self.adaptive:
|
||||
return self.min_delay
|
||||
|
||||
delay = self.min_delay * (self.backoff_multiplier ** attempt)
|
||||
return min(delay, self.max_delay)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren