feat(voicemail): refactor VoicemailManager to use config for settings, add greeting conversion and removal methods

This commit is contained in:
2026-01-01 17:34:47 -06:00
parent 0889cd0a04
commit cadf6fe66f

View File

@@ -14,8 +14,9 @@ from LXST.Sources import OpusFileSource
class VoicemailManager:
def __init__(self, db, telephone_manager, storage_dir):
def __init__(self, db, config, telephone_manager, storage_dir):
self.db = db
self.config = config
self.telephone_manager = telephone_manager
self.storage_dir = os.path.join(storage_dir, "voicemails")
self.greetings_dir = os.path.join(self.storage_dir, "greetings")
@@ -50,6 +51,10 @@ class VoicemailManager:
else:
RNS.log("Voicemail: ffmpeg not found", RNS.LOG_ERROR)
def get_name_for_identity_hash(self, identity_hash):
"""Default implementation, should be patched by ReticulumMeshChat"""
return None
def _find_espeak(self):
# Try standard name first
path = shutil.which("espeak-ng")
@@ -103,53 +108,82 @@ class VoicemailManager:
# espeak-ng to WAV
subprocess.run([self.espeak_path, "-w", wav_path, text], check=True)
# ffmpeg to Opus
if os.path.exists(opus_path):
os.remove(opus_path)
subprocess.run(
[
self.ffmpeg_path,
"-i",
wav_path,
"-c:a",
"libopus",
"-b:a",
"16k",
"-vbr",
"on",
opus_path,
],
check=True,
)
return opus_path
# Convert WAV to Opus
return self.convert_to_greeting(wav_path)
finally:
if os.path.exists(wav_path):
os.remove(wav_path)
def convert_to_greeting(self, input_path):
if not self.has_ffmpeg:
msg = "ffmpeg is required for audio conversion"
raise RuntimeError(msg)
opus_path = os.path.join(self.greetings_dir, "greeting.opus")
if os.path.exists(opus_path):
os.remove(opus_path)
subprocess.run(
[
self.ffmpeg_path,
"-i",
input_path,
"-c:a",
"libopus",
"-b:a",
"16k",
"-vbr",
"on",
opus_path,
],
check=True,
)
return opus_path
def remove_greeting(self):
opus_path = os.path.join(self.greetings_dir, "greeting.opus")
if os.path.exists(opus_path):
os.remove(opus_path)
return True
def handle_incoming_call(self, caller_identity):
if not self.db.config.voicemail_enabled.get():
RNS.log(f"Voicemail: handle_incoming_call from {RNS.prettyhexrep(caller_identity.hash)}", RNS.LOG_DEBUG)
if not self.config.voicemail_enabled.get():
RNS.log("Voicemail: Voicemail is disabled", RNS.LOG_DEBUG)
return
delay = self.db.config.voicemail_auto_answer_delay_seconds.get()
delay = self.config.voicemail_auto_answer_delay_seconds.get()
RNS.log(f"Voicemail: Will auto-answer in {delay} seconds", RNS.LOG_DEBUG)
def voicemail_job():
RNS.log(f"Voicemail: Auto-answer timer started for {RNS.prettyhexrep(caller_identity.hash)}", RNS.LOG_DEBUG)
time.sleep(delay)
# Check if still ringing and no other active call
telephone = self.telephone_manager.telephone
if not telephone:
RNS.log("Voicemail: No telephone object", RNS.LOG_ERROR)
return
RNS.log(f"Voicemail: Checking status. Call status: {telephone.call_status}, Active call: {telephone.active_call}", RNS.LOG_DEBUG)
if (
telephone
and telephone.active_call
and telephone.active_call.get_remote_identity() == caller_identity
and telephone.active_call.get_remote_identity().hash == caller_identity.hash
and telephone.call_status == LXST.Signalling.STATUS_RINGING
):
RNS.log(
f"Auto-answering call from {RNS.prettyhexrep(caller_identity.hash)} for voicemail",
RNS.LOG_DEBUG,
RNS.LOG_INFO,
)
self.start_voicemail_session(caller_identity)
else:
RNS.log("Voicemail: Auto-answer conditions not met after delay", RNS.LOG_DEBUG)
if telephone.active_call:
RNS.log(f"Voicemail: Active call remote: {RNS.prettyhexrep(telephone.active_call.get_remote_identity().hash)}", RNS.LOG_DEBUG)
threading.Thread(target=voicemail_job, daemon=True).start()
@@ -170,7 +204,7 @@ class VoicemailManager:
greeting_path = os.path.join(self.greetings_dir, "greeting.opus")
if not os.path.exists(greeting_path):
# Fallback if no greeting generated yet
self.generate_greeting(self.db.config.voicemail_greeting.get())
self.generate_greeting(self.config.voicemail_greeting.get())
def session_job():
try:
@@ -206,7 +240,7 @@ class VoicemailManager:
self.start_recording(caller_identity)
# 4. Wait for max recording time or hangup
max_time = self.db.config.voicemail_max_recording_seconds.get()
max_time = self.config.voicemail_max_recording_seconds.get()
start_wait = time.time()
while self.is_recording and (time.time() - start_wait < max_time):
time.sleep(0.5)
@@ -271,7 +305,7 @@ class VoicemailManager:
# Save to database if long enough
if duration >= 1:
remote_name = self.telephone_manager.get_name_for_identity_hash(
remote_name = self.get_name_for_identity_hash(
self.recording_remote_identity.hash.hex()
)
self.db.voicemails.add_voicemail(