Architecture cleanup after migration to ultrathin monolith: - Remove INTERNAL_API_PORT from .env files (was port 8002) - Clean up bot_main.py: remove uvicorn, Thread, run_internal_api() - Update validate.md to check /api/telegram/health instead of port 8002 - Add deprecation notices to old Windows deployment docs - Update docs/telegram/README.md with architecture note The Telegram internal API is now served at /api/telegram/internal/* on the main backend port (8000/8001) instead of separate port 8002. Also includes: menu updates, ServerLogsView improvements, script fixes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
297 lines
11 KiB
Python
297 lines
11 KiB
Python
"""
|
|
Main entry point for ROA2WEB Telegram Bot
|
|
|
|
This bot provides access to the ROA2WEB ERP system through Telegram
|
|
using direct command handlers for financial data queries.
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
import os
|
|
from pathlib import Path
|
|
from dotenv import load_dotenv
|
|
# Note: uvicorn and threading removed - internal API now served via main.py
|
|
|
|
# ============================================================================
|
|
# LOAD ENVIRONMENT VARIABLES FIRST - BEFORE ANY APP IMPORTS
|
|
# ============================================================================
|
|
# This ensures all modules can access environment variables at import time
|
|
env_path = Path(__file__).parent.parent / '.env'
|
|
load_dotenv(env_path)
|
|
|
|
# Telegram imports
|
|
from telegram.ext import (
|
|
Application,
|
|
CommandHandler,
|
|
CallbackQueryHandler,
|
|
MessageHandler,
|
|
filters
|
|
)
|
|
|
|
# Import database initialization
|
|
from backend.modules.telegram.db import (
|
|
init_database,
|
|
cleanup_expired_codes,
|
|
cleanup_expired_sessions,
|
|
cleanup_expired_email_codes
|
|
)
|
|
|
|
# Import bot handlers
|
|
from backend.modules.telegram.bot.handlers import (
|
|
start_command,
|
|
help_command,
|
|
clear_command,
|
|
companies_command,
|
|
unlink_command,
|
|
selectcompany_command,
|
|
dashboard_command,
|
|
sold_command,
|
|
facturi_command,
|
|
trezorerie_command,
|
|
# FAZA 3: New command handlers with button interface
|
|
menu_command,
|
|
trezorerie_casa_command,
|
|
trezorerie_banca_command,
|
|
clienti_command,
|
|
furnizori_command,
|
|
evolutie_command,
|
|
# FAZA 6: Cache management commands
|
|
clearcache_command,
|
|
togglecache_command,
|
|
# Text message handlers
|
|
handle_text_message,
|
|
# FAZA 4: Callback and error handlers
|
|
button_callback,
|
|
error_handler
|
|
)
|
|
|
|
# Import email authentication handler
|
|
from backend.modules.telegram.bot.email_handlers import email_login_handler
|
|
|
|
# Note: internal_api import removed - now served via main.py at /api/telegram/internal/*
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Environment variables (already loaded above)
|
|
TELEGRAM_BOT_TOKEN = os.getenv('TELEGRAM_BOT_TOKEN')
|
|
BACKEND_URL = os.getenv('BACKEND_URL', 'http://localhost:8000')
|
|
# Note: INTERNAL_API_PORT removed - internal API now served via main.py
|
|
|
|
|
|
# ============================================================================
|
|
# TELEGRAM BOT SETUP
|
|
# ============================================================================
|
|
|
|
def create_telegram_application() -> Application:
|
|
"""
|
|
Create and configure the Telegram bot application.
|
|
|
|
Returns:
|
|
Application: Configured Telegram application
|
|
"""
|
|
logger.info("Creating Telegram application...")
|
|
|
|
# Create application
|
|
application = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
|
|
|
# Register email authentication conversation handler (must be before other handlers)
|
|
application.add_handler(email_login_handler)
|
|
|
|
# Register essential command handlers
|
|
application.add_handler(CommandHandler("start", start_command))
|
|
application.add_handler(CommandHandler("menu", menu_command))
|
|
application.add_handler(CommandHandler("help", help_command))
|
|
application.add_handler(CommandHandler("unlink", unlink_command))
|
|
|
|
# =========================================================================
|
|
# LEGACY COMMAND HANDLERS (kept for backwards compatibility, hidden from help)
|
|
# =========================================================================
|
|
# NOTE: These commands are redundant with the button interface.
|
|
# They're kept for users who already know them, but we push buttons in help.
|
|
# Consider removing completely if migration is successful.
|
|
|
|
application.add_handler(CommandHandler("clear", clear_command))
|
|
application.add_handler(CommandHandler("companies", companies_command))
|
|
application.add_handler(CommandHandler("selectcompany", selectcompany_command))
|
|
application.add_handler(CommandHandler("dashboard", dashboard_command))
|
|
application.add_handler(CommandHandler("sold", sold_command))
|
|
application.add_handler(CommandHandler("facturi", facturi_command))
|
|
application.add_handler(CommandHandler("trezorerie", trezorerie_command))
|
|
application.add_handler(CommandHandler("trezorerie_casa", trezorerie_casa_command))
|
|
application.add_handler(CommandHandler("trezorerie_banca", trezorerie_banca_command))
|
|
application.add_handler(CommandHandler("clienti", clienti_command))
|
|
application.add_handler(CommandHandler("furnizori", furnizori_command))
|
|
application.add_handler(CommandHandler("evolutie", evolutie_command))
|
|
|
|
# FAZA 6: Cache management commands
|
|
application.add_handler(CommandHandler("clearcache", clearcache_command))
|
|
application.add_handler(CommandHandler("togglecache", togglecache_command))
|
|
|
|
# Text message handler (for direct code input and future NLP)
|
|
# IMPORTANT: This must be registered BEFORE CallbackQueryHandler
|
|
# filters.TEXT & ~filters.COMMAND ensures we only process non-command text messages
|
|
application.add_handler(MessageHandler(
|
|
filters.TEXT & ~filters.COMMAND,
|
|
handle_text_message
|
|
))
|
|
|
|
# FAZA 4: Register callback query handler (for inline buttons)
|
|
application.add_handler(CallbackQueryHandler(button_callback))
|
|
|
|
# Register error handler
|
|
application.add_error_handler(error_handler)
|
|
|
|
logger.info("Telegram application configured with all handlers")
|
|
|
|
return application
|
|
|
|
|
|
# ============================================================================
|
|
# STARTUP/SHUTDOWN
|
|
# ============================================================================
|
|
# Note: Internal API server removed - now served via main.py at /api/telegram/internal/*
|
|
|
|
async def startup():
|
|
"""
|
|
Initialize the bot application on startup.
|
|
"""
|
|
logger.info("🚀 ROA2WEB Telegram Bot - Starting up...")
|
|
|
|
# Initialize database
|
|
try:
|
|
logger.info("Initializing SQLite database...")
|
|
await init_database()
|
|
logger.info("✅ Database initialized successfully")
|
|
except Exception as e:
|
|
logger.error(f"❌ Failed to initialize database: {e}")
|
|
raise
|
|
|
|
# Cleanup expired data
|
|
try:
|
|
logger.info("Cleaning up expired data...")
|
|
expired_codes = await cleanup_expired_codes()
|
|
expired_sessions = await cleanup_expired_sessions()
|
|
expired_email_codes = await cleanup_expired_email_codes()
|
|
logger.info(f"✅ Cleanup complete: {expired_codes} codes, {expired_sessions} sessions, {expired_email_codes} email codes removed")
|
|
except Exception as e:
|
|
logger.warning(f"⚠️ Cleanup failed (non-critical): {e}")
|
|
|
|
logger.info("✅ Startup complete")
|
|
|
|
|
|
async def shutdown():
|
|
"""
|
|
Clean up resources on shutdown.
|
|
"""
|
|
logger.info("👋 ROA2WEB Telegram Bot - Shutting down...")
|
|
logger.info("✅ Shutdown complete")
|
|
|
|
|
|
async def scheduled_cleanup():
|
|
"""
|
|
Background task to periodically clean up expired data.
|
|
Runs every hour to remove expired auth codes, sessions, and email codes.
|
|
"""
|
|
while True:
|
|
try:
|
|
await asyncio.sleep(3600) # Sleep for 1 hour
|
|
logger.info("🧹 Running scheduled cleanup...")
|
|
expired_codes = await cleanup_expired_codes()
|
|
expired_sessions = await cleanup_expired_sessions()
|
|
expired_email_codes = await cleanup_expired_email_codes()
|
|
logger.info(f"✅ Scheduled cleanup: {expired_codes} codes, {expired_sessions} sessions, {expired_email_codes} email codes removed")
|
|
except Exception as e:
|
|
logger.error(f"❌ Error in scheduled cleanup: {e}")
|
|
|
|
|
|
# ============================================================================
|
|
# MAIN APPLICATION
|
|
# ============================================================================
|
|
|
|
async def main():
|
|
"""
|
|
Main application entry point.
|
|
|
|
Runs both the Telegram bot and internal API server concurrently.
|
|
"""
|
|
try:
|
|
# Run startup
|
|
await startup()
|
|
|
|
# Create Telegram application
|
|
telegram_app = create_telegram_application()
|
|
|
|
# Note: Internal API server removed - now served via main.py
|
|
|
|
# Start scheduled cleanup task in background
|
|
cleanup_task = asyncio.create_task(scheduled_cleanup())
|
|
logger.info("✅ Scheduled cleanup task started")
|
|
|
|
# Initialize and start Telegram bot
|
|
logger.info("🤖 Starting Telegram bot polling...")
|
|
await telegram_app.initialize()
|
|
await telegram_app.start()
|
|
await telegram_app.updater.start_polling(
|
|
drop_pending_updates=True,
|
|
poll_interval=0, # No delay between polls
|
|
timeout=30 # Long poll timeout 30 seconds (reduces requests from ~6/min to ~2/min)
|
|
)
|
|
|
|
logger.info("✅ Telegram bot is now running and polling for updates")
|
|
logger.info(f"📱 Bot ready to receive messages at @{(await telegram_app.bot.get_me()).username}")
|
|
logger.info("🎯 Bot is operational with direct command handlers!")
|
|
|
|
# Keep running until interrupted
|
|
await asyncio.Event().wait()
|
|
|
|
except KeyboardInterrupt:
|
|
logger.info("⚠️ Received interrupt signal")
|
|
except Exception as e:
|
|
logger.error(f"❌ Fatal error: {e}", exc_info=True)
|
|
raise
|
|
finally:
|
|
# Stop Telegram bot gracefully
|
|
try:
|
|
if 'telegram_app' in locals():
|
|
logger.info("Stopping Telegram bot...")
|
|
await telegram_app.updater.stop()
|
|
await telegram_app.stop()
|
|
await telegram_app.shutdown()
|
|
logger.info("✅ Telegram bot stopped")
|
|
except Exception as e:
|
|
logger.error(f"Error stopping Telegram bot: {e}")
|
|
|
|
await shutdown()
|
|
|
|
|
|
# ============================================================================
|
|
# ENTRY POINT
|
|
# ============================================================================
|
|
|
|
if __name__ == "__main__":
|
|
# Check required environment variables
|
|
if not os.getenv('TELEGRAM_BOT_TOKEN'):
|
|
logger.error("❌ TELEGRAM_BOT_TOKEN is required")
|
|
logger.error("Please set it in .env file")
|
|
exit(1)
|
|
|
|
# Display startup banner
|
|
logger.info("=" * 60)
|
|
logger.info(" ROA2WEB TELEGRAM BOT")
|
|
logger.info(" Financial ERP Assistant with Direct Commands")
|
|
logger.info("=" * 60)
|
|
|
|
# Run the main application
|
|
try:
|
|
asyncio.run(main())
|
|
except KeyboardInterrupt:
|
|
logger.info("👋 Application stopped by user")
|
|
except Exception as e:
|
|
logger.error(f"❌ Application failed: {e}", exc_info=True)
|
|
exit(1)
|