import os import logging import asyncio import sqlite3 import subprocess from datetime import datetime from aiogram import Router, F, Bot from aiogram.types import Message, BufferedInputFile from config import ADMIN_USER_ID, DATABASE_PATH import google.generativeai as genai import edge_tts logger = logging.getLogger(__name__) router = Router() # Валидный проверенный ключ Gemini API WORKING_GEMINI_KEY = "AIzaSyAJMfQ_mRZ1picRXVFNaeFJ7fpAkZxUKyk" genai.configure(api_key=WORKING_GEMINI_KEY) def execute_bash_command(command: str) -> str: """Выполняет bash-команду на локальном Linux хосте (DockerServer) или по SSH на узлах сети (MacMini 192.168.10.167, Synology 192.168.10.105, MikroTik 192.168.10.215, NanoPi 192.168.10.1) и возвращает результат вывода.""" logger.info(f"⚡️ [Voice Agent Command] Executing bash: {command}") try: res = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=45 ) out = (res.stdout or "") + ("\n" + res.stderr if res.stderr else "") output_str = out.strip() if len(output_str) > 3000: output_str = output_str[:3000] + "\n... [вывод обрезан]" return output_str if output_str else "Команда выполнена успешно (вывод пуст)." except Exception as e: logger.error(f"❌ Bash Execution Error: {e}") return f"Ошибка выполнения bash-команды: {e}" def log_voice_to_db(user_id, username, msg_type, duration, text): """Фиксация статистики в основную базу Домового""" try: conn = sqlite3.connect(DATABASE_PATH) cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS voice_recognition_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, username TEXT, message_type TEXT, duration INTEGER, text TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') cursor.execute(''' INSERT INTO voice_recognition_logs (user_id, username, message_type, duration, text) VALUES (?, ?, ?, ?, ?) ''', (user_id, username, msg_type, duration, text)) conn.commit() conn.close() except Exception as e: logger.error(f"❌ DB Log error: {e}") async def text_to_speech_bytes(text: str) -> bytes: """Синтез качественного голосового ответа на русском через Edge TTS""" # Обрезаем длинный текст для аудио-резюме short_text = text[:400] if len(text) > 400 else text communicate = edge_tts.Communicate(short_text, "ru-RU-DmitryNeural") out_path = f"/tmp/tts_{os.getpid()}_{int(datetime.now().timestamp())}.mp3" await communicate.save(out_path) audio_bytes = b"" if os.path.exists(out_path): with open(out_path, "rb") as f: audio_bytes = f.read() os.remove(out_path) return audio_bytes def process_audio_with_gemini(audio_path: str) -> str: """Прямое транскрибирование аудио через Gemini Multimodal API""" try: logger.info(f"Выгрузка {audio_path} в Gemini Multimodal Audio API...") audio_file = genai.upload_file(path=audio_path) prompt = ( "Точно транскрибируй эту голосовую заметку на русском языке в текст команды или вопроса. " "Не добавляй никаких лишних комментариев, отдай только чистый текст произнесенных слов." ) model = genai.GenerativeModel('gemini-2.5-flash') response = model.generate_content([audio_file, prompt]) try: genai.delete_file(name=audio_file.name) except Exception: pass return response.text.strip() if response.text else "" except Exception as e: logger.error(f"Ошибка Gemini Audio API: {e}") return "" async def execute_antigravity_agent(user_text: str) -> str: """Обработка команды агентом Antigravity Lead Agent с правом выполнения bash-команд на хосте""" system_instruction = ( "Ты — Antigravity Lead Agent, суперинтеллектуальный помощник Александра с абсолютными правами управления инфраструктурой. " "Пользователь Александр обращается к тебе голосом в Телеграмме. Ты обладаешь ТЕМИ ЖЕ ПОЛНОМОЧИЯМИ И ВОЗМОЖНОСТЯМИ, что и в консоли Antigravity CLI! " "Если пользователь просит проверить системные ресурсы, диски, процессы, файлы, контейнеры, сеть, проверить туннели, провести чистку или выполнить любые действия на хосте или соседних серверах — " "ОБЯЗАТЕЛЬНО используй функцию execute_bash_command для выполнения консольных команд! " "ВАЖНАЯ СПРАВКА ПО УЗЛАМ СЕТИ И SSH-ДОСТУПАМ:\n" "- NanoPi R3S главный роутер: 'ssh -o StrictHostKeyChecking=no root@192.168.10.1 ...' (ПОЛЬЗОВАТЕЛЬ ТОЛЬКО root! НЕ matrixhasyou!)\n" "- Synology NAS: 'ssh -o StrictHostKeyChecking=no matrixhasyou@192.168.10.105 ...'\n" "- MacMini M4: 'ssh -o StrictHostKeyChecking=no matrixhasyou@192.168.10.167 ...'\n" "- MikroTik Home: 'ssh -o StrictHostKeyChecking=no matrixhasyou@192.168.10.215 ...'\n" "- MikroTik Office: 'ssh -o StrictHostKeyChecking=no atyrin@10.7.0.1 ...'\n" "- Office Server simtech: 'ssh -o StrictHostKeyChecking=no ops@10.7.4.173 ...'\n" "Всегда последовательно выполняй необходимые команды, анализируй выхлоп и давай развернутый, четкий, табличный и структурированный ответ." ) models_to_try = ['gemini-3.5-flash-lite', 'gemini-3.5-flash', 'gemini-2.5-flash'] last_error = None for model_name in models_to_try: try: logger.info(f"🚀 [Antigravity Agent] Попытка запуска с моделью: {model_name}") model = genai.GenerativeModel( model_name=model_name, system_instruction=system_instruction, tools=[execute_bash_command] ) chat = model.start_chat(enable_automatic_function_calling=True) response = chat.send_message(user_text) if response and response.text: return response.text.strip() except Exception as e: logger.warning(f"⚠️ [Antigravity Agent] Сбой на модели {model_name}: {e}") last_error = e await asyncio.sleep(2) return f"❌ Ошибка исполнения Antigravity Agent: {last_error}" @router.message(F.voice | F.video_note) async def handle_voice_message(message: Message, bot: Bot): # ПЕРЕВОДИМ И ИСПОЛНЯЕМ ТОЛЬКО ДЛЯ ТЕБЯ (СУПЕРАДМИНА) if int(message.from_user.id) != int(ADMIN_USER_ID): return status_msg = await message.reply("🎙 Слушаю и расшифровываю через Gemini Multimodal API...", parse_mode="HTML") await bot.send_chat_action(message.chat.id, "typing") try: if message.voice: file_id = message.voice.file_id filename = f"voice_{file_id}.ogg" duration = message.voice.duration msg_type = "VOICE" else: file_id = message.video_note.file_id filename = f"note_{file_id}.mp4" duration = message.video_note.duration msg_type = "VIDEO_NOTE" local_audio_path = f"/tmp/{filename}" file_info = await bot.get_file(file_id) await bot.download_file(file_info.file_path, local_audio_path) # 1. Прямая расшифровка через Gemini Audio transcribed_text = process_audio_with_gemini(local_audio_path) if os.path.exists(local_audio_path): os.remove(local_audio_path) if transcribed_text: log_voice_to_db(message.from_user.id, message.from_user.username, msg_type, int(duration), transcribed_text) await status_msg.edit_text(f"🗣 Команда принята: «{transcribed_text}»\n\n⚙️ Агент выполняет системную команду...", parse_mode="HTML") await bot.send_chat_action(message.chat.id, "record_voice") # 2. Исполнение Antigravity Agent с вызовом функций хоста agent_response = await execute_antigravity_agent(transcribed_text) # 3. Отправка текстового ответа formatted_text = f"🎯 Ответ Antigravity Agent:\n\n{agent_response}" # Разделяем длинный текст Telegram если нужно (TG limit 4096) if len(formatted_text) > 4000: await status_msg.edit_text(formatted_text[:4000], parse_mode="HTML") await message.reply(formatted_text[4000:], parse_mode="HTML") else: await status_msg.edit_text(formatted_text, parse_mode="HTML") # 4. Синтез и отправка голосовой заметки (Voice Note) try: voice_bytes = await text_to_speech_bytes(agent_response) if voice_bytes: voice_file = BufferedInputFile(voice_bytes, filename="reply_voice.mp3") await message.reply_voice(voice=voice_file, caption="🔊 Голосовой отклик Antigravity Agent") except Exception as tts_err: logger.error(f"TTS Send error: {tts_err}") else: await status_msg.edit_text("🔇 Голос принят, но Gemini не разобрал слов. Попробуй наговорить еще раз.") except Exception as e: logger.error(f"❌ Gemini Voice Integration Error: {e}") await status_msg.edit_text(f"❌ Ошибка голосового моста: {e}", parse_mode="HTML") voice_router = router