use super::kind::{LifecycleEvent, MessageKind, SipInviteDirection};
use super::media::detect_media;
use super::parts::set_export_parts;
use super::varname::VarName;
pub(super) fn originate_success(msg: &str) -> Option<MessageKind> {
let rest = msg.strip_prefix("Originate Resulted in Success: ")?;
let inner = rest.strip_prefix('[')?;
let close = inner.find(']')?;
let peer_uuid = inner[close..]
.split_once("Peer UUID: ")
.map(|(_, u)| u.trim().to_string());
Some(MessageKind::OriginateSuccess {
channel: inner[..close].to_string(),
peer_uuid,
})
}
pub(crate) fn channel_lifecycle(detail: &str) -> MessageKind {
let event = if detail.starts_with("New Channel ") {
LifecycleEvent::NewChannel
} else if detail.starts_with("Hangup ") {
LifecycleEvent::Hangup
} else if detail.starts_with("Close Channel ") {
LifecycleEvent::Destroy
} else if detail.contains("has been answered") {
LifecycleEvent::Answered
} else {
LifecycleEvent::Other
};
MessageKind::ChannelLifecycle {
event,
detail: detail.to_string(),
}
}
pub(crate) fn is_channel_variable_narration(rest: &str) -> bool {
rest.starts_with("EXPORTING[") || rest.starts_with("setting variable [")
}
pub(super) fn classify_channel_prefixed(channel_part: &str, rest: &str) -> MessageKind {
if let Some(direction) = sip_invite_direction(rest) {
let profile = extract_sofia_profile(channel_part).unwrap_or_default();
let call_id = extract_call_id(rest);
return MessageKind::SipInvite {
direction,
profile,
call_id,
};
}
if is_channel_variable_narration(rest) {
if let Some(parts) = set_export_parts(rest) {
return MessageKind::Variable {
name: VarName::new(parts.name),
value: parts.value.to_string(),
};
}
}
if rest.starts_with("SOFIA ") || rest.starts_with("Standard ") || rest.starts_with("RTC ") {
return MessageKind::StateChange {
detail: rest.to_string(),
};
}
if let Some(kind) = detect_media(rest) {
return kind;
}
channel_lifecycle(rest)
}
pub(crate) fn sip_invite_direction(rest: &str) -> Option<SipInviteDirection> {
if rest.starts_with("receiving invite") {
Some(SipInviteDirection::Receiving)
} else if rest.starts_with("sending invite") {
Some(SipInviteDirection::Sending)
} else {
None
}
}
fn extract_sofia_profile(channel_part: &str) -> Option<String> {
let after = channel_part.strip_prefix("sofia/")?;
let end = after.find('/').unwrap_or(after.len());
if end == 0 {
None
} else {
Some(after[..end].to_string())
}
}
pub(crate) fn call_id_token(rest: &str) -> Option<&str> {
let after = rest.split_once("call-id: ")?.1;
let token = after.split_whitespace().next()?;
if token == "(null)" {
None
} else {
Some(token)
}
}
fn extract_call_id(rest: &str) -> Option<String> {
call_id_token(rest).map(str::to_string)
}
pub(super) fn detect_channel_lifecycle(msg: &str) -> Option<MessageKind> {
let lifecycle_prefixes = [
"New Channel ",
"Close Channel ",
"Hangup ",
"Ring-Ready ",
"Ring Ready ",
"Pre-Answer ",
"Sending early media",
"Sending BYE",
"Sending CANCEL",
"Channel is hung up",
"Call appears",
"Found channel",
"3PCC ",
"Subscribed to 3PCC",
"New log started",
"Received a ",
"Session ",
"BRIDGE ",
"Originate ",
"USAGE:",
"Split into",
"Part ",
"Responding to INVITE",
"Redirecting to",
"subscribing to",
"Queue digit delay",
"Channel ",
];
if lifecycle_prefixes.iter().any(|p| msg.starts_with(p)) {
return Some(channel_lifecycle(msg));
}
if msg.starts_with("Application ") && msg.contains("Requires media") {
return Some(channel_lifecycle(msg));
}
None
}