import asyncio from playwright.async_api import async_playwright from application_handler import ApplicationHandler from telegram_bot import TelegramBot from handlers.wgcompany_notifier import WGCompanyNotifier import logging from logging.handlers import RotatingFileHandler import os from dotenv import load_dotenv from state_manager import StateManager from pathlib import Path # --- Environment & Logging Setup --- # Load environment variables from .env file load_dotenv() # Configure logging: file (rotating) + console for Docker visibility, enforce for all modules logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[ RotatingFileHandler("data/monitor.log", maxBytes=1 * 1024 * 1024, backupCount=5), # 1 MB per file, 5 backups logging.StreamHandler() ], force=True # Enforce for all modules, Python 3.8+ ) logger = logging.getLogger() # Use root logger for universal logging logger.info("Logging initialized: outputting to both data/monitor.log and console (Docker logs)") # Interval (seconds) between checks for new listings CHECK_INTERVAL = int(os.getenv("CHECK_INTERVAL", 300)) # Default: 300 seconds def _flush_rotating_file_handlers(): """Flush all RotatingFileHandlers attached to the root logger.""" root_logger = logging.getLogger() for handler in root_logger.handlers: if isinstance(handler, RotatingFileHandler): handler.flush() async def main(): logger.info("Starting the bot...") # Initialize state manager state_manager = StateManager(Path("data/state.json")) # --- Playwright browser/context setup --- playwright = await async_playwright().start() browser = await playwright.chromium.launch(headless=True) browser_context = await browser.new_context( user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" ) logger.info("Playwright browser context initialized.") # Application handler manages browser/context app_handler = ApplicationHandler(browser_context, state_manager) # Set up Telegram bot and inject into handler, passing the main event loop event_loop = asyncio.get_running_loop() telegram_bot = TelegramBot(app_handler, event_loop=event_loop) telegram_bot.start() # Start Telegram command listener for reactivity app_handler.set_telegram_bot(telegram_bot) # Start WGCompanyNotifier as a background task wg_notifier = WGCompanyNotifier(telegram_bot=telegram_bot, refresh_minutes=10) wg_task = asyncio.create_task(wg_notifier.run()) try: logger.info(f"Bot is now running. Refreshing every {CHECK_INTERVAL} seconds...") while True: current_listings = await app_handler.fetch_listings() if not current_listings: logger.warning("No listings fetched") await asyncio.sleep(CHECK_INTERVAL) _flush_rotating_file_handlers() continue previous_listings = app_handler.load_previous_listings() if not previous_listings: logger.info(f"First run - saving {len(current_listings)} listings as baseline and marking as failed applications") # Mark all as failed applications so /retryfailed can be used for listing in current_listings: result = { "listing_id": listing.get("id"), "company": app_handler._detect_company(listing.get("link", "")), "link": listing.get("link"), "timestamp": str(listing.get("timestamp", "")) or str(listing.get("date", "")) or "", "success": False, "message": "First run, not auto-applied. Use /retryfailed to attempt.", "address": listing.get("address", ""), "rooms": listing.get("rooms", ""), "price": listing.get("price", ""), "retries": 0 } app_handler.save_application(result) app_handler.save_listings(current_listings) await asyncio.sleep(CHECK_INTERVAL) _flush_rotating_file_handlers() continue new_listings = app_handler.find_new_listings(current_listings, previous_listings) application_results = {} if new_listings: logger.info(f"Found {len(new_listings)} new listing(s)") app_handler.log_listing_times(new_listings) if app_handler.is_autopilot_enabled(): logger.info("Autopilot enabled - applying to listings...") 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) await asyncio.sleep(CHECK_INTERVAL) _flush_rotating_file_handlers() except (KeyboardInterrupt, SystemExit): logger.info("Shutting down...") except Exception as e: logger.error(f"[MAIN] Error in main loop: {e}") finally: await browser.close() logger.info("Browser closed successfully.") if __name__ == "__main__": asyncio.run(main())