import os import logging import httpx import asyncio from telegram import Update from telegram.ext import ApplicationBuilder, ContextTypes, MessageHandler, filters from dotenv import load_dotenv # Carrega as variáveis do arquivo .env load_dotenv() # Configurações obtidas do .env TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") ALLOWED_USER_ID = os.getenv("TELEGRAM_CHAT_ID") # Sincroniza com a PORTA definida no .env (Dica: .env diz 8000) API_PORT = os.getenv("PORT", "8001") API_BASE_URL = f"http://localhost:{API_PORT}" # O ID permitido deve ser comparado como string ou int, padronizando aqui if ALLOWED_USER_ID: ALLOWED_USER_ID = int(ALLOWED_USER_ID) # Configuração de Logs logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO ) logger = logging.getLogger(__name__) # Dicionário global para manter o histórico (Em um sistema de produção, usar Redis ou DB) chat_histories = {} async def call_antigravity_api(endpoint: str, payload: dict) -> str: """Faz a chamada para a API interna do BotVPS.""" async with httpx.AsyncClient(timeout=120.0) as client: try: logger.info(f"Enviando payload para {endpoint}: {payload.get('text', payload.get('task'))}") response = await client.post(f"{API_BASE_URL}{endpoint}", json=payload) response.raise_for_status() data = response.json() # Tenta extrair a resposta de diferentes formatos possíveis reply = data.get("reply") or data.get("message") or str(data) return reply except Exception as e: logger.error(f"Erro ao chamar API Antigravity: {str(e)}") return f"❌ *Erro na API:* {str(e)}" async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): """Manipulador central de mensagens.""" if not update.message or not update.message.text: return chat_id = update.effective_chat.id user_id = update.effective_user.id # Filtro de Segurança if ALLOWED_USER_ID and user_id != ALLOWED_USER_ID: logger.warning(f"Acesso negado para o ID: {user_id}. Esperado: {ALLOWED_USER_ID}") return text = update.message.text logger.info(f"Mensagem recebida de {user_id}: {text}") # Inicializa histórico se não existir if chat_id not in chat_histories: chat_histories[chat_id] = [] # Diferenciação entre comandos de sistema e conhecimento geral if text.startswith(('/bash', '/vps', '/cmd')): # Comandos de sistema geralmente não precisam de histórico de chat natural task = text.replace('/bash', '').replace('/vps', '').replace('/cmd', '').strip() if not task: await update.message.reply_text("❓ Por favor, envie o comando após o prefixo.") return await update.message.reply_text("⚙️ *Processando tarefa no Claw System...*", parse_mode='Markdown') reply = await call_antigravity_api("/api/orchestrate", {"task": task}) else: # Chat Natural com contexto await context.bot.send_chat_action(chat_id=chat_id, action="typing") # Prepara payload com histórico payload = { "text": text, "history": chat_histories[chat_id][-10:] # Envia os últimos 10 turnos } reply = await call_antigravity_api("/api/chat", payload) # Atualiza histórico Local chat_histories[chat_id].append({"user": text, "bot": reply}) # Mantém apenas os últimos 15 para não crescer infinito no middleware if len(chat_histories[chat_id]) > 15: chat_histories[chat_id].pop(0) # Envia a resposta de volta para o usuário if len(reply) > 4000: reply = reply[:3900] + "... [Texto truncado]" try: await update.message.reply_text(reply, parse_mode='Markdown') except Exception as e: logger.error(f"Erro ao enviar Markdown: {e}. Tentando texto puro.") await update.message.reply_text(reply) async def handle_voice(update: Update, context: ContextTypes.DEFAULT_TYPE): """Manipula mensagens de voz do Telegram.""" if not update.message or not update.message.voice: return chat_id = update.effective_chat.id user_id = update.effective_user.id # Filtro de Segurança if ALLOWED_USER_ID and user_id != ALLOWED_USER_ID: return await context.bot.send_chat_action(chat_id=chat_id, action="record_voice") # 1. Download do áudio do Telegram voice_file = await update.message.voice.get_file() temp_path = f"/tmp/tg_voice_{uuid.uuid4().hex}.ogg" await voice_file.download_to_drive(temp_path) logger.info(f"Voz recebida de {user_id}. Enviando para API de Áudio...") # 2. Envia para a API interna de áudio # Como o bridge e API estão na mesma máquina, compartilhamos o /tmp se necessário # Mas vamos usar multipart para ser fiel à API async with httpx.AsyncClient(timeout=120.0) as client: try: with open(temp_path, "rb") as f: # O parâmetro history pode ser adicionado futuramente similar ao chat files = {"audio": (os.path.basename(temp_path), f, "audio/ogg")} response = await client.post(f"{API_BASE_URL}/api/chat-audio", files=files) response.raise_for_status() data = response.json() user_text = data.get("text", "[Voz não transcrita]") bot_reply = data.get("reply", "Erro no processamento.") audio_url = data.get("audio_url") # Ex: /api/audio/file.mp3 # Envia transcrição do usuário (sem markdown no conteúdo para evitar bugs) await update.message.reply_text(f"🎤 *Sua mensagem:* {user_text}") # Envia resposta em texto com fallback se o Markdown falhar try: await update.message.reply_text(bot_reply, parse_mode='Markdown') except Exception as e: logger.error(f"Erro Markdown na voz: {e}") await update.message.reply_text(bot_reply) # 3. Envia resposta em áudio (TTS) if audio_url: filename = audio_url.split("/")[-1] audio_path = os.path.join("/tmp", filename) if os.path.exists(audio_path): with open(audio_path, "rb") as audio_file: await context.bot.send_voice(chat_id=chat_id, voice=audio_file) # Atualiza histórico local if chat_id not in chat_histories: chat_histories[chat_id] = [] chat_histories[chat_id].append({"user": user_text, "bot": bot_reply}) except Exception as e: logger.error(f"Erro ao processar áudio: {str(e)}") await update.message.reply_text(f"❌ *Erro no áudio:* {str(e)}") finally: if os.path.exists(temp_path): os.remove(temp_path) if __name__ == '__main__': if not TOKEN: logger.error("ERRO: TELEGRAM_BOT_TOKEN não encontrado no .env!") exit(1) # Inicializa o Bot (python-telegram-bot v20+) application = ApplicationBuilder().token(TOKEN).build() # Adiciona o handler para mensagens de texto text_handler = MessageHandler(filters.TEXT & (~filters.COMMAND), handle_message) application.add_handler(text_handler) # Adiciona o handler para mensagens de VOZ import uuid voice_handler = MessageHandler(filters.VOICE, handle_voice) application.add_handler(voice_handler) logger.info("Bot Ponte Antigravity (Middleware - Texto & Voz) iniciado...") application.run_polling()