feat(voice): transcribe Telegram voice notes with SpeechRecognition
Add a voice message handler that downloads OGG audio, converts it to WAV via pydub/ffmpeg, and transcribes it with Google Speech Recognition (pt-BR) before feeding the text into the normal message pipeline. Also extract shared text processing into process_text_message, guard against updates without a text attribute, switch the default AI provider to openrouter with deepseek-v4-flash, and document the ffmpeg system requirement in DEPLOY.md.
This commit is contained in:
+86
-3
@@ -1068,7 +1068,7 @@ def update_profile_with_preference(text: str) -> Dict[str, Any]:
|
||||
return {"section": section, "field": field, "content": content, "profile": profile}
|
||||
|
||||
async def handle_profile_update_if_triggered(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
||||
user_message = update.message.text.strip()
|
||||
user_message = (getattr(update.message, "text", None) or "").strip()
|
||||
content = _extract_profile_update_content(user_message)
|
||||
|
||||
if not content:
|
||||
@@ -3239,6 +3239,68 @@ async def listfiles_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await update.message.reply_text("\n".join(lines))
|
||||
|
||||
|
||||
async def _transcribe_voice_file(ogg_path: str) -> Optional[str]:
|
||||
wav_path = ogg_path.replace(".ogg", ".wav")
|
||||
try:
|
||||
from pydub import AudioSegment
|
||||
audio = AudioSegment.from_ogg(ogg_path)
|
||||
audio.export(wav_path, format="wav")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _recognize():
|
||||
import speech_recognition as sr
|
||||
r = sr.Recognizer()
|
||||
with sr.AudioFile(wav_path) as source:
|
||||
audio_data = r.record(source)
|
||||
return r.recognize_google(audio_data, language="pt-BR")
|
||||
|
||||
try:
|
||||
text = await asyncio.to_thread(_recognize)
|
||||
except Exception:
|
||||
text = None
|
||||
finally:
|
||||
for path in (ogg_path, wav_path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return text
|
||||
|
||||
|
||||
async def voice_message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await auto_detect_chat_id(update)
|
||||
|
||||
voice = getattr(update.message, "voice", None)
|
||||
if not voice:
|
||||
await update.message.reply_text("Não detectei mensagem de voz.")
|
||||
print("[VOICE] update.message.voice is None")
|
||||
return
|
||||
|
||||
print("[VOICE] received voice message")
|
||||
await update.message.reply_text("🎤 Recebi seu áudio, processando...")
|
||||
|
||||
try:
|
||||
file = await context.bot.get_file(voice.file_id)
|
||||
ogg_path = f"/tmp/voice_{voice.file_id}.ogg"
|
||||
await file.download_to_drive(ogg_path)
|
||||
print("[VOICE] downloaded to", ogg_path)
|
||||
except Exception as e:
|
||||
print("[VOICE] download error:", e)
|
||||
await update.message.reply_text("Erro ao baixar o áudio. Tente novamente.")
|
||||
return
|
||||
|
||||
text = await _transcribe_voice_file(ogg_path)
|
||||
print("[VOICE] transcription result:", repr(text))
|
||||
|
||||
if text:
|
||||
await update.message.reply_text(f"🎤 Transcrevi: \"{text}\"")
|
||||
await process_text_message(update, context, text)
|
||||
else:
|
||||
await update.message.reply_text("Não consegui entender o áudio. Pode enviar como texto?")
|
||||
|
||||
|
||||
async def handle_file_upload(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
await auto_detect_chat_id(update)
|
||||
|
||||
@@ -3736,58 +3798,78 @@ async def onboarding_command(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
||||
await start_onboarding(update, context)
|
||||
|
||||
async def message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
print("[MSG] handler called")
|
||||
await auto_detect_chat_id(update)
|
||||
user_message = update.message.text.strip()
|
||||
user_message = (update.message.text or "").strip()
|
||||
print("[MSG] text=", repr(user_message))
|
||||
await process_text_message(update, context, user_message)
|
||||
|
||||
|
||||
async def process_text_message(update: Update, context: ContextTypes.DEFAULT_TYPE, user_message: str) -> None:
|
||||
print("[PROCESS] text=", repr(user_message))
|
||||
if not user_message:
|
||||
print("[PROCESS] empty message, returning")
|
||||
return
|
||||
global is_onboarding, is_newconfig, is_configagentprofile, is_edit_user_profile, is_edit_agent_profile, is_activity_crud, is_agendamento, is_useragenda, is_configia
|
||||
if is_configia:
|
||||
handled = await handle_configia_message(update, context)
|
||||
print("[PROCESS] configia handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
if is_activity_crud:
|
||||
if await handle_activity_crud_answer(update, context):
|
||||
handled = await handle_activity_crud_answer(update, context)
|
||||
print("[PROCESS] activity_crud handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
if is_onboarding:
|
||||
handled = await handle_onboarding_answer(update, context)
|
||||
print("[PROCESS] onboarding handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
|
||||
if is_newconfig:
|
||||
handled = await handle_newconfig_answer(update, context)
|
||||
print("[PROCESS] newconfig handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
|
||||
if is_configagentprofile:
|
||||
handled = await handle_configagentprofile_answer(update, context)
|
||||
print("[PROCESS] configagentprofile handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
|
||||
if is_edit_user_profile:
|
||||
handled = await handle_edit_user_profile_answer(update, context)
|
||||
print("[PROCESS] edit_user_profile handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
|
||||
if is_edit_agent_profile:
|
||||
handled = await handle_edit_agent_profile_answer(update, context)
|
||||
print("[PROCESS] edit_agent_profile handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
|
||||
if is_agendamento:
|
||||
handled = await handle_agendamento_answer(update, context)
|
||||
print("[PROCESS] agendamento handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
|
||||
if is_useragenda:
|
||||
handled = await handle_useragenda_answer(update, context)
|
||||
print("[PROCESS] useragenda handled=", handled)
|
||||
if handled:
|
||||
return
|
||||
|
||||
profile_updated = await handle_profile_update_if_triggered(update, context)
|
||||
print("[PROCESS] profile_updated=", profile_updated)
|
||||
if profile_updated:
|
||||
return
|
||||
|
||||
add_to_context("user", user_message)
|
||||
print("[PROCESS] added context, getting AI response...")
|
||||
|
||||
# Check if user query references an uploaded file
|
||||
file_context = ""
|
||||
@@ -4060,6 +4142,7 @@ def main():
|
||||
app_instance.add_handler(CommandHandler("uploadfile", uploadfile_command))
|
||||
app_instance.add_handler(CommandHandler("listfiles", listfiles_command))
|
||||
app_instance.add_handler(CallbackQueryHandler(configia_callback, pattern=r"^configia_(provider|model)_"))
|
||||
app_instance.add_handler(MessageHandler(filters.VOICE, voice_message_handler))
|
||||
app_instance.add_handler(MessageHandler(filters.Document.ALL, handle_file_upload))
|
||||
app_instance.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, message_handler))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user