""" Professional Telegram Bot for Climate-Resilient Agriculture ============================================================ Real-time notifications, farmer dashboard, and interactive commands """ import json import logging import asyncio import os import sys from datetime import datetime, timedelta from typing import Optional, Dict, Any from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram.ext import Application, CommandHandler, CallbackQueryHandler, ContextTypes from telegram.constants import ParseMode, ChatAction from farm_controller import run_farm_dashboard from database import db # Fix Unicode on Windows (safe check for buffer attribute) if sys.platform == "win32": import io try: if hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(encoding='utf-8') if hasattr(sys.stderr, 'reconfigure'): sys.stderr.reconfigure(encoding='utf-8') except (AttributeError, io.UnsupportedOperation): if hasattr(sys.stdout, 'buffer'): sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') if hasattr(sys.stderr, 'buffer'): sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') # Use child logger instead of basicConfig to play nice with app.py logger = logging.getLogger("TelegramBot") # Configuration TOKEN: str = os.getenv("TELEGRAM_BOT_TOKEN", "8589326773:AAERc6eyATYmb8-Dr9yttiDKK9LJGa47-0M") if os.getenv("TELEGRAM_BOT_TOKEN"): logger.info("✅ TELEGRAM_BOT_TOKEN loaded from environment variable") else: logger.warning("⚠️ TELEGRAM_BOT_TOKEN not in env, using hardcoded fallback token") # API URLs - Works both locally AND in Docker # In Docker: services communicate via localhost (same container) # In HF Spaces: main app runs on port 7860, services integrated internally WEATHER_API_URL = os.getenv("WEATHER_API_URL", "http://localhost:8001/weather") PEST_API_URL = os.getenv("PEST_API_URL", "http://localhost:8000/api/predict") WATER_API_URL = os.getenv("WATER_API_URL", "http://localhost:8002/predict") # ════════════════════════════════════════════════════════════════════════════ # PERFORMANCE: CACHING LAYER # ════════════════════════════════════════════════════════════════════════════ class DashboardCache: """Fast in-memory cache with TTL (Time-To-Live)""" def __init__(self, ttl_seconds: int = 120): # 2-minute cache self.data: Dict[str, Any] = {} self.timestamp: Dict[str, datetime] = {} self.ttl = ttl_seconds def get(self) -> Optional[Dict[str, Any]]: """Get cached dashboard data if available and not expired""" if "dashboard" in self.data: age = (datetime.now() - self.timestamp["dashboard"]).total_seconds() if age < self.ttl: logger.debug(f"✅ Cache HIT (age: {age:.1f}s)") return self.data["dashboard"] return None def set(self, data: Dict[str, Any]) -> None: """Cache dashboard data with timestamp""" self.data["dashboard"] = data self.timestamp["dashboard"] = datetime.now() logger.debug("💾 Dashboard cached") def clear(self) -> None: """Clear cache""" self.data.clear() self.timestamp.clear() dashboard_cache = DashboardCache(ttl_seconds=120) async def get_dashboard_data() -> Dict[str, Any]: """Get dashboard data with intelligent caching""" # Try cache first (50x faster than API call) cached = dashboard_cache.get() if cached: return cached # If cache miss, fetch fresh data try: dashboard_data = await run_farm_dashboard() dashboard_cache.set(dashboard_data) return dashboard_data except Exception as e: logger.error(f"Dashboard fetch error: {e}") return {} # ════════════════════════════════════════════════════════════════════════════ # STARTUP HANDLERS # ════════════════════════════════════════════════════════════════════════════ async def start(update: Update, context: ContextTypes.DEFAULT_TYPE): """Bot startup - Show main dashboard (OPTIMIZED)""" user = update.effective_user chat_id = update.effective_chat.id # Save user subscription (non-blocking) try: with open("user.json", "r") as f: farmer = json.load(f) # Fire and forget (don't await) asyncio.create_task(asyncio.to_thread( db.save_subscription, farmer.get("farmer_id", "DEFAULT"), str(chat_id) )) except: pass # Get cached or fresh dashboard data dashboard_data = await get_dashboard_data() farmer_profile = dashboard_data.get("farmer_profile", {}) weather_intel = dashboard_data.get("weather_intelligence", {}) alerts = dashboard_data.get("alerts", []) # Calculate risk level (fast) risk_score = 0 for alert in alerts: if alert.get("severity") == "CRITICAL": risk_score += 35 elif alert.get("severity") == "HIGH": risk_score += 20 elif alert.get("severity") == "MEDIUM": risk_score += 10 risk_score = min(100, risk_score) risk_level = "🟢 STABLE" if risk_score < 30 else "🟡 ALERT" if risk_score < 70 else "🔴 CRITICAL" # Main Dashboard Message welcome_text = f""" 🌾 CLIMATE-RESILIENT FARM DASHBOARD 🌾 {'═' * 50} 👨‍🌾 Farmer: {farmer_profile.get('name', 'Unknown')} 📍 Location: {farmer_profile.get('village', 'N/A')} 🌱 Crop: {farmer_profile.get('crop_type', 'N/A')} | Season: {farmer_profile.get('season', 'N/A')} 📐 Farm Size: {farmer_profile.get('farm_size_hectares', 1.0)} Ha {'─' * 50} 📊 CLIMATE RESILIENCE INDEX: {risk_score:.0f}/100 Status: {risk_level} Critical Alerts: {len([a for a in alerts if a.get('severity') == 'CRITICAL'])} High Priority: {len([a for a in alerts if a.get('severity') == 'HIGH'])} {'─' * 50} 🌡️ CURRENT CONDITIONS: Temp: {weather_intel.get('current', {}).get('temp', 'N/A')}°C | Humidity: {weather_intel.get('current', {}).get('humidity', 'N/A')}% Wind: {weather_intel.get('current', {}).get('wind', 'N/A')} km/h Condition: {weather_intel.get('current', {}).get('desc', 'N/A')} {'─' * 50} Select an option below for detailed information: """ keyboard = [ [ InlineKeyboardButton("🔴 CRITICAL ALERTS", callback_data="critical_alerts"), InlineKeyboardButton("🌦️ WEATHER INTEL", callback_data="weather_detail") ], [ InlineKeyboardButton("🐛 PEST MONITORING", callback_data="pest_detail"), InlineKeyboardButton("💧 WATER MANAGEMENT", callback_data="water_detail") ], [ InlineKeyboardButton("📈 RESILIENCE SCORE", callback_data="resilience_score"), InlineKeyboardButton("🌍 SUSTAINABILITY", callback_data="sustainability") ], [ InlineKeyboardButton("⚙️ SETTINGS", callback_data="settings"), InlineKeyboardButton("📞 SUPPORT", callback_data="support") ] ] await update.message.reply_html( welcome_text, reply_markup=InlineKeyboardMarkup(keyboard) ) logger.info(f"✅ Dashboard sent to {user.first_name} (Chat ID: {chat_id})") # ════════════════════════════════════════════════════════════════════════════ # CALLBACK HANDLERS # ════════════════════════════════════════════════════════════════════════════ async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): """Handle inline button clicks (OPTIMIZED - no API calls)""" query = update.callback_query await query.answer() # Acknowledge immediately action = query.data # Get cached dashboard data (no API calls!) dashboard_data = await get_dashboard_data() weather_intel = dashboard_data.get("weather_intelligence", {}) pest_intel = dashboard_data.get("pest_intelligence", {}) water_intel = dashboard_data.get("water_intelligence", {}) alerts = dashboard_data.get("alerts", []) # ──────────────────────────────────────────────────────────────────────── if action == "critical_alerts": critical_alerts: list[dict] = [a for a in alerts if a.get("severity") in ["CRITICAL", "HIGH"]] if not critical_alerts: text = "✅ NO CRITICAL ALERTS\n\nFarm conditions are stable. Continue monitoring." else: text = "🔴 CRITICAL & HIGH PRIORITY ALERTS\n\n" for idx, alert in enumerate(critical_alerts[:5], 1): severity_icon = "🔴" if alert.get("severity") == "CRITICAL" else "🟠" text += f"\n{severity_icon} {alert.get('title', 'Alert')} (ID: {alert.get('id', 'N/A')})\n" text += f"Message: {alert.get('message', 'N/A')}\n" if alert.get("action_items"): text += "Action Items:\n" for item in alert.get("action_items", [])[:3]: text += f" • {item}\n" text += "\n" keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data="back_main")]] await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ──────────────────────────────────────────────────────────────────────── elif action == "weather_detail": current = weather_intel.get("current", {}) forecast = weather_intel.get("forecast", []) text = f""" 🌦️ DETAILED WEATHER INTELLIGENCE 🌡️ CURRENT CONDITIONS: Temperature: {current.get('temp', 'N/A')}°C Humidity: {current.get('humidity', 'N/A')}% Wind Speed: {current.get('wind', 'N/A')} km/h Condition: {current.get('desc', 'N/A')} Last Updated: {current.get('updated', 'N/A')} 📅 7-DAY FORECAST: """ for day in forecast[:7]: text += f"\n{day.get('day_name', 'N/A')}: " text += f"{day.get('temp_min', 'N/A')}°C - {day.get('temp_max', 'N/A')}°C | " text += f"{day.get('description', 'N/A')}" text += f"\n\n🎯 RECOMMENDATION:\n" text += f"{weather_intel.get('ai_recommendations', 'Monitor weather conditions regularly.')}" keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data="back_main")]] await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ──────────────────────────────────────────────────────────────────────── elif action == "pest_detail": pest_table = pest_intel.get("pest_prediction_table", []) text = "🐛 PEST & DISEASE MONITORING\n\n" if not pest_table: text += "✅ No significant pests predicted\n\nContinue preventive measures." else: for pest in pest_table[:5]: severity_icon = "🔴" if pest.get("severity", "").upper() == "CRITICAL" else "🟡" if pest.get("severity", "").upper() == "HIGH" else "🟢" text += f"{severity_icon} {pest.get('pest_name', 'Unknown')}\n" text += f" Severity: {pest.get('severity', 'N/A')}\n" text += f" Confidence: {pest.get('confidence', 'N/A')}%\n" text += f" Action: {pest.get('action', 'Monitor crop regularly')}\n\n" keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data="back_main")]] await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ──────────────────────────────────────────────────────────────────────── elif action == "water_detail": water = water_intel.get("water_m3_sqm", 0) irrigation_mins = water_intel.get("irrigation_minutes", 0) text = f""" 💧 IRRIGATION & WATER MANAGEMENT 📊 WATER REQUIREMENTS: Water Needed: {water:.4f} m³/m² Irrigation Duration: {irrigation_mins:.1f} minutes Weather Condition: {water_intel.get('weather', {}).get('cond', 'N/A')} Temperature: {water_intel.get('weather', {}).get('temp', 'N/A')}°C 💡 RECOMMENDATIONS: • Irrigate using drip irrigation for 50% water savings • Best time: 5-7 AM or 6-8 PM (minimal evaporation) • Apply mulch to reduce water loss by 30-40% • Monitor soil moisture with meter 🌱 OPTIMAL SCHEDULE: • Morning irrigation: 5-7 AM • Evening irrigation: 6-8 PM • Avoid midday (high evaporation) """ keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data="back_main")]] await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ──────────────────────────────────────────────────────────────────────── elif action == "resilience_score": critical_days = weather_intel.get("critical_days", []) warning_days = weather_intel.get("warning_days", []) risk_score = len(critical_days) * 25 + len(warning_days) * 15 risk_score = min(100, risk_score) resilience_level = "🟢 GREEN" if risk_score < 30 else "🟡 YELLOW" if risk_score < 70 else "🔴 RED" text = f""" 📈 CLIMATE RESILIENCE INDEX: {risk_score:.0f}/100 Status: {resilience_level} 🔍 RISK ANALYSIS: Critical Weather Days: {len(critical_days)} Warning Days: {len(warning_days)} 🛡️ RESILIENCE STRATEGIES: 1. Diversify Crops: Reduces single pest risk by 40% 2. Mulching: Reduces water loss 30-40% 3. Intercropping: Fixes nitrogen naturally 4. Windbreaks: Protects against extreme wind 5. Drip Irrigation: Saves 50% water vs. flood irrigation 6. Soil Testing: Optimize fertilizer use 📊 NEXT ACTIONS: • Implement at least 2 strategies this month • Monitor soil health monthly • Update crop rotation annually """ keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data="back_main")]] await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ──────────────────────────────────────────────────────────────────────── elif action == "sustainability": text = """ 🌍 SUSTAINABLE AGRICULTURE PRACTICES 💧 WATER CONSERVATION: ✓ Mulching: Reduce evaporation 30-40% ✓ Drip Irrigation: Save 50% water vs. flood ✓ Rainwater Harvesting: Capture monsoon excess ✓ Early morning irrigation: Minimize evaporation 🌱 SOIL HEALTH: ✓ Intercropping with legumes: Fix nitrogen naturally ✓ Crop rotation: Prevent soil depletion ✓ Compost: Make from crop residue (don't burn) ✓ No-till farming: Preserve soil structure 🐛 PEST MANAGEMENT: ✓ Beneficial insects: Ladybugs, parasitoid wasps ✓ Biopesticides: Neem oil over synthetic chemicals ✓ Crop monitoring: Scout weekly for pests ✓ Companion planting: Marigolds, mint deter pests ♻️ CIRCULAR BIOECONOMY: ✓ Crop residue composting ✓ Biogas from farm waste ✓ Integrated livestock farming ✓ On-farm seed saving 🔋 RENEWABLE ENERGY: ✓ Solar pumps: 0 fuel cost, reliable ✓ Biogas generators: Waste → Energy ✓ Wind power: If available in region 💰 ECONOMIC BENEFITS: • 30-40% reduction in input costs • 15-20% yield increase with soil health • Premium market prices for organic/sustainable • Government subsidies for green agriculture """ keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data="back_main")]] await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ──────────────────────────────────────────────────────────────────────── elif action == "settings": text = """ ⚙️ SETTINGS & PREFERENCES 📱 CURRENT CONFIGURATION: • Bot Status: ACTIVE • Alert Frequency: REAL-TIME • Weather Alerts: ENABLED • Pest Alerts: ENABLED • Water Alerts: ENABLED 📝 CUSTOMIZE (Coming Soon): • Set alert thresholds • Choose notification times • Select alert types • Language preference 💡 TIPS: • Check dashboard every morning • Respond to critical alerts within 1 hour • Keep your profile updated • Share feedback to improve alerts """ keyboard = [ [InlineKeyboardButton("🔔 Alert Settings", callback_data="alert_settings")], [InlineKeyboardButton("📍 Location", callback_data="location_settings")], [InlineKeyboardButton("⬅️ Back", callback_data="back_main")] ] await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ──────────────────────────────────────────────────────────────────────── elif action == "support": text = """ 📞 SUPPORT & RESOURCES ❓ FREQUENTLY ASKED QUESTIONS: Q: When should I irrigate? A: Early morning (5-7 AM) or evening (6-8 PM) when evaporation is low. Q: How do I reduce water usage? A: Use drip irrigation, mulching, and monitor rainfall to skip irrigation. Q: What does High Pest Risk mean? A: Conditions are favorable for pest reproduction. Scout crops and spray if needed. Q: How often should I update my profile? A: Update crop age monthly and location if you change fields. 📚 USEFUL RESOURCES: • Agritech Documentation • Pest Management Guidelines • Sustainable Farming Manual • Government Agricultural Schemes 🎯 EMERGENCY CONTACTS: • Agricultural Officer: +91-XX-XXXXX • Extension Services: AGRI-HELPLINE • Pest Control Hotline: PEST-HOTLINE 💬 FEEDBACK: Have suggestions? Reply to any message with "feedback" """ keyboard = [[InlineKeyboardButton("⬅️ Back", callback_data="back_main")]] await query.edit_message_text(text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ──────────────────────────────────────────────────────────────────────── elif action == "back_main": # Go back to main dashboard (for callback query, not message) dashboard_data = await get_dashboard_data() farmer_profile = dashboard_data.get("farmer_profile", {}) weather_intel = dashboard_data.get("weather_intelligence", {}) alerts = dashboard_data.get("alerts", []) risk_score = 0 for alert in alerts: if alert.get("severity") == "CRITICAL": risk_score += 35 elif alert.get("severity") == "HIGH": risk_score += 20 elif alert.get("severity") == "MEDIUM": risk_score += 10 risk_score = min(100, risk_score) risk_level = "🟢 STABLE" if risk_score < 30 else "🟡 ALERT" if risk_score < 70 else "🔴 CRITICAL" welcome_text = f""" 🌾 CLIMATE-RESILIENT FARM DASHBOARD 🌾 {'═' * 50} 👨‍🌾 Farmer: {farmer_profile.get('name', 'Unknown')} 📍 Location: {farmer_profile.get('village', 'N/A')} 🌱 Crop: {farmer_profile.get('crop_type', 'N/A')} | Season: {farmer_profile.get('season', 'N/A')} 📐 Farm Size: {farmer_profile.get('farm_size_hectares', 1.0)} Ha {'─' * 50} 📊 CLIMATE RESILIENCE INDEX: {risk_score:.0f}/100 Status: {risk_level} Critical Alerts: {len([a for a in alerts if a.get('severity') == 'CRITICAL'])} High Priority: {len([a for a in alerts if a.get('severity') == 'HIGH'])} {'─' * 50} 🌡️ CURRENT CONDITIONS: Temp: {weather_intel.get('current', {}).get('temp', 'N/A')}°C | Humidity: {weather_intel.get('current', {}).get('humidity', 'N/A')}% Wind: {weather_intel.get('current', {}).get('wind', 'N/A')} km/h Condition: {weather_intel.get('current', {}).get('desc', 'N/A')} {'─' * 50} Select an option below for detailed information: """ keyboard = [ [ InlineKeyboardButton("🔴 CRITICAL ALERTS", callback_data="critical_alerts"), InlineKeyboardButton("🌦️ WEATHER INTEL", callback_data="weather_detail") ], [ InlineKeyboardButton("🐛 PEST MONITORING", callback_data="pest_detail"), InlineKeyboardButton("💧 WATER MANAGEMENT", callback_data="water_detail") ], [ InlineKeyboardButton("📈 RESILIENCE SCORE", callback_data="resilience_score"), InlineKeyboardButton("🌍 SUSTAINABILITY", callback_data="sustainability") ], [ InlineKeyboardButton("⚙️ SETTINGS", callback_data="settings"), InlineKeyboardButton("📞 SUPPORT", callback_data="support") ] ] await query.edit_message_text(welcome_text, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup(keyboard)) # ════════════════════════════════════════════════════════════════════════════ # UTILITY COMMANDS # ════════════════════════════════════════════════════════════════════════════ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Show help information""" help_text = """ 🤖 BOT COMMANDS: /start - Show main dashboard /help - Show this help message /alerts - Get current alerts /weather - Quick weather check /water - Irrigation status /pest - Pest monitoring /farm - Farm profile /feedback - Send feedback 💡 TIPS: • Use inline buttons for quick navigation • Check alerts at least twice daily • Act on CRITICAL alerts within 1 hour • Share this bot with neighboring farmers """ await update.message.reply_html(help_text) async def alerts_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Quick alerts summary (OPTIMIZED)""" dashboard_data = await get_dashboard_data() alerts = dashboard_data.get("alerts", []) if not alerts: await update.message.reply_text("✅ No alerts at this time!") return text = "🚨 CURRENT ALERTS SUMMARY:\n\n" for alert in alerts[:10]: severity = alert.get("severity", "INFO") icon = "🔴" if severity == "CRITICAL" else "🟠" if severity == "HIGH" else "🟡" if severity == "MEDIUM" else "ℹ️" text += f"{icon} {alert.get('title', 'Alert')}\n" text += f" {alert.get('message', '')}\n\n" await update.message.reply_html(text) async def feedback_command(update: Update, context: ContextTypes.DEFAULT_TYPE): """Feedback handler""" await update.message.reply_text( "📝 FEEDBACK RECEIVED!\n\n" "Thank you for your feedback. Our team will review and improve the bot.\n\n" "👉 Reply with your suggestions and we'll implement them!", parse_mode=ParseMode.HTML ) # ════════════════════════════════════════════════════════════════════════════ # BACKGROUND JOBS # ════════════════════════════════════════════════════════════════════════════ async def send_daily_alerts(context: ContextTypes.DEFAULT_TYPE): """Send daily alerts to all subscribed farmers (OPTIMIZED)""" subscriptions = db.get_active_subscriptions() dashboard_data = await get_dashboard_data() # Single fetch for all critical_count = len([a for a in dashboard_data.get("alerts", []) if a.get("severity") == "CRITICAL"]) for sub in subscriptions: chat_id = sub.get("telegram_chat_id") try: # Send only if there are critical alerts if critical_count > 0: await context.bot.send_message( chat_id, f"🚨 CRITICAL ALERT DETECTED!\n\n" f"You have {critical_count} critical alerts.\n" f"Use /start to view your dashboard immediately.", parse_mode=ParseMode.HTML ) else: await context.bot.send_message( chat_id, "✅ FARM STATUS NORMAL\n\n" "All conditions are within safe parameters..\n" "Check dashboard with /start for full details.", parse_mode=ParseMode.HTML ) except Exception as e: logger.debug(f"Alert delivery to {chat_id}: {e}") # ════════════════════════════════════════════════════════════════════════════ # MAIN ENTRY POINT # ════════════════════════════════════════════════════════════════════════════ def main(): """Start the bot (OPTIMIZED)""" app = Application.builder().token(TOKEN).build() # Handlers app.add_handler(CommandHandler("start", start)) app.add_handler(CommandHandler("help", help_command)) app.add_handler(CommandHandler("alerts", alerts_command)) app.add_handler(CommandHandler("feedback", feedback_command)) app.add_handler(CallbackQueryHandler(button_handler)) # Jobs job_queue = app.job_queue job_queue.run_daily(send_daily_alerts, time=datetime.strptime("06:00", "%H:%M").time(), name="daily_alerts") # Periodic cache refresh (every 2 minutes) - keeps data fresh async def refresh_cache(context): dashboard_cache.clear() await get_dashboard_data() logger.debug("🔄 Cache refreshed") job_queue.run_repeating(refresh_cache, interval=120, first=60, name="cache_refresh") logger.info("🤖 Climate-Resilient Agriculture Bot Started!") logger.info(f"✅ Dashboard caching enabled (2-min TTL)") logger.info(f"✅ Auto-cache refresh every 2 minutes") safe_token = str(TOKEN)[:20] if TOKEN else "MISSING" logger.info(f"Token: {safe_token}...") # Use allowed_updates to reduce unnecessary processing # Note: When running in Docker with threading, signal handlers may not work try: app.run_polling(allowed_updates=["message", "callback_query"], close_loop=False) except ValueError as e: if "add_signal_handler()" in str(e): logger.warning("⚠️ Running in non-main thread (Docker), using fallback polling") # Fallback for non-main thread execution import asyncio try: loop = asyncio.get_running_loop() except RuntimeError: loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) retry_count = 0 max_retries = 3 # Reduced from 5 to 3 for faster feedback while retry_count < max_retries: try: loop.run_until_complete(app.initialize()) loop.run_until_complete(app.start()) logger.info("🤖 Bot polling started (non-signal mode)") try: loop.run_until_complete(app.updater.start_polling(allowed_updates=["message", "callback_query"])) except KeyboardInterrupt: pass finally: loop.run_until_complete(app.stop()) break except Exception as init_error: retry_count += 1 if retry_count >= max_retries: error_type = type(init_error).__name__ logger.error(f"Bot initialization failed ({error_type})") logger.info("This is expected if there's no internet/network access.") logger.info("REST APIs will continue working without the Telegram bot.") raise # Re-raise so app.py can catch it wait_time = min(2 ** retry_count, 16) # Exponential backoff logger.warning(f"⚠️ Bot init failed (attempt {retry_count}/{max_retries}), retrying in {wait_time}s...") import time time.sleep(wait_time) else: raise if __name__ == "__main__": main()