Gmail weiter gemacht
Dieser Commit ist enthalten in:
@ -5,7 +5,7 @@ Gmail Automatisierung - Hauptklasse
|
||||
import logging
|
||||
import time
|
||||
import random
|
||||
from typing import Dict, Optional, Tuple, Any
|
||||
from typing import Dict, Optional, Any
|
||||
from playwright.sync_api import Page
|
||||
|
||||
from social_networks.base_automation import BaseAutomation
|
||||
@ -18,240 +18,183 @@ 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 = None
|
||||
self.registration = None
|
||||
self.login_helper = None
|
||||
self.verification = None
|
||||
self.utils = None
|
||||
|
||||
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)
|
||||
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)
|
||||
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]:
|
||||
|
||||
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
|
||||
|
||||
Args:
|
||||
full_name: Vollständiger Name für den Account
|
||||
age: Alter des Benutzers
|
||||
registration_method: Registrierungsmethode (nur "email" für Gmail)
|
||||
phone_number: Telefonnummer (optional, aber oft erforderlich)
|
||||
**kwargs: Weitere optionale Parameter
|
||||
Erstellt einen neuen Gmail/Google Account.
|
||||
Delegiert nach Navigation an den GmailRegistration-Flow und gibt ein konsistentes Ergebnis zurück.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"[GMAIL AUTOMATION] register_account aufgerufen")
|
||||
logger.info(f"[GMAIL AUTOMATION] full_name: {full_name}")
|
||||
logger.info(f"[GMAIL AUTOMATION] age: {age}")
|
||||
logger.info(f"[GMAIL AUTOMATION] phone_number: {phone_number}")
|
||||
logger.info(f"[GMAIL AUTOMATION] kwargs: {kwargs}")
|
||||
|
||||
# Erstelle account_data aus den Parametern
|
||||
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 ""),
|
||||
"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_email": kwargs.get("recovery_email", "")
|
||||
# Recovery-E-Mail reduziert häufig das Phone-Requirement
|
||||
"recovery_email": kwargs.get("recovery_email", kwargs.get("backup_email", ""))
|
||||
}
|
||||
|
||||
# Initialisiere Browser, falls noch nicht geschehen
|
||||
logger.info(f"[GMAIL AUTOMATION] Prüfe Browser-Status...")
|
||||
logger.info(f"[GMAIL AUTOMATION] self.browser: {self.browser}")
|
||||
if self.browser:
|
||||
logger.info(f"[GMAIL AUTOMATION] hasattr(self.browser, 'page'): {hasattr(self.browser, 'page')}")
|
||||
|
||||
if not self.browser or not hasattr(self.browser, 'page'):
|
||||
logger.info(f"[GMAIL AUTOMATION] Browser muss initialisiert werden")
|
||||
if not self._initialize_browser():
|
||||
logger.error(f"[GMAIL AUTOMATION] Browser-Initialisierung fehlgeschlagen!")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Browser konnte nicht initialisiert werden",
|
||||
"message": "Browser-Initialisierung fehlgeschlagen"
|
||||
}
|
||||
logger.info(f"[GMAIL AUTOMATION] Browser erfolgreich initialisiert")
|
||||
|
||||
# Page-Objekt holen
|
||||
|
||||
# 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)
|
||||
|
||||
# Direkt zur Registrierungs-URL navigieren
|
||||
logger.info("Navigiere zur Gmail Registrierungsseite")
|
||||
page.goto(selectors.REGISTRATION_URL, wait_until="networkidle")
|
||||
|
||||
# Warte auf vollständiges Laden der Seite
|
||||
logger.info("Warte auf vollständiges Laden der Seite...")
|
||||
time.sleep(random.uniform(5, 7))
|
||||
|
||||
# Prüfe ob wir auf der richtigen Seite sind
|
||||
current_url = page.url
|
||||
logger.info(f"Aktuelle URL nach Navigation: {current_url}")
|
||||
|
||||
# Screenshot der Startseite
|
||||
self.ui_helper.take_screenshot("gmail_start_page")
|
||||
|
||||
# Finde und klicke auf "Konto erstellen" Button (Dropdown)
|
||||
try:
|
||||
# Warte bis die Seite interaktiv ist
|
||||
logger.info("Warte auf vollständiges Laden der Gmail Workspace Seite...")
|
||||
page.wait_for_load_state("networkidle")
|
||||
time.sleep(2)
|
||||
|
||||
# Debug: Alle sichtbaren Links/Buttons mit "Konto" ausgeben
|
||||
|
||||
# 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:
|
||||
konto_elements = page.locator("*:has-text('Konto')").all()
|
||||
logger.info(f"Gefundene Elemente mit 'Konto': {len(konto_elements)}")
|
||||
for i, elem in enumerate(konto_elements[:5]): # Erste 5 Elemente
|
||||
# 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:
|
||||
tag = elem.evaluate("el => el.tagName")
|
||||
text = elem.inner_text()
|
||||
logger.info(f"Element {i}: <{tag}> - {text}")
|
||||
except:
|
||||
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:
|
||||
logger.debug(f"Debug-Ausgabe fehlgeschlagen: {e}")
|
||||
|
||||
# Schritt 1: Klicke auf "Konto erstellen" Dropdown
|
||||
create_account_selectors = [
|
||||
"[aria-label='Konto erstellen']",
|
||||
"div[aria-label='Konto erstellen']",
|
||||
"[data-g-action='create an account']",
|
||||
"button:has-text('Konto erstellen')",
|
||||
"a:has-text('Konto erstellen')",
|
||||
"*:has-text('Konto erstellen')", # Beliebiges Element mit dem Text
|
||||
"[slot='label']:has-text('Konto erstellen')" # Spezifisch für Web Components
|
||||
]
|
||||
|
||||
clicked_dropdown = False
|
||||
for selector in create_account_selectors:
|
||||
try:
|
||||
elements = page.locator(selector).all()
|
||||
logger.info(f"Selector {selector}: {len(elements)} Elemente gefunden")
|
||||
|
||||
if page.locator(selector).is_visible(timeout=3000):
|
||||
# Versuche normale Klick-Methode
|
||||
try:
|
||||
page.locator(selector).first.click()
|
||||
logger.info(f"Dropdown 'Konto erstellen' geklickt mit: {selector}")
|
||||
clicked_dropdown = True
|
||||
break
|
||||
except:
|
||||
# Versuche JavaScript-Klick als Fallback
|
||||
page.locator(selector).first.evaluate("el => el.click()")
|
||||
logger.info(f"Dropdown 'Konto erstellen' via JS geklickt mit: {selector}")
|
||||
clicked_dropdown = True
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(f"Fehler mit Selector {selector}: {e}")
|
||||
continue
|
||||
|
||||
if not clicked_dropdown:
|
||||
logger.error("Konnte 'Konto erstellen' Dropdown nicht finden")
|
||||
self.ui_helper.take_screenshot("konto_erstellen_not_found")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Konto erstellen Dropdown nicht gefunden",
|
||||
"message": "Navigation fehlgeschlagen"
|
||||
}
|
||||
|
||||
# Kurz warten bis Dropdown geöffnet ist
|
||||
time.sleep(1)
|
||||
|
||||
# Schritt 2: Klicke auf "Für die private Nutzung"
|
||||
private_use_selectors = [
|
||||
"a[aria-label='Gmail - Für die private Nutzung']",
|
||||
"a:has-text('Für die private Nutzung')",
|
||||
"[data-g-action='für die private nutzung']",
|
||||
"span:has-text('Für die private Nutzung')"
|
||||
]
|
||||
|
||||
clicked_private = False
|
||||
for selector in private_use_selectors:
|
||||
try:
|
||||
if page.locator(selector).is_visible(timeout=2000):
|
||||
page.locator(selector).click()
|
||||
logger.info(f"'Für die private Nutzung' geklickt mit: {selector}")
|
||||
clicked_private = True
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not clicked_private:
|
||||
logger.error("Konnte 'Für die private Nutzung' nicht finden")
|
||||
self.ui_helper.take_screenshot("private_nutzung_not_found")
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Für die private Nutzung Option nicht gefunden",
|
||||
"message": "Navigation fehlgeschlagen"
|
||||
}
|
||||
|
||||
# Warte auf die Registrierungsseite
|
||||
time.sleep(random.uniform(3, 5))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Navigieren zur Registrierung: {e}")
|
||||
|
||||
# Screenshot der Registrierungsseite
|
||||
self.ui_helper.take_screenshot("gmail_registration_page")
|
||||
|
||||
# Registrierungsprozess starten
|
||||
registration_result = self.registration.start_registration_flow(account_data)
|
||||
if not registration_result["success"]:
|
||||
return registration_result
|
||||
|
||||
# Nach erfolgreicher Registrierung
|
||||
logger.info("Gmail Account-Registrierung erfolgreich abgeschlossen")
|
||||
return {
|
||||
"success": True,
|
||||
"username": registration_result.get("username"),
|
||||
"password": account_data.get("password"),
|
||||
"email": registration_result.get("email"),
|
||||
"phone": account_data.get("phone"),
|
||||
"recovery_email": account_data.get("recovery_email"),
|
||||
"message": "Account erfolgreich erstellt"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der Gmail-Registrierung: {str(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": str(e),
|
||||
"message": f"Registrierung fehlgeschlagen: {str(e)}"
|
||||
"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]:
|
||||
|
||||
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():
|
||||
@ -260,14 +203,14 @@ class GmailAutomation(BaseAutomation):
|
||||
"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 {
|
||||
@ -277,8 +220,8 @@ class GmailAutomation(BaseAutomation):
|
||||
}
|
||||
finally:
|
||||
self._close_browser()
|
||||
|
||||
def get_account_info(self) -> Dict[str, any]:
|
||||
|
||||
def get_account_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Ruft Informationen über den aktuellen Account ab
|
||||
"""
|
||||
@ -287,21 +230,21 @@ class GmailAutomation(BaseAutomation):
|
||||
"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.
|
||||
@ -322,7 +265,7 @@ class GmailAutomation(BaseAutomation):
|
||||
"error": str(e),
|
||||
"message": f"Verifizierung fehlgeschlagen: {str(e)}"
|
||||
}
|
||||
|
||||
|
||||
def _generate_birthday(self, age: int) -> str:
|
||||
"""
|
||||
Generiert ein Geburtsdatum basierend auf dem Alter
|
||||
@ -333,4 +276,349 @@ class GmailAutomation(BaseAutomation):
|
||||
# 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")
|
||||
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
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren