import asyncio import logging import signal import os from dotenv import load_dotenv from db.database import init_db, close_db_pool from db.repository import Repository from core.metrics import start_metrics_server from core.llm import LLMClient from core.queue import RedisQueue from services.ai_processor import AIProcessor from services.collector import CollectorService from services.admin_bot import AdminBotService from services.publisher import PublisherService from services.website_analyzer import WebsiteAnalyzer from services.website_collector import WebsiteCollectorService # Load environment variables load_dotenv() # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s" ) logger = logging.getLogger("copykar.main") async def main(): logger.info("Starting Copykar System with Immediate Admin Review & Paced Target Queues...") # 1. Start Prometheus metrics server metrics_port = int(os.getenv("METRICS_PORT", "8008")) start_metrics_server(metrics_port) # 2. Initialize Database schema & Redis Queue await init_db() logger.info("Database schema initialized.") redis_queue = RedisQueue() await redis_queue.connect() repo = Repository() await repo.ensure_default_providers() # 3. Create Services admin_bot = AdminBotService(repo=repo, queue=redis_queue) llm = LLMClient( repo=repo, on_fallback_alert=admin_bot.on_ai_fallback_alert, on_chain_failure_alert=admin_bot.on_ai_chain_failure_alert ) ai_processor = AIProcessor(repo=repo, llm=llm) admin_bot.set_ai_processor(ai_processor) website_analyzer = WebsiteAnalyzer(llm=llm) website_collector = WebsiteCollectorService( repo=repo, analyzer=website_analyzer, ai_processor=ai_processor, on_post_received=admin_bot.handle_collected_post, on_error_alert=admin_bot.on_website_error_alert ) admin_bot.set_website_services(website_analyzer, website_collector) collector = CollectorService(repo=repo, on_post_received=admin_bot.handle_collected_post, ai_processor=ai_processor) admin_bot.set_collector(collector) # Publisher handles per-target delivery queues, intervals, and sleep windows. # Delivery goes out over the bot account so target channels only need to grant # posting rights to the bot, never to the personal userbot account. publisher = PublisherService( repo=repo, queue=redis_queue, client=admin_bot.client, notify_fn=admin_bot.notify_admins, ) # 4. Start all services await admin_bot.start() await collector.start(notify_fn=admin_bot.notify_admins) await website_collector.start() await publisher.start() logger.info("All Copykar services are active and running.") try: await admin_bot.broadcast_change_notes() except Exception as e: logger.debug(f"Could not broadcast change notes on startup: {e}") # Graceful shutdown event stop_event = asyncio.Event() loop = asyncio.get_running_loop() for sig in (signal.SIGINT, signal.SIGTERM): try: loop.add_signal_handler(sig, stop_event.set) except NotImplementedError: pass try: await stop_event.wait() except (asyncio.CancelledError, KeyboardInterrupt): pass finally: logger.info("Shutting down Copykar services...") await website_collector.stop() await collector.stop() await publisher.stop() await admin_bot.stop() await redis_queue.close() await close_db_pool() logger.info("Copykar cleanly shut down.") if __name__ == "__main__": asyncio.run(main())