fix emojis

This commit is contained in:
Aron Petau 2026-01-02 11:23:35 +01:00
parent c45c6992ae
commit 287b1b154f
5 changed files with 39 additions and 32 deletions

View file

@ -85,7 +85,7 @@ class ApplicationHandler:
company_label = company.capitalize() if company != "unknown" else "Wohnung"
message = (
f"🏠 <b>[{company_label}] Neue Wohnung!</b>\n\n"
f"<b>[{company_label}] Neue Wohnung!</b>\n\n"
f"🚪 <b>{listing['rooms']}</b>\n"
f"📏 {listing['size']}\n"
f"💰 {listing['price']}\n"
@ -136,7 +136,7 @@ class ApplicationHandler:
result = await self.apply(listing)
results[listing["id"]] = result
self.save_application(result)
status = "" if result["success"] else ""
status = "[SUCCESS]" if result["success"] else "[FAILED]"
logger.info(f"{status} {listing['address'][:30]}... | {result['message'][:50]}")
await asyncio.sleep(2)
return results
@ -409,7 +409,7 @@ class ApplicationHandler:
else:
stats_text = "🎯 Peak time: N/A"
stats_text = f"""📊 Summary Statistics
stats_text = f"""<b>Summary Statistics</b>
Total listings tracked: {total_listings}
@ -632,15 +632,15 @@ Total listings tracked: {total_listings}
try:
listings = await self._fetch_listings_attempt()
if attempt > 0:
logger.info(f"Fetch succeeded (attempt {attempt + 1})")
logger.info(f"Fetch succeeded (attempt {attempt + 1})")
return listings
except Exception as e:
if attempt < max_retries - 1:
wait_time = retry_delay * (2 ** attempt) # Exponential backoff
logger.warning(f"⚠️ Fetch failed (attempt {attempt + 1}/{max_retries}): {str(e)[:50]}... Retrying in {wait_time}s")
logger.warning(f"Fetch failed (attempt {attempt + 1}/{max_retries}): {str(e)[:50]}... Retrying in {wait_time}s")
await asyncio.sleep(wait_time)
else:
logger.error(f"Fetch failed after {max_retries} attempts")
logger.error(f"Fetch failed after {max_retries} attempts")
return []
return []
@ -782,14 +782,14 @@ Total listings tracked: {total_listings}
listings = unique_listings
if not listings:
logger.warning("⚠️ No listings parsed")
logger.warning("No listings parsed")
await page.close()
logger.info(f"📊 Fetched {len(listings)} listings")
logger.info(f"Fetched {len(listings)} listings")
return listings
except Exception as e:
logger.error(f"Fetch error: {str(e)[:100]}")
logger.error(f"Fetch error: {str(e)[:100]}")
return []

View file

@ -92,6 +92,8 @@ class WBMHandler(BaseHandler):
# Look for application button on detail page
logger.info("[WBM] Looking for application button on detail page...")
selectors = [
'button:has-text("Anfrage absenden")',
'a:has-text("Anfrage absenden")',
'a[href*="expose-anfordern"]',
'a[href*="bewerben"]',
'a:has-text("Anfragen")',

View file

@ -151,7 +151,7 @@ class WGCompanyNotifier:
if listing['id'] not in previous:
new.append(listing)
if new:
logger.info(f"[WG] 🏠 {len(new)} new listing{'s' if len(new) > 1 else ''} detected")
logger.info(f"[WG] {len(new)} new listing{'s' if len(new) > 1 else ''} detected")
return new
def log_listing_times(self, new_listings):
@ -185,7 +185,7 @@ class WGCompanyNotifier:
for idx, listing in enumerate(new_listings, start=1):
try:
message = (
f"🏠 <b>[WG-Company] Neues WG-Zimmer!</b>\n\n"
f"<b>[WG-Company] Neues WG-Zimmer!</b>\n\n"
f"🚪 <b>{listing['rooms']}</b>\n"
f"📏 {listing['size']}\n"
f"💰 {listing['price']}\n"
@ -196,7 +196,7 @@ class WGCompanyNotifier:
asyncio.run_coroutine_threadsafe(self.telegram_bot._send_message(message), loop)
await asyncio.sleep(0.5)
except Exception as e:
logger.error(f"[WG] Telegram failed for listing {idx}: {str(e)[:50]}")
logger.error(f"[WG] Telegram failed for listing {idx}: {str(e)[:50]}")
async def run(self):
await self.init_browser()

23
main.py
View file

@ -1,4 +1,3 @@
import asyncio
from playwright.async_api import async_playwright
from application_handler import ApplicationHandler
@ -22,13 +21,13 @@ logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)-5s] %(name)-20s | %(message)s",
handlers=[
RotatingFileHandler("data/monitor.log", maxBytes=1 * 1024 * 1024, backupCount=3), # 1 MB per file, 3 backups
RotatingFileHandler("data/monitor.log", maxBytes=1 * 1024 * 1024, backupCount=3),
logging.StreamHandler()
],
force=True # Enforce for all modules, Python 3.8+
)
logger = logging.getLogger(__name__) # Use named logger
logger.info("🚀 Bot starting | Logs: data/monitor.log + console")
logger.info("Bot starting | Logs: data/monitor.log + console")
# Interval (seconds) between checks for new listings
CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", 300)) # Default: 300 seconds
@ -96,7 +95,7 @@ async def init_browser_context() -> tuple:
return playwright, browser, browser_context
async def main() -> None:
logger.info("🤖 Initializing wohn-bot...")
logger.info("Initializing wohn-bot...")
# Validate configuration before starting
if not validate_config():
@ -106,7 +105,7 @@ async def main() -> None:
state_manager = StateManager(Path("data/state.json"))
# --- Playwright browser/context setup with recovery ---
logger.info("🌐 Initializing browser...")
logger.info("Initializing browser...")
playwright, browser, browser_context = await init_browser_context()
# Application handler manages browser/context
@ -134,16 +133,16 @@ async def main() -> None:
try:
deleted = autoclean_debug_material()
if deleted:
logger.info(f"🧹 Cleaned {len(deleted)} debug files (48h)")
logger.info(f"Cleaned {len(deleted)} debug files (48h)")
except Exception as e:
logger.warning(f"⚠️ Autoclean failed: {e}")
logger.warning(f"Autoclean failed: {e}")
last_clean = now
try:
current_listings = await app_handler.fetch_listings()
except Exception as e:
logger.error(f"💥 Browser crash: {e}")
logger.info("🔄 Recovering...")
logger.info("Recovering...")
try:
await browser.close()
await playwright.stop()
@ -154,7 +153,7 @@ async def main() -> None:
try:
playwright, browser, browser_context = await init_browser_context()
app_handler.context = browser_context
logger.info("Browser recovered")
logger.info("Browser recovered")
await asyncio.sleep(5)
continue
except Exception as recovery_error:
@ -163,7 +162,7 @@ async def main() -> None:
continue
if not current_listings:
logger.warning("⚠️ No listings fetched")
logger.warning("No listings fetched")
await asyncio.sleep(CHECK_INTERVAL)
_flush_rotating_file_handlers()
continue
@ -192,10 +191,10 @@ async def main() -> None:
new_listings = app_handler.find_new_listings(current_listings, previous_listings)
application_results = {}
if new_listings:
logger.info(f"\ud83c\udfe0 {len(new_listings)} new listing{'s' if len(new_listings) > 1 else ''} detected")
logger.info(f"{len(new_listings)} new listing{'s' if len(new_listings) > 1 else ''} detected")
app_handler.log_listing_times(new_listings)
if app_handler.is_autopilot_enabled():
logger.info("\ud83e\udd16 Autopilot active - applying...")
logger.info("Autopilot active - applying...")
application_results = await app_handler.apply_to_listings(new_listings)
app_handler.notify_new_listings(new_listings, application_results)
app_handler.save_listings(current_listings)

View file

@ -62,7 +62,7 @@ class TelegramBot:
await self._send_message(msg)
except Exception as e:
logger.error(f"Error resetting listings: {e}")
await self._send_message(f" Error resetting listings: {str(e)}")
await self._send_message(f"[ERROR] Error resetting listings: {str(e)}")
def __init__(self, monitor, bot_token: str | None = None, chat_id: str | None = None, event_loop=None) -> None:
self.monitor = monitor
@ -174,9 +174,9 @@ class TelegramBot:
and app.get("retries", 0) < max_retries
and not app.get("deactivated", False)
]
await self._send_message(f"🔄 Retrying {len(failed)} failed applications (max retries: {max_retries})...")
await self._send_message(f"[RETRY] Retrying {len(failed)} failed applications (max retries: {max_retries})...")
if not failed:
await self._send_message(" No failed applications to retry (or all reached max retries).")
await self._send_message("[INFO] No failed applications to retry (or all reached max retries).")
return
results = {}
details = []
@ -197,7 +197,7 @@ class TelegramBot:
result["timestamp"] = app.get("timestamp", result["timestamp"])
self.app_handler.save_application(result)
results[listing["id"]] = result
status_emoji = "" if result["success"] else ""
status_emoji = "[SUCCESS]" if result["success"] else "[FAILED]"
details.append(
f"{status_emoji} <b>{result.get('address', '')}</b> ({result.get('company', '')})\n"
f"<code>{result.get('link', '')}</code>\n"
@ -205,7 +205,7 @@ class TelegramBot:
)
n_success = sum(1 for r in results.values() if r["success"])
n_fail = sum(1 for r in results.values() if not r["success"])
summary = f"🔄 Retried {len(results)} failed applications.\n✅ Success: {n_success}\n❌ Still failed: {n_fail}"
summary = f"[RETRY] Retried {len(results)} failed applications.\n[SUCCESS]: {n_success}\n[FAILED]: {n_fail}"
if details:
summary += "\n\n<b>Details:</b>\n" + "\n".join(details)
await self._send_message(summary)
@ -220,7 +220,7 @@ class TelegramBot:
if action == "on":
logger.info("Enabling autopilot mode")
self.monitor.set_autopilot(True)
await self._send_message("🤖 <b>Autopilot ENABLED</b>\n\nI will automatically apply to new listings!")
await self._send_message("<b>Autopilot ENABLED</b>\n\nI will automatically apply to new listings!")
elif action == "off":
self.monitor.set_autopilot(False)
await self._send_message("🛑 <b>Autopilot DISABLED</b>\n\nI will only notify you of new listings.")
@ -231,7 +231,7 @@ class TelegramBot:
state = self.app_handler.load_state()
autopilot = state.get("autopilot", False)
applications = self.app_handler.load_applications()
status = "🤖 <b>Autopilot:</b> " + ("ON" if autopilot else "OFF")
status = "<b>Autopilot:</b> " + ("ON" if autopilot else "OFF")
status += f"\n📝 <b>Applications sent:</b> {len(applications)}"
by_company: dict[str, int] = {}
for app in applications.values():
@ -264,7 +264,7 @@ class TelegramBot:
logger.error(f"Error generating errorrate plot: {e}")
import traceback
logger.error(traceback.format_exc())
await self._send_message(f" Error generating errorrate plot: {str(e)}")
await self._send_message(f"[ERROR] Error generating errorrate plot: {str(e)}")
async def _send_message(self, text: str) -> None:
@ -274,6 +274,9 @@ class TelegramBot:
logger.warning("Telegram bot token or chat ID not configured, cannot send message")
return
# Clean text: remove invalid unicode surrogates
text = text.encode('utf-8', errors='ignore').decode('utf-8')
url = f"https://api.telegram.org/bot{self.bot_token}/sendMessage"
# Split message into chunks if too long
@ -340,6 +343,9 @@ class TelegramBot:
logger.warning("Telegram bot token or chat ID not configured, cannot send photo")
return
# Clean caption: remove invalid unicode surrogates
caption = caption.encode('utf-8', errors='ignore').decode('utf-8')
url = f"https://api.telegram.org/bot{self.bot_token}/sendPhoto"
max_retries = 3
retry_delay = 1 # Initial delay in seconds