feat(voicemail): implement new voicemail notification callback and enhance call history retrieval with search and pagination support
This commit is contained in:
@@ -372,6 +372,7 @@ class ReticulumMeshChat:
|
|||||||
self.voicemail_manager.get_name_for_identity_hash = (
|
self.voicemail_manager.get_name_for_identity_hash = (
|
||||||
self.get_name_for_identity_hash
|
self.get_name_for_identity_hash
|
||||||
)
|
)
|
||||||
|
self.voicemail_manager.on_new_voicemail_callback = self.on_new_voicemail_received
|
||||||
|
|
||||||
# init Ringtone Manager
|
# init Ringtone Manager
|
||||||
self.ringtone_manager = RingtoneManager(
|
self.ringtone_manager = RingtoneManager(
|
||||||
@@ -1335,8 +1336,39 @@ class ReticulumMeshChat:
|
|||||||
value = value.lower()
|
value = value.lower()
|
||||||
return value in {"1", "true", "yes", "on"}
|
return value in {"1", "true", "yes", "on"}
|
||||||
|
|
||||||
|
def on_new_voicemail_received(self, remote_hash, remote_name, duration):
|
||||||
|
AsyncUtils.run_async(
|
||||||
|
self.websocket_broadcast(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"type": "new_voicemail",
|
||||||
|
"remote_identity_hash": remote_hash,
|
||||||
|
"remote_identity_name": remote_name,
|
||||||
|
"duration": duration,
|
||||||
|
"timestamp": time.time(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# handle receiving a new audio call
|
# handle receiving a new audio call
|
||||||
def on_incoming_telephone_call(self, caller_identity: RNS.Identity):
|
def on_incoming_telephone_call(self, caller_identity: RNS.Identity):
|
||||||
|
caller_hash = caller_identity.hash.hex()
|
||||||
|
|
||||||
|
# Check if caller is blocked
|
||||||
|
if self.is_destination_blocked(caller_hash):
|
||||||
|
print(f"Rejecting incoming call from blocked source: {caller_hash}")
|
||||||
|
if self.telephone_manager.telephone:
|
||||||
|
self.telephone_manager.telephone.hangup()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check for Do Not Disturb
|
||||||
|
if self.config.do_not_disturb_enabled.get():
|
||||||
|
print(f"Rejecting incoming call due to Do Not Disturb: {caller_hash}")
|
||||||
|
if self.telephone_manager.telephone:
|
||||||
|
self.telephone_manager.telephone.hangup()
|
||||||
|
return
|
||||||
|
|
||||||
# Trigger voicemail handling
|
# Trigger voicemail handling
|
||||||
self.voicemail_manager.handle_incoming_call(caller_identity)
|
self.voicemail_manager.handle_incoming_call(caller_identity)
|
||||||
|
|
||||||
@@ -1403,6 +1435,21 @@ class ReticulumMeshChat:
|
|||||||
timestamp=time.time(),
|
timestamp=time.time(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Trigger missed call notification if it was an incoming call that ended while ringing
|
||||||
|
if is_incoming and status_code == 4:
|
||||||
|
AsyncUtils.run_async(
|
||||||
|
self.websocket_broadcast(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"type": "telephone_missed_call",
|
||||||
|
"remote_identity_hash": remote_identity_hash,
|
||||||
|
"remote_identity_name": remote_identity_name,
|
||||||
|
"timestamp": time.time(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
AsyncUtils.run_async(
|
AsyncUtils.run_async(
|
||||||
self.websocket_broadcast(
|
self.websocket_broadcast(
|
||||||
json.dumps(
|
json.dumps(
|
||||||
@@ -3139,7 +3186,13 @@ class ReticulumMeshChat:
|
|||||||
@routes.get("/api/v1/telephone/history")
|
@routes.get("/api/v1/telephone/history")
|
||||||
async def telephone_history(request):
|
async def telephone_history(request):
|
||||||
limit = int(request.query.get("limit", 10))
|
limit = int(request.query.get("limit", 10))
|
||||||
history = self.database.telephone.get_call_history(limit=limit)
|
offset = int(request.query.get("offset", 0))
|
||||||
|
search = request.query.get("search", None)
|
||||||
|
history = self.database.telephone.get_call_history(
|
||||||
|
search=search,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset,
|
||||||
|
)
|
||||||
|
|
||||||
call_history = []
|
call_history = []
|
||||||
for row in history:
|
for row in history:
|
||||||
|
|||||||
@@ -135,6 +135,9 @@ class ConfigManager:
|
|||||||
self.custom_ringtone_enabled = self.BoolConfig(self, "custom_ringtone_enabled", False)
|
self.custom_ringtone_enabled = self.BoolConfig(self, "custom_ringtone_enabled", False)
|
||||||
self.ringtone_filename = self.StringConfig(self, "ringtone_filename", None)
|
self.ringtone_filename = self.StringConfig(self, "ringtone_filename", None)
|
||||||
|
|
||||||
|
# telephony config
|
||||||
|
self.do_not_disturb_enabled = self.BoolConfig(self, "do_not_disturb_enabled", False)
|
||||||
|
|
||||||
# map config
|
# map config
|
||||||
self.map_offline_enabled = self.BoolConfig(self, "map_offline_enabled", False)
|
self.map_offline_enabled = self.BoolConfig(self, "map_offline_enabled", False)
|
||||||
self.map_offline_path = self.StringConfig(self, "map_offline_path", None)
|
self.map_offline_path = self.StringConfig(self, "map_offline_path", None)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from .provider import DatabaseProvider
|
|||||||
|
|
||||||
|
|
||||||
class DatabaseSchema:
|
class DatabaseSchema:
|
||||||
LATEST_VERSION = 18
|
LATEST_VERSION = 19
|
||||||
|
|
||||||
def __init__(self, provider: DatabaseProvider):
|
def __init__(self, provider: DatabaseProvider):
|
||||||
self.provider = provider
|
self.provider = provider
|
||||||
@@ -551,6 +551,11 @@ class DatabaseSchema:
|
|||||||
"CREATE INDEX IF NOT EXISTS idx_contacts_remote_identity_hash ON contacts(remote_identity_hash)",
|
"CREATE INDEX IF NOT EXISTS idx_contacts_remote_identity_hash ON contacts(remote_identity_hash)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if current_version < 19:
|
||||||
|
self.provider.execute(
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_call_history_remote_name ON call_history(remote_identity_name)",
|
||||||
|
)
|
||||||
|
|
||||||
# Update version in config
|
# Update version in config
|
||||||
self.provider.execute(
|
self.provider.execute(
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -40,10 +40,19 @@ class TelephoneDAO:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_call_history(self, limit=10):
|
def get_call_history(self, search=None, limit=10, offset=0):
|
||||||
|
if search:
|
||||||
|
return self.provider.fetchall(
|
||||||
|
"""
|
||||||
|
SELECT * FROM call_history
|
||||||
|
WHERE remote_identity_name LIKE ? OR remote_identity_hash LIKE ?
|
||||||
|
ORDER BY timestamp DESC LIMIT ? OFFSET ?
|
||||||
|
""",
|
||||||
|
(f"%{search}%", f"%{search}%", limit, offset),
|
||||||
|
)
|
||||||
return self.provider.fetchall(
|
return self.provider.fetchall(
|
||||||
"SELECT * FROM call_history ORDER BY timestamp DESC LIMIT ?",
|
"SELECT * FROM call_history ORDER BY timestamp DESC LIMIT ? OFFSET ?",
|
||||||
(limit,),
|
(limit, offset),
|
||||||
)
|
)
|
||||||
|
|
||||||
def clear_call_history(self):
|
def clear_call_history(self):
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class VoicemailManager:
|
|||||||
self.recording_remote_identity = None
|
self.recording_remote_identity = None
|
||||||
self.recording_filename = None
|
self.recording_filename = None
|
||||||
|
|
||||||
|
self.on_new_voicemail_callback = None
|
||||||
|
|
||||||
# Paths to executables
|
# Paths to executables
|
||||||
self.espeak_path = self._find_espeak()
|
self.espeak_path = self._find_espeak()
|
||||||
self.ffmpeg_path = self._find_ffmpeg()
|
self.ffmpeg_path = self._find_ffmpeg()
|
||||||
@@ -377,6 +379,13 @@ class VoicemailManager:
|
|||||||
f"Saved voicemail from {RNS.prettyhexrep(self.recording_remote_identity.hash)} ({duration}s)",
|
f"Saved voicemail from {RNS.prettyhexrep(self.recording_remote_identity.hash)} ({duration}s)",
|
||||||
RNS.LOG_DEBUG,
|
RNS.LOG_DEBUG,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.on_new_voicemail_callback:
|
||||||
|
self.on_new_voicemail_callback(
|
||||||
|
self.recording_remote_identity.hash.hex(),
|
||||||
|
remote_name,
|
||||||
|
duration,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Delete short/empty recording
|
# Delete short/empty recording
|
||||||
filepath = os.path.join(self.recordings_dir, self.recording_filename)
|
filepath = os.path.join(self.recordings_dir, self.recording_filename)
|
||||||
|
|||||||
Reference in New Issue
Block a user