import os import asyncio import re import logging from telegram import Update from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes from orchestrator import ( handle_message_async, orchestrate_async, format_confirmation_message, format_completion_message, execute_step_async ) from ai_agent import query_agent_async from audio_handler import transcribe_audio, text_to_speech_async from config import get_config # Configuração de logging logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") ALLOWED_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID") async def synthesize_audio(text: str) -> str: """Wrapper assíncrono para a síntese de voz centralizada.""" try: filename = await text_to_speech_async(text) return os.path.join("/tmp", filename) except Exception as e: logger.error(f"TTS Error: {e}") return "" async def auth_check(update: Update) -> bool: if not update.message or str(update.message.chat_id) != ALLOWED_CHAT_ID: if update.message: await update.message.reply_text("Acesso negado.") return False return True async def handle_text(update: Update, context: ContextTypes.DEFAULT_TYPE): if not await auth_check(update): return await update.message.reply_chat_action(action="typing") await process_logic(update, context, update.message.text) async def handle_voice(update: Update, context: ContextTypes.DEFAULT_TYPE): if not await auth_check(update): return await update.message.reply_chat_action(action="record_voice") # Baixa o arquivo de voz do Telegram voice_file = await context.bot.get_file(update.message.voice.file_id) ogg_path = f"/tmp/{update.message.voice.file_id}.ogg" await voice_file.download_to_drive(ogg_path) try: # Transcreve (STT) text = transcribe_audio(ogg_path) if not text: await update.message.reply_text("Não consegui entender o áudio.") return await update.message.reply_text(f"🎤 Entendi: \"{text}\"") # Processa como se fosse texto (reutiliza handle_text logic) # Gambiarra rápida: cria um objeto Update fake ou chama a lógica diretamente # Melhor: extrair a lógica de handle_text para uma função pura await process_logic(update, context, text, is_voice=True) except Exception as e: logger.error(f"Erro Voz: {e}") await update.message.reply_text(f"Erro ao processar voz: {e}") finally: if os.path.exists(ogg_path): os.remove(ogg_path) async def process_logic(update: Update, context: ContextTypes.DEFAULT_TYPE, user_msg: str, is_voice: bool = False): chat_id = update.message.chat_id # 1. COMANDOS DIRETOS if user_msg.startswith('/') and user_msg.split()[0] in ['/status', '/tools', '/sync']: reply = await handle_message_async(user_msg) await update.message.reply_text(reply) return # 2. CONFIRMAÇÃO DE PLANO if user_msg.lower() in ['sim', 's', 'confirmar'] and 'pending_plan' in context.bot_data: plan = context.bot_data.pop('pending_plan') results = [] for step in plan.get("steps", []): res = await execute_step_async(step) results.append(res) if not res["success"] and step.get("danger") == "dangerous": break reply = format_completion_message({"plan": plan, "results": results}) await update.message.reply_text(reply) return # 3. ORCHESTRATOR OU AI AGENT orchestrator_keywords = ['deploy', 'restart', 'git', 'docker', 'atualiza', 'status'] is_task = any(kw in user_msg.lower() for kw in orchestrator_keywords) or user_msg.startswith('/orchestrate') if is_task: task = user_msg.replace('/orchestrate', '').strip() result = await orchestrate_async(task) if result["status"] == "needs_confirmation": context.bot_data['pending_plan'] = result["plan"] reply = format_confirmation_message(result) else: reply = format_completion_message(result) else: # Fallback AI Agent cfg = get_config() # No Telegram, ainda não estamos mantendo histórico complexo no bot_data (pode ser futuro) reply = await query_agent_async(user_msg, override_provider=cfg.get("active_provider")) # Normaliza a resposta: remove tags legadas ou [REFINED] e garante o prefixo RESUMO: reply_clean = re.sub(r'[<\[]/?REFINED[>\]]', '', reply, flags=re.IGNORECASE).strip() # Se a resposta não tiver "RESUMO:" mas tiver conteúdo, o orquestrador/IA já deve ter colocado. # Se por algum motivo não houver, mantemos o texto limpo. await update.message.reply_text(reply_clean) # Se foi por voz, responde por voz também if is_voice: audio_path = await synthesize_audio(reply_clean) if audio_path and os.path.exists(audio_path): with open(audio_path, 'rb') as voice_file: await update.message.reply_voice(voice_file) os.remove(audio_path) def get_telegram_app(): if not TOKEN: print("AVISO: TELEGRAM_BOT_TOKEN não encontrado.") app = Application.builder().token(TOKEN).build() app.add_handler(CommandHandler("start", lambda u, c: u.message.reply_text("Online!"))) app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_text)) app.add_handler(MessageHandler(filters.COMMAND, handle_text)) app.add_handler(MessageHandler(filters.VOICE, handle_voice)) return app if __name__ == "__main__": logger.info("Bot começando a inicialização...") try: app = get_telegram_app() logger.info("Bot online. Iniciando polling...") app.run_polling(drop_pending_updates=True) except Exception as e: logger.error(f"Erro fatal ao iniciar bot: {e}")