Dieser Commit ist enthalten in:
Claude Project Manager
2025-08-01 23:50:28 +02:00
Commit 04585e95b6
290 geänderte Dateien mit 64086 neuen und 0 gelöschten Zeilen

3
domain/__init__.py Normale Datei
Datei anzeigen

@ -0,0 +1,3 @@
"""
Domain Layer - Enthält die Geschäftslogik und Kernkonzepte der Anwendung
"""

15
domain/entities/__init__.py Normale Datei
Datei anzeigen

@ -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'
]

Datei anzeigen

@ -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()
}

Datei anzeigen

@ -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
Datei anzeigen

@ -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()
}

Datei anzeigen

@ -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()
}

Datei anzeigen

@ -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)

96
domain/exceptions.py Normale Datei
Datei anzeigen

@ -0,0 +1,96 @@
"""
Domain-spezifische Exceptions für AccountForger
"""
from typing import Optional, Dict, Any
class AccountForgerException(Exception):
"""Basis-Exception für alle AccountForger-spezifischen Fehler"""
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
super().__init__(message)
self.message = message
self.details = details or {}
class AccountCreationException(AccountForgerException):
"""Fehler bei Account-Erstellung"""
def __init__(self, message: str, platform: Optional[str] = None,
error_type: Optional[str] = None, recovery_suggestion: Optional[str] = None):
details = {
"platform": platform,
"error_type": error_type,
"recovery_suggestion": recovery_suggestion
}
super().__init__(message, details)
self.platform = platform
self.error_type = error_type
self.recovery_suggestion = recovery_suggestion
@property
def user_friendly_message(self) -> str:
"""Gibt eine benutzerfreundliche Fehlermeldung zurück"""
if self.recovery_suggestion:
return f"{self.message}\n\nLösungsvorschlag: {self.recovery_suggestion}"
return self.message
class FingerprintException(AccountForgerException):
"""Fehler bei Fingerprint-Operationen"""
pass
class SessionException(AccountForgerException):
"""Fehler bei Session-Operationen"""
pass
class RateLimitException(AccountCreationException):
"""Rate-Limit wurde erreicht"""
def __init__(self, platform: str, retry_after: Optional[int] = None):
message = f"Zu viele Anfragen an {platform}"
recovery = f"Bitte warten Sie {retry_after} Sekunden" if retry_after else "Bitte warten Sie einige Minuten"
super().__init__(
message=message,
platform=platform,
error_type="rate_limit",
recovery_suggestion=recovery
)
self.retry_after = retry_after
class CaptchaRequiredException(AccountCreationException):
"""Captcha-Verifizierung erforderlich"""
def __init__(self, platform: str):
super().__init__(
message=f"{platform} erfordert Captcha-Verifizierung",
platform=platform,
error_type="captcha",
recovery_suggestion="Versuchen Sie es später erneut oder nutzen Sie einen anderen Proxy"
)
class ValidationException(AccountForgerException):
"""Validierungsfehler"""
def __init__(self, field: str, message: str):
super().__init__(f"Validierungsfehler bei {field}: {message}")
self.field = field
class ProxyException(AccountForgerException):
"""Proxy-bezogene Fehler"""
pass
class NetworkException(AccountForgerException):
"""Netzwerk-bezogene Fehler"""
def __init__(self, message: str = "Netzwerkfehler aufgetreten"):
super().__init__(
message=message,
details={"recovery_suggestion": "Überprüfen Sie Ihre Internetverbindung"}
)

Datei anzeigen

@ -0,0 +1,17 @@
"""
Domain repository interfaces.
These interfaces define the contracts for data persistence,
following the Dependency Inversion Principle.
Infrastructure layer will implement these interfaces.
"""
from .fingerprint_repository import IFingerprintRepository
from .analytics_repository import IAnalyticsRepository
from .rate_limit_repository import IRateLimitRepository
__all__ = [
'IFingerprintRepository',
'IAnalyticsRepository',
'IRateLimitRepository'
]

Datei anzeigen

@ -0,0 +1,79 @@
"""
Analytics repository interface.
"""
from abc import ABC, abstractmethod
from typing import List, Dict, Optional
from datetime import datetime
from domain.entities.account_creation_event import AccountCreationEvent
from domain.entities.error_event import ErrorEvent
from domain.value_objects.error_summary import ErrorSummary
class IAnalyticsRepository(ABC):
"""Interface for analytics data persistence."""
@abstractmethod
def save_account_event(self, event: AccountCreationEvent) -> None:
"""Save an account creation event."""
pass
@abstractmethod
def save_error_event(self, event: ErrorEvent) -> None:
"""Save an error event."""
pass
@abstractmethod
def get_account_events(self, platform: str = None,
start_date: datetime = None,
end_date: datetime = None) -> List[AccountCreationEvent]:
"""Get account creation events with optional filters."""
pass
@abstractmethod
def get_error_events(self, platform: str = None,
error_type: str = None,
start_date: datetime = None,
end_date: datetime = None) -> List[ErrorEvent]:
"""Get error events with optional filters."""
pass
@abstractmethod
def get_success_rate(self, platform: str = None,
start_date: datetime = None,
end_date: datetime = None) -> float:
"""Calculate success rate for account creation."""
pass
@abstractmethod
def get_error_summary(self, platform: str = None,
days: int = 7) -> ErrorSummary:
"""Get error summary for specified period."""
pass
@abstractmethod
def get_platform_statistics(self) -> Dict[str, Dict[str, int]]:
"""Get statistics grouped by platform."""
pass
@abstractmethod
def get_hourly_distribution(self, platform: str = None,
days: int = 7) -> Dict[int, int]:
"""Get hourly distribution of account creation."""
pass
@abstractmethod
def get_fingerprint_performance(self, fingerprint_id: str) -> Dict[str, any]:
"""Get performance metrics for a specific fingerprint."""
pass
@abstractmethod
def get_proxy_performance(self, days: int = 7) -> Dict[str, Dict[str, int]]:
"""Get proxy performance metrics."""
pass
@abstractmethod
def cleanup_old_events(self, days_to_keep: int = 30) -> int:
"""Remove events older than specified days. Returns count deleted."""
pass

Datei anzeigen

@ -0,0 +1,63 @@
"""
Fingerprint repository interface.
"""
from abc import ABC, abstractmethod
from typing import Optional, List
from datetime import datetime
from domain.entities.browser_fingerprint import BrowserFingerprint
class IFingerprintRepository(ABC):
"""Interface for fingerprint persistence."""
@abstractmethod
def save(self, fingerprint: BrowserFingerprint) -> str:
"""Save a fingerprint and return its ID."""
pass
@abstractmethod
def find_by_id(self, fingerprint_id: str) -> Optional[BrowserFingerprint]:
"""Find a fingerprint by ID."""
pass
@abstractmethod
def find_by_account_id(self, account_id: str) -> Optional[BrowserFingerprint]:
"""Find a fingerprint associated with an account."""
pass
@abstractmethod
def find_all(self) -> List[BrowserFingerprint]:
"""Find all fingerprints."""
pass
@abstractmethod
def update(self, fingerprint: BrowserFingerprint) -> bool:
"""Update an existing fingerprint."""
pass
@abstractmethod
def delete(self, fingerprint_id: str) -> bool:
"""Delete a fingerprint by ID."""
pass
@abstractmethod
def find_by_platform(self, platform: str) -> List[BrowserFingerprint]:
"""Find all fingerprints for a specific platform."""
pass
@abstractmethod
def exists(self, fingerprint_id: str) -> bool:
"""Check if a fingerprint exists."""
pass
@abstractmethod
def count(self) -> int:
"""Count total fingerprints."""
pass
@abstractmethod
def find_recent(self, limit: int = 10) -> List[BrowserFingerprint]:
"""Find most recently created fingerprints."""
pass

