use crate::call::domain::*;
use crate::callrecord::CallRecordHangupReason;
use crate::proxy::proxy_call::state::SessionAction;
use anyhow::Result;
use super::AdapterError;
pub fn call_command_to_session_action(cmd: CallCommand) -> Result<SessionAction> {
match cmd {
CallCommand::Answer { leg_id: _ } => {
Ok(SessionAction::AcceptCall {
callee: None,
sdp: None,
dialog_id: None,
})
}
CallCommand::Reject { leg_id: _, reason } => {
Ok(SessionAction::Hangup {
reason: reason.as_deref().map(|r| match r.to_lowercase().as_str() {
"busy" => CallRecordHangupReason::Failed,
"declined" | "rejected" => CallRecordHangupReason::Rejected,
_ => CallRecordHangupReason::BySystem,
}),
code: Some(603), initiator: Some("reject".to_string()),
})
}
CallCommand::Ring {
leg_id: _,
ringback,
} => {
let (rb, passthrough) = match ringback {
Some(RingbackPolicy::PassThrough) => (None, true),
Some(
RingbackPolicy::Replace { source } | RingbackPolicy::EarlyMedia { source },
) => {
let path = match source {
MediaSource::File { path } => path,
_ => String::new(),
};
(Some(path), false)
}
Some(RingbackPolicy::Block) => (None, false),
_ => (None, true),
};
Ok(SessionAction::StartRinging {
ringback: rb,
passthrough,
})
}
CallCommand::Hangup(hangup_cmd) => Ok(SessionAction::Hangup {
reason: hangup_cmd.reason,
code: hangup_cmd.code,
initiator: Some("local".to_string()),
}),
CallCommand::Bridge {
leg_a: _, leg_b, ..
} => {
Ok(SessionAction::BridgeTo {
target_session_id: leg_b.into(),
})
}
CallCommand::Unbridge { leg_id: _ } => Ok(SessionAction::Unbridge),
CallCommand::Transfer {
leg_id: _,
target,
attended: _,
} => Ok(SessionAction::TransferTarget(target)),
CallCommand::TransferComplete { consult_leg: _ }
| CallCommand::TransferCancel { consult_leg: _ }
| CallCommand::TransferCompleteCrossSession { .. }
| CallCommand::BridgeCrossSession { .. } => {
Err(AdapterError::NotSupported(
"transfer complete/cancel requires multi-leg coordination".to_string(),
)
.into())
}
CallCommand::Hold { leg_id: _, music } => Ok(SessionAction::Hold {
music_source: music.and_then(|m| match m {
MediaSource::File { path } => Some(path),
_ => None,
}),
}),
CallCommand::Unhold { leg_id: _ } => Ok(SessionAction::Unhold),
CallCommand::Play {
leg_id: _,
source,
options,
} => {
let path = match source {
MediaSource::File { path } => path,
_ => {
return Err(
AdapterError::NotSupported("non-file media source".to_string()).into(),
);
}
};
let opts = options.unwrap_or_default();
Ok(SessionAction::PlayPrompt {
audio_file: path,
send_progress: opts.send_progress,
await_completion: opts.await_completion,
track_id: opts.track_id,
loop_playback: opts.loop_playback,
interrupt_on_dtmf: opts.interrupt_on_dtmf,
})
}
CallCommand::StopPlayback { leg_id: _ } => Ok(SessionAction::StopPlayback),
CallCommand::SendDtmf { .. } => {
Err(AdapterError::NotSupported("dtmf sending".to_string()).into())
}
CallCommand::StartRecording { config } => Ok(SessionAction::StartRecording {
path: config.path,
max_duration: config
.max_duration_secs
.map(|s| std::time::Duration::from_secs(s as u64)),
beep: config.beep,
}),
CallCommand::PauseRecording => Ok(SessionAction::PauseRecording),
CallCommand::ResumeRecording => Ok(SessionAction::ResumeRecording),
CallCommand::StopRecording => Ok(SessionAction::StopRecording),
CallCommand::SupervisorListen { target_leg, .. } => Ok(SessionAction::SupervisorListen {
target_session_id: target_leg.into(),
}),
CallCommand::SupervisorWhisper { target_leg, .. } => Ok(SessionAction::SupervisorWhisper {
target_session_id: target_leg.into(),
}),
CallCommand::SupervisorBarge { target_leg, .. } => Ok(SessionAction::SupervisorBarge {
target_session_id: target_leg.into(),
}),
CallCommand::SupervisorTakeover { target_leg, .. } => {
Ok(SessionAction::SupervisorTakeover {
target_session_id: target_leg.into(),
})
}
CallCommand::SupervisorStop { .. } => Ok(SessionAction::SupervisorStop),
CallCommand::HandleReInvite { leg_id: _, sdp } => {
Ok(SessionAction::HandleReInvite("INVITE".to_string(), sdp))
}
CallCommand::RefreshSession => Ok(SessionAction::RefreshSession),
CallCommand::MuteTrack { track_id } => Ok(SessionAction::MuteTrack(track_id)),
CallCommand::UnmuteTrack { track_id } => Ok(SessionAction::UnmuteTrack(track_id)),
CallCommand::ConferenceCreate { .. }
| CallCommand::ConferenceAdd { .. }
| CallCommand::ConferenceRemove { .. }
| CallCommand::ConferenceMute { .. }
| CallCommand::ConferenceUnmute { .. }
| CallCommand::ConferenceDestroy { .. }
| CallCommand::ConferenceEnd { .. }
| CallCommand::ConferenceKick { .. }
| CallCommand::ConferenceMuteAll { .. }
| CallCommand::ConferenceInfo { .. }
| CallCommand::ConferenceList
| CallCommand::JoinMixer { .. }
| CallCommand::LeaveMixer => {
Err(AdapterError::NotSupported("conference commands".to_string()).into())
}
CallCommand::QueueEnqueue { .. } | CallCommand::QueueDequeue { .. } => {
Err(AdapterError::NotSupported("queue commands".to_string()).into())
}
CallCommand::SendSipMessage { .. }
| CallCommand::SendSipNotify { .. }
| CallCommand::SendSipOptionsPing
| CallCommand::StartApp { .. }
| CallCommand::StopApp { .. }
| CallCommand::InjectAppEvent { .. }
| CallCommand::DtmfCollect { .. } => {
Err(AdapterError::NotSupported(format!("{:?}", cmd)).into())
}
CallCommand::LegAdd { .. }
| CallCommand::LegRemove { .. }
| CallCommand::LegConnected { .. }
| CallCommand::LegFailed { .. } => {
Err(AdapterError::NotSupported("dynamic leg commands".to_string()).into())
}
}
}
pub fn session_action_to_call_command(action: SessionAction) -> Result<CallCommand> {
match action {
SessionAction::AcceptCall { .. } => Ok(CallCommand::Answer {
leg_id: LegId::new("caller"),
}),
SessionAction::TransferTarget(target) => Ok(CallCommand::Transfer {
leg_id: LegId::new("caller"),
target,
attended: false,
}),
SessionAction::PlayPrompt {
audio_file,
send_progress,
await_completion,
track_id,
loop_playback,
interrupt_on_dtmf,
} => Ok(CallCommand::Play {
leg_id: None,
source: MediaSource::File { path: audio_file },
options: Some(PlayOptions {
send_progress,
await_completion,
track_id,
loop_playback,
interrupt_on_dtmf,
}),
}),
SessionAction::StartRecording {
path,
max_duration,
beep,
} => Ok(CallCommand::StartRecording {
config: crate::call::domain::RecordConfig {
path,
max_duration_secs: max_duration.map(|d| d.as_secs() as u32),
beep,
format: None,
},
}),
SessionAction::PauseRecording => Ok(CallCommand::PauseRecording),
SessionAction::ResumeRecording => Ok(CallCommand::ResumeRecording),
SessionAction::StopRecording => Ok(CallCommand::StopRecording),
SessionAction::Hangup {
reason,
code,
initiator,
} => Ok(CallCommand::Hangup(HangupCommand {
leg_id: None,
cascade: crate::call::domain::HangupCascade::All,
initiator: crate::call::domain::HangupInitiator::Local {
source: initiator.unwrap_or_else(|| "app".to_string()),
command_id: None,
},
reason,
code,
})),
SessionAction::StopPlayback => Ok(CallCommand::StopPlayback { leg_id: None }),
SessionAction::Hold { music_source } => Ok(CallCommand::Hold {
leg_id: LegId::new("caller"),
music: music_source.map(|path| MediaSource::File { path }),
}),
SessionAction::Unhold => Ok(CallCommand::Unhold {
leg_id: LegId::new("caller"),
}),
SessionAction::BridgeTo { target_session_id } => Ok(CallCommand::Bridge {
leg_a: LegId::new("caller"),
leg_b: target_session_id.into(),
mode: crate::call::domain::P2PMode::Audio,
}),
SessionAction::Unbridge => Ok(CallCommand::Unbridge {
leg_id: LegId::new("caller"),
}),
SessionAction::HandleReInvite(_method, sdp) => Ok(CallCommand::HandleReInvite {
leg_id: LegId::new("caller"),
sdp,
}),
SessionAction::RefreshSession => Ok(CallCommand::RefreshSession),
SessionAction::MuteTrack(track_id) => Ok(CallCommand::MuteTrack { track_id }),
SessionAction::UnmuteTrack(track_id) => Ok(CallCommand::UnmuteTrack { track_id }),
SessionAction::SupervisorListen { target_session_id } => {
Ok(CallCommand::SupervisorListen {
supervisor_leg: LegId::new("supervisor"),
target_leg: target_session_id.into(),
supervisor_session_id: None,
})
}
SessionAction::SupervisorWhisper { target_session_id } => {
Ok(CallCommand::SupervisorWhisper {
supervisor_leg: LegId::new("supervisor"),
target_leg: target_session_id.into(),
supervisor_session_id: None,
})
}
SessionAction::SupervisorBarge { target_session_id } => Ok(CallCommand::SupervisorBarge {
supervisor_leg: LegId::new("supervisor"),
target_leg: target_session_id.into(),
supervisor_session_id: None,
}),
SessionAction::SupervisorTakeover { target_session_id } => {
Ok(CallCommand::SupervisorTakeover {
supervisor_leg: LegId::new("supervisor"),
target_leg: target_session_id.into(),
supervisor_session_id: None,
})
}
SessionAction::SupervisorStop => Ok(CallCommand::SupervisorStop {
supervisor_leg: LegId::new("supervisor"),
}),
SessionAction::StartRinging {
ringback,
passthrough,
} => Ok(CallCommand::Ring {
leg_id: LegId::new("caller"),
ringback: if passthrough {
Some(RingbackPolicy::PassThrough)
} else {
ringback.map(|path| RingbackPolicy::Replace {
source: MediaSource::File { path },
})
},
}),
_ => Err(AdapterError::NotSupported(format!("{:?}", action)).into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hangup_to_session_action() {
let cmd = CallCommand::Hangup(HangupCommand::all(
Some(CallRecordHangupReason::BySystem),
Some(200),
));
let action = call_command_to_session_action(cmd).unwrap();
if let SessionAction::Hangup { code, .. } = action {
assert_eq!(code, Some(200));
} else {
panic!("Expected Hangup action");
}
}
#[test]
fn test_hold_to_session_action() {
let cmd = CallCommand::Hold {
leg_id: LegId::new("leg-1"),
music: Some(MediaSource::file("music.wav")),
};
let action = call_command_to_session_action(cmd).unwrap();
if let SessionAction::Hold { music_source } = action {
assert_eq!(music_source, Some("music.wav".to_string()));
} else {
panic!("Expected Hold action");
}
}
#[test]
fn test_play_to_session_action() {
let cmd = CallCommand::Play {
leg_id: None,
source: MediaSource::file("prompt.wav"),
options: Some(PlayOptions {
interrupt_on_dtmf: true,
..Default::default()
}),
};
let action = call_command_to_session_action(cmd).unwrap();
if let SessionAction::PlayPrompt {
audio_file,
interrupt_on_dtmf,
..
} = action
{
assert_eq!(audio_file, "prompt.wav");
assert!(interrupt_on_dtmf);
} else {
panic!("Expected PlayPrompt action");
}
}
#[test]
fn test_unsupported_command() {
let cmd = CallCommand::StartApp {
app_name: "ivr".to_string(),
params: None,
auto_answer: false,
};
let result = call_command_to_session_action(cmd);
assert!(result.is_err());
}
}