rustybook-messenger 0.2.1

Messenger client for Rustybook
Documentation
use serde_json::Value;

#[derive(Debug, Clone)]
pub enum RespOp {
    UpdateTypingIndicator {
        thread_id: String,
        user_id: String,
        is_typing: bool,
    },
    InsertMessage {
        thread_id: String,
        sender_id: String,
        message_id: String,
        text: Option<String>,
        timestamp_ms: Option<i64>,
    },
    TaskExists {
        task_id: String,
    },
    UpsertSyncGroupThreadsRange,
    UpsertInboxThreadsRange,
    UpdateThreadsRangesV2,
    RemoveTask {
        task_id: String,
    },
    ExecuteFirstBlockForSyncTransaction,
    UpsertSequenceId,
    VerifyContactRowExists,
    ExecuteFinallyBlockForSyncTransaction,
    Unknown {
        name: String,
        args: Vec<Value>,
        raw: Value,
    },
}

pub fn parse_operations(step: &Value) -> Vec<RespOp> {
    let mut output = Vec::new();
    collect_step_operations(step, &mut output);
    output
}

fn collect_step_operations(value: &Value, output: &mut Vec<RespOp>) {
    if let Some(operation) = parse_operation(value) {
        output.push(operation);
    }

    match value {
        Value::Array(items) => {
            for item in items {
                collect_step_operations(item, output);
            }
        }
        Value::Object(map) => {
            for child in map.values() {
                collect_step_operations(child, output);
            }
        }
        _ => {}
    }
}

fn parse_operation(value: &Value) -> Option<RespOp> {
    let items = value.as_array()?;
    if items.len() < 2 {
        return None;
    }

    if items[0].as_i64()? != 5 {
        return None;
    }

    let name = items[1].as_str()?.to_string();
    let args = items[2..].to_vec();

    match name.as_str() {
        "updateTypingIndicator" => {
            let thread_id = decode_ls_token(items.get(2)?)?;
            let user_id = decode_ls_token(items.get(3)?)?;
            let is_typing = items.get(4)?.as_bool()?;

            Some(RespOp::UpdateTypingIndicator {
                thread_id,
                user_id,
                is_typing,
            })
        }
        "insertMessage" => parse_insert_message(items),
        "taskExists" => Some(RespOp::TaskExists {
            task_id: decode_ls_token(items.get(2)?)?,
        }),
        "upsertSyncGroupThreadsRange" => Some(RespOp::UpsertSyncGroupThreadsRange),
        "upsertInboxThreadsRange" => Some(RespOp::UpsertInboxThreadsRange),
        "updateThreadsRangesV2" => Some(RespOp::UpdateThreadsRangesV2),
        "removeTask" => Some(RespOp::RemoveTask {
            task_id: decode_ls_token(items.get(2)?)?,
        }),
        "executeFirstBlockForSyncTransaction" => Some(RespOp::ExecuteFirstBlockForSyncTransaction),
        "upsertSequenceId" => Some(RespOp::UpsertSequenceId),
        "verifyContactRowExists" => Some(RespOp::VerifyContactRowExists),
        "executeFinallyBlockForSyncTransaction" => {
            Some(RespOp::ExecuteFinallyBlockForSyncTransaction)
        }
        _ => Some(RespOp::Unknown {
            name,
            args,
            raw: value.clone(),
        }),
    }
}

fn parse_insert_message(items: &[Value]) -> Option<RespOp> {
    if items.len() < 13 {
        return None;
    }

    let text = items.get(2).and_then(Value::as_str).map(str::to_owned);
    let thread_id = decode_ls_token(items.get(5)?)?;
    let timestamp_ms = items.get(7).and_then(decode_ls_token_as_i64);
    let message_id = items.get(10).and_then(Value::as_str)?.to_owned();
    let sender_id = decode_ls_token(items.get(12)?)?;

    Some(RespOp::InsertMessage {
        thread_id,
        sender_id,
        message_id,
        text,
        timestamp_ms,
    })
}

fn decode_ls_token(value: &Value) -> Option<String> {
    match value {
        Value::String(inner) => Some(inner.clone()),
        Value::Number(inner) => Some(inner.to_string()),
        Value::Array(items) => {
            if items.len() >= 2 && items[0].as_i64() == Some(19) {
                return decode_ls_token(&items[1]);
            }
            None
        }
        _ => None,
    }
}

fn decode_ls_token_as_i64(value: &Value) -> Option<i64> {
    decode_ls_token(value)?.parse::<i64>().ok()
}