"""
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()