| """
|
| 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
|
|
|
|
|
| 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')
|
|
|
|
|
| logger = logging.getLogger("TelegramBot")
|
|
|
|
|
| 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")
|
|
|
|
|
|
|
|
|
| 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")
|
|
|
|
|
|
|
|
|
|
|
| class DashboardCache:
|
| """Fast in-memory cache with TTL (Time-To-Live)"""
|
| def __init__(self, ttl_seconds: int = 120):
|
| 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"""
|
|
|
| cached = dashboard_cache.get()
|
| if cached:
|
| return cached
|
|
|
|
|
| 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 {}
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
| try:
|
| with open("user.json", "r") as f:
|
| farmer = json.load(f)
|
|
|
| asyncio.create_task(asyncio.to_thread(
|
| db.save_subscription,
|
| farmer.get("farmer_id", "DEFAULT"),
|
| str(chat_id)
|
| ))
|
| except:
|
| pass
|
|
|
|
|
| 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"""
|
| πΎ <b>CLIMATE-RESILIENT FARM DASHBOARD</b> πΎ
|
| {'β' * 50}
|
|
|
| π¨βπΎ <b>Farmer:</b> {farmer_profile.get('name', 'Unknown')}
|
| π <b>Location:</b> {farmer_profile.get('village', 'N/A')}
|
| π± <b>Crop:</b> {farmer_profile.get('crop_type', 'N/A')} | <b>Season:</b> {farmer_profile.get('season', 'N/A')}
|
| π <b>Farm Size:</b> {farmer_profile.get('farm_size_hectares', 1.0)} Ha
|
|
|
| {'β' * 50}
|
| π <b>CLIMATE RESILIENCE INDEX: {risk_score:.0f}/100</b>
|
| Status: <b>{risk_level}</b>
|
| Critical Alerts: <b>{len([a for a in alerts if a.get('severity') == 'CRITICAL'])}</b>
|
| High Priority: <b>{len([a for a in alerts if a.get('severity') == 'HIGH'])}</b>
|
|
|
| {'β' * 50}
|
| π‘οΈ <b>CURRENT CONDITIONS:</b>
|
| Temp: <b>{weather_intel.get('current', {}).get('temp', 'N/A')}Β°C</b> | Humidity: <b>{weather_intel.get('current', {}).get('humidity', 'N/A')}%</b>
|
| Wind: <b>{weather_intel.get('current', {}).get('wind', 'N/A')} km/h</b>
|
| Condition: <b>{weather_intel.get('current', {}).get('desc', 'N/A')}</b>
|
|
|
| {'β' * 50}
|
|
|
| <i>Select an option below for detailed information:</i>
|
| """
|
|
|
| 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})")
|
|
|
|
|
|
|
|
|
|
|
| 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()
|
|
|
| action = query.data
|
|
|
|
|
| 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 = "β
<b>NO CRITICAL ALERTS</b>\n\nFarm conditions are stable. Continue monitoring."
|
| else:
|
| text = "π΄ <b>CRITICAL & HIGH PRIORITY ALERTS</b>\n\n"
|
| for idx, alert in enumerate(critical_alerts[:5], 1):
|
| severity_icon = "π΄" if alert.get("severity") == "CRITICAL" else "π "
|
| text += f"\n{severity_icon} <b>{alert.get('title', 'Alert')} (ID: {alert.get('id', 'N/A')})</b>\n"
|
| text += f"Message: {alert.get('message', 'N/A')}\n"
|
|
|
| if alert.get("action_items"):
|
| text += "<b>Action Items:</b>\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"""
|
| π¦οΈ <b>DETAILED WEATHER INTELLIGENCE</b>
|
|
|
| <b>π‘οΈ CURRENT CONDITIONS:</b>
|
| Temperature: <b>{current.get('temp', 'N/A')}Β°C</b>
|
| Humidity: <b>{current.get('humidity', 'N/A')}%</b>
|
| Wind Speed: <b>{current.get('wind', 'N/A')} km/h</b>
|
| Condition: <b>{current.get('desc', 'N/A')}</b>
|
| Last Updated: <b>{current.get('updated', 'N/A')}</b>
|
|
|
| <b>π
7-DAY FORECAST:</b>
|
| """
|
| for day in forecast[:7]:
|
| text += f"\n<b>{day.get('day_name', 'N/A')}:</b> "
|
| 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<b>π― RECOMMENDATION:</b>\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 = "π <b>PEST & DISEASE MONITORING</b>\n\n"
|
|
|
| if not pest_table:
|
| text += "β
<b>No significant pests predicted</b>\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} <b>{pest.get('pest_name', 'Unknown')}</b>\n"
|
| text += f" Severity: <b>{pest.get('severity', 'N/A')}</b>\n"
|
| text += f" Confidence: <b>{pest.get('confidence', 'N/A')}%</b>\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"""
|
| π§ <b>IRRIGATION & WATER MANAGEMENT</b>
|
|
|
| <b>π WATER REQUIREMENTS:</b>
|
| Water Needed: <b>{water:.4f} mΒ³/mΒ²</b>
|
| Irrigation Duration: <b>{irrigation_mins:.1f} minutes</b>
|
| Weather Condition: <b>{water_intel.get('weather', {}).get('cond', 'N/A')}</b>
|
| Temperature: <b>{water_intel.get('weather', {}).get('temp', 'N/A')}Β°C</b>
|
|
|
| <b>π‘ RECOMMENDATIONS:</b>
|
| β’ Irrigate using <b>drip irrigation</b> for 50% water savings
|
| β’ Best time: <b>5-7 AM</b> or <b>6-8 PM</b> (minimal evaporation)
|
| β’ Apply mulch to reduce water loss by 30-40%
|
| β’ Monitor soil moisture with meter
|
|
|
| <b>π± OPTIMAL SCHEDULE:</b>
|
| β’ 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"""
|
| π <b>CLIMATE RESILIENCE INDEX: {risk_score:.0f}/100</b>
|
| Status: <b>{resilience_level}</b>
|
|
|
| <b>π RISK ANALYSIS:</b>
|
| Critical Weather Days: <b>{len(critical_days)}</b>
|
| Warning Days: <b>{len(warning_days)}</b>
|
|
|
| <b>π‘οΈ RESILIENCE STRATEGIES:</b>
|
| 1. <b>Diversify Crops:</b> Reduces single pest risk by 40%
|
| 2. <b>Mulching:</b> Reduces water loss 30-40%
|
| 3. <b>Intercropping:</b> Fixes nitrogen naturally
|
| 4. <b>Windbreaks:</b> Protects against extreme wind
|
| 5. <b>Drip Irrigation:</b> Saves 50% water vs. flood irrigation
|
| 6. <b>Soil Testing:</b> Optimize fertilizer use
|
|
|
| <b>π NEXT ACTIONS:</b>
|
| β’ 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 = """
|
| π <b>SUSTAINABLE AGRICULTURE PRACTICES</b>
|
|
|
| <b>π§ WATER CONSERVATION:</b>
|
| β Mulching: Reduce evaporation 30-40%
|
| β Drip Irrigation: Save 50% water vs. flood
|
| β Rainwater Harvesting: Capture monsoon excess
|
| β Early morning irrigation: Minimize evaporation
|
|
|
| <b>π± SOIL HEALTH:</b>
|
| β 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
|
|
|
| <b>π PEST MANAGEMENT:</b>
|
| β Beneficial insects: Ladybugs, parasitoid wasps
|
| β Biopesticides: Neem oil over synthetic chemicals
|
| β Crop monitoring: Scout weekly for pests
|
| β Companion planting: Marigolds, mint deter pests
|
|
|
| <b>β»οΈ CIRCULAR BIOECONOMY:</b>
|
| β Crop residue composting
|
| β Biogas from farm waste
|
| β Integrated livestock farming
|
| β On-farm seed saving
|
|
|
| <b>π RENEWABLE ENERGY:</b>
|
| β Solar pumps: 0 fuel cost, reliable
|
| β Biogas generators: Waste β Energy
|
| β Wind power: If available in region
|
|
|
| <b>π° ECONOMIC BENEFITS:</b>
|
| β’ 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 = """
|
| βοΈ <b>SETTINGS & PREFERENCES</b>
|
|
|
| π± <b>CURRENT CONFIGURATION:</b>
|
| β’ Bot Status: <b>ACTIVE</b>
|
| β’ Alert Frequency: <b>REAL-TIME</b>
|
| β’ Weather Alerts: <b>ENABLED</b>
|
| β’ Pest Alerts: <b>ENABLED</b>
|
| β’ Water Alerts: <b>ENABLED</b>
|
|
|
| <b>π CUSTOMIZE (Coming Soon):</b>
|
| β’ Set alert thresholds
|
| β’ Choose notification times
|
| β’ Select alert types
|
| β’ Language preference
|
|
|
| <b>π‘ TIPS:</b>
|
| β’ 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 = """
|
| π <b>SUPPORT & RESOURCES</b>
|
|
|
| <b>β FREQUENTLY ASKED QUESTIONS:</b>
|
|
|
| <b>Q: When should I irrigate?</b>
|
| A: Early morning (5-7 AM) or evening (6-8 PM) when evaporation is low.
|
|
|
| <b>Q: How do I reduce water usage?</b>
|
| A: Use drip irrigation, mulching, and monitor rainfall to skip irrigation.
|
|
|
| <b>Q: What does High Pest Risk mean?</b>
|
| A: Conditions are favorable for pest reproduction. Scout crops and spray if needed.
|
|
|
| <b>Q: How often should I update my profile?</b>
|
| A: Update crop age monthly and location if you change fields.
|
|
|
| <b>π USEFUL RESOURCES:</b>
|
| β’ Agritech Documentation
|
| β’ Pest Management Guidelines
|
| β’ Sustainable Farming Manual
|
| β’ Government Agricultural Schemes
|
|
|
| <b>π― EMERGENCY CONTACTS:</b>
|
| β’ Agricultural Officer: +91-XX-XXXXX
|
| β’ Extension Services: AGRI-HELPLINE
|
| β’ Pest Control Hotline: PEST-HOTLINE
|
|
|
| <b>π¬ FEEDBACK:</b>
|
| 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":
|
|
|
| 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"""
|
| πΎ <b>CLIMATE-RESILIENT FARM DASHBOARD</b> πΎ
|
| {'β' * 50}
|
|
|
| π¨βπΎ <b>Farmer:</b> {farmer_profile.get('name', 'Unknown')}
|
| π <b>Location:</b> {farmer_profile.get('village', 'N/A')}
|
| π± <b>Crop:</b> {farmer_profile.get('crop_type', 'N/A')} | <b>Season:</b> {farmer_profile.get('season', 'N/A')}
|
| π <b>Farm Size:</b> {farmer_profile.get('farm_size_hectares', 1.0)} Ha
|
|
|
| {'β' * 50}
|
| π <b>CLIMATE RESILIENCE INDEX: {risk_score:.0f}/100</b>
|
| Status: <b>{risk_level}</b>
|
| Critical Alerts: <b>{len([a for a in alerts if a.get('severity') == 'CRITICAL'])}</b>
|
| High Priority: <b>{len([a for a in alerts if a.get('severity') == 'HIGH'])}</b>
|
|
|
| {'β' * 50}
|
| π‘οΈ <b>CURRENT CONDITIONS:</b>
|
| Temp: <b>{weather_intel.get('current', {}).get('temp', 'N/A')}Β°C</b> | Humidity: <b>{weather_intel.get('current', {}).get('humidity', 'N/A')}%</b>
|
| Wind: <b>{weather_intel.get('current', {}).get('wind', 'N/A')} km/h</b>
|
| Condition: <b>{weather_intel.get('current', {}).get('desc', 'N/A')}</b>
|
|
|
| {'β' * 50}
|
|
|
| <i>Select an option below for detailed information:</i>
|
| """
|
|
|
| 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))
|
|
|
|
|
|
|
|
|
|
|
| async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
| """Show help information"""
|
| help_text = """
|
| π€ <b>BOT COMMANDS:</b>
|
|
|
| /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
|
|
|
| <b>π‘ TIPS:</b>
|
| β’ 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 = "π¨ <b>CURRENT ALERTS SUMMARY:</b>\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} <b>{alert.get('title', 'Alert')}</b>\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(
|
| "π <b>FEEDBACK RECEIVED!</b>\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
|
| )
|
|
|
|
|
|
|
|
|
|
|
| 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()
|
|
|
| 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:
|
|
|
| if critical_count > 0:
|
| await context.bot.send_message(
|
| chat_id,
|
| f"π¨ <b>CRITICAL ALERT DETECTED!</b>\n\n"
|
| f"You have <b>{critical_count}</b> critical alerts.\n"
|
| f"Use /start to view your dashboard immediately.",
|
| parse_mode=ParseMode.HTML
|
| )
|
| else:
|
| await context.bot.send_message(
|
| chat_id,
|
| "β
<b>FARM STATUS NORMAL</b>\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}")
|
|
|
|
|
|
|
|
|
|
|
| def main():
|
| """Start the bot (OPTIMIZED)"""
|
| app = Application.builder().token(TOKEN).build()
|
|
|
|
|
| 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))
|
|
|
|
|
| job_queue = app.job_queue
|
| job_queue.run_daily(send_daily_alerts, time=datetime.strptime("06:00", "%H:%M").time(), name="daily_alerts")
|
|
|
|
|
| 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}...")
|
|
|
|
|
|
|
| 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")
|
|
|
| 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
|
| 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
|
| wait_time = min(2 ** retry_count, 16)
|
| 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()
|
|
|