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

Datei anzeigen

@ -0,0 +1,13 @@
"""
Infrastructure Repositories - Datenpersistierung und -zugriff
"""
from .fingerprint_repository import FingerprintRepository
from .analytics_repository import AnalyticsRepository
from .rate_limit_repository import RateLimitRepository
__all__ = [
'FingerprintRepository',
'AnalyticsRepository',
'RateLimitRepository'
]

Datei anzeigen

@ -0,0 +1,179 @@
"""
Account Repository - Zugriff auf Account-Daten in der Datenbank
"""
import sqlite3
import json
from typing import List, Dict, Any, Optional
from datetime import datetime
from infrastructure.repositories.base_repository import BaseRepository
class AccountRepository(BaseRepository):
"""Repository für Account-Datenzugriff"""
def get_by_id(self, account_id: int) -> Optional[Dict[str, Any]]:
"""
Holt einen Account nach ID.
Args:
account_id: Account ID
Returns:
Dict mit Account-Daten oder None
"""
# Sichere Abfrage die mit verschiedenen Schema-Versionen funktioniert
query = "SELECT * FROM accounts WHERE id = ?"
rows = self._execute_query(query, (account_id,))
if not rows:
return None
return self._row_to_account(rows[0])
def get_by_username(self, username: str, platform: str = None) -> Optional[Dict[str, Any]]:
"""
Holt einen Account nach Username.
Args:
username: Username
platform: Optional platform filter
Returns:
Dict mit Account-Daten oder None
"""
if platform:
query = "SELECT * FROM accounts WHERE username = ? AND platform = ?"
params = (username, platform)
else:
query = "SELECT * FROM accounts WHERE username = ?"
params = (username,)
rows = self._execute_query(query, params)
if not rows:
return None
return self._row_to_account(rows[0])
def get_all(self, platform: str = None, status: str = None) -> List[Dict[str, Any]]:
"""
Holt alle Accounts mit optionalen Filtern.
Args:
platform: Optional platform filter
status: Optional status filter
Returns:
Liste von Account-Dicts
"""
query = "SELECT * FROM accounts WHERE 1=1"
params = []
if platform:
query += " AND platform = ?"
params.append(platform)
if status:
query += " AND status = ?"
params.append(status)
query += " ORDER BY created_at DESC"
rows = self._execute_query(query, params)
return [self._row_to_account(row) for row in rows]
def update_fingerprint_id(self, account_id: int, fingerprint_id: str) -> bool:
"""
Aktualisiert die Fingerprint ID eines Accounts.
Args:
account_id: Account ID
fingerprint_id: Neue Fingerprint ID
Returns:
True bei Erfolg, False bei Fehler
"""
query = "UPDATE accounts SET fingerprint_id = ? WHERE id = ?"
return self._execute_update(query, (fingerprint_id, account_id)) > 0
def update_session_id(self, account_id: int, session_id: str) -> bool:
"""
Aktualisiert die Session ID eines Accounts.
Args:
account_id: Account ID
session_id: Neue Session ID
Returns:
True bei Erfolg, False bei Fehler
"""
query = """
UPDATE accounts
SET session_id = ?, last_session_update = datetime('now')
WHERE id = ?
"""
return self._execute_update(query, (session_id, account_id)) > 0
def update_status(self, account_id: int, status: str) -> bool:
"""
Aktualisiert den Status eines Accounts.
Args:
account_id: Account ID
status: Neuer Status
Returns:
True bei Erfolg, False bei Fehler
"""
query = "UPDATE accounts SET status = ? WHERE id = ?"
return self._execute_update(query, (status, account_id)) > 0
def _row_to_account(self, row) -> Dict[str, Any]:
"""Konvertiert eine Datenbankzeile zu einem Account-Dict"""
# sqlite3.Row unterstützt dict() Konvertierung direkt
if hasattr(row, 'keys'):
# Es ist ein sqlite3.Row Objekt
account = dict(row)
else:
# Fallback für normale Tuples
# Hole die tatsächlichen Spaltennamen aus der Datenbank
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(accounts)")
columns_info = cursor.fetchall()
conn.close()
# Extrahiere Spaltennamen
columns = [col[1] for col in columns_info]
# Erstelle Dict mit vorhandenen Spalten
account = {}
for i, value in enumerate(row):
if i < len(columns):
account[columns[i]] = value
# Parse metadata wenn vorhanden und die Spalte existiert
if 'metadata' in account and account.get('metadata'):
try:
metadata = json.loads(account['metadata'])
account['metadata'] = metadata
# Extrahiere platform aus metadata wenn vorhanden
if isinstance(metadata, dict) and 'platform' in metadata:
account['platform'] = metadata['platform']
except:
account['metadata'] = {}
# Setze Standardwerte für fehlende Felder
if 'platform' not in account:
# Standardmäßig auf instagram setzen
account['platform'] = 'instagram'
# Stelle sicher dass wichtige Felder existieren
for field in ['fingerprint_id', 'metadata']:
if field not in account:
account[field] = None
return account

Datei anzeigen

