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")) # Application handler manages browser/context app_handler = ApplicationHandler(None, 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()) await app_handler.init_browser() 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") 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: if hasattr(app_handler, 'browser') and app_handler.browser: await app_handler.browser.close() logger.info("Browser closed successfully.") if __name__ == "__main__": asyncio.run(main())