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