use chrono::DateTime;
use crate::signal::types::*;
use super::message::{parse_data_message, parse_edit_message, parse_sent_sync};
pub fn parse_signal_event(
resp: &JsonRpcResponse,
download_dir: &std::path::Path,
) -> Option<SignalEvent> {
let method = resp.method.as_deref()?;
let params = resp.params.as_ref()?;
match method {
"receive" => parse_receive_event(params, download_dir),
_ => None,
}
}
pub(super) fn envelope_source(envelope: &serde_json::Value) -> String {
envelope
.get("sourceNumber")
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.or_else(|| envelope.get("sourceUuid").and_then(|v| v.as_str()))
.unwrap_or("unknown")
.to_string()
}
pub(super) fn sent_destination(sent: &serde_json::Value) -> Option<String> {
sent.get("destinationNumber")
.or_else(|| sent.get("destination"))
.or_else(|| sent.get("destinationUuid"))
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
}
fn parse_receive_event(
params: &serde_json::Value,
download_dir: &std::path::Path,
) -> Option<SignalEvent> {
if let Some(exc) = params.get("exception") {
let msg = exc
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("unknown error");
if msg.contains("SyncMessage missing destination") {
return None; }
let exc_type = exc.get("type").and_then(|v| v.as_str()).unwrap_or("");
if exc_type == "UntrustedIdentityException" {
let envelope = params.get("envelope");
let conv_id = envelope
.map(envelope_source)
.filter(|s| s != "unknown")
.or_else(|| {
exc.get("sender")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
})
.unwrap_or_else(|| "unknown".to_string());
let timestamp_ms = envelope
.and_then(|e| e.get("timestamp"))
.and_then(|v| v.as_i64())
.unwrap_or(0);
let timestamp = DateTime::from_timestamp_millis(timestamp_ms).unwrap_or_default();
return Some(SignalEvent::SystemMessage {
conv_id,
body: "\u{26A0} Safety number changed".to_string(),
timestamp,
timestamp_ms,
});
}
return Some(SignalEvent::Error(format!("signal-cli: {msg}")));
}
let envelope = params.get("envelope")?;
if envelope.get("typingMessage").is_some() {
return parse_typing_indicator(envelope);
}
if envelope.get("receiptMessage").is_some() {
return parse_receipt_message(envelope);
}
if let Some(call_msg) = envelope.get("callMessage") {
if call_msg.get("offerMessage").is_some() {
let call_type = call_msg
.get("offerMessage")
.and_then(|o| o.get("type"))
.and_then(|v| v.as_str())
.unwrap_or("AUDIO_CALL");
let kind = if call_type == "VIDEO_CALL" {
"video"
} else {
"voice"
};
let conv_id = envelope_source(envelope);
let timestamp_ms = envelope
.get("timestamp")
.and_then(|v| v.as_i64())
.unwrap_or(0);
let timestamp = DateTime::from_timestamp_millis(timestamp_ms).unwrap_or_default();
return Some(SignalEvent::SystemMessage {
conv_id,
body: format!("Missed {kind} call"),
timestamp,
timestamp_ms,
});
}
return None;
}
if let Some(edit_msg) = envelope.get("editMessage") {
return parse_edit_message(envelope, edit_msg, false, None);
}
if let Some(sync) = envelope.get("syncMessage") {
if let Some(sent) = sync.get("sentMessage") {
if let Some(edit_msg) = sent.get("editMessage") {
let dest = sent_destination(sent);
return parse_edit_message(envelope, edit_msg, true, dest.as_deref());
}
return parse_sent_sync(envelope, sent, download_dir);
}
if let Some(event) = parse_read_sync(sync) {
return Some(event);
}
return None;
}
parse_data_message(envelope, download_dir)
}
fn parse_read_sync(sync: &serde_json::Value) -> Option<SignalEvent> {
let read_messages = sync.get("readMessages")?.as_array()?;
if read_messages.is_empty() {
return None;
}
let entries: Vec<(String, i64)> = read_messages
.iter()
.filter_map(|entry| {
let sender = entry.get("sender").and_then(|v| v.as_str())?.to_string();
let timestamp = entry.get("timestamp").and_then(|v| v.as_i64())?;
Some((sender, timestamp))
})
.collect();
if entries.is_empty() {
return None;
}
Some(SignalEvent::ReadSyncReceived {
read_messages: entries,
})
}
fn parse_typing_indicator(envelope: &serde_json::Value) -> Option<SignalEvent> {
let typing = envelope.get("typingMessage")?;
let sender = envelope_source(envelope);
let sender_name = envelope
.get("sourceName")
.and_then(|v| v.as_str())
.filter(|s| !s.is_empty())
.map(|s| s.to_string());
let is_typing = typing
.get("action")
.and_then(|v| v.as_str())
.map(|a| a == "STARTED")
.unwrap_or(false);
let group_id = typing
.get("groupId")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Some(SignalEvent::TypingIndicator {
sender,
sender_name,
is_typing,
group_id,
})
}
fn parse_receipt_message(envelope: &serde_json::Value) -> Option<SignalEvent> {
let receipt = envelope.get("receiptMessage")?;
let sender = envelope_source(envelope);
let receipt_type = if receipt
.get("isRead")
.and_then(|v| v.as_bool())
.unwrap_or(false)
{
"READ"
} else if receipt
.get("isViewed")
.and_then(|v| v.as_bool())
.unwrap_or(false)
{
"VIEWED"
} else if receipt
.get("isDelivery")
.and_then(|v| v.as_bool())
.unwrap_or(false)
{
"DELIVERY"
} else {
receipt
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("UNKNOWN")
}
.to_string();
let timestamps: Vec<i64> = receipt
.get("timestamps")
.and_then(|v| v.as_array())
.map(|arr| arr.iter().filter_map(|v| v.as_i64()).collect())
.unwrap_or_default();
Some(SignalEvent::ReceiptReceived {
sender,
receipt_type,
timestamps,
})
}