use serde_json::Value;
use crate::error::MessengerError;
use crate::gateway::events::{
Event,
MessageEvent,
PresenceEvent,
TypingEvent,
};
pub(super) fn parse_messages(payload: &[u8]) -> Result<Vec<Event>, MessengerError> {
let value: Value = serde_json::from_slice(payload)?;
let message_payload = normalize_message_payload(value);
let mut output = Vec::new();
let deltas = extract_deltas(&message_payload);
for delta in deltas {
if let Some(event) = parse_message_delta(&delta) {
output.push(Event::Message(event));
}
}
Ok(output)
}
pub(super) fn parse_typing(payload: &[u8]) -> Result<Vec<Event>, MessengerError> {
let value: Value = serde_json::from_slice(payload)?;
let user_id = value
.get("sender_fbid")
.or_else(|| value.get("from"))
.and_then(value_as_string)
.unwrap_or_default();
if user_id.is_empty() {
return Ok(Vec::new());
}
let is_typing = value
.get("state")
.and_then(Value::as_i64)
.map(|state| state == 1)
.unwrap_or(false);
let thread_id = value
.get("thread")
.or_else(|| value.get("thread_fbid"))
.and_then(value_as_string);
Ok(vec![Event::Typing(TypingEvent {
user_id,
thread_id,
is_typing,
})])
}
pub(super) fn parse_presence(payload: &[u8]) -> Result<Vec<Event>, MessengerError> {
let value: Value = serde_json::from_slice(payload)?;
let mut output = Vec::new();
let list = value
.get("list")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default();
for item in list {
let Some(user_id) = item.get("u").and_then(value_as_string) else {
continue;
};
let is_active = item
.get("p")
.and_then(Value::as_i64)
.map(|presence| presence > 0)
.unwrap_or(false);
let last_active_ms = item.get("l").and_then(Value::as_i64);
output.push(Event::Presence(PresenceEvent {
user_id,
is_active,
last_active_ms,
}));
}
Ok(output)
}
fn normalize_message_payload(mut value: Value) -> Value {
if let Some(inner) = value.get("d").and_then(parse_nested_json) {
value = inner;
}
if let Some(inner) = value.get("payload").and_then(parse_nested_json) {
value = inner;
}
value
}
fn parse_nested_json(value: &Value) -> Option<Value> {
if value.is_object() || value.is_array() {
return Some(value.clone());
}
let raw = value.as_str()?;
serde_json::from_str::<Value>(raw).ok()
}
fn extract_deltas(value: &Value) -> Vec<Value> {
if let Some(deltas) = value.get("deltas").and_then(Value::as_array) {
return deltas.clone();
}
if let Some(delta) = value.get("delta") {
if let Some(array) = delta.as_array() {
return array.clone();
}
return vec![delta.clone()];
}
if let Some(inner) = value.get("d").and_then(parse_nested_json) {
return extract_deltas(&inner);
}
if let Some(inner) = value.get("payload").and_then(parse_nested_json) {
return extract_deltas(&inner);
}
Vec::new()
}
pub(super) fn parse_message_delta(delta: &Value) -> Option<MessageEvent> {
if let Some(message) = parse_message_like(delta) {
return Some(message);
}
if let Some(inner) = delta.get("delta").and_then(parse_message_like) {
return Some(inner);
}
if let Some(inner) = delta.get("message").and_then(parse_message_like) {
return Some(inner);
}
if let Some(inner) = delta.get("deltaNewMessage").and_then(parse_message_like) {
return Some(inner);
}
if let Some(inner) = delta
.get("deltaMessageReply")
.and_then(|reply| reply.get("message"))
.and_then(parse_message_like)
{
return Some(inner);
}
None
}
fn parse_message_like(value: &Value) -> Option<MessageEvent> {
if let Some(class) = value.get("class").and_then(value_as_string)
&& class != "NewMessage"
{
return None;
}
let metadata = value.get("messageMetadata")?;
let thread_id = metadata
.get("threadKey")
.and_then(|thread_key| {
thread_key
.get("threadFbId")
.or_else(|| thread_key.get("otherUserFbId"))
})
.and_then(value_as_string)?;
let sender_id = metadata
.get("actorFbId")
.and_then(value_as_string)
.unwrap_or_default();
let message_id = metadata.get("messageId").and_then(value_as_string);
let timestamp_ms = metadata.get("timestamp").and_then(value_as_i64);
let text = value.get("body").and_then(value_as_string);
Some(MessageEvent {
message_id,
thread_id,
sender_id,
text,
timestamp_ms,
})
}
pub(super) fn value_as_string(value: &Value) -> Option<String> {
match value {
Value::String(inner) => Some(inner.clone()),
Value::Number(inner) => Some(inner.to_string()),
_ => None,
}
}
pub(super) fn value_as_i64(value: &Value) -> Option<i64> {
match value {
Value::Number(inner) => inner.as_i64(),
Value::String(inner) => inner.parse::<i64>().ok(),
_ => None,
}
}