Files
AccountForger-neuerUpload/social_networks/gmail/gmail_automation.py
Claude Project Manager fe1bb9baaa Gmail weiter gemacht
2025-08-10 14:23:51 +02:00

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