Datei anzeigen

@ -0,0 +1,310 @@
"""
Repository interfaces for method rotation system.
These interfaces define the contracts for data access without implementation details.
"""
from abc import ABC, abstractmethod
from datetime import datetime, date
from typing import List, Optional, Dict, Any
from domain.entities.method_rotation import (
MethodStrategy, RotationSession, RotationEvent, PlatformMethodState
)
class IMethodStrategyRepository(ABC):
"""Interface for method strategy data access"""
@abstractmethod
def save(self, strategy: MethodStrategy) -> None:
"""Save or update a method strategy"""
pass
@abstractmethod
def find_by_id(self, strategy_id: str) -> Optional[MethodStrategy]:
"""Find a strategy by its ID"""
pass
@abstractmethod
def find_by_platform(self, platform: str) -> List[MethodStrategy]:
"""Find all strategies for a platform"""
pass
@abstractmethod
def find_active_by_platform(self, platform: str) -> List[MethodStrategy]:
"""Find all active strategies for a platform, ordered by effectiveness"""
pass
@abstractmethod
def find_by_platform_and_method(self, platform: str, method_name: str) -> Optional[MethodStrategy]:
"""Find a specific method strategy"""
pass
@abstractmethod
def update_performance_metrics(self, strategy_id: str, success: bool,
execution_time: float = 0.0) -> None:
"""Update performance metrics for a strategy"""
pass
@abstractmethod
def get_next_available_method(self, platform: str,
excluded_methods: List[str] = None,
max_risk_level: str = "HIGH") -> Optional[MethodStrategy]:
"""Get the next best available method for a platform"""
pass
@abstractmethod
def disable_method(self, platform: str, method_name: str, reason: str) -> None:
"""Disable a method temporarily or permanently"""
pass
@abstractmethod
def enable_method(self, platform: str, method_name: str) -> None:
"""Re-enable a disabled method"""
pass
@abstractmethod
def get_platform_statistics(self, platform: str) -> Dict[str, Any]:
"""Get aggregated statistics for all methods on a platform"""
pass
@abstractmethod
def cleanup_old_data(self, days_to_keep: int = 90) -> int:
"""Clean up old performance data and return number of records removed"""
pass
class IRotationSessionRepository(ABC):
"""Interface for rotation session data access"""
@abstractmethod
def save(self, session: RotationSession) -> None:
"""Save or update a rotation session"""
pass
@abstractmethod
def find_by_id(self, session_id: str) -> Optional[RotationSession]:
"""Find a session by its ID"""
pass
@abstractmethod
def find_active_session(self, platform: str, account_id: Optional[str] = None) -> Optional[RotationSession]:
"""Find an active session for a platform/account"""
pass
@abstractmethod
def find_active_sessions_by_platform(self, platform: str) -> List[RotationSession]:
"""Find all active sessions for a platform"""
pass
@abstractmethod
def update_session_metrics(self, session_id: str, success: bool,
method_name: str, error_message: Optional[str] = None) -> None:
"""Update session metrics after a method attempt"""
pass
@abstractmethod
def archive_session(self, session_id: str, final_success: bool = False) -> None:
"""Mark a session as completed/archived"""
pass
@abstractmethod
def get_session_history(self, platform: str, limit: int = 100) -> List[RotationSession]:
"""Get recent session history for a platform"""
pass
@abstractmethod
def get_session_statistics(self, platform: str, days: int = 30) -> Dict[str, Any]:
"""Get session statistics for a platform over specified days"""
pass
@abstractmethod
def cleanup_old_sessions(self, days_to_keep: int = 30) -> int:
"""Clean up old session data and return number of records removed"""
pass
class IRotationEventRepository(ABC):
"""Interface for rotation event data access"""
@abstractmethod
def save(self, event: RotationEvent) -> None:
"""Save a rotation event"""
pass
@abstractmethod
def save_batch(self, events: List[RotationEvent]) -> None:
"""Save multiple events in a batch for performance"""
pass
@abstractmethod
def find_by_session(self, session_id: str) -> List[RotationEvent]:
"""Find all events for a specific session"""
pass
@abstractmethod
def find_by_method(self, platform: str, method_name: str,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None) -> List[RotationEvent]:
"""Find events for a specific method within date range"""
pass
@abstractmethod
def find_recent_failures(self, platform: str, method_name: str,
hours: int = 24) -> List[RotationEvent]:
"""Find recent failure events for a method"""
pass
@abstractmethod
def get_event_statistics(self, platform: str,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None) -> Dict[str, Any]:
"""Get event statistics for analysis"""
pass
@abstractmethod
def get_error_patterns(self, platform: str, method_name: str,
days: int = 7) -> Dict[str, int]:
"""Get error patterns for a method to identify common issues"""
pass
@abstractmethod
def cleanup_old_events(self, days_to_keep: int = 90) -> int:
"""Clean up old event data and return number of records removed"""
pass
class IPlatformMethodStateRepository(ABC):
"""Interface for platform method state data access"""
@abstractmethod
def save(self, state: PlatformMethodState) -> None:
"""Save or update platform method state"""
pass
@abstractmethod
def find_by_platform(self, platform: str) -> Optional[PlatformMethodState]:
"""Find method state for a platform"""
pass
@abstractmethod
def get_or_create_state(self, platform: str) -> PlatformMethodState:
"""Get existing state or create new one with defaults"""
pass
@abstractmethod
def update_daily_attempts(self, platform: str, method_name: str) -> None:
"""Increment daily attempt counter for a method"""
pass
@abstractmethod
def reset_daily_counters(self, platform: str) -> None:
"""Reset daily attempt counters (typically called at midnight)"""
pass
@abstractmethod
def block_method(self, platform: str, method_name: str, reason: str) -> None:
"""Block a method temporarily"""
pass
@abstractmethod
def unblock_method(self, platform: str, method_name: str) -> None:
"""Unblock a previously blocked method"""
pass
@abstractmethod
def record_method_success(self, platform: str, method_name: str) -> None:
"""Record successful method execution"""
pass
@abstractmethod
def get_preferred_method_order(self, platform: str) -> List[str]:
"""Get preferred method order for a platform"""
pass
@abstractmethod
def set_emergency_mode(self, platform: str, enabled: bool) -> None:
"""Enable/disable emergency mode for a platform"""
pass
class IMethodPerformanceRepository(ABC):
"""Interface for method performance analytics data access"""
@abstractmethod
def record_daily_performance(self, platform: str, method_name: str,
success: bool, execution_time: float = 0.0) -> None:
"""Record performance data for daily aggregation"""
pass
@abstractmethod
def get_daily_performance(self, platform: str, method_name: str,
start_date: date, end_date: date) -> List[Dict[str, Any]]:
"""Get daily performance data for a method within date range"""
pass
@abstractmethod
def get_method_trends(self, platform: str, days: int = 30) -> Dict[str, Any]:
"""Get performance trends for all methods on a platform"""
pass
@abstractmethod
def get_success_rate_history(self, platform: str, method_name: str,
days: int = 30) -> List[Dict[str, Any]]:
"""Get success rate history for trend analysis"""
pass
@abstractmethod
def get_peak_usage_patterns(self, platform: str, method_name: str) -> Dict[str, Any]:
"""Get usage patterns to identify peak hours and optimize timing"""
pass
@abstractmethod
def aggregate_daily_stats(self, target_date: date) -> int:
"""Aggregate raw performance data into daily statistics"""
pass
@abstractmethod
def cleanup_old_performance_data(self, days_to_keep: int = 365) -> int:
"""Clean up old performance data and return number of records removed"""
pass
class IMethodCooldownRepository(ABC):
"""Interface for method cooldown data access"""
@abstractmethod
def add_cooldown(self, platform: str, method_name: str,
cooldown_until: datetime, reason: str) -> None:
"""Add a cooldown period for a method"""
pass
@abstractmethod
def remove_cooldown(self, platform: str, method_name: str) -> None:
"""Remove cooldown for a method"""
pass
@abstractmethod
def is_method_on_cooldown(self, platform: str, method_name: str) -> bool:
"""Check if a method is currently on cooldown"""
pass
@abstractmethod
def get_cooldown_info(self, platform: str, method_name: str) -> Optional[Dict[str, Any]]:
"""Get cooldown information for a method"""
pass
@abstractmethod
def get_active_cooldowns(self, platform: str) -> List[Dict[str, Any]]:
"""Get all active cooldowns for a platform"""
pass
@abstractmethod
def cleanup_expired_cooldowns(self) -> int:
"""Remove expired cooldowns and return number of records removed"""
pass
@abstractmethod
def extend_cooldown(self, platform: str, method_name: str,
additional_seconds: int) -> None:
"""Extend existing cooldown period"""
pass

Datei anzeigen

@ -0,0 +1,75 @@
"""
Rate limit repository interface.
"""
from abc import ABC, abstractmethod
from typing import Optional, List, Dict
from datetime import datetime
from domain.entities.rate_limit_policy import RateLimitPolicy
class IRateLimitRepository(ABC):
"""Interface for rate limit data persistence."""
@abstractmethod
def save_policy(self, policy: RateLimitPolicy) -> None:
"""Save or update a rate limit policy."""
pass
@abstractmethod
def get_policy(self, platform: str, action: str) -> Optional[RateLimitPolicy]:
"""Get rate limit policy for platform and action."""
pass
@abstractmethod
def get_all_policies(self, platform: str = None) -> List[RateLimitPolicy]:
"""Get all policies, optionally filtered by platform."""
pass
@abstractmethod
def record_action(self, platform: str, action: str,
success: bool = True, proxy: str = None) -> None:
"""Record an action for rate limit tracking."""
pass
@abstractmethod
def get_action_count(self, platform: str, action: str,
window_minutes: int = 60,
proxy: str = None) -> int:
"""Get action count within time window."""
pass
@abstractmethod
def get_recent_actions(self, platform: str, action: str,
limit: int = 100) -> List[Dict[str, any]]:
"""Get recent actions for analysis."""
pass
@abstractmethod
def is_rate_limited(self, platform: str, action: str,
proxy: str = None) -> bool:
"""Check if action is currently rate limited."""
pass
@abstractmethod
def get_wait_time(self, platform: str, action: str,
proxy: str = None) -> int:
"""Get wait time in seconds before next action allowed."""
pass
@abstractmethod
def reset_limits(self, platform: str = None, action: str = None,
proxy: str = None) -> int:
"""Reset rate limits. Returns count of records affected."""
pass
@abstractmethod
def get_limit_status(self, platform: str) -> Dict[str, Dict[str, any]]:
"""Get current rate limit status for all actions on platform."""
pass
@abstractmethod
def cleanup_old_records(self, days_to_keep: int = 7) -> int:
"""Remove old rate limit records. Returns count deleted."""
pass

13
domain/services/__init__.py Normale Datei
Datei anzeigen

@ -0,0 +1,13 @@
"""
Domain Services - Geschäftslogik-Interfaces die nicht in Entities gehören
"""
from .rate_limit_service import IRateLimitService
from .fingerprint_service import IFingerprintService
from .analytics_service import IAnalyticsService
__all__ = [
'IRateLimitService',
'IFingerprintService',
'IAnalyticsService'
]

Datei anzeigen

