diff --git a/monitor.py b/monitor.py index fa6be36..95670c8 100644 --- a/monitor.py +++ b/monitor.py @@ -912,9 +912,11 @@ class ApplicationHandler: async def _apply_stadtundland(self, listing: dict, result: dict) -> dict: """ - Stadt und Land uses Wohnungshelden (app.wohnungshelden.de) for their application system. - The application form is loaded in an iframe from a different domain. - We need to navigate directly to the iframe URL. + Stadt und Land has an embedded contact form directly on their listing page. + No iframe - the form fields are directly accessible. + Fields: name, surname, street, houseNo, postalCode, city, phone, email + Checkboxes: privacy, provision + Submit: "Eingaben prüfen" """ page = await self.context.new_page() try: @@ -932,180 +934,196 @@ class ApplicationHandler: await asyncio.sleep(1) except: pass - # Look for "Kontakt" or "Anfragen" button to open the form - logger.info("[STADTUNDLAND] Looking for contact button...") - apply_btn = await page.query_selector('a:has-text("Kontakt"), button:has-text("Kontakt"), a:has-text("Anfragen"), button:has-text("Anfragen"), a:has-text("kontaktieren"), button:has-text("kontaktieren")') - if apply_btn and await apply_btn.is_visible(): - logger.info("[STADTUNDLAND] Found contact button, clicking...") - await apply_btn.click() - await asyncio.sleep(3) + # Scroll down to the contact form + await page.evaluate("window.scrollBy(0, 500)") + await asyncio.sleep(0.5) - # Stadt und Land uses Wohnungshelden iframe for the application form - iframe_element = await page.query_selector('iframe[src*="wohnungshelden.de"]') - if iframe_element: - iframe_url = await iframe_element.get_attribute('src') - logger.info(f"[STADTUNDLAND] Found Wohnungshelden iframe: {iframe_url}") + # Take initial screenshot + screenshot_path = DATA_DIR / f"stadtundland_page_{listing['id']}.png" + await page.screenshot(path=str(screenshot_path), full_page=True) + logger.info(f"[STADTUNDLAND] Saved page screenshot to {screenshot_path}") - # Navigate to the iframe URL directly in a new page for full access - iframe_page = await self.context.new_page() + # Fill out the embedded form directly + form_filled = False + + # Vorname (name field) + try: + 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 + except Exception as e: + logger.warning(f"[STADTUNDLAND] Could not fill Vorname: {e}") + + # Nachname (surname field) + try: + 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 + except Exception as e: + logger.warning(f"[STADTUNDLAND] Could not fill Nachname: {e}") + + # Straße (street field) + try: + 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 + except Exception as e: + logger.warning(f"[STADTUNDLAND] Could not fill Straße: {e}") + + # Hausnummer (houseNo field) + try: + 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 + except Exception as e: + logger.warning(f"[STADTUNDLAND] Could not fill Hausnummer: {e}") + + # PLZ (postalCode field) + try: + 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 + except Exception as e: + logger.warning(f"[STADTUNDLAND] Could not fill PLZ: {e}") + + # Ort (city field) + try: + 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 + except Exception as e: + logger.warning(f"[STADTUNDLAND] Could not fill Ort: {e}") + + # Telefon (phone field) + try: + 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 + except Exception as e: + logger.warning(f"[STADTUNDLAND] Could not fill Telefon: {e}") + + # E-Mail (email field) + try: + 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] Could not fill E-Mail: {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}") + + # Click provision checkbox (optional) + try: + 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") + except Exception as e: + logger.warning(f"[STADTUNDLAND] Could not click provision checkbox: {e}") + + await asyncio.sleep(1) + + # Take screenshot after filling form + screenshot_path = DATA_DIR / f"stadtundland_filled_{listing['id']}.png" + await page.screenshot(path=str(screenshot_path), full_page=True) + logger.info(f"[STADTUNDLAND] Saved filled form screenshot to {screenshot_path}") + + # Submit form + if form_filled: try: - await iframe_page.goto(iframe_url, wait_until="networkidle") - await asyncio.sleep(2) - logger.info("[STADTUNDLAND] Loaded Wohnungshelden application page") + # Stadt und Land uses "Eingaben prüfen" button + # Step 1: Click "Eingaben prüfen" button + 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") - # Take screenshot of the Wohnungshelden form - screenshot_path = DATA_DIR / f"stadtundland_wohnungshelden_{listing['id']}.png" - await iframe_page.screenshot(path=str(screenshot_path), full_page=True) - logger.info(f"[STADTUNDLAND] Saved Wohnungshelden screenshot to {screenshot_path}") + # Take screenshot after validation + screenshot_path = DATA_DIR / f"stadtundland_validated_{listing['id']}.png" + await page.screenshot(path=str(screenshot_path), full_page=True) + logger.info(f"[STADTUNDLAND] Saved validation screenshot") - # Save HTML for debugging - html_content = await iframe_page.content() - html_path = DATA_DIR / f"stadtundland_wohnungshelden_{listing['id']}.html" - with open(html_path, 'w', encoding='utf-8') as f: - f.write(html_content) - logger.info(f"[STADTUNDLAND] Saved HTML to {html_path}") + # Step 2: Click the final submit button + final_submit_selectors = [ + 'button:has-text("Absenden")', + 'button:has-text("Senden")', + 'button:has-text("Anfrage senden")', + 'button:has-text("Bestätigen")', + 'button[type="submit"]', + ] - # Fill out Wohnungshelden form (same as Degewo) - form_filled = False + final_btn = None + for selector in final_submit_selectors: + final_btn = await page.query_selector(selector) + if final_btn and await final_btn.is_visible(): + logger.info(f"[STADTUNDLAND] Found final submit button: {selector}") + break + final_btn = None - # Anrede (Salutation) - ng-select dropdown - try: - salutation_dropdown = await iframe_page.query_selector('#salutation-dropdown, ng-select[id*="salutation"]') - if salutation_dropdown: - await salutation_dropdown.click() - await asyncio.sleep(0.5) - anrede_option = await iframe_page.query_selector(f'.ng-option:has-text("{FORM_ANREDE}")') - if anrede_option: - await anrede_option.click() - logger.info(f"[STADTUNDLAND] Selected Anrede: {FORM_ANREDE}") - form_filled = True - except Exception as e: - logger.warning(f"[STADTUNDLAND] Could not set Anrede: {e}") + 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") - # Vorname (First name) - try: - vorname_field = await iframe_page.query_selector('#firstName') - if vorname_field: - await vorname_field.fill(FORM_VORNAME) - logger.info(f"[STADTUNDLAND] Filled Vorname: {FORM_VORNAME}") - form_filled = True - except Exception as e: - logger.warning(f"[STADTUNDLAND] Could not fill Vorname: {e}") - - # Nachname (Last name) - try: - nachname_field = await iframe_page.query_selector('#lastName') - if nachname_field: - await nachname_field.fill(FORM_NACHNAME) - logger.info(f"[STADTUNDLAND] Filled Nachname: {FORM_NACHNAME}") - form_filled = True - except Exception as e: - logger.warning(f"[STADTUNDLAND] Could not fill Nachname: {e}") - - # E-Mail - try: - email_field = await iframe_page.query_selector('#email') - if email_field: - 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] Could not fill E-Mail: {e}") - - # Telefonnummer - try: - tel_field = await iframe_page.query_selector('input[id*="telefonnummer"]') - if tel_field: - await tel_field.fill(FORM_PHONE) - logger.info(f"[STADTUNDLAND] Filled Telefon: {FORM_PHONE}") - form_filled = True - except Exception as e: - logger.warning(f"[STADTUNDLAND] Could not fill Telefon: {e}") - - # Anzahl einziehende Personen - try: - personen_field = await iframe_page.query_selector('input[id*="numberPersonsTotal"]') - if personen_field: - await personen_field.fill(FORM_PERSONS) - logger.info(f"[STADTUNDLAND] Filled Anzahl Personen: {FORM_PERSONS}") - form_filled = True - except Exception as e: - logger.warning(f"[STADTUNDLAND] Could not fill Anzahl Personen: {e}") - - # "Für sich selbst" dropdown - try: - selbst_dropdown = await iframe_page.query_selector('ng-select[id*="fuer_wen"]') - if selbst_dropdown: - await selbst_dropdown.click() - await asyncio.sleep(0.5) - selbst_option = await iframe_page.query_selector('.ng-option:has-text("Für mich selbst"), .ng-option:has-text("selbst")') - if selbst_option: - await selbst_option.click() - logger.info("[STADTUNDLAND] Selected: Für mich selbst") - form_filled = True - except Exception as e: - logger.warning(f"[STADTUNDLAND] Could not set 'Für sich selbst': {e}") - - await asyncio.sleep(1) - - # Take screenshot after filling form - screenshot_path = DATA_DIR / f"stadtundland_form_filled_{listing['id']}.png" - await iframe_page.screenshot(path=str(screenshot_path), full_page=True) - logger.info(f"[STADTUNDLAND] Saved filled form screenshot to {screenshot_path}") - - # Try to submit - if form_filled: - try: - submit_selectors = [ - 'button[type="submit"]', - 'input[type="submit"]', - 'button:has-text("Absenden")', - 'button:has-text("Senden")', - 'button:has-text("Anfrage")', - '.btn-primary', - ] - - submit_btn = None - for selector in submit_selectors: - submit_btn = await iframe_page.query_selector(selector) - if submit_btn and await submit_btn.is_visible(): - logger.info(f"[STADTUNDLAND] Found submit button with selector: {selector}") - break - submit_btn = None - - if submit_btn: - await submit_btn.click() - logger.info("[STADTUNDLAND] Clicked submit button") - await asyncio.sleep(3) - - # Take screenshot after submission - screenshot_path = DATA_DIR / f"stadtundland_submitted_{listing['id']}.png" - await iframe_page.screenshot(path=str(screenshot_path), full_page=True) - logger.info(f"[STADTUNDLAND] Saved submission screenshot to {screenshot_path}") + # Take screenshot after final submission + screenshot_path = DATA_DIR / f"stadtundland_submitted_{listing['id']}.png" + await page.screenshot(path=str(screenshot_path), full_page=True) + logger.info(f"[STADTUNDLAND] Saved submission screenshot") + # Check for confirmation message + content = await page.content() + if "erfolgreich" in content.lower() or "gesendet" in content.lower() or "danke" in content.lower() or "bestätigung" in content.lower(): result["success"] = True - result["message"] = "Application submitted via Wohnungshelden" + result["message"] = "Application submitted successfully" + logger.info("[STADTUNDLAND] Success! Confirmation message detected") else: - result["success"] = False - result["message"] = "Form filled, submit button not found" - logger.warning("[STADTUNDLAND] Submit button not found") - except Exception as e: + result["success"] = True + result["message"] = "Form submitted" + logger.info("[STADTUNDLAND] Form submitted") + else: result["success"] = False - result["message"] = f"Form filled, submit error: {str(e)}" - logger.warning(f"[STADTUNDLAND] Submit error: {e}") + result["message"] = "Validated but final submit button not found" + logger.warning("[STADTUNDLAND] Final submit button not found") else: result["success"] = False - result["message"] = "No form fields found in Wohnungshelden" - logger.warning("[STADTUNDLAND] Could not find form fields in Wohnungshelden") - finally: - await iframe_page.close() + 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}") else: - # No Wohnungshelden iframe found result["success"] = False - result["message"] = "No Wohnungshelden iframe found" - logger.warning("[STADTUNDLAND] No Wohnungshelden iframe found") - screenshot_path = DATA_DIR / f"stadtundland_nobtn_{listing['id']}.png" - await page.screenshot(path=str(screenshot_path), full_page=True) + result["message"] = "No form fields found on page" + logger.warning("[STADTUNDLAND] Could not find form fields") except Exception as e: result["success"] = False