@ -0,0 +1,306 @@
"""
Analytics Repository - Persistierung von Analytics und Events
"""
import json
import sqlite3
from typing import List, Optional, Dict, Any, Union
from datetime import datetime, timedelta
from collections import defaultdict
from infrastructure.repositories.base_repository import BaseRepository
from domain.entities.account_creation_event import AccountCreationEvent, WorkflowStep
from domain.entities.error_event import ErrorEvent, ErrorType
from domain.value_objects.error_summary import ErrorSummary
class AnalyticsRepository(BaseRepository):
"""Repository für Analytics Events und Reporting"""
def save_account_creation_event(self, event: AccountCreationEvent) -> None:
"""Speichert ein Account Creation Event"""
query = """
INSERT INTO account_creation_analytics (
event_id, timestamp, account_id, session_id, fingerprint_id,
duration_seconds, success, error_type, error_message,
workflow_steps, metadata, total_retry_count, network_requests,
screenshots_taken, proxy_used, proxy_type, browser_type,
headless, success_rate
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
# Serialisiere komplexe Daten
workflow_steps_json = self._serialize_json([
step.to_dict() for step in event.steps_completed
])
metadata = {
'platform': event.account_data.platform if event.account_data else None,
'username': event.account_data.username if event.account_data else None,
'email': event.account_data.email if event.account_data else None,
'additional': event.account_data.metadata if event.account_data else {}
}
params = (
event.event_id,
event.timestamp,
event.account_data.username if event.account_data else None,
event.session_id,
event.fingerprint_id,
event.duration.total_seconds() if event.duration else 0,
event.success,
event.error_details.error_type if event.error_details else None,
event.error_details.error_message if event.error_details else None,
workflow_steps_json,
self._serialize_json(metadata),
event.total_retry_count,
event.network_requests,
event.screenshots_taken,
event.proxy_used,
event.proxy_type,
event.browser_type,
event.headless,
event.get_success_rate()
)
self._execute_insert(query, params)
def save_error_event(self, event: ErrorEvent) -> None:
"""Speichert ein Error Event"""
query = """
INSERT INTO error_events (
error_id, timestamp, error_type, error_message, stack_trace,
context, recovery_attempted, recovery_successful, recovery_attempts,
severity, platform, session_id, account_id, correlation_id,
user_impact, system_impact, data_loss
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
context_json = self._serialize_json({
'url': event.context.url,
'action': event.context.action,
'step_name': event.context.step_name,
'screenshot_path': event.context.screenshot_path,
'additional_data': event.context.additional_data
})
recovery_attempts_json = self._serialize_json([
{
'strategy': attempt.strategy,
'timestamp': attempt.timestamp.isoformat(),
'successful': attempt.successful,
'error_message': attempt.error_message,
'duration_seconds': attempt.duration_seconds
}
for attempt in event.recovery_attempts
])
params = (
event.error_id,
event.timestamp,
event.error_type.value,
event.error_message,
event.stack_trace,
context_json,
event.recovery_attempted,
event.recovery_successful,
recovery_attempts_json,
event.severity.value,
event.platform,
event.session_id,
event.account_id,
event.correlation_id,
event.user_impact,
event.system_impact,
event.data_loss
)
self._execute_insert(query, params)
def get_success_rate(self, timeframe: Optional[timedelta] = None,
platform: Optional[str] = None) -> float:
"""Berechnet die Erfolgsrate"""
query = """
SELECT
COUNT(*) as total,
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful
FROM account_creation_analytics
WHERE 1=1
"""
params = []
if timeframe:
query += " AND timestamp > datetime('now', '-' || ? || ' seconds')"
params.append(int(timeframe.total_seconds()))
if platform:
query += " AND json_extract(metadata, '$.platform') = ?"
params.append(platform)
row = self._execute_query(query, tuple(params))[0]
if row['total'] > 0:
return row['successful'] / row['total']
return 0.0
def get_common_errors(self, limit: int = 10,
timeframe: Optional[timedelta] = None) -> List[ErrorSummary]:
"""Holt die häufigsten Fehler"""
query = """
SELECT
error_type,
COUNT(*) as error_count,
MIN(timestamp) as first_occurrence,
MAX(timestamp) as last_occurrence,
AVG(CASE WHEN recovery_successful = 1 THEN 1.0 ELSE 0.0 END) as recovery_rate,
GROUP_CONCAT(DISTINCT session_id) as sessions,
GROUP_CONCAT(DISTINCT account_id) as accounts,
SUM(user_impact) as total_user_impact,
SUM(system_impact) as total_system_impact,
SUM(data_loss) as data_loss_incidents
FROM error_events
WHERE 1=1
"""
params = []
if timeframe:
query += " AND timestamp > datetime('now', '-' || ? || ' seconds')"
params.append(int(timeframe.total_seconds()))
query += " GROUP BY error_type ORDER BY error_count DESC LIMIT ?"
params.append(limit)
rows = self._execute_query(query, tuple(params))
summaries = []
for row in rows:
# Hole zusätzliche Details für diesen Fehlertyp
detail_query = """
SELECT
json_extract(context, '$.url') as url,
json_extract(context, '$.action') as action,
json_extract(context, '$.step_name') as step,
COUNT(*) as count
FROM error_events
WHERE error_type = ?
GROUP BY url, action, step
ORDER BY count DESC
LIMIT 5
"""
details = self._execute_query(detail_query, (row['error_type'],))
urls = []
actions = []
steps = []
for detail in details:
if detail['url']:
urls.append(detail['url'])
if detail['action']:
actions.append(detail['action'])
if detail['step']:
steps.append(detail['step'])
summary = ErrorSummary(
error_type=row['error_type'],
error_count=row['error_count'],
first_occurrence=self._parse_datetime(row['first_occurrence']),
last_occurrence=self._parse_datetime(row['last_occurrence']),
affected_sessions=row['sessions'].split(',') if row['sessions'] else [],
affected_accounts=row['accounts'].split(',') if row['accounts'] else [],
avg_recovery_time=0.0, # TODO: Berechnen aus recovery_attempts
recovery_success_rate=row['recovery_rate'] or 0.0,
most_common_urls=urls,
most_common_actions=actions,
most_common_steps=steps,
total_user_impact=row['total_user_impact'] or 0,
total_system_impact=row['total_system_impact'] or 0,
data_loss_incidents=row['data_loss_incidents'] or 0
)
summaries.append(summary)
return summaries
def get_timeline_data(self, metric: str, hours: int = 24,
platform: Optional[str] = None) -> List[Dict[str, Any]]:
"""Holt Timeline-Daten für Graphen"""
# Erstelle stündliche Buckets
query = """
SELECT
strftime('%Y-%m-%d %H:00:00', timestamp) as hour,
COUNT(*) as total,
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful,
AVG(duration_seconds) as avg_duration
FROM account_creation_analytics
WHERE timestamp > datetime('now', '-' || ? || ' hours')
"""
params = [hours]
if platform:
query += " AND json_extract(metadata, '$.platform') = ?"
params.append(platform)
query += " GROUP BY hour ORDER BY hour"
rows = self._execute_query(query, tuple(params))
timeline = []
for row in rows:
data = {
'timestamp': row['hour'],
'total': row['total'],
'successful': row['successful'],
'success_rate': row['successful'] / row['total'] if row['total'] > 0 else 0,
'avg_duration': row['avg_duration']
}
timeline.append(data)
return timeline
def get_platform_stats(self, timeframe: Optional[timedelta] = None) -> Dict[str, Dict[str, Any]]:
"""Holt Statistiken pro Plattform"""
query = """
SELECT
json_extract(metadata, '$.platform') as platform,
COUNT(*) as total_attempts,
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful,
AVG(duration_seconds) as avg_duration,
AVG(total_retry_count) as avg_retries
FROM account_creation_analytics
WHERE json_extract(metadata, '$.platform') IS NOT NULL
"""
params = []
if timeframe:
query += " AND timestamp > datetime('now', '-' || ? || ' seconds')"
params.append(int(timeframe.total_seconds()))
query += " GROUP BY platform"
rows = self._execute_query(query, tuple(params))
stats = {}
for row in rows:
stats[row['platform']] = {
'total_attempts': row['total_attempts'],
'successful_accounts': row['successful'],
'failed_attempts': row['total_attempts'] - row['successful'],
'success_rate': row['successful'] / row['total_attempts'] if row['total_attempts'] > 0 else 0,
'avg_duration_seconds': row['avg_duration'],
'avg_retries': row['avg_retries']
}
return stats
def cleanup_old_events(self, older_than: datetime) -> int:
"""Bereinigt alte Events"""
count1 = self._execute_delete(
"DELETE FROM account_creation_analytics WHERE timestamp < ?",
(older_than,)
)
count2 = self._execute_delete(
"DELETE FROM error_events WHERE timestamp < ?",
(older_than,)
)
return count1 + count2

Datei anzeigen

@ -0,0 +1,112 @@
"""
Base Repository - Abstrakte Basis für alle Repositories
"""
import sqlite3
import json
import logging
from typing import Dict, List, Any, Optional, Union
from datetime import datetime
from contextlib import contextmanager
from config.paths import PathConfig
logger = logging.getLogger("base_repository")
class BaseRepository:
"""Basis-Repository mit gemeinsamen Datenbankfunktionen"""
def __init__(self, db_path: str = None):
"""
Initialisiert das Repository.
Args:
db_path: Pfad zur Datenbank (falls None, wird PathConfig.MAIN_DB verwendet)
"""
self.db_path = db_path if db_path is not None else PathConfig.MAIN_DB
self._ensure_schema()
def _ensure_schema(self):
"""Stellt sicher dass das erweiterte Schema existiert"""
try:
if PathConfig.file_exists(PathConfig.SCHEMA_V2):
with open(PathConfig.SCHEMA_V2, "r", encoding='utf-8') as f:
schema_sql = f.read()
with self.get_connection() as conn:
conn.executescript(schema_sql)
conn.commit()
logger.info("Schema v2 erfolgreich geladen")
else:
logger.warning(f"schema_v2.sql nicht gefunden unter {PathConfig.SCHEMA_V2}, nutze existierendes Schema")
except Exception as e:
logger.error(f"Fehler beim Schema-Update: {e}")
@contextmanager
def get_connection(self):
"""Context Manager für Datenbankverbindungen"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
try:
yield conn
finally:
conn.close()
def _serialize_json(self, data: Any) -> str:
"""Serialisiert Daten zu JSON"""
if data is None:
return None
return json.dumps(data, default=str)
def _deserialize_json(self, data: str) -> Any:
"""Deserialisiert JSON zu Python-Objekten"""
if data is None:
return None
try:
return json.loads(data)
except json.JSONDecodeError:
logger.error(f"Fehler beim JSON-Parsing: {data}")
return None
def _parse_datetime(self, dt_string: str) -> Optional[datetime]:
"""Parst einen Datetime-String"""
if not dt_string:
return None
try:
# SQLite datetime format
return datetime.strptime(dt_string, "%Y-%m-%d %H:%M:%S")
except ValueError:
try:
# ISO format
return datetime.fromisoformat(dt_string.replace('Z', '+00:00'))
except:
logger.error(f"Konnte Datetime nicht parsen: {dt_string}")
return None
def _execute_query(self, query: str, params: tuple = ()) -> List[sqlite3.Row]:
"""Führt eine SELECT-Query aus"""
with self.get_connection() as conn:
cursor = conn.execute(query, params)
return cursor.fetchall()
def _execute_insert(self, query: str, params: tuple = ()) -> int:
"""Führt eine INSERT-Query aus und gibt die ID zurück"""
with self.get_connection() as conn:
cursor = conn.execute(query, params)
conn.commit()
return cursor.lastrowid
def _execute_update(self, query: str, params: tuple = ()) -> int:
"""Führt eine UPDATE-Query aus und gibt affected rows zurück"""
with self.get_connection() as conn:
cursor = conn.execute(query, params)
conn.commit()
return cursor.rowcount
def _execute_delete(self, query: str, params: tuple = ()) -> int:
"""Führt eine DELETE-Query aus und gibt affected rows zurück"""
with self.get_connection() as conn:
cursor = conn.execute(query, params)
conn.commit()
return cursor.rowcount

Datei anzeigen

@ -0,0 +1,273 @@
"""
Fingerprint Repository - Persistierung von Browser Fingerprints
"""
import json
import sqlite3
from typing import List, Optional, Dict, Any
from datetime import datetime
from infrastructure.repositories.base_repository import BaseRepository
from domain.entities.browser_fingerprint import (
BrowserFingerprint, CanvasNoise, WebRTCConfig,
HardwareConfig, NavigatorProperties, StaticComponents
)
class FingerprintRepository(BaseRepository):
"""Repository für Browser Fingerprint Persistierung"""
def save(self, fingerprint: BrowserFingerprint) -> None:
"""Speichert einen Fingerprint in der Datenbank"""
query = """
INSERT OR REPLACE INTO browser_fingerprints (
id, canvas_noise_config, webrtc_config, fonts,
hardware_config, navigator_props, webgl_vendor,
webgl_renderer, audio_context_config, timezone,
timezone_offset, plugins, created_at, last_rotated,
platform_specific, static_components, rotation_seed,
account_bound
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
audio_config = {
'base_latency': fingerprint.audio_context_base_latency,
'output_latency': fingerprint.audio_context_output_latency,
'sample_rate': fingerprint.audio_context_sample_rate
}
params = (
fingerprint.fingerprint_id,
self._serialize_json({
'noise_level': fingerprint.canvas_noise.noise_level,
'seed': fingerprint.canvas_noise.seed,
'algorithm': fingerprint.canvas_noise.algorithm
}),
self._serialize_json({
'enabled': fingerprint.webrtc_config.enabled,
'ice_servers': fingerprint.webrtc_config.ice_servers,
'local_ip_mask': fingerprint.webrtc_config.local_ip_mask,
'disable_webrtc': fingerprint.webrtc_config.disable_webrtc
}),
self._serialize_json(fingerprint.font_list),
self._serialize_json({
'hardware_concurrency': fingerprint.hardware_config.hardware_concurrency,
'device_memory': fingerprint.hardware_config.device_memory,
'max_touch_points': fingerprint.hardware_config.max_touch_points,
'screen_resolution': fingerprint.hardware_config.screen_resolution,
'color_depth': fingerprint.hardware_config.color_depth,
'pixel_ratio': fingerprint.hardware_config.pixel_ratio
}),
self._serialize_json({
'platform': fingerprint.navigator_props.platform,
'vendor': fingerprint.navigator_props.vendor,
'vendor_sub': fingerprint.navigator_props.vendor_sub,
'product': fingerprint.navigator_props.product,
'product_sub': fingerprint.navigator_props.product_sub,
'app_name': fingerprint.navigator_props.app_name,
'app_version': fingerprint.navigator_props.app_version,
'user_agent': fingerprint.navigator_props.user_agent,
'language': fingerprint.navigator_props.language,
'languages': fingerprint.navigator_props.languages,
'online': fingerprint.navigator_props.online,
'do_not_track': fingerprint.navigator_props.do_not_track
}),
fingerprint.webgl_vendor,
fingerprint.webgl_renderer,
self._serialize_json(audio_config),
fingerprint.timezone,
fingerprint.timezone_offset,
self._serialize_json(fingerprint.plugins),
fingerprint.created_at,
fingerprint.last_rotated,
self._serialize_json(fingerprint.platform_specific_config), # platform_specific
self._serialize_json(fingerprint.static_components.to_dict() if fingerprint.static_components else None),
fingerprint.rotation_seed,
fingerprint.account_bound
)
self._execute_insert(query, params)
def find_by_id(self, fingerprint_id: str) -> Optional[BrowserFingerprint]:
"""Findet einen Fingerprint nach ID"""
query = "SELECT * FROM browser_fingerprints WHERE id = ?"
rows = self._execute_query(query, (fingerprint_id,))
if not rows:
return None
return self._row_to_fingerprint(rows[0])
def find_all(self, limit: int = 100) -> List[BrowserFingerprint]:
"""Holt alle Fingerprints (mit Limit)"""
query = "SELECT * FROM browser_fingerprints ORDER BY created_at DESC LIMIT ?"
rows = self._execute_query(query, (limit,))
return [self._row_to_fingerprint(row) for row in rows]
def find_recent(self, hours: int = 24) -> List[BrowserFingerprint]:
"""Findet kürzlich erstellte Fingerprints"""
query = """
SELECT * FROM browser_fingerprints
WHERE created_at > datetime('now', '-' || ? || ' hours')
ORDER BY created_at DESC
"""
rows = self._execute_query(query, (hours,))
return [self._row_to_fingerprint(row) for row in rows]
def update_last_rotated(self, fingerprint_id: str, timestamp: datetime) -> None:
"""Aktualisiert den last_rotated Timestamp"""
query = "UPDATE browser_fingerprints SET last_rotated = ? WHERE id = ?"
self._execute_update(query, (timestamp, fingerprint_id))
def delete_older_than(self, timestamp: datetime) -> int:
"""Löscht Fingerprints älter als timestamp"""
query = "DELETE FROM browser_fingerprints WHERE created_at < ?"
return self._execute_delete(query, (timestamp,))
def get_random_fingerprints(self, count: int = 10) -> List[BrowserFingerprint]:
"""Holt zufällige Fingerprints für Pool"""
query = """
SELECT * FROM browser_fingerprints
ORDER BY RANDOM()
LIMIT ?
"""
rows = self._execute_query(query, (count,))
return [self._row_to_fingerprint(row) for row in rows]
def link_to_account(self, fingerprint_id: str, account_id: str, primary: bool = True) -> None:
"""Links a fingerprint to an account using simple 1:1 relationship"""
query = """
UPDATE accounts SET fingerprint_id = ? WHERE id = ?
"""
self._execute_update(query, (fingerprint_id, account_id))
def get_primary_fingerprint_for_account(self, account_id: str) -> Optional[str]:
"""Gets the fingerprint ID for an account (1:1 relationship)"""
query = """
SELECT fingerprint_id FROM accounts
WHERE id = ? AND fingerprint_id IS NOT NULL
"""
rows = self._execute_query(query, (account_id,))
return dict(rows[0])['fingerprint_id'] if rows else None
def get_fingerprints_for_account(self, account_id: str) -> List[BrowserFingerprint]:
"""Gets the fingerprint associated with an account (1:1 relationship)"""
fingerprint_id = self.get_primary_fingerprint_for_account(account_id)
if fingerprint_id:
fingerprint = self.find_by_id(fingerprint_id)
return [fingerprint] if fingerprint else []
return []
def update_fingerprint_stats(self, fingerprint_id: str, account_id: str,
success: bool) -> None:
"""Updates fingerprint last used timestamp (simplified for 1:1)"""
# Update the fingerprint's last used time
query = """
UPDATE browser_fingerprints
SET last_rotated = datetime('now')
WHERE id = ?
"""
self._execute_update(query, (fingerprint_id,))
# Also update account's last login
query = """
UPDATE accounts
SET last_login = datetime('now')
WHERE id = ? AND fingerprint_id = ?
"""
self._execute_update(query, (account_id, fingerprint_id))
def _row_to_fingerprint(self, row: sqlite3.Row) -> BrowserFingerprint:
"""Konvertiert eine Datenbankzeile zu einem Fingerprint"""
# Canvas Noise
canvas_config = self._deserialize_json(row['canvas_noise_config'])
canvas_noise = CanvasNoise(
noise_level=canvas_config.get('noise_level', 0.02),
seed=canvas_config.get('seed', 42),
algorithm=canvas_config.get('algorithm', 'gaussian')
)
# WebRTC Config
webrtc_config_data = self._deserialize_json(row['webrtc_config'])
webrtc_config = WebRTCConfig(
enabled=webrtc_config_data.get('enabled', True),
ice_servers=webrtc_config_data.get('ice_servers', []),
local_ip_mask=webrtc_config_data.get('local_ip_mask', '10.0.0.x'),
disable_webrtc=webrtc_config_data.get('disable_webrtc', False)
)
# Hardware Config
hw_config = self._deserialize_json(row['hardware_config'])
hardware_config = HardwareConfig(
hardware_concurrency=hw_config.get('hardware_concurrency', 4),
device_memory=hw_config.get('device_memory', 8),
max_touch_points=hw_config.get('max_touch_points', 0),
screen_resolution=tuple(hw_config.get('screen_resolution', [1920, 1080])),
color_depth=hw_config.get('color_depth', 24),
pixel_ratio=hw_config.get('pixel_ratio', 1.0)
)
# Navigator Properties
nav_props = self._deserialize_json(row['navigator_props'])
navigator_props = NavigatorProperties(
platform=nav_props.get('platform', 'Win32'),
vendor=nav_props.get('vendor', 'Google Inc.'),
vendor_sub=nav_props.get('vendor_sub', ''),
product=nav_props.get('product', 'Gecko'),
product_sub=nav_props.get('product_sub', '20030107'),
app_name=nav_props.get('app_name', 'Netscape'),
app_version=nav_props.get('app_version', '5.0'),
user_agent=nav_props.get('user_agent', ''),
language=nav_props.get('language', 'de-DE'),
languages=nav_props.get('languages', ['de-DE', 'de', 'en-US', 'en']),
online=nav_props.get('online', True),
do_not_track=nav_props.get('do_not_track', '1')
)
# Audio Context
audio_config = self._deserialize_json(row['audio_context_config']) or {}
# Static Components
static_components = None
if 'static_components' in row.keys() and row['static_components']:
sc_data = self._deserialize_json(row['static_components'])
if sc_data:
static_components = StaticComponents(
device_type=sc_data.get('device_type', 'desktop'),
os_family=sc_data.get('os_family', 'windows'),
browser_family=sc_data.get('browser_family', 'chromium'),
gpu_vendor=sc_data.get('gpu_vendor', 'Intel Inc.'),
gpu_model=sc_data.get('gpu_model', 'Intel Iris OpenGL Engine'),
cpu_architecture=sc_data.get('cpu_architecture', 'x86_64'),
base_fonts=sc_data.get('base_fonts', []),
base_resolution=tuple(sc_data.get('base_resolution', [1920, 1080])),
base_timezone=sc_data.get('base_timezone', 'Europe/Berlin')
)
return BrowserFingerprint(
fingerprint_id=row['id'],
canvas_noise=canvas_noise,
webrtc_config=webrtc_config,
font_list=self._deserialize_json(row['fonts']) or [],
hardware_config=hardware_config,
navigator_props=navigator_props,
created_at=self._parse_datetime(row['created_at']),
last_rotated=self._parse_datetime(row['last_rotated']),
webgl_vendor=row['webgl_vendor'],
webgl_renderer=row['webgl_renderer'],
audio_context_base_latency=audio_config.get('base_latency', 0.0),
audio_context_output_latency=audio_config.get('output_latency', 0.0),
audio_context_sample_rate=audio_config.get('sample_rate', 48000),
timezone=row['timezone'],
timezone_offset=row['timezone_offset'],
plugins=self._deserialize_json(row['plugins']) or [],
static_components=static_components,
rotation_seed=row['rotation_seed'] if 'rotation_seed' in row.keys() else None,
account_bound=row['account_bound'] if 'account_bound' in row.keys() else False,
platform_specific_config=self._deserialize_json(row['platform_specific'] if 'platform_specific' in row.keys() else '{}') or {}
)

Datei anzeigen

@ -0,0 +1,282 @@
"""
SQLite implementation of method strategy repository.
Handles persistence and retrieval of method strategies with performance optimization.
"""
import json
import sqlite3
from datetime import datetime, timedelta
from typing import List, Optional, Dict, Any
from domain.entities.method_rotation import MethodStrategy, RiskLevel
from domain.repositories.method_rotation_repository import IMethodStrategyRepository
from database.db_manager import DatabaseManager
class MethodStrategyRepository(IMethodStrategyRepository):
"""SQLite implementation of method strategy repository"""
def __init__(self, db_manager):
self.db_manager = db_manager
def save(self, strategy: MethodStrategy) -> None:
"""Save or update a method strategy"""
strategy.updated_at = datetime.now()
query = """
INSERT OR REPLACE INTO method_strategies (
id, platform, method_name, priority, success_rate, failure_rate,
last_success, last_failure, cooldown_period, max_daily_attempts,
risk_level, is_active, configuration, tags, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
params = (
strategy.strategy_id,
strategy.platform,
strategy.method_name,
strategy.priority,
strategy.success_rate,
strategy.failure_rate,
strategy.last_success.isoformat() if strategy.last_success else None,
strategy.last_failure.isoformat() if strategy.last_failure else None,
strategy.cooldown_period,
strategy.max_daily_attempts,
strategy.risk_level.value,
strategy.is_active,
json.dumps(strategy.configuration),
json.dumps(strategy.tags),
strategy.created_at.isoformat(),
strategy.updated_at.isoformat()
)
self.db_manager.execute_query(query, params)
def find_by_id(self, strategy_id: str) -> Optional[MethodStrategy]:
"""Find a strategy by its ID"""
query = "SELECT * FROM method_strategies WHERE id = ?"
result = self.db_manager.fetch_one(query, (strategy_id,))
return self._row_to_strategy(result) if result else None
def find_by_platform(self, platform: str) -> List[MethodStrategy]:
"""Find all strategies for a platform"""
query = """
SELECT * FROM method_strategies
WHERE platform = ?
ORDER BY priority DESC, success_rate DESC
"""
results = self.db_manager.fetch_all(query, (platform,))
return [self._row_to_strategy(row) for row in results]
def find_active_by_platform(self, platform: str) -> List[MethodStrategy]:
"""Find all active strategies for a platform, ordered by effectiveness"""
query = """
SELECT * FROM method_strategies
WHERE platform = ? AND is_active = 1
ORDER BY priority DESC, success_rate DESC, last_success DESC
"""
results = self.db_manager.fetch_all(query, (platform,))
strategies = [self._row_to_strategy(row) for row in results]
# Sort by effectiveness score
strategies.sort(key=lambda s: s.effectiveness_score, reverse=True)
return strategies
def find_by_platform_and_method(self, platform: str, method_name: str) -> Optional[MethodStrategy]:
"""Find a specific method strategy"""
query = "SELECT * FROM method_strategies WHERE platform = ? AND method_name = ?"
result = self.db_manager.fetch_one(query, (platform, method_name))
return self._row_to_strategy(result) if result else None
def update_performance_metrics(self, strategy_id: str, success: bool,
execution_time: float = 0.0) -> None:
"""Update performance metrics for a strategy"""
strategy = self.find_by_id(strategy_id)
if not strategy:
return
strategy.update_performance(success, execution_time)
self.save(strategy)
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"""
if excluded_methods is None:
excluded_methods = []
# Build query with exclusions
placeholders = ','.join(['?' for _ in excluded_methods])
exclusion_clause = f"AND method_name NOT IN ({placeholders})" if excluded_methods else ""
# Build risk level clause
risk_clause = "'LOW', 'MEDIUM'"
if max_risk_level == 'HIGH':
risk_clause += ", 'HIGH'"
query = f"""
SELECT * FROM method_strategies
WHERE platform = ?
AND is_active = 1
AND risk_level IN ({risk_clause})
{exclusion_clause}
ORDER BY priority DESC, success_rate DESC
LIMIT 1
"""
params = [platform] + excluded_methods
result = self.db_manager.fetch_one(query, params)
if not result:
return None
strategy = self._row_to_strategy(result)
# Check if method is on cooldown
if strategy.is_on_cooldown:
# Try to find another method
excluded_methods.append(strategy.method_name)
return self.get_next_available_method(platform, excluded_methods, max_risk_level)
return strategy
def disable_method(self, platform: str, method_name: str, reason: str) -> None:
"""Disable a method temporarily or permanently"""
query = """
UPDATE method_strategies
SET is_active = 0, updated_at = ?
WHERE platform = ? AND method_name = ?
"""
self.db_manager.execute_query(query, (datetime.now().isoformat(), platform, method_name))
# Log the reason in configuration
strategy = self.find_by_platform_and_method(platform, method_name)
if strategy:
strategy.configuration['disabled_reason'] = reason
strategy.configuration['disabled_at'] = datetime.now().isoformat()
self.save(strategy)
def enable_method(self, platform: str, method_name: str) -> None:
"""Re-enable a disabled method"""
query = """
UPDATE method_strategies
SET is_active = 1, updated_at = ?
WHERE platform = ? AND method_name = ?
"""
self.db_manager.execute_query(query, (datetime.now().isoformat(), platform, method_name))
# Clear disabled reason from configuration
strategy = self.find_by_platform_and_method(platform, method_name)
if strategy:
strategy.configuration.pop('disabled_reason', None)
strategy.configuration.pop('disabled_at', None)
self.save(strategy)
def get_platform_statistics(self, platform: str) -> Dict[str, Any]:
"""Get aggregated statistics for all methods on a platform"""
query = """
SELECT
COUNT(*) as total_methods,
COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_methods,
AVG(success_rate) as avg_success_rate,
MAX(success_rate) as best_success_rate,
MIN(success_rate) as worst_success_rate,
AVG(priority) as avg_priority,
COUNT(CASE WHEN last_success > datetime('now', '-24 hours') THEN 1 END) as recent_successes,
COUNT(CASE WHEN last_failure > datetime('now', '-24 hours') THEN 1 END) as recent_failures
FROM method_strategies
WHERE platform = ?
"""
result = self.db_manager.fetch_one(query, (platform,))
if not result:
return {}
return {
'total_methods': result[0] or 0,
'active_methods': result[1] or 0,
'avg_success_rate': round(result[2] or 0.0, 3),
'best_success_rate': result[3] or 0.0,
'worst_success_rate': result[4] or 0.0,
'avg_priority': round(result[5] or 0.0, 1),
'recent_successes_24h': result[6] or 0,
'recent_failures_24h': result[7] or 0
}
def cleanup_old_data(self, days_to_keep: int = 90) -> int:
"""Clean up old performance data and return number of records removed"""
# This implementation doesn't remove strategies but resets old performance data
cutoff_date = datetime.now() - timedelta(days=days_to_keep)
query = """
UPDATE method_strategies
SET last_success = NULL, last_failure = NULL, success_rate = 0.0, failure_rate = 0.0
WHERE (last_success < ? OR last_failure < ?)
AND (last_success IS NOT NULL OR last_failure IS NOT NULL)
"""
cursor = self.db_manager.execute_query(query, (cutoff_date.isoformat(), cutoff_date.isoformat()))
return cursor.rowcount if cursor else 0
def get_methods_by_risk_level(self, platform: str, risk_level: RiskLevel) -> List[MethodStrategy]:
"""Get methods filtered by risk level"""
query = """
SELECT * FROM method_strategies
WHERE platform = ? AND risk_level = ? AND is_active = 1
ORDER BY priority DESC, success_rate DESC
"""
results = self.db_manager.fetch_all(query, (platform, risk_level.value))
return [self._row_to_strategy(row) for row in results]
def get_emergency_methods(self, platform: str) -> List[MethodStrategy]:
"""Get only the most reliable methods for emergency mode"""
query = """
SELECT * FROM method_strategies
WHERE platform = ?
AND is_active = 1
AND risk_level = 'LOW'
AND success_rate > 0.5
ORDER BY success_rate DESC, priority DESC
LIMIT 2
"""
results = self.db_manager.fetch_all(query, (platform,))
return [self._row_to_strategy(row) for row in results]
def bulk_update_priorities(self, platform: str, priority_updates: Dict[str, int]) -> None:
"""Bulk update method priorities for a platform"""
query = """
UPDATE method_strategies
SET priority = ?, updated_at = ?
WHERE platform = ? AND method_name = ?
"""
params_list = [
(priority, datetime.now().isoformat(), platform, method_name)
for method_name, priority in priority_updates.items()
]
with self.db_manager.get_connection() as conn:
conn.executemany(query, params_list)
conn.commit()
def _row_to_strategy(self, row) -> MethodStrategy:
"""Convert database row to MethodStrategy entity"""
return MethodStrategy(
strategy_id=row[0],
platform=row[1],
method_name=row[2],
priority=row[3],
success_rate=row[4],
failure_rate=row[5],
last_success=datetime.fromisoformat(row[6]) if row[6] else None,
last_failure=datetime.fromisoformat(row[7]) if row[7] else None,
cooldown_period=row[8],
max_daily_attempts=row[9],
risk_level=RiskLevel(row[10]),
is_active=bool(row[11]),
configuration=json.loads(row[12]) if row[12] else {},
tags=json.loads(row[13]) if row[13] else [],
created_at=datetime.fromisoformat(row[14]),
updated_at=datetime.fromisoformat(row[15])
)

Datei anzeigen

@ -0,0 +1,233 @@
"""
SQLite implementation of platform method state repository.
Handles persistence and retrieval of platform-specific rotation states.
"""
import json
from datetime import datetime, date
from typing import List, Optional
from domain.entities.method_rotation import PlatformMethodState, RotationStrategy
from domain.repositories.method_rotation_repository import IPlatformMethodStateRepository
from database.db_manager import DatabaseManager
class PlatformMethodStateRepository(IPlatformMethodStateRepository):
"""SQLite implementation of platform method state repository"""
def __init__(self, db_manager):
self.db_manager = db_manager
def save(self, state: PlatformMethodState) -> None:
"""Save or update platform method state"""
state.updated_at = datetime.now()
query = """
INSERT OR REPLACE INTO platform_method_states (
id, platform, last_successful_method, last_successful_at,
preferred_methods, blocked_methods, daily_attempt_counts,
reset_date, rotation_strategy, emergency_mode, metadata, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
state_id = f"state_{state.platform}"
params = (
state_id,
state.platform,
state.last_successful_method,
state.last_successful_at.isoformat() if state.last_successful_at else None,
json.dumps(state.preferred_methods),
json.dumps(state.blocked_methods),
json.dumps(state.daily_attempt_counts),
state.reset_date.isoformat(),
state.rotation_strategy.value,
state.emergency_mode,
json.dumps(state.metadata),
state.updated_at.isoformat()
)
self.db_manager.execute_query(query, params)
def find_by_platform(self, platform: str) -> Optional[PlatformMethodState]:
"""Find method state for a platform"""
query = "SELECT * FROM platform_method_states WHERE platform = ?"
result = self.db_manager.fetch_one(query, (platform,))
return self._row_to_state(result) if result else None
def get_or_create_state(self, platform: str) -> PlatformMethodState:
"""Get existing state or create new one with defaults"""
state = self.find_by_platform(platform)
if state:
return state
# Create new state with defaults
new_state = PlatformMethodState(
platform=platform,
preferred_methods=self._get_default_methods(platform),
rotation_strategy=RotationStrategy.ADAPTIVE,
reset_date=datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
)
self.save(new_state)
return new_state
def update_daily_attempts(self, platform: str, method_name: str) -> None:
"""Increment daily attempt counter for a method"""
state = self.get_or_create_state(platform)
state.increment_daily_attempts(method_name)
self.save(state)
def reset_daily_counters(self, platform: str) -> None:
"""Reset daily attempt counters (typically called at midnight)"""
state = self.find_by_platform(platform)
if not state:
return
state.daily_attempt_counts = {}
state.reset_date = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
self.save(state)
def block_method(self, platform: str, method_name: str, reason: str) -> None:
"""Block a method temporarily"""
state = self.get_or_create_state(platform)
state.block_method(method_name, reason)
self.save(state)
def unblock_method(self, platform: str, method_name: str) -> None:
"""Unblock a previously blocked method"""
state = self.get_or_create_state(platform)
state.unblock_method(method_name)
self.save(state)
def record_method_success(self, platform: str, method_name: str) -> None:
"""Record successful method execution"""
state = self.get_or_create_state(platform)
state.record_success(method_name)
self.save(state)
def get_preferred_method_order(self, platform: str) -> List[str]:
"""Get preferred method order for a platform"""
state = self.find_by_platform(platform)
if not state:
return self._get_default_methods(platform)
return state.preferred_methods
def set_emergency_mode(self, platform: str, enabled: bool) -> None:
"""Enable/disable emergency mode for a platform"""
state = self.get_or_create_state(platform)
state.emergency_mode = enabled
if enabled:
# In emergency mode, prefer only low-risk methods
state.metadata['emergency_activated_at'] = datetime.now().isoformat()
state.metadata['pre_emergency_preferred'] = state.preferred_methods.copy()
# Filter to only include low-risk methods
emergency_methods = [m for m in state.preferred_methods if m in ['email', 'standard_registration']]
if emergency_methods:
state.preferred_methods = emergency_methods
else:
# Restore previous preferred methods
if 'pre_emergency_preferred' in state.metadata:
state.preferred_methods = state.metadata.pop('pre_emergency_preferred')
state.metadata.pop('emergency_activated_at', None)
self.save(state)
def get_daily_attempt_counts(self, platform: str) -> dict:
"""Get current daily attempt counts for all methods"""
state = self.find_by_platform(platform)
if not state:
return {}
return state.daily_attempt_counts.copy()
def is_method_available(self, platform: str, method_name: str, max_daily_attempts: int) -> bool:
"""Check if a method is available for use"""
state = self.find_by_platform(platform)
if not state:
return True
return state.is_method_available(method_name, max_daily_attempts)
def get_blocked_methods(self, platform: str) -> List[str]:
"""Get list of currently blocked methods"""
state = self.find_by_platform(platform)
if not state:
return []
return state.blocked_methods.copy()
def update_rotation_strategy(self, platform: str, strategy: RotationStrategy) -> None:
"""Update rotation strategy for a platform"""
state = self.get_or_create_state(platform)
state.rotation_strategy = strategy
state.metadata['strategy_changed_at'] = datetime.now().isoformat()
self.save(state)
def bulk_reset_daily_counters(self) -> int:
"""Reset daily counters for all platforms (maintenance operation)"""
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
query = """
UPDATE platform_method_states
SET daily_attempt_counts = '{}',
reset_date = ?,
updated_at = ?
WHERE reset_date < ?
"""
cursor = self.db_manager.execute_query(query, (
today.isoformat(),
datetime.now().isoformat(),
today.isoformat()
))
return cursor.rowcount if cursor else 0
def get_all_platform_states(self) -> List[PlatformMethodState]:
"""Get states for all platforms"""
query = "SELECT * FROM platform_method_states ORDER BY platform"
results = self.db_manager.fetch_all(query)
return [self._row_to_state(row) for row in results]
def cleanup_emergency_modes(self, hours_threshold: int = 24) -> int:
"""Automatically disable emergency modes that have been active too long"""
cutoff_time = datetime.now() - datetime.timedelta(hours=hours_threshold)
query = """
SELECT platform FROM platform_method_states
WHERE emergency_mode = 1
AND JSON_EXTRACT(metadata, '$.emergency_activated_at') < ?
"""
results = self.db_manager.fetch_all(query, (cutoff_time.isoformat(),))
count = 0
for row in results:
platform = row[0]
self.set_emergency_mode(platform, False)
count += 1
return count
def _row_to_state(self, row) -> PlatformMethodState:
"""Convert database row to PlatformMethodState entity"""
return PlatformMethodState(
platform=row[1],
last_successful_method=row[2],
last_successful_at=datetime.fromisoformat(row[3]) if row[3] else None,
preferred_methods=json.loads(row[4]) if row[4] else [],
blocked_methods=json.loads(row[5]) if row[5] else [],
daily_attempt_counts=json.loads(row[6]) if row[6] else {},
reset_date=datetime.fromisoformat(row[7]),
rotation_strategy=RotationStrategy(row[8]),
emergency_mode=bool(row[9]),
metadata=json.loads(row[10]) if row[10] else {},
updated_at=datetime.fromisoformat(row[11])
)
def _get_default_methods(self, platform: str) -> List[str]:
"""Get default method order for a platform"""
default_methods = {
'instagram': ['email', 'phone', 'social_login'],
'tiktok': ['email', 'phone'],
'x': ['email', 'phone'],
'gmail': ['standard_registration', 'recovery_registration']
}
return default_methods.get(platform, ['email'])

Datei anzeigen

@ -0,0 +1,252 @@
"""
Rate Limit Repository - Persistierung von Rate Limit Events und Policies
"""
import json
import sqlite3
from typing import List, Optional, Dict, Any
from datetime import datetime, timedelta
from collections import defaultdict
from infrastructure.repositories.base_repository import BaseRepository
from domain.entities.rate_limit_policy import RateLimitPolicy
from domain.value_objects.action_timing import ActionTiming, ActionType
class RateLimitRepository(BaseRepository):
"""Repository für Rate Limit Daten"""
def save_timing(self, timing: ActionTiming) -> None:
"""Speichert ein Action Timing Event"""
query = """
INSERT INTO rate_limit_events (
timestamp, action_type, duration_ms, success, response_code,
session_id, url, element_selector, error_message, retry_count, metadata
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
params = (
timing.timestamp,
timing.action_type.value,
int(timing.duration_ms),
timing.success,
timing.metadata.get('response_code') if timing.metadata else None,
timing.metadata.get('session_id') if timing.metadata else None,
timing.url,
timing.element_selector,
timing.error_message,
timing.retry_count,
self._serialize_json(timing.metadata) if timing.metadata else None
)
self._execute_insert(query, params)
def get_recent_timings(self, action_type: Optional[ActionType] = None,
hours: int = 1) -> List[ActionTiming]:
"""Holt kürzliche Timing-Events"""
query = """
SELECT * FROM rate_limit_events
WHERE timestamp > datetime('now', '-' || ? || ' hours')
"""
params = [hours]
if action_type:
query += " AND action_type = ?"
params.append(action_type.value)
query += " ORDER BY timestamp DESC"
rows = self._execute_query(query, tuple(params))
timings = []
for row in rows:
timing = ActionTiming(
action_type=ActionType(row['action_type']),
timestamp=self._parse_datetime(row['timestamp']),
duration=row['duration_ms'] / 1000.0,
success=bool(row['success']),
url=row['url'],
element_selector=row['element_selector'],
error_message=row['error_message'],
retry_count=row['retry_count'],
metadata=self._deserialize_json(row['metadata'])
)
timings.append(timing)
return timings
def save_policy(self, action_type: ActionType, policy: RateLimitPolicy) -> None:
"""Speichert oder aktualisiert eine Rate Limit Policy"""
query = """
INSERT OR REPLACE INTO rate_limit_policies (
action_type, min_delay, max_delay, adaptive,
backoff_multiplier, max_retries, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?)
"""
params = (
action_type.value,
policy.min_delay,
policy.max_delay,
policy.adaptive,
policy.backoff_multiplier,
policy.max_retries,
datetime.now()
)
self._execute_insert(query, params)
def get_policy(self, action_type: ActionType) -> Optional[RateLimitPolicy]:
"""Holt eine Rate Limit Policy"""
query = "SELECT * FROM rate_limit_policies WHERE action_type = ?"
rows = self._execute_query(query, (action_type.value,))
if not rows:
return None
row = rows[0]
return RateLimitPolicy(
min_delay=row['min_delay'],
max_delay=row['max_delay'],
adaptive=bool(row['adaptive']),
backoff_multiplier=row['backoff_multiplier'],
max_retries=row['max_retries']
)
def get_all_policies(self) -> Dict[ActionType, RateLimitPolicy]:
"""Holt alle gespeicherten Policies"""
query = "SELECT * FROM rate_limit_policies"
rows = self._execute_query(query)
policies = {}
for row in rows:
try:
action_type = ActionType(row['action_type'])
policy = RateLimitPolicy(
min_delay=row['min_delay'],
max_delay=row['max_delay'],
adaptive=bool(row['adaptive']),
backoff_multiplier=row['backoff_multiplier'],
max_retries=row['max_retries']
)
policies[action_type] = policy
except ValueError:
# Unbekannter ActionType
pass
return policies
def get_statistics(self, action_type: Optional[ActionType] = None,
timeframe: Optional[timedelta] = None) -> Dict[str, Any]:
"""Berechnet Statistiken über Rate Limiting"""
query = """
SELECT
action_type,
COUNT(*) as total_actions,
AVG(duration_ms) as avg_duration_ms,
MIN(duration_ms) as min_duration_ms,
MAX(duration_ms) as max_duration_ms,
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as successful_actions,
SUM(CASE WHEN success = 0 THEN 1 ELSE 0 END) as failed_actions,
AVG(retry_count) as avg_retry_count,
MAX(retry_count) as max_retry_count
FROM rate_limit_events
WHERE 1=1
"""
params = []
if timeframe:
query += " AND timestamp > datetime('now', '-' || ? || ' seconds')"
params.append(int(timeframe.total_seconds()))
if action_type:
query += " AND action_type = ?"
params.append(action_type.value)
query += " GROUP BY action_type"
else:
query += " GROUP BY action_type"
rows = self._execute_query(query, tuple(params))
if action_type and rows:
# Einzelne Action Type Statistik
row = rows[0]
return {
'action_type': row['action_type'],
'total_actions': row['total_actions'],
'avg_duration_ms': row['avg_duration_ms'],
'min_duration_ms': row['min_duration_ms'],
'max_duration_ms': row['max_duration_ms'],
'success_rate': row['successful_actions'] / row['total_actions'] if row['total_actions'] > 0 else 0,
'failed_actions': row['failed_actions'],
'avg_retry_count': row['avg_retry_count'],
'max_retry_count': row['max_retry_count']
}
else:
# Statistiken für alle Action Types
stats = {}
for row in rows:
stats[row['action_type']] = {
'total_actions': row['total_actions'],
'avg_duration_ms': row['avg_duration_ms'],
'min_duration_ms': row['min_duration_ms'],
'max_duration_ms': row['max_duration_ms'],
'success_rate': row['successful_actions'] / row['total_actions'] if row['total_actions'] > 0 else 0,
'failed_actions': row['failed_actions'],
'avg_retry_count': row['avg_retry_count'],
'max_retry_count': row['max_retry_count']
}
return stats
def detect_anomalies(self, action_type: ActionType,
threshold_multiplier: float = 2.0) -> List[Dict[str, Any]]:
"""Erkennt Anomalien in den Timing-Daten"""
# Berechne Durchschnitt und Standardabweichung
query = """
SELECT
AVG(duration_ms) as avg_duration,
AVG(duration_ms * duration_ms) - AVG(duration_ms) * AVG(duration_ms) as variance
FROM rate_limit_events
WHERE action_type = ?
AND timestamp > datetime('now', '-1 hour')
AND success = 1
"""
row = self._execute_query(query, (action_type.value,))[0]
if not row['avg_duration']:
return []
avg_duration = row['avg_duration']
std_dev = (row['variance'] ** 0.5) if row['variance'] > 0 else 0
threshold = avg_duration + (std_dev * threshold_multiplier)
# Finde Anomalien
query = """
SELECT * FROM rate_limit_events
WHERE action_type = ?
AND timestamp > datetime('now', '-1 hour')
AND duration_ms > ?
ORDER BY duration_ms DESC
LIMIT 10
"""
rows = self._execute_query(query, (action_type.value, threshold))
anomalies = []
for row in rows:
anomalies.append({
'timestamp': row['timestamp'],
'duration_ms': row['duration_ms'],
'deviation': (row['duration_ms'] - avg_duration) / std_dev if std_dev > 0 else 0,
'success': bool(row['success']),
'url': row['url'],
'error_message': row['error_message']
})
return anomalies
def cleanup_old_events(self, older_than: datetime) -> int:
"""Bereinigt alte Rate Limit Events"""
query = "DELETE FROM rate_limit_events WHERE timestamp < ?"
return self._execute_delete(query, (older_than,))

Datei anzeigen

@ -0,0 +1,254 @@
"""
SQLite implementation of rotation session repository.
Handles persistence and retrieval of rotation sessions.
"""
import json
import sqlite3
from datetime import datetime, timedelta
from typing import List, Optional, Dict, Any
from domain.entities.method_rotation import RotationSession
from domain.repositories.method_rotation_repository import IRotationSessionRepository
from database.db_manager import DatabaseManager
class RotationSessionRepository(IRotationSessionRepository):
"""SQLite implementation of rotation session repository"""
def __init__(self, db_manager):
self.db_manager = db_manager
def save(self, session: RotationSession) -> None:
"""Save or update a rotation session"""
query = """
INSERT OR REPLACE INTO rotation_sessions (
id, platform, account_id, current_method, attempted_methods,
session_start, last_rotation, rotation_count, success_count,
failure_count, is_active, rotation_reason, fingerprint_id, session_metadata
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
params = (
session.session_id,
session.platform,
session.account_id,
session.current_method,
json.dumps(session.attempted_methods),
session.session_start.isoformat(),
session.last_rotation.isoformat() if session.last_rotation else None,
session.rotation_count,
session.success_count,
session.failure_count,
session.is_active,
session.rotation_reason,
session.fingerprint_id,
json.dumps(session.session_metadata)
)
self.db_manager.execute_query(query, params)
def find_by_id(self, session_id: str) -> Optional[RotationSession]:
"""Find a session by its ID"""
query = "SELECT * FROM rotation_sessions WHERE id = ?"
result = self.db_manager.fetch_one(query, (session_id,))
return self._row_to_session(result) if result else None
def find_active_session(self, platform: str, account_id: Optional[str] = None) -> Optional[RotationSession]:
"""Find an active session for a platform/account"""
if account_id:
query = """
SELECT * FROM rotation_sessions
WHERE platform = ? AND account_id = ? AND is_active = 1
ORDER BY session_start DESC
LIMIT 1
"""
params = (platform, account_id)
else:
query = """
SELECT * FROM rotation_sessions
WHERE platform = ? AND is_active = 1
ORDER BY session_start DESC
LIMIT 1
"""
params = (platform,)
result = self.db_manager.fetch_one(query, params)
return self._row_to_session(result) if result else None
def find_active_sessions_by_platform(self, platform: str) -> List[RotationSession]:
"""Find all active sessions for a platform"""
query = """
SELECT * FROM rotation_sessions
WHERE platform = ? AND is_active = 1
ORDER BY session_start DESC
"""
results = self.db_manager.fetch_all(query, (platform,))
return [self._row_to_session(row) for row in results]
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"""
session = self.find_by_id(session_id)
if not session:
return
session.add_attempt(method_name, success, error_message)
self.save(session)
def archive_session(self, session_id: str, final_success: bool = False) -> None:
"""Mark a session as completed/archived"""
session = self.find_by_id(session_id)
if not session:
return
session.complete_session(final_success)
self.save(session)
def get_session_history(self, platform: str, limit: int = 100) -> List[RotationSession]:
"""Get recent session history for a platform"""
query = """
SELECT * FROM rotation_sessions
WHERE platform = ?
ORDER BY session_start DESC
LIMIT ?
"""
results = self.db_manager.fetch_all(query, (platform, limit))
return [self._row_to_session(row) for row in results]
def get_session_statistics(self, platform: str, days: int = 30) -> Dict[str, Any]:
"""Get session statistics for a platform over specified days"""
cutoff_date = datetime.now() - timedelta(days=days)
query = """
SELECT
COUNT(*) as total_sessions,
COUNT(CASE WHEN is_active = 1 THEN 1 END) as active_sessions,
COUNT(CASE WHEN is_active = 0 AND JSON_EXTRACT(session_metadata, '$.final_success') = 1 THEN 1 END) as successful_sessions,
COUNT(CASE WHEN is_active = 0 AND JSON_EXTRACT(session_metadata, '$.final_success') = 0 THEN 1 END) as failed_sessions,
AVG(rotation_count) as avg_rotations,
MAX(rotation_count) as max_rotations,
AVG(success_count + failure_count) as avg_attempts,
AVG(CASE WHEN success_count + failure_count > 0 THEN success_count * 1.0 / (success_count + failure_count) ELSE 0 END) as avg_success_rate
FROM rotation_sessions
WHERE platform = ? AND session_start >= ?
"""
result = self.db_manager.fetch_one(query, (platform, cutoff_date.isoformat()))
if not result:
return {}
return {
'total_sessions': result[0] or 0,
'active_sessions': result[1] or 0,
'successful_sessions': result[2] or 0,
'failed_sessions': result[3] or 0,
'avg_rotations_per_session': round(result[4] or 0.0, 2),
'max_rotations_in_session': result[5] or 0,
'avg_attempts_per_session': round(result[6] or 0.0, 2),
'avg_session_success_rate': round(result[7] or 0.0, 3)
}
def cleanup_old_sessions(self, days_to_keep: int = 30) -> int:
"""Clean up old session data and return number of records removed"""
cutoff_date = datetime.now() - timedelta(days=days_to_keep)
query = """
DELETE FROM rotation_sessions
WHERE is_active = 0 AND session_start < ?
"""
cursor = self.db_manager.execute_query(query, (cutoff_date.isoformat(),))
return cursor.rowcount if cursor else 0
def get_method_usage_statistics(self, platform: str, days: int = 30) -> Dict[str, Any]:
"""Get method usage statistics from sessions"""
cutoff_date = datetime.now() - timedelta(days=days)
query = """
SELECT
current_method,
COUNT(*) as usage_count,
AVG(success_count) as avg_success_count,
AVG(failure_count) as avg_failure_count,
AVG(rotation_count) as avg_rotation_count
FROM rotation_sessions
WHERE platform = ? AND session_start >= ?
GROUP BY current_method
ORDER BY usage_count DESC
"""
results = self.db_manager.fetch_all(query, (platform, cutoff_date.isoformat()))
method_stats = {}
for row in results:
method_stats[row[0]] = {
'usage_count': row[1],
'avg_success_count': round(row[2] or 0.0, 2),
'avg_failure_count': round(row[3] or 0.0, 2),
'avg_rotation_count': round(row[4] or 0.0, 2)
}
return method_stats
def find_sessions_by_fingerprint(self, fingerprint_id: str) -> List[RotationSession]:
"""Find sessions associated with a specific fingerprint"""
query = """
SELECT * FROM rotation_sessions
WHERE fingerprint_id = ?
ORDER BY session_start DESC
"""
results = self.db_manager.fetch_all(query, (fingerprint_id,))
return [self._row_to_session(row) for row in results]
def get_long_running_sessions(self, hours: int = 24) -> List[RotationSession]:
"""Find sessions that have been running for too long"""
cutoff_time = datetime.now() - timedelta(hours=hours)
query = """
SELECT * FROM rotation_sessions
WHERE is_active = 1 AND session_start < ?
ORDER BY session_start ASC
"""
results = self.db_manager.fetch_all(query, (cutoff_time.isoformat(),))
return [self._row_to_session(row) for row in results]
def force_archive_stale_sessions(self, hours: int = 24) -> int:
"""Force archive sessions that have been running too long"""
cutoff_time = datetime.now() - timedelta(hours=hours)
query = """
UPDATE rotation_sessions
SET is_active = 0,
session_metadata = JSON_SET(
session_metadata,
'$.completed_at', ?,
'$.final_success', 0,
'$.force_archived', 1
)
WHERE is_active = 1 AND session_start < ?
"""
cursor = self.db_manager.execute_query(query, (datetime.now().isoformat(), cutoff_time.isoformat()))
return cursor.rowcount if cursor else 0
def _row_to_session(self, row) -> RotationSession:
"""Convert database row to RotationSession entity"""
return RotationSession(
session_id=row[0],
platform=row[1],
account_id=row[2],
current_method=row[3],
attempted_methods=json.loads(row[4]) if row[4] else [],
session_start=datetime.fromisoformat(row[5]),
last_rotation=datetime.fromisoformat(row[6]) if row[6] else None,
rotation_count=row[7],
success_count=row[8],
failure_count=row[9],
is_active=bool(row[10]),
rotation_reason=row[11],
fingerprint_id=row[12],
session_metadata=json.loads(row[13]) if row[13] else {}
)