@ -0,0 +1,181 @@
"""
Analytics Service Interface - Domain Service für Analytics und Reporting
"""
from abc import ABC, abstractmethod
from typing import List, Optional, Dict, Any, Union
from datetime import datetime, timedelta
from domain.entities.account_creation_event import AccountCreationEvent
from domain.entities.error_event import ErrorEvent
from domain.value_objects.error_summary import ErrorSummary
from domain.value_objects.report import Report, ReportType
class IAnalyticsService(ABC):
"""
Interface für Analytics Service.
Definiert die Geschäftslogik für Event-Tracking und Reporting.
"""
@abstractmethod
def log_event(self, event: Union[AccountCreationEvent, ErrorEvent, Any]) -> None:
"""
Loggt ein Event für spätere Analyse.
Args:
event: Zu loggendes Event
"""
pass
@abstractmethod
def get_success_rate(self,
timeframe: Optional[timedelta] = None,
platform: Optional[str] = None) -> float:
"""
Berechnet die Erfolgsrate für Account-Erstellung.
Args:
timeframe: Optional - Zeitrahmen für Berechnung
platform: Optional - Spezifische Plattform
Returns:
Erfolgsrate zwischen 0.0 und 1.0
"""
pass
@abstractmethod
def get_common_errors(self,
limit: int = 10,
timeframe: Optional[timedelta] = None) -> List[ErrorSummary]:
"""
Holt die häufigsten Fehler.
Args:
limit: Maximale Anzahl von Fehlern
timeframe: Optional - Zeitrahmen für Analyse
Returns:
Liste von Fehler-Zusammenfassungen
"""
pass
@abstractmethod
def generate_report(self,
report_type: ReportType,
start: datetime,
end: datetime,
platforms: Optional[List[str]] = None) -> Report:
"""
Generiert einen detaillierten Report.
Args:
report_type: Typ des Reports
start: Startdatum
end: Enddatum
platforms: Optional - Filter für spezifische Plattformen
Returns:
Generierter Report
"""
pass
@abstractmethod
def get_real_time_metrics(self) -> Dict[str, Any]:
"""
Holt Echtzeit-Metriken für Dashboard.
Returns:
Dictionary mit aktuellen Metriken
"""
pass
@abstractmethod
def track_performance(self,
metric_name: str,
value: float,
tags: Optional[Dict[str, str]] = None) -> None:
"""
Trackt eine Performance-Metrik.
Args:
metric_name: Name der Metrik
value: Wert der Metrik
tags: Optional - Zusätzliche Tags
"""
pass
@abstractmethod
def get_account_creation_timeline(self,
hours: int = 24,
platform: Optional[str] = None) -> Dict[str, Any]:
"""
Holt Timeline der Account-Erstellungen.
Args:
hours: Anzahl Stunden zurück
platform: Optional - Spezifische Plattform
Returns:
Timeline-Daten für Visualisierung
"""
pass
@abstractmethod
def analyze_failure_patterns(self,
timeframe: Optional[timedelta] = None) -> Dict[str, Any]:
"""
Analysiert Muster in Fehlern.
Args:
timeframe: Optional - Zeitrahmen für Analyse
Returns:
Dictionary mit Fehler-Mustern und Insights
"""
pass
@abstractmethod
def get_platform_comparison(self,
timeframe: Optional[timedelta] = None) -> Dict[str, Any]:
"""
Vergleicht Performance zwischen Plattformen.
Args:
timeframe: Optional - Zeitrahmen für Vergleich
Returns:
Dictionary mit Plattform-Vergleichsdaten
"""
pass
@abstractmethod
def export_data(self,
format: str = "json",
start: Optional[datetime] = None,
end: Optional[datetime] = None) -> bytes:
"""
Exportiert Analytics-Daten.
Args:
format: Export-Format ("json", "csv", "excel")
start: Optional - Startdatum
end: Optional - Enddatum
Returns:
Exportierte Daten als Bytes
"""
pass
@abstractmethod
def cleanup_old_events(self, older_than: datetime) -> int:
"""
Bereinigt alte Events.
Args:
older_than: Lösche Events älter als dieses Datum
Returns:
Anzahl gelöschter Events
"""
pass

Datei anzeigen

@ -0,0 +1,152 @@
"""
Fingerprint Service Interface - Domain Service für Browser Fingerprinting
"""
from abc import ABC, abstractmethod
from typing import List, Optional, Dict, Any
from datetime import datetime
from domain.entities.browser_fingerprint import BrowserFingerprint
class IFingerprintService(ABC):
"""
Interface für Fingerprint Service.
Definiert die Geschäftslogik für Browser Fingerprint Management.
"""
@abstractmethod
def generate_fingerprint(self,
profile_type: Optional[str] = None,
platform: Optional[str] = None) -> BrowserFingerprint:
"""
Generiert einen neuen, realistischen Browser-Fingerprint.
Args:
profile_type: Optional - Typ des Profils (z.B. "mobile", "desktop")
platform: Optional - Zielplattform (z.B. "instagram", "tiktok")
Returns:
Neuer Browser-Fingerprint
"""
pass
@abstractmethod
def rotate_fingerprint(self,
current: BrowserFingerprint,
rotation_strategy: str = "gradual") -> BrowserFingerprint:
"""
Rotiert einen bestehenden Fingerprint für mehr Anonymität.
Args:
current: Aktueller Fingerprint
rotation_strategy: Strategie für Rotation ("gradual", "complete", "minimal")
Returns:
Neuer rotierter Fingerprint
"""
pass
@abstractmethod
def validate_fingerprint(self, fingerprint: BrowserFingerprint) -> tuple[bool, List[str]]:
"""
Validiert einen Fingerprint auf Konsistenz und Realismus.
Args:
fingerprint: Zu validierender Fingerprint
Returns:
Tuple aus (ist_valide, liste_von_problemen)
"""
pass
@abstractmethod
def save_fingerprint(self, fingerprint: BrowserFingerprint) -> None:
"""
Speichert einen Fingerprint für spätere Verwendung.
Args:
fingerprint: Zu speichernder Fingerprint
"""
pass
@abstractmethod
def load_fingerprint(self, fingerprint_id: str) -> Optional[BrowserFingerprint]:
"""
Lädt einen gespeicherten Fingerprint.
Args:
fingerprint_id: ID des Fingerprints
Returns:
Fingerprint oder None wenn nicht gefunden
"""
pass
@abstractmethod
def get_fingerprint_pool(self,
count: int = 10,
platform: Optional[str] = None) -> List[BrowserFingerprint]:
"""
Holt einen Pool von Fingerprints für Rotation.
Args:
count: Anzahl der gewünschten Fingerprints
platform: Optional - Filter für spezifische Plattform
Returns:
Liste von Fingerprints
"""
pass
@abstractmethod
def apply_fingerprint(self,
browser_context: Any,
fingerprint: BrowserFingerprint) -> None:
"""
Wendet einen Fingerprint auf einen Browser-Kontext an.
Args:
browser_context: Playwright Browser Context
fingerprint: Anzuwendender Fingerprint
"""
pass
@abstractmethod
def detect_fingerprinting(self, page_content: str) -> Dict[str, Any]:
"""
Erkennt Fingerprinting-Versuche auf einer Webseite.
Args:
page_content: HTML oder JavaScript Content der Seite
Returns:
Dictionary mit erkannten Fingerprinting-Techniken
"""
pass
@abstractmethod
def get_fingerprint_score(self, fingerprint: BrowserFingerprint) -> float:
"""
Bewertet die Qualität/Einzigartigkeit eines Fingerprints.
Args:
fingerprint: Zu bewertender Fingerprint
Returns:
Score zwischen 0.0 (schlecht) und 1.0 (gut)
"""
pass
@abstractmethod
def cleanup_old_fingerprints(self, older_than: datetime) -> int:
"""
Bereinigt alte, nicht mehr verwendete Fingerprints.
Args:
older_than: Lösche Fingerprints älter als dieses Datum
Returns:
Anzahl gelöschter Fingerprints
"""
pass

Datei anzeigen

@ -0,0 +1,125 @@
"""
Rate Limit Service Interface - Domain Service für Rate Limiting
"""
from abc import ABC, abstractmethod
from typing import Optional, List, Dict, Any
from datetime import datetime, timedelta
from domain.value_objects.action_timing import ActionTiming, ActionType
from domain.entities.rate_limit_policy import RateLimitPolicy
class IRateLimitService(ABC):
"""
Interface für Rate Limit Service.
Definiert die Geschäftslogik für adaptives Rate Limiting.
"""
@abstractmethod
def calculate_delay(self, action_type: ActionType, context: Optional[Dict[str, Any]] = None) -> float:
"""
Berechnet die optimale Verzögerung für eine Aktion.
Args:
action_type: Typ der auszuführenden Aktion
context: Optionaler Kontext (z.B. Platform, Session-ID)
Returns:
Verzögerung in Sekunden
"""
pass
@abstractmethod
def record_action(self, timing: ActionTiming) -> None:
"""
Zeichnet eine ausgeführte Aktion für Analyse auf.
Args:
timing: Timing-Informationen der Aktion
"""
pass
@abstractmethod
def detect_rate_limit(self, response: Any) -> bool:
"""
Erkennt ob eine Response auf Rate Limiting hindeutet.
Args:
response: HTTP Response oder Browser-Seite
Returns:
True wenn Rate Limit erkannt wurde
"""
pass
@abstractmethod
def get_policy(self, action_type: ActionType) -> RateLimitPolicy:
"""
Holt die aktuelle Rate Limit Policy für einen Action Type.
Args:
action_type: Typ der Aktion
Returns:
Rate Limit Policy
"""
pass
@abstractmethod
def update_policy(self, action_type: ActionType, policy: RateLimitPolicy) -> None:
"""
Aktualisiert die Rate Limit Policy für einen Action Type.
Args:
action_type: Typ der Aktion
policy: Neue Policy
"""
pass
@abstractmethod
def get_statistics(self,
action_type: Optional[ActionType] = None,
timeframe: Optional[timedelta] = None) -> Dict[str, Any]:
"""
Holt Statistiken über Rate Limiting.
Args:
action_type: Optional - nur für spezifischen Action Type
timeframe: Optional - nur für bestimmten Zeitraum
Returns:
Dictionary mit Statistiken
"""
pass
@abstractmethod
def reset_statistics(self) -> None:
"""Setzt alle gesammelten Statistiken zurück."""
pass
@abstractmethod
def is_action_allowed(self, action_type: ActionType) -> bool:
"""
Prüft ob eine Aktion basierend auf Rate Limits erlaubt ist.
Args:
action_type: Typ der Aktion
Returns:
True wenn Aktion erlaubt ist
"""
pass
@abstractmethod
def wait_if_needed(self, action_type: ActionType) -> float:
"""
Wartet die notwendige Zeit bevor eine Aktion ausgeführt werden kann.
Args:
action_type: Typ der Aktion
Returns:
Tatsächlich gewartete Zeit in Sekunden
"""
pass

Datei anzeigen

@ -0,0 +1,17 @@
"""
Domain Value Objects - Unveränderliche Wertobjekte ohne Identität
"""
from .action_timing import ActionTiming, ActionType
from .error_summary import ErrorSummary
from .report import Report, ReportType
from .login_credentials import LoginCredentials
__all__ = [
'ActionTiming',
'ActionType',
'ErrorSummary',
'Report',
'ReportType',
'LoginCredentials'
]

Datei anzeigen

@ -0,0 +1,120 @@
"""
Typsichere Parameter für Account-Erstellung
"""
from dataclasses import dataclass
from typing import Optional, Dict, Any, List
from domain.entities.browser_fingerprint import BrowserFingerprint
@dataclass
class ValidationResult:
"""Ergebnis einer Validierung"""
is_valid: bool
errors: List[str]
def get_error_message(self) -> str:
"""Gibt eine formatierte Fehlermeldung zurück"""
if self.is_valid:
return ""
return "\n".join(self.errors)
@dataclass
class AccountCreationParams:
"""Typsichere Parameter für Account-Erstellung"""
full_name: str
age: int
registration_method: str = "email"
show_browser: bool = False
proxy_type: Optional[str] = None
fingerprint: Optional[BrowserFingerprint] = None
email_domain: str = "z5m7q9dk3ah2v1plx6ju.com"
username: Optional[str] = None
password: Optional[str] = None
phone_number: Optional[str] = None
imap_handler: Optional[Any] = None
phone_service: Optional[Any] = None
additional_params: Dict[str, Any] = None
# Platform-spezifische Konstanten
MIN_AGE: int = 13
MAX_AGE: int = 99
def __post_init__(self):
if self.additional_params is None:
self.additional_params = {}
def validate(self) -> ValidationResult:
"""Validiert alle Parameter"""
errors = []
# Name validieren
if not self.full_name or len(self.full_name.strip()) < 2:
errors.append("Der Name muss mindestens 2 Zeichen lang sein")
# Alter validieren
if self.age < self.MIN_AGE:
errors.append(f"Das Alter muss mindestens {self.MIN_AGE} sein")
elif self.age > self.MAX_AGE:
errors.append(f"Das Alter darf maximal {self.MAX_AGE} sein")
# Registrierungsmethode validieren
if self.registration_method not in ["email", "phone"]:
errors.append("Ungültige Registrierungsmethode")
# Telefonnummer bei Phone-Registrierung
if self.registration_method == "phone" and not self.phone_number:
errors.append("Telefonnummer erforderlich für Phone-Registrierung")
# E-Mail-Domain validieren
if self.registration_method == "email" and not self.email_domain:
errors.append("E-Mail-Domain erforderlich für Email-Registrierung")
return ValidationResult(is_valid=len(errors)==0, errors=errors)
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für Kompatibilität"""
result = {
"full_name": self.full_name,
"age": self.age,
"registration_method": self.registration_method,
"show_browser": self.show_browser,
"proxy_type": self.proxy_type,
"fingerprint": self.fingerprint,
"email_domain": self.email_domain,
"username": self.username,
"password": self.password,
"phone_number": self.phone_number,
"imap_handler": self.imap_handler,
"phone_service": self.phone_service
}
# Additional params hinzufügen
result.update(self.additional_params)
return result
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'AccountCreationParams':
"""Erstellt aus Dictionary"""
# Bekannte Parameter extrahieren
known_params = {
"full_name": data.get("full_name", ""),
"age": data.get("age", 18),
"registration_method": data.get("registration_method", "email"),
"show_browser": data.get("show_browser", False),
"proxy_type": data.get("proxy_type"),
"fingerprint": data.get("fingerprint"),
"email_domain": data.get("email_domain", "z5m7q9dk3ah2v1plx6ju.com"),
"username": data.get("username"),
"password": data.get("password"),
"phone_number": data.get("phone_number"),
"imap_handler": data.get("imap_handler"),
"phone_service": data.get("phone_service")
}
# Alle anderen Parameter als additional_params
additional = {k: v for k, v in data.items() if k not in known_params}
known_params["additional_params"] = additional
return cls(**known_params)

Datei anzeigen

@ -0,0 +1,102 @@
"""
Action Timing Value Object - Repräsentiert Timing-Informationen einer Aktion
"""
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Optional, Dict, Any
class ActionType(Enum):
"""Typen von Aktionen die getimed werden"""
# Navigation
PAGE_LOAD = "page_load"
PAGE_NAVIGATION = "page_navigation"
# Form-Interaktionen
FORM_FILL = "form_fill"
BUTTON_CLICK = "button_click"
INPUT_TYPE = "input_type"
DROPDOWN_SELECT = "dropdown_select"
CHECKBOX_TOGGLE = "checkbox_toggle"
# Verifizierung
EMAIL_CHECK = "email_check"
SMS_CHECK = "sms_check"
CAPTCHA_SOLVE = "captcha_solve"
# Account-Aktionen
REGISTRATION_START = "registration_start"
REGISTRATION_COMPLETE = "registration_complete"
LOGIN_ATTEMPT = "login_attempt"
LOGOUT = "logout"
# Daten-Operationen
SCREENSHOT = "screenshot"
DATA_SAVE = "data_save"
SESSION_SAVE = "session_save"
# Netzwerk
API_REQUEST = "api_request"
FILE_UPLOAD = "file_upload"
FILE_DOWNLOAD = "file_download"
@dataclass(frozen=True)
class ActionTiming:
"""
Repräsentiert Timing-Informationen einer Aktion.
Frozen dataclass macht es unveränderlich (Value Object).
"""
action_type: ActionType
timestamp: datetime
duration: float # in Sekunden
success: bool
# Optionale Metadaten
url: Optional[str] = None
element_selector: Optional[str] = None
error_message: Optional[str] = None
retry_count: int = 0
metadata: Optional[Dict[str, Any]] = None
def __post_init__(self):
"""Validierung der Timing-Daten"""
if self.duration < 0:
raise ValueError("Duration kann nicht negativ sein")
if self.retry_count < 0:
raise ValueError("Retry count kann nicht negativ sein")
@property
def duration_ms(self) -> float:
"""Gibt die Dauer in Millisekunden zurück"""
return self.duration * 1000
@property
def is_slow(self) -> bool:
"""Prüft ob die Aktion langsam war (> 3 Sekunden)"""
return self.duration > 3.0
@property
def is_very_slow(self) -> bool:
"""Prüft ob die Aktion sehr langsam war (> 10 Sekunden)"""
return self.duration > 10.0
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für Serialisierung"""
return {
'action_type': self.action_type.value,
'timestamp': self.timestamp.isoformat(),
'duration': self.duration,
'duration_ms': self.duration_ms,
'success': self.success,
'url': self.url,
'element_selector': self.element_selector,
'error_message': self.error_message,
'retry_count': self.retry_count,
'metadata': self.metadata or {},
'is_slow': self.is_slow,
'is_very_slow': self.is_very_slow
}

Datei anzeigen

@ -0,0 +1,29 @@
"""Browser protection style value object."""
from enum import Enum
from dataclasses import dataclass
class ProtectionLevel(Enum):
"""Defines the level of browser protection during automation."""
NONE = "none" # No protection
LIGHT = "light" # Visual indicator only
MEDIUM = "medium" # Transparent overlay with interaction blocking
STRONG = "strong" # Full blocking with opaque overlay
@dataclass
class BrowserProtectionStyle:
"""Configuration for browser protection during automation."""
level: ProtectionLevel = ProtectionLevel.MEDIUM
show_border: bool = True # Show animated border
show_badge: bool = True # Show info badge
blur_effect: bool = False # Apply blur to page content
opacity: float = 0.1 # Overlay opacity (0.0 - 1.0)
badge_text: str = "🔒 Automatisierung läuft - Nicht eingreifen"
badge_position: str = "top-right" # top-left, top-right, bottom-left, bottom-right
border_color: str = "rgba(255, 0, 0, 0.5)"
overlay_color: str = "rgba(0, 0, 0, {opacity})" # {opacity} will be replaced
def get_overlay_color(self) -> str:
"""Get the overlay color with the configured opacity."""
return self.overlay_color.format(opacity=self.opacity)

Datei anzeigen

@ -0,0 +1,98 @@
"""
Error Summary Value Object - Zusammenfassung von Fehlerinformationen
"""
from dataclasses import dataclass
from datetime import datetime
from typing import List, Dict, Any
@dataclass(frozen=True)
class ErrorSummary:
"""
Zusammenfassung von Fehlerinformationen für Berichte und Analysen.
Frozen dataclass macht es unveränderlich (Value Object).
"""
error_type: str
error_count: int
first_occurrence: datetime
last_occurrence: datetime
affected_sessions: List[str]
affected_accounts: List[str]
# Statistiken
avg_recovery_time: float # in Sekunden
recovery_success_rate: float # 0.0 - 1.0
# Häufigste Kontexte
most_common_urls: List[str]
most_common_actions: List[str]
most_common_steps: List[str]
# Impact
total_user_impact: int
total_system_impact: int
data_loss_incidents: int
def __post_init__(self):
"""Validierung der Summary-Daten"""
if self.error_count < 0:
raise ValueError("Error count kann nicht negativ sein")
if not 0.0 <= self.recovery_success_rate <= 1.0:
raise ValueError("Recovery success rate muss zwischen 0.0 und 1.0 liegen")
if self.first_occurrence > self.last_occurrence:
raise ValueError("First occurrence kann nicht nach last occurrence liegen")
@property
def duration(self) -> float:
"""Zeitspanne zwischen erstem und letztem Auftreten in Stunden"""
delta = self.last_occurrence - self.first_occurrence
return delta.total_seconds() / 3600
@property
def frequency(self) -> float:
"""Fehler pro Stunde"""
if self.duration > 0:
return self.error_count / self.duration
return self.error_count
@property
def severity_score(self) -> float:
"""
Berechnet einen Schweregrad-Score basierend auf:
- Häufigkeit
- Impact
- Wiederherstellungsrate
"""
frequency_factor = min(self.frequency / 10, 1.0) # Normalisiert auf 0-1
impact_factor = min((self.total_user_impact + self.total_system_impact) / 100, 1.0)
recovery_factor = 1.0 - self.recovery_success_rate
data_loss_factor = min(self.data_loss_incidents / 10, 1.0)
return (frequency_factor * 0.3 +
impact_factor * 0.3 +
recovery_factor * 0.2 +
data_loss_factor * 0.2)
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für Serialisierung"""
return {
'error_type': self.error_type,
'error_count': self.error_count,
'first_occurrence': self.first_occurrence.isoformat(),
'last_occurrence': self.last_occurrence.isoformat(),
'duration_hours': self.duration,
'frequency_per_hour': self.frequency,
'affected_sessions': self.affected_sessions,
'affected_accounts': self.affected_accounts,
'avg_recovery_time': self.avg_recovery_time,
'recovery_success_rate': self.recovery_success_rate,
'most_common_urls': self.most_common_urls[:5],
'most_common_actions': self.most_common_actions[:5],
'most_common_steps': self.most_common_steps[:5],
'total_user_impact': self.total_user_impact,
'total_system_impact': self.total_system_impact,
'data_loss_incidents': self.data_loss_incidents,
'severity_score': self.severity_score
}

Datei anzeigen

@ -0,0 +1,44 @@
"""
Login Credentials Value Object - Repräsentiert Login-Daten mit Session-Status
"""
from dataclasses import dataclass
from typing import Optional
from datetime import datetime
@dataclass(frozen=True)
class LoginCredentials:
"""Unveränderliche Login-Daten für einen Account"""
username: str
password: str
platform: str
session_status: str # ACTIVE, EXPIRED, LOCKED, REQUIRES_2FA, UNKNOWN
last_successful_login: Optional[datetime] = None
session_id: Optional[str] = None
fingerprint_id: Optional[str] = None
def is_session_active(self) -> bool:
"""Prüft ob die Session aktiv ist"""
return self.session_status == "ACTIVE"
def requires_manual_login(self) -> bool:
"""Prüft ob manueller Login erforderlich ist"""
return self.session_status in ["EXPIRED", "LOCKED", "REQUIRES_2FA", "UNKNOWN"]
def has_session_data(self) -> bool:
"""Prüft ob Session-Daten vorhanden sind"""
return self.session_id is not None and self.fingerprint_id is not None
def to_dict(self) -> dict:
"""Konvertiert zu Dictionary für Serialisierung"""
return {
'username': self.username,
'password': self.password,
'platform': self.platform,
'session_status': self.session_status,
'last_successful_login': self.last_successful_login.isoformat() if self.last_successful_login else None,
'session_id': self.session_id,
'fingerprint_id': self.fingerprint_id
}

Datei anzeigen

@ -0,0 +1,229 @@
"""
Operation Result Value Object - Standardisierte Ergebnisstruktur
Backward-compatible Wrapper für konsistente Fehlerbehandlung
"""
from dataclasses import dataclass
from typing import Optional, Any, Dict, Union
from datetime import datetime
import traceback
@dataclass
class OperationResult:
"""
Standardisierte Ergebnisstruktur für alle Operationen.
Kompatibel mit bestehenden boolean und dict returns.
"""
success: bool
data: Optional[Any] = None
error_message: Optional[str] = None
error_code: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
timestamp: Optional[datetime] = None
legacy_result: Optional[Any] = None # Für backward compatibility
def __post_init__(self):
if self.timestamp is None:
self.timestamp = datetime.now()
@classmethod
def success_result(cls, data: Any = None, metadata: Dict[str, Any] = None, legacy_result: Any = None):
"""Erstellt ein Erfolgsergebnis"""
return cls(
success=True,
data=data,
metadata=metadata or {},
legacy_result=legacy_result if legacy_result is not None else data
)
@classmethod
def error_result(cls, message: str, code: str = None,
metadata: Dict[str, Any] = None, legacy_result: Any = None):
"""Erstellt ein Fehlerergebnis"""
return cls(
success=False,
error_message=message,
error_code=code,
metadata=metadata or {},
legacy_result=legacy_result if legacy_result is not None else False
)
@classmethod
def from_exception(cls, exception: Exception, code: str = None,
metadata: Dict[str, Any] = None):
"""Erstellt Fehlerergebnis aus Exception"""
metadata = metadata or {}
metadata.update({
'exception_type': type(exception).__name__,
'traceback': traceback.format_exc()
})
return cls(
success=False,
error_message=str(exception),
error_code=code or type(exception).__name__,
metadata=metadata,
legacy_result=False
)
@classmethod
def from_legacy_boolean(cls, result: bool, success_data: Any = None, error_message: str = None):
"""Konvertiert legacy boolean zu OperationResult"""
if result:
return cls.success_result(data=success_data, legacy_result=result)
else:
return cls.error_result(
message=error_message or "Operation failed",
legacy_result=result
)
@classmethod
def from_legacy_dict(cls, result: Dict[str, Any]):
"""Konvertiert legacy dict zu OperationResult"""
success = result.get('success', False)
if success:
return cls.success_result(
data=result.get('data'),
metadata=result.get('metadata', {}),
legacy_result=result
)
else:
return cls.error_result(
message=result.get('error', 'Operation failed'),
code=result.get('error_code'),
metadata=result.get('metadata', {}),
legacy_result=result
)
def is_success(self) -> bool:
"""Prüft ob Operation erfolgreich war"""
return self.success
def is_error(self) -> bool:
"""Prüft ob Operation fehlgeschlagen ist"""
return not self.success
def get_legacy_result(self) -> Any:
"""Gibt das ursprüngliche Result-Format zurück für backward compatibility"""
return self.legacy_result
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für API/JSON Serialisierung"""
result = {
'success': self.success,
'timestamp': self.timestamp.isoformat() if self.timestamp else None
}
if self.data is not None:
result['data'] = self.data
if self.error_message:
result['error'] = self.error_message
if self.error_code:
result['error_code'] = self.error_code
if self.metadata:
result['metadata'] = self.metadata
return result
def to_legacy_dict(self) -> Dict[str, Any]:
"""Konvertiert zu legacy dict format"""
return {
'success': self.success,
'data': self.data,
'error': self.error_message,
'error_code': self.error_code,
'metadata': self.metadata
}
def __bool__(self) -> bool:
"""Ermöglicht if result: syntax"""
return self.success
def __str__(self) -> str:
if self.success:
return f"Success: {self.data}"
else:
return f"Error: {self.error_message} ({self.error_code})"
class ResultWrapper:
"""
Utility-Klasse für backward-compatible Wrapping von bestehenden Methoden
"""
@staticmethod
def wrap_boolean_method(method, *args, **kwargs) -> OperationResult:
"""Wrapper für bestehende boolean-Methoden"""
try:
success = method(*args, **kwargs)
return OperationResult.from_legacy_boolean(
result=success,
success_data=success,
error_message="Operation failed" if not success else None
)
except Exception as e:
return OperationResult.from_exception(e)
@staticmethod
def wrap_dict_method(method, *args, **kwargs) -> OperationResult:
"""Wrapper für bestehende dict-Methoden"""
try:
result = method(*args, **kwargs)
if isinstance(result, dict):
return OperationResult.from_legacy_dict(result)
else:
return OperationResult.success_result(data=result, legacy_result=result)
except Exception as e:
return OperationResult.from_exception(e)
@staticmethod
def wrap_any_method(method, *args, **kwargs) -> OperationResult:
"""Universal wrapper für beliebige Methoden"""
try:
result = method(*args, **kwargs)
if isinstance(result, bool):
return OperationResult.from_legacy_boolean(result)
elif isinstance(result, dict) and 'success' in result:
return OperationResult.from_legacy_dict(result)
elif result is None:
return OperationResult.error_result("Method returned None", legacy_result=result)
else:
return OperationResult.success_result(data=result, legacy_result=result)
except Exception as e:
return OperationResult.from_exception(e)
# Error Codes für häufige Fehlertypen
class CommonErrorCodes:
"""Häufig verwendete Fehlercodes"""
# Instagram spezifisch
CAPTCHA_REQUIRED = "CAPTCHA_REQUIRED"
EMAIL_TIMEOUT = "EMAIL_TIMEOUT"
SMS_NOT_IMPLEMENTED = "SMS_NOT_IMPLEMENTED"
USERNAME_TAKEN = "USERNAME_TAKEN"
SELECTOR_NOT_FOUND = "SELECTOR_NOT_FOUND"
BIRTHDAY_SELECTOR_FAILED = "BIRTHDAY_SELECTOR_FAILED"
# Allgemein
PROXY_ERROR = "PROXY_ERROR"
RATE_LIMITED = "RATE_LIMITED"
NETWORK_TIMEOUT = "NETWORK_TIMEOUT"
BROWSER_ERROR = "BROWSER_ERROR"
# Fingerprint spezifisch
FINGERPRINT_GENERATION_FAILED = "FINGERPRINT_GENERATION_FAILED"
FINGERPRINT_RACE_CONDITION = "FINGERPRINT_RACE_CONDITION"
FINGERPRINT_NOT_FOUND = "FINGERPRINT_NOT_FOUND"
# Session spezifisch
SESSION_EXPIRED = "SESSION_EXPIRED"
SESSION_INVALID = "SESSION_INVALID"
SESSION_SAVE_FAILED = "SESSION_SAVE_FAILED"

204
domain/value_objects/report.py Normale Datei
Datei anzeigen

@ -0,0 +1,204 @@
"""
Report Value Object - Strukturierte Berichte für Analytics
"""
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import List, Dict, Any, Optional
from enum import Enum
class ReportType(Enum):
"""Typen von verfügbaren Berichten"""
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
CUSTOM = "custom"
REAL_TIME = "real_time"
class MetricType(Enum):
"""Typen von Metriken in Berichten"""
SUCCESS_RATE = "success_rate"
ERROR_RATE = "error_rate"
AVG_DURATION = "avg_duration"
TOTAL_ACCOUNTS = "total_accounts"
ACCOUNTS_PER_HOUR = "accounts_per_hour"
RETRY_RATE = "retry_rate"
RECOVERY_RATE = "recovery_rate"
@dataclass(frozen=True)
class Metric:
"""Einzelne Metrik im Bericht"""
name: str
value: float
unit: str
trend: float = 0.0 # Prozentuale Veränderung zum Vorperiode
@property
def is_improving(self) -> bool:
"""Prüft ob sich die Metrik verbessert"""
positive_metrics = ["success_rate", "recovery_rate", "accounts_per_hour"]
if self.name in positive_metrics:
return self.trend > 0
return self.trend < 0
@dataclass(frozen=True)
class PlatformStats:
"""Statistiken für eine spezifische Plattform"""
platform: str
total_attempts: int
successful_accounts: int
failed_attempts: int
avg_duration_seconds: float
error_distribution: Dict[str, int]
@property
def success_rate(self) -> float:
"""Berechnet die Erfolgsrate"""
if self.total_attempts > 0:
return self.successful_accounts / self.total_attempts
return 0.0
@dataclass(frozen=True)
class TimeSeriesData:
"""Zeitreihen-Daten für Graphen"""
timestamps: List[datetime]
values: List[float]
label: str
def get_average(self) -> float:
"""Berechnet den Durchschnitt der Werte"""
if self.values:
return sum(self.values) / len(self.values)
return 0.0
def get_trend(self) -> float:
"""Berechnet den Trend (vereinfacht: Vergleich erste/letzte Hälfte)"""
if len(self.values) < 2:
return 0.0
mid = len(self.values) // 2
first_half_avg = sum(self.values[:mid]) / mid
second_half_avg = sum(self.values[mid:]) / (len(self.values) - mid)
if first_half_avg > 0:
return ((second_half_avg - first_half_avg) / first_half_avg) * 100
return 0.0
@dataclass(frozen=True)
class Report:
"""
Strukturierter Bericht für Analytics.
Frozen dataclass macht es unveränderlich (Value Object).
"""
report_id: str
report_type: ReportType
start_date: datetime
end_date: datetime
generated_at: datetime
# Zusammenfassende Metriken
total_accounts_created: int
total_attempts: int
overall_success_rate: float
avg_creation_time: float # in Sekunden
# Detaillierte Metriken
metrics: List[Metric]
platform_stats: List[PlatformStats]
error_summaries: List[Dict[str, Any]] # ErrorSummary als Dict
# Zeitreihen-Daten
success_rate_timeline: Optional[TimeSeriesData] = None
creation_rate_timeline: Optional[TimeSeriesData] = None
error_rate_timeline: Optional[TimeSeriesData] = None
# Top-Erkenntnisse
insights: List[str] = field(default_factory=list)
recommendations: List[str] = field(default_factory=list)
def __post_init__(self):
"""Validierung der Report-Daten"""
if self.start_date > self.end_date:
raise ValueError("Start date kann nicht nach end date liegen")
if not 0.0 <= self.overall_success_rate <= 1.0:
raise ValueError("Success rate muss zwischen 0.0 und 1.0 liegen")
if self.avg_creation_time < 0:
raise ValueError("Average creation time kann nicht negativ sein")
@property
def duration(self) -> timedelta:
"""Dauer des Berichtszeitraums"""
return self.end_date - self.start_date
@property
def accounts_per_day(self) -> float:
"""Durchschnittliche Accounts pro Tag"""
days = self.duration.days or 1
return self.total_accounts_created / days
def get_metric(self, metric_type: MetricType) -> Optional[Metric]:
"""Holt eine spezifische Metrik"""
for metric in self.metrics:
if metric.name == metric_type.value:
return metric
return None
def get_platform_stats(self, platform: str) -> Optional[PlatformStats]:
"""Holt Statistiken für eine spezifische Plattform"""
for stats in self.platform_stats:
if stats.platform.lower() == platform.lower():
return stats
return None
def to_dict(self) -> Dict[str, Any]:
"""Konvertiert zu Dictionary für Serialisierung"""
return {
'report_id': self.report_id,
'report_type': self.report_type.value,
'start_date': self.start_date.isoformat(),
'end_date': self.end_date.isoformat(),
'generated_at': self.generated_at.isoformat(),
'duration_days': self.duration.days,
'total_accounts_created': self.total_accounts_created,
'total_attempts': self.total_attempts,
'overall_success_rate': self.overall_success_rate,
'avg_creation_time': self.avg_creation_time,
'accounts_per_day': self.accounts_per_day,
'metrics': [
{
'name': m.name,
'value': m.value,
'unit': m.unit,
'trend': m.trend,
'is_improving': m.is_improving
}
for m in self.metrics
],
'platform_stats': [
{
'platform': ps.platform,
'total_attempts': ps.total_attempts,
'successful_accounts': ps.successful_accounts,
'failed_attempts': ps.failed_attempts,
'success_rate': ps.success_rate,
'avg_duration_seconds': ps.avg_duration_seconds,
'error_distribution': ps.error_distribution
}
for ps in self.platform_stats
],
'error_summaries': self.error_summaries,
'success_rate_timeline': {
'timestamps': [t.isoformat() for t in self.success_rate_timeline.timestamps],
'values': self.success_rate_timeline.values,
'label': self.success_rate_timeline.label,
'average': self.success_rate_timeline.get_average(),
'trend': self.success_rate_timeline.get_trend()
} if self.success_rate_timeline else None,
'insights': self.insights,
'recommendations': self.recommendations
}