2025-12-27 11:59:04 +01:00
|
|
|
from .base_handler import BaseHandler
|
2025-12-29 22:46:10 +01:00
|
|
|
|
2025-12-27 11:59:04 +01:00
|
|
|
import logging
|
|
|
|
|
import asyncio
|
2025-12-29 22:46:10 +01:00
|
|
|
import os
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
# Load environment variables for form fields and data dir
|
|
|
|
|
FORM_VORNAME = os.environ.get("FORM_VORNAME", "")
|
|
|
|
|
FORM_NACHNAME = os.environ.get("FORM_NACHNAME", "")
|
|
|
|
|
FORM_STRASSE = os.environ.get("FORM_STRASSE", "")
|
|
|
|
|
FORM_HAUSNUMMER = os.environ.get("FORM_HAUSNUMMER", "")
|
|
|
|
|
FORM_PLZ = os.environ.get("FORM_PLZ", "")
|
|
|
|
|
FORM_ORT = os.environ.get("FORM_ORT", "")
|
|
|
|
|
FORM_PHONE = os.environ.get("FORM_PHONE", "")
|
|
|
|
|
FORM_EMAIL = os.environ.get("FORM_EMAIL", "")
|
|
|
|
|
DATA_DIR = Path(os.environ.get("DATA_DIR", "data"))
|
2025-12-27 11:59:04 +01:00
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
class StadtUndLandHandler(BaseHandler):
|
2025-12-28 19:59:31 +01:00
|
|
|
def __init__(self, browser_context):
|
|
|
|
|
self.context = browser_context
|
|
|
|
|
|
2025-12-27 11:59:04 +01:00
|
|
|
async def apply(self, listing: dict, result: dict) -> dict:
|
|
|
|
|
page = await self.context.new_page()
|
|
|
|
|
try:
|
2025-12-29 22:46:10 +01:00
|
|
|
logger.info(f"[STADTUNDLAND] Opening page: {listing['link']}")
|
2025-12-27 11:59:04 +01:00
|
|
|
await page.goto(listing["link"], wait_until="networkidle")
|
2025-12-29 22:46:10 +01:00
|
|
|
logger.info("[STADTUNDLAND] Page loaded")
|
2025-12-27 11:59:04 +01:00
|
|
|
await asyncio.sleep(2)
|
|
|
|
|
|
2025-12-29 22:46:10 +01:00
|
|
|
# Dismiss cookie banner
|
|
|
|
|
try:
|
|
|
|
|
cookie_btn = await page.query_selector('button:has-text("Akzeptieren"), button:has-text("Alle akzeptieren")')
|
|
|
|
|
if cookie_btn and await cookie_btn.is_visible():
|
|
|
|
|
await cookie_btn.click()
|
|
|
|
|
logger.info("[STADTUNDLAND] Dismissed cookie banner")
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.debug(f"[STADTUNDLAND] Cookie banner dismiss failed: {e}")
|
|
|
|
|
|
|
|
|
|
# Scroll to form
|
|
|
|
|
await page.evaluate("window.scrollBy(0, 500)")
|
|
|
|
|
await asyncio.sleep(0.5)
|
|
|
|
|
|
|
|
|
|
# Fill out the embedded form directly
|
|
|
|
|
form_filled = False
|
|
|
|
|
try:
|
|
|
|
|
# Vorname
|
|
|
|
|
vorname_field = await page.query_selector('input[name="name"]')
|
|
|
|
|
if vorname_field and await vorname_field.is_visible():
|
|
|
|
|
await vorname_field.fill(FORM_VORNAME)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Filled Vorname: {FORM_VORNAME}")
|
|
|
|
|
form_filled = True
|
|
|
|
|
# Nachname
|
|
|
|
|
nachname_field = await page.query_selector('input[name="surname"]')
|
|
|
|
|
if nachname_field and await nachname_field.is_visible():
|
|
|
|
|
await nachname_field.fill(FORM_NACHNAME)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Filled Nachname: {FORM_NACHNAME}")
|
|
|
|
|
form_filled = True
|
|
|
|
|
# Straße
|
|
|
|
|
street_field = await page.query_selector('input[name="street"]')
|
|
|
|
|
if street_field and await street_field.is_visible():
|
|
|
|
|
await street_field.fill(FORM_STRASSE)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Filled Straße: {FORM_STRASSE}")
|
|
|
|
|
form_filled = True
|
|
|
|
|
# Hausnummer
|
|
|
|
|
house_field = await page.query_selector('input[name="houseNo"]')
|
|
|
|
|
if house_field and await house_field.is_visible():
|
|
|
|
|
await house_field.fill(FORM_HAUSNUMMER)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Filled Hausnummer: {FORM_HAUSNUMMER}")
|
|
|
|
|
form_filled = True
|
|
|
|
|
# PLZ
|
|
|
|
|
plz_field = await page.query_selector('input[name="postalCode"]')
|
|
|
|
|
if plz_field and await plz_field.is_visible():
|
|
|
|
|
await plz_field.fill(FORM_PLZ)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Filled PLZ: {FORM_PLZ}")
|
|
|
|
|
form_filled = True
|
|
|
|
|
# Ort
|
|
|
|
|
city_field = await page.query_selector('input[name="city"]')
|
|
|
|
|
if city_field and await city_field.is_visible():
|
|
|
|
|
await city_field.fill(FORM_ORT)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Filled Ort: {FORM_ORT}")
|
|
|
|
|
form_filled = True
|
|
|
|
|
# Telefon
|
|
|
|
|
phone_field = await page.query_selector('input[name="phone"]')
|
|
|
|
|
if phone_field and await phone_field.is_visible():
|
|
|
|
|
await phone_field.fill(FORM_PHONE)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Filled Telefon: {FORM_PHONE}")
|
|
|
|
|
form_filled = True
|
|
|
|
|
# E-Mail
|
|
|
|
|
email_field = await page.query_selector('input[name="email"]')
|
|
|
|
|
if email_field and await email_field.is_visible():
|
|
|
|
|
await email_field.fill(FORM_EMAIL)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Filled E-Mail: {FORM_EMAIL}")
|
|
|
|
|
form_filled = True
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.warning(f"[STADTUNDLAND] Error filling form fields: {e}")
|
|
|
|
|
|
|
|
|
|
# Click privacy checkbox
|
|
|
|
|
try:
|
|
|
|
|
privacy_checkbox = await page.query_selector('input[name="privacy"]')
|
|
|
|
|
if privacy_checkbox and await privacy_checkbox.is_visible():
|
|
|
|
|
if not await privacy_checkbox.is_checked():
|
|
|
|
|
await privacy_checkbox.click()
|
|
|
|
|
logger.info("[STADTUNDLAND] Clicked privacy checkbox")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.warning(f"[STADTUNDLAND] Could not click privacy checkbox: {e}")
|
2025-12-27 11:59:04 +01:00
|
|
|
|
2025-12-29 22:46:10 +01:00
|
|
|
# Click provision checkbox (optional)
|
2025-12-28 19:59:31 +01:00
|
|
|
try:
|
2025-12-29 22:46:10 +01:00
|
|
|
provision_checkbox = await page.query_selector('input[name="provision"]')
|
|
|
|
|
if provision_checkbox and await provision_checkbox.is_visible():
|
|
|
|
|
if not await provision_checkbox.is_checked():
|
|
|
|
|
await provision_checkbox.click()
|
|
|
|
|
logger.info("[STADTUNDLAND] Clicked provision checkbox")
|
2025-12-28 19:59:31 +01:00
|
|
|
except Exception as e:
|
2025-12-29 22:46:10 +01:00
|
|
|
logger.warning(f"[STADTUNDLAND] Could not click provision checkbox: {e}")
|
|
|
|
|
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
|
|
|
|
|
# Screenshot after filling form
|
|
|
|
|
screenshot_path2 = DATA_DIR / f"stadtundland_filled_{listing['id']}.png"
|
|
|
|
|
await page.screenshot(path=str(screenshot_path2), full_page=True)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Saved filled form screenshot to {screenshot_path2}")
|
|
|
|
|
|
|
|
|
|
# Submit form
|
|
|
|
|
if form_filled:
|
|
|
|
|
try:
|
|
|
|
|
pruefen_btn = await page.query_selector('button:has-text("Eingaben prüfen")')
|
|
|
|
|
if pruefen_btn and await pruefen_btn.is_visible():
|
|
|
|
|
await pruefen_btn.click()
|
|
|
|
|
logger.info("[STADTUNDLAND] Clicked 'Eingaben prüfen' button")
|
|
|
|
|
await asyncio.sleep(2)
|
|
|
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
|
|
|
|
|
|
# Screenshot after validation
|
|
|
|
|
screenshot_path3 = DATA_DIR / f"stadtundland_validated_{listing['id']}.png"
|
|
|
|
|
await page.screenshot(path=str(screenshot_path3), full_page=True)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Saved validation screenshot to {screenshot_path3}")
|
|
|
|
|
|
|
|
|
|
# Final submit
|
|
|
|
|
final_submit_selectors = [
|
|
|
|
|
'button:has-text("Absenden")',
|
|
|
|
|
'button:has-text("Senden")',
|
|
|
|
|
'button:has-text("Anfrage senden")',
|
|
|
|
|
'button:has-text("Bestätigen")',
|
|
|
|
|
'button[type="submit"]',
|
|
|
|
|
]
|
|
|
|
|
final_btn = None
|
|
|
|
|
for selector in final_submit_selectors:
|
|
|
|
|
btn = await page.query_selector(selector)
|
|
|
|
|
if btn and await btn.is_visible():
|
|
|
|
|
final_btn = btn
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Found final submit button: {selector}")
|
|
|
|
|
break
|
|
|
|
|
if final_btn:
|
|
|
|
|
await final_btn.click()
|
|
|
|
|
logger.info("[STADTUNDLAND] Clicked final submit button")
|
|
|
|
|
await asyncio.sleep(3)
|
|
|
|
|
await page.wait_for_load_state("networkidle")
|
|
|
|
|
|
|
|
|
|
# Screenshot after submission
|
|
|
|
|
screenshot_path4 = DATA_DIR / f"stadtundland_submitted_{listing['id']}.png"
|
|
|
|
|
await page.screenshot(path=str(screenshot_path4), full_page=True)
|
|
|
|
|
logger.info(f"[STADTUNDLAND] Saved submission screenshot to {screenshot_path4}")
|
|
|
|
|
|
|
|
|
|
# Check for confirmation
|
|
|
|
|
content = await page.content()
|
|
|
|
|
if any(word in content.lower() for word in ["erfolgreich", "gesendet", "danke", "bestätigung"]):
|
|
|
|
|
result["success"] = True
|
|
|
|
|
result["message"] = "Application submitted successfully"
|
|
|
|
|
logger.info("[STADTUNDLAND] Success! Confirmation message detected")
|
|
|
|
|
else:
|
|
|
|
|
result["success"] = True
|
|
|
|
|
result["message"] = "Form submitted"
|
|
|
|
|
logger.info("[STADTUNDLAND] Form submitted")
|
|
|
|
|
else:
|
|
|
|
|
result["success"] = False
|
|
|
|
|
result["message"] = "Validated but final submit button not found"
|
|
|
|
|
logger.warning("[STADTUNDLAND] Final submit button not found")
|
|
|
|
|
else:
|
|
|
|
|
result["success"] = False
|
|
|
|
|
result["message"] = "Form filled but 'Eingaben prüfen' button not found"
|
|
|
|
|
logger.warning("[STADTUNDLAND] 'Eingaben prüfen' button not found")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
result["success"] = False
|
|
|
|
|
result["message"] = f"Submit error: {str(e)}"
|
|
|
|
|
logger.warning(f"[STADTUNDLAND] Submit error: {e}")
|
2025-12-27 11:59:04 +01:00
|
|
|
else:
|
2025-12-29 22:46:10 +01:00
|
|
|
result["success"] = False
|
|
|
|
|
result["message"] = "No form fields found on page"
|
|
|
|
|
logger.warning("[STADTUNDLAND] Could not find form fields")
|
|
|
|
|
|
2025-12-27 11:59:04 +01:00
|
|
|
except Exception as e:
|
2025-12-29 22:46:10 +01:00
|
|
|
result["success"] = False
|
|
|
|
|
result["message"] = f"Error: {str(e)}"
|
|
|
|
|
logger.error(f"[STADTUNDLAND] Exception: {str(e)}")
|
2025-12-27 11:59:04 +01:00
|
|
|
finally:
|
|
|
|
|
await page.close()
|
|
|
|
|
return result
|