domovoy_bot/handlers/voice.py

202 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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! "
"Если пользователь просит проверить системные ресурсы, диски, процессы, файлы, контейнеры, сеть, провести чистку или выполнить любые действия на хосте или соседних серверах (MacMini, Synology, MikroTik, NanoPi) — "
"ОБЯЗАТЕЛЬНО используй функцию execute_bash_command для выполнения консольных команд! "
"Пример команд: 'df -h', 'du -d 1 -h /home/matrixhasyou', 'docker ps', 'docker system df', 'ssh matrixhasyou@192.168.10.167 ...'. "
"Всегда последовательно выполняй необходимые команды, анализируй выхлоп и давай развернутый, четкий, табличный и структурированный ответ."
)
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("🎙 <i>Слушаю и расшифровываю через Gemini Multimodal API...</i>", 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"🗣 <b>Команда принята:</b> «<i>{transcribed_text}</i>»\n\n⚙️ <b>Агент выполняет системную команду...</b>", 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"🎯 <b>Ответ Antigravity Agent:</b>\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