625 Zeilen
28 KiB
Python
625 Zeilen
28 KiB
Python
"""
|
|
Gmail Automatisierung - Hauptklasse
|
|
"""
|
|
|
|
import logging
|
|
import time
|
|
import random
|
|
from typing import Dict, Optional, Any
|
|
from playwright.sync_api import Page
|
|
|
|
from social_networks.base_automation import BaseAutomation
|
|
from social_networks.gmail import gmail_selectors as selectors
|
|
from social_networks.gmail.gmail_ui_helper import GmailUIHelper
|
|
from social_networks.gmail.gmail_registration import GmailRegistration
|
|
from social_networks.gmail.gmail_login import GmailLogin
|
|
from social_networks.gmail.gmail_verification import GmailVerification
|
|
from social_networks.gmail.gmail_utils import GmailUtils
|
|
|
|
logger = logging.getLogger("gmail_automation")
|
|
|
|
|
|
class GmailAutomation(BaseAutomation):
|
|
"""
|
|
Gmail/Google Account-spezifische Automatisierung
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
"""
|
|
Initialisiert die Gmail-Automatisierung
|
|
"""
|
|
super().__init__(**kwargs)
|
|
self.platform_name = "gmail"
|
|
self.ui_helper: Optional[GmailUIHelper] = None
|
|
self.registration: Optional[GmailRegistration] = None
|
|
self.login_helper: Optional[GmailLogin] = None
|
|
self.verification: Optional[GmailVerification] = None
|
|
self.utils: Optional[GmailUtils] = None
|
|
# Optionaler SMS-Dienst (z.B. 5sim, sms-activate), vom Worker injizierbar
|
|
self.phone_service = kwargs.get("phone_service")
|
|
|
|
def _initialize_helpers(self, page: Page):
|
|
"""
|
|
Initialisiert die Hilfsklassen
|
|
"""
|
|
self.ui_helper = GmailUIHelper(page, self.screenshots_dir, self.save_screenshots)
|
|
self.registration = GmailRegistration(page, self.ui_helper, self.screenshots_dir, self.save_screenshots, phone_service=self.phone_service)
|
|
self.login_helper = GmailLogin(page, self.ui_helper, self.screenshots_dir, self.save_screenshots)
|
|
self.verification = GmailVerification(
|
|
page,
|
|
self.ui_helper,
|
|
self.email_handler,
|
|
self.screenshots_dir,
|
|
self.save_screenshots,
|
|
phone_service=self.phone_service
|
|
)
|
|
self.utils = GmailUtils()
|
|
|
|
def register_account(self, full_name: str, age: int, registration_method: str = "email",
|
|
phone_number: str = None, **kwargs) -> Dict[str, Any]:
|
|
"""
|
|
Erstellt einen neuen Gmail/Google Account.
|
|
Delegiert nach Navigation an den GmailRegistration-Flow und gibt ein konsistentes Ergebnis zurück.
|
|
"""
|
|
try:
|
|
logger.info("[GMAIL AUTOMATION] register_account gestartet")
|
|
|
|
# Accountdaten zusammenstellen
|
|
account_data = {
|
|
"full_name": full_name,
|
|
"first_name": kwargs.get("first_name", (full_name.split()[0] if full_name else "")),
|
|
"last_name": kwargs.get("last_name", (full_name.split()[-1] if full_name and len(full_name.split()) > 1 else "")),
|
|
"age": age,
|
|
"birthday": kwargs.get("birthday", self._generate_birthday(age)),
|
|
"gender": kwargs.get("gender", random.choice(["male", "female"])),
|
|
"username": kwargs.get("username", ""),
|
|
"password": kwargs.get("password", ""),
|
|
"phone": phone_number,
|
|
# Recovery-E-Mail reduziert häufig das Phone-Requirement
|
|
"recovery_email": kwargs.get("recovery_email", kwargs.get("backup_email", ""))
|
|
}
|
|
|
|
# Falls keine Recovery-Email übergeben wurde, generiere eine Fallback-Adresse
|
|
if not account_data.get("recovery_email"):
|
|
try:
|
|
imap_user = self.email_handler.get_config().get("imap_user", "")
|
|
domain = imap_user.split("@")[-1] if "@" in imap_user else self.email_domain
|
|
suffix = str(int(time.time()))[-6:]
|
|
local = "account.recovery" # generischer Local-Part
|
|
account_data["recovery_email"] = f"{local}+{suffix}@{domain}"
|
|
logger.info(f"Recovery-Email automatisch gesetzt: {account_data['recovery_email']}")
|
|
except Exception:
|
|
pass
|
|
|
|
# Browser/Seite sicherstellen - Fresh Browser Profile für bessere Bypass-Chance
|
|
if not self._initialize_fresh_browser():
|
|
return {
|
|
"success": False,
|
|
"error": "Browser konnte nicht initialisiert werden",
|
|
"message": "Browser-Initialisierung fehlgeschlagen"
|
|
}
|
|
|
|
page = self.browser.page
|
|
self._initialize_helpers(page)
|
|
|
|
# Versuche mehrere Locale-Varianten der direkten Signup-URL (reduziert UI-Abhängigkeit)
|
|
# Standardmäßig DE verwenden – versuche DE-Varianten (DE, AT, CH) in deutscher Sprache
|
|
locales = kwargs.get("locales_try", ["de", "de-AT", "de-CH"])
|
|
signup_base = "https://accounts.google.com/signup/v2/createaccount?service=mail&flowName=GlifWebSignIn&flowEntry=SignUp&hl={hl}"
|
|
|
|
last_error: Optional[str] = None
|
|
for hl in locales:
|
|
try:
|
|
# Pro Locale mehrere Versuche mit variierenden Profilen (Geschlecht)
|
|
for attempt in range(3):
|
|
# Re-Init Browser/Seite für völlig frischen Kontext (Fresh Browser Profile)
|
|
try:
|
|
self._close_browser()
|
|
except Exception:
|
|
pass
|
|
if not self._initialize_fresh_browser():
|
|
return {"success": False, "error": "Browser init failed"}
|
|
page = self.browser.page
|
|
self._initialize_helpers(page)
|
|
|
|
# Varianz: Geschlecht wechseln für verschiedene Versuche
|
|
account_data["gender"] = random.choice(["male", "female"]) if attempt % 2 == 0 else account_data.get("gender", "male")
|
|
|
|
url = signup_base.format(hl=hl)
|
|
logger.info(f"[Versuch {attempt+1}/3] Öffne Signup-URL (hl={hl}): {url}")
|
|
page.goto(url, wait_until="networkidle")
|
|
time.sleep(2)
|
|
|
|
# Sicherstellen, dass die Signup-Felder sichtbar sind
|
|
try:
|
|
if not page.locator(selectors.FIRST_NAME_INPUT).is_visible(timeout=7000):
|
|
logger.info("Namensfeld nicht sichtbar – versuche Reload")
|
|
page.goto(url, wait_until="networkidle")
|
|
time.sleep(2)
|
|
except Exception:
|
|
page.goto(url, wait_until="networkidle")
|
|
time.sleep(2)
|
|
|
|
# Registrierungs-Flow ausführen
|
|
flow_result = self.registration.start_registration_flow(account_data)
|
|
if flow_result.get("success"):
|
|
username = flow_result.get("username") or account_data.get("username")
|
|
email = flow_result.get("email") or (f"{username}@gmail.com" if username else "")
|
|
return {
|
|
"success": True,
|
|
"username": username,
|
|
"email": email,
|
|
"password": account_data.get("password", ""),
|
|
"account_data": {
|
|
"username": username,
|
|
"email": email,
|
|
"password": account_data.get("password", ""),
|
|
"phone": account_data.get("phone", ""),
|
|
"full_name": account_data.get("full_name", "")
|
|
},
|
|
"message": f"Gmail-Registrierung erfolgreich (hl={hl}, attempt={attempt+1})"
|
|
}
|
|
|
|
# Prüfe Fehlertext
|
|
err_text = (flow_result.get("error") or flow_result.get("message") or "").lower()
|
|
last_error = err_text or last_error
|
|
if "phone required" in err_text or "telefon" in err_text:
|
|
logger.info(f"(hl={hl}, attempt={attempt+1}) verlangte Telefon – wiederhole mit geänderten Parametern")
|
|
continue # versuche nächsten Attempt in gleicher Locale
|
|
|
|
# Sonst: gib den Fehler direkt zurück
|
|
return flow_result
|
|
# Nach drei Versuchen in dieser Locale – zur nächsten Locale wechseln
|
|
logger.info(f"Wechsle Locale nach 3 Versuchen ohne Erfolg: {hl}")
|
|
except Exception as e:
|
|
last_error = str(e)
|
|
logger.warning(f"Fehler bei Locale {hl}: {e}")
|
|
|
|
# Wenn alle Locales scheitern, gib letzten Fehler zurück
|
|
return {
|
|
"success": False,
|
|
"error": last_error or "Unbekannter Fehler",
|
|
"message": "Registrierung ohne Telefonnummer nicht möglich in dieser Umgebung"
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"Fehler bei der Gmail-Registrierung: {e}")
|
|
return {"success": False, "error": str(e), "message": f"Registrierung fehlgeschlagen: {str(e)}"}
|
|
finally:
|
|
# Browser aktuell schließen, um Ressourcen freizugeben
|
|
self._close_browser()
|
|
|
|
def login(self, username: str, password: str) -> Dict[str, Any]:
|
|
"""
|
|
Meldet sich bei einem bestehenden Gmail/Google Account an
|
|
"""
|
|
try:
|
|
logger.info(f"Starte Gmail Login für {username}")
|
|
|
|
# Initialisiere Browser, falls noch nicht geschehen
|
|
if not self.browser or not hasattr(self.browser, 'page'):
|
|
if not self._initialize_browser():
|
|
return {
|
|
"success": False,
|
|
"error": "Browser konnte nicht initialisiert werden",
|
|
"message": "Browser-Initialisierung fehlgeschlagen"
|
|
}
|
|
|
|
# Page-Objekt holen
|
|
page = self.browser.page
|
|
self._initialize_helpers(page)
|
|
|
|
# Login durchführen
|
|
return self.login_helper.login(username, password)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Fehler beim Gmail Login: {str(e)}")
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"message": f"Login fehlgeschlagen: {str(e)}"
|
|
}
|
|
finally:
|
|
self._close_browser()
|
|
|
|
def get_account_info(self) -> Dict[str, Any]:
|
|
"""
|
|
Ruft Informationen über den aktuellen Account ab
|
|
"""
|
|
# TODO: Implementierung
|
|
return {
|
|
"success": False,
|
|
"message": "Noch nicht implementiert"
|
|
}
|
|
|
|
def logout(self) -> bool:
|
|
"""
|
|
Meldet sich vom aktuellen Account ab
|
|
"""
|
|
# TODO: Implementierung
|
|
return False
|
|
|
|
def login_account(self, username_or_email: str, password: str, **kwargs) -> Dict[str, Any]:
|
|
"""
|
|
Meldet sich bei einem bestehenden Gmail Account an.
|
|
Implementiert die abstrakte Methode aus BaseAutomation.
|
|
"""
|
|
return self.login(username_or_email, password)
|
|
|
|
def verify_account(self, verification_code: str, **kwargs) -> Dict[str, Any]:
|
|
"""
|
|
Verifiziert einen Gmail Account mit einem Bestätigungscode.
|
|
Implementiert die abstrakte Methode aus BaseAutomation.
|
|
"""
|
|
try:
|
|
if self.verification:
|
|
return self.verification.verify_with_code(verification_code)
|
|
else:
|
|
return {
|
|
"success": False,
|
|
"error": "Verification helper nicht initialisiert",
|
|
"message": "Verifizierung kann nicht durchgeführt werden"
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"success": False,
|
|
"error": str(e),
|
|
"message": f"Verifizierung fehlgeschlagen: {str(e)}"
|
|
}
|
|
|
|
def _generate_birthday(self, age: int) -> str:
|
|
"""
|
|
Generiert ein Geburtsdatum basierend auf dem Alter
|
|
"""
|
|
from datetime import datetime, timedelta
|
|
today = datetime.now()
|
|
birth_year = today.year - age
|
|
# Zufälliger Tag im Jahr
|
|
random_days = random.randint(0, 364)
|
|
birthday = datetime(birth_year, 1, 1) + timedelta(days=random_days)
|
|
return birthday.strftime("%Y-%m-%d")
|
|
|
|
def _initialize_fresh_browser(self) -> bool:
|
|
"""
|
|
Initialisiert einen völlig frischen Browser-Kontext für optimale Phone-Bypass-Chancen.
|
|
Verwendet erweiterte 2025-Techniken für bessere Erfolgschancen.
|
|
"""
|
|
try:
|
|
logger.info("[FRESH BROWSER] Initialisiere enhanced Browser-Kontext mit 2025 Optimierungen")
|
|
|
|
# Sicherheitsschließung des alten Browsers
|
|
if self.browser:
|
|
try:
|
|
self.browser.close()
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
self.browser = None
|
|
|
|
# Erstelle temporäres User-Data-Directory für isolierten Kontext
|
|
import tempfile
|
|
import shutil
|
|
temp_profile_dir = tempfile.mkdtemp(prefix="gmail_fresh_profile_")
|
|
|
|
# Proxy-Konfiguration übernehmen falls vorhanden
|
|
proxy_config = None
|
|
if self.use_proxy:
|
|
proxy_config = self.proxy_rotator.get_proxy(self.proxy_type)
|
|
if not proxy_config:
|
|
logger.warning("Kein Proxy verfügbar, verwende direkten Zugriff")
|
|
|
|
# Enhanced Browser-Konfiguration mit 2025 Optimierungen
|
|
from browser.playwright_manager import PlaywrightManager
|
|
|
|
# Randomisiere Browser-Eigenschaften für besseren Fingerprint
|
|
import random
|
|
viewport_configs = [
|
|
{"width": 1920, "height": 1080},
|
|
{"width": 1366, "height": 768},
|
|
{"width": 1440, "height": 900},
|
|
{"width": 1536, "height": 864},
|
|
{"width": 1280, "height": 720}
|
|
]
|
|
selected_viewport = random.choice(viewport_configs)
|
|
|
|
self.browser = PlaywrightManager(
|
|
headless=False, # Niemals headless für Gmail (erhöht Erkennungsrisiko)
|
|
proxy=proxy_config,
|
|
browser_type="chromium",
|
|
screenshots_dir=self.screenshots_dir,
|
|
slowmo=random.randint(50, 150), # Randomisierte Geschwindigkeit
|
|
window_position=self.window_position
|
|
)
|
|
|
|
# ÜBERSCHREIBE Launch-Optionen für Fresh Profile
|
|
# Backup der ursprünglichen Start-Methode
|
|
original_start = self.browser.start
|
|
|
|
def fresh_start():
|
|
# Enhanced Chrome-Args mit 2025 Anti-Detection Features
|
|
fresh_args = [
|
|
'--no-first-run',
|
|
'--no-default-browser-check',
|
|
'--disable-default-apps',
|
|
'--disable-blink-features=AutomationControlled', # Kritisch für 2025
|
|
'--disable-dev-shm-usage',
|
|
'--disable-web-security',
|
|
'--disable-features=IsolateOrigins,site-per-process',
|
|
'--disable-site-isolation-trials',
|
|
'--disable-translate',
|
|
'--disable-background-timer-throttling',
|
|
'--disable-backgrounding-occluded-windows',
|
|
'--disable-renderer-backgrounding',
|
|
'--disable-component-update',
|
|
'--disable-client-side-phishing-detection',
|
|
'--disable-sync',
|
|
'--disable-features=TranslateUI,BlinkGenPropertyTrees',
|
|
'--disable-ipc-flooding-protection',
|
|
'--enable-features=NetworkService,NetworkServiceInProcess',
|
|
'--force-color-profile=srgb',
|
|
'--metrics-recording-only',
|
|
'--incognito' # Incognito-Modus für frischen Start
|
|
]
|
|
|
|
# Temporär Launch-Optionen erweitern
|
|
if hasattr(self.browser, 'playwright') and self.browser.playwright:
|
|
self.browser.playwright = None # Force re-init
|
|
|
|
# Browser mit Fresh Args starten
|
|
from playwright.sync_api import sync_playwright
|
|
self.browser.playwright = sync_playwright().start()
|
|
browser_instance = self.browser.playwright.chromium
|
|
|
|
# Kombiniere bestehende und fresh args
|
|
existing_args = getattr(self.browser, 'browser_args', [])
|
|
combined_args = list(set(existing_args + fresh_args)) # Duplikate entfernen
|
|
|
|
# Verwende launch_persistent_context für user-data-dir
|
|
launch_options = {
|
|
"headless": self.browser.headless,
|
|
"args": combined_args,
|
|
"slow_mo": self.browser.slowmo
|
|
}
|
|
|
|
# Fensterposition
|
|
if self.browser.window_position and not self.browser.headless:
|
|
x, y = self.browser.window_position
|
|
combined_args.append(f'--window-position={x},{y}')
|
|
|
|
# Enhanced Context mit 2025 Anti-Detection Eigenschaften
|
|
import random
|
|
user_agents = [
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
]
|
|
|
|
context_options = {
|
|
"viewport": selected_viewport,
|
|
"user_agent": random.choice(user_agents),
|
|
"device_scale_factor": random.choice([1.0, 1.25, 1.5, 2.0]),
|
|
"is_mobile": False,
|
|
"has_touch": False,
|
|
"locale": random.choice(["de-DE", "en-US", "de-AT", "de-CH"]),
|
|
"timezone_id": random.choice([
|
|
"Europe/Berlin", "Europe/Vienna", "Europe/Zurich",
|
|
"America/New_York", "America/Chicago", "Europe/London"
|
|
]),
|
|
"accept_downloads": True,
|
|
"ignore_https_errors": True,
|
|
"bypass_csp": True, # Bypass Content Security Policy
|
|
"java_script_enabled": True,
|
|
"extra_http_headers": {
|
|
"Accept-Language": "de-DE,de;q=0.9,en;q=0.8",
|
|
"Accept-Encoding": "gzip, deflate, br",
|
|
"DNT": "1"
|
|
}
|
|
}
|
|
|
|
# Randomized User-Agent
|
|
user_agents = [
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0"
|
|
]
|
|
context_options["user_agent"] = random.choice(user_agents)
|
|
|
|
# Proxy falls vorhanden
|
|
if proxy_config:
|
|
context_options["proxy"] = proxy_config
|
|
|
|
# Kombiniere Launch-Optionen mit Context-Optionen
|
|
launch_options.update(context_options)
|
|
|
|
# KORREKTUR: Verwende launch_persistent_context (erstellt Browser UND Context)
|
|
self.browser.context = browser_instance.launch_persistent_context(
|
|
user_data_dir=temp_profile_dir,
|
|
**launch_options
|
|
)
|
|
|
|
# Bei launch_persistent_context ist der Browser im Context enthalten
|
|
self.browser.browser = None # Nicht benötigt bei persistent context
|
|
|
|
# Fresh Stealth-Scripts anwenden (variiert)
|
|
self._apply_fresh_stealth_scripts()
|
|
|
|
# Bei launch_persistent_context ist bereits eine Seite vorhanden
|
|
pages = self.browser.context.pages
|
|
if pages:
|
|
self.browser.page = pages[0] # Verwende erste verfügbare Seite
|
|
else:
|
|
self.browser.page = self.browser.context.new_page()
|
|
|
|
return self.browser.page
|
|
|
|
# Ersetze die start-Methode
|
|
self.browser.start = fresh_start
|
|
|
|
# Browser starten
|
|
page = self.browser.start()
|
|
|
|
logger.info("[FRESH BROWSER] Fresh Browser erfolgreich initialisiert")
|
|
|
|
# Cleanup-Handler für temp directory (speichere Referenz für späteren Cleanup)
|
|
self._temp_profile_cleanup = lambda: shutil.rmtree(temp_profile_dir, ignore_errors=True)
|
|
import atexit
|
|
atexit.register(self._temp_profile_cleanup)
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"[FRESH BROWSER] Fehler bei Fresh Browser Initialisierung: {e}")
|
|
return False
|
|
|
|
def _apply_fresh_stealth_scripts(self):
|
|
"""
|
|
Wendet variierte Stealth-Scripts an, die für jeden Fresh Browser unterschiedlich sind
|
|
"""
|
|
import random
|
|
|
|
# Base Stealth Scripts
|
|
base_scripts = [
|
|
# WebDriver detection removal
|
|
"""
|
|
() => {
|
|
Object.defineProperty(navigator, 'webdriver', {
|
|
get: () => undefined,
|
|
});
|
|
delete navigator.__proto__.webdriver;
|
|
}
|
|
""",
|
|
|
|
# Randomized navigator properties
|
|
f"""
|
|
() => {{
|
|
Object.defineProperty(navigator, 'platform', {{
|
|
get: () => '{random.choice(["Win32", "MacIntel", "Linux x86_64"])}'
|
|
}});
|
|
|
|
Object.defineProperty(navigator, 'languages', {{
|
|
get: () => {random.choice([
|
|
"['de-DE', 'de', 'en-US', 'en']",
|
|
"['en-US', 'en', 'de-DE', 'de']",
|
|
"['de-AT', 'de', 'en-US', 'en']"
|
|
])}
|
|
}});
|
|
|
|
Object.defineProperty(navigator, 'hardwareConcurrency', {{
|
|
get: () => {random.choice([4, 8, 12, 16])}
|
|
}});
|
|
|
|
Object.defineProperty(navigator, 'deviceMemory', {{
|
|
get: () => {random.choice([4, 8, 16, 32])}
|
|
}});
|
|
}}
|
|
""",
|
|
|
|
# Chrome runtime
|
|
"""
|
|
() => {
|
|
if (!window.chrome) {
|
|
window.chrome = {};
|
|
}
|
|
if (!window.chrome.runtime) {
|
|
window.chrome.runtime = {
|
|
onConnect: undefined,
|
|
onMessage: undefined,
|
|
sendMessage: function() {},
|
|
};
|
|
}
|
|
}
|
|
""",
|
|
|
|
# Permissions randomization
|
|
f"""
|
|
() => {{
|
|
const originalQuery = window.navigator.permissions.query;
|
|
window.navigator.permissions.query = (parameters) => {{
|
|
const responses = {{
|
|
'notifications': '{random.choice(['granted', 'denied', 'prompt'])}',
|
|
'geolocation': '{random.choice(['denied', 'prompt'])}',
|
|
'camera': '{random.choice(['denied', 'prompt'])}',
|
|
'microphone': '{random.choice(['denied', 'prompt'])}'
|
|
}};
|
|
const state = responses[parameters.name] || 'prompt';
|
|
return Promise.resolve({{ state }});
|
|
}};
|
|
}}
|
|
"""
|
|
]
|
|
|
|
# Randomized Canvas fingerprinting
|
|
noise_level = random.uniform(0.1, 2.0)
|
|
canvas_script = f"""
|
|
() => {{
|
|
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
HTMLCanvasElement.prototype.toDataURL = function(type) {{
|
|
const context = this.getContext('2d');
|
|
const originalImageData = context.getImageData;
|
|
|
|
context.getImageData = function(x, y, width, height) {{
|
|
const imageData = originalImageData.apply(this, arguments);
|
|
const data = imageData.data;
|
|
|
|
// Add subtle random noise
|
|
for (let i = 0; i < data.length; i += 4) {{
|
|
if (Math.random() < 0.1) {{ // 10% chance per pixel
|
|
const noise = (Math.random() - 0.5) * {noise_level};
|
|
data[i] = Math.min(255, Math.max(0, data[i] + noise));
|
|
data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + noise));
|
|
data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + noise));
|
|
}}
|
|
}}
|
|
|
|
return imageData;
|
|
}};
|
|
|
|
return originalToDataURL.apply(this, arguments);
|
|
}};
|
|
}}
|
|
"""
|
|
base_scripts.append(canvas_script)
|
|
|
|
# Apply all scripts to context
|
|
for script in base_scripts:
|
|
try:
|
|
self.browser.context.add_init_script(script)
|
|
except Exception as e:
|
|
logger.warning(f"[FRESH BROWSER] Script application failed: {e}")
|
|
continue
|
|
|
|
logger.info(f"[FRESH BROWSER] Applied {len(base_scripts)} fresh stealth scripts")
|
|
|
|
def _close_browser(self) -> None:
|
|
"""
|
|
Überschreibt die Standard-Browser-Schließung für Fresh Browser Cleanup
|
|
"""
|
|
try:
|
|
# Cleanup temp directory falls vorhanden
|
|
if hasattr(self, '_temp_profile_cleanup') and self._temp_profile_cleanup:
|
|
try:
|
|
self._temp_profile_cleanup()
|
|
logger.info("[FRESH BROWSER] Temp profile directory cleaned up")
|
|
except Exception as e:
|
|
logger.warning(f"[FRESH BROWSER] Cleanup-Warnung: {e}")
|
|
|
|
# Standard Browser-Schließung
|
|
super()._close_browser()
|
|
|
|
except Exception as e:
|
|
logger.error(f"[FRESH BROWSER] Fehler beim Browser-Cleanup: {e}")
|
|
# Fallback: Force Browser schließen
|
|
if self.browser:
|
|
try:
|
|
if hasattr(self.browser, 'context') and self.browser.context:
|
|
self.browser.context.close()
|
|
if hasattr(self.browser, 'browser') and self.browser.browser:
|
|
self.browser.browser.close()
|
|
if hasattr(self.browser, 'playwright') and self.browser.playwright:
|
|
self.browser.playwright.stop()
|
|
except Exception:
|
|
pass
|
|
finally:
|
|
self.browser = None
|