concord 2.5.0

A terminal user interface client for Discord
use serde_json::Value;

use crate::discord::{
    StreamCreateInfo, StreamDeleteInfo, StreamServerInfo, StreamUpdateInfo,
    events::AppEvent,
    ids::{
        Id,
        marker::{ChannelMarker, UserMarker},
    },
};

use super::shared::parse_id;

pub(super) fn parse_stream_create(data: &Value) -> Option<AppEvent> {
    let stream_key = required_string(data, "stream_key")?;
    let rtc_server_id = required_string(data, "rtc_server_id")?;
    let rtc_channel_id = data
        .get("rtc_channel_id")
        .and_then(parse_id::<ChannelMarker>)?;
    let viewer_ids = parse_viewer_ids(data).unwrap_or_default();
    let paused = data.get("paused").and_then(Value::as_bool).unwrap_or(false);

    Some(AppEvent::StreamCreate {
        stream: StreamCreateInfo {
            stream_key,
            rtc_server_id,
            rtc_channel_id,
            viewer_ids,
            paused,
        },
    })
}

pub(super) fn parse_stream_update(data: &Value) -> Option<AppEvent> {
    Some(AppEvent::StreamUpdate {
        stream: StreamUpdateInfo {
            stream_key: required_string(data, "stream_key")?,
            viewer_ids: parse_viewer_ids(data)?,
            paused: data.get("paused").and_then(Value::as_bool).unwrap_or(false),
        },
    })
}

pub(super) fn parse_stream_server_update(data: &Value) -> Option<AppEvent> {
    let stream_key = required_string(data, "stream_key")?;
    let token = required_string(data, "token")?;
    let endpoint = data
        .get("endpoint")
        .filter(|endpoint| !endpoint.is_null())
        .and_then(Value::as_str)
        .filter(|endpoint| !endpoint.is_empty())
        .map(str::to_owned);

    Some(AppEvent::StreamServerUpdate {
        server: StreamServerInfo {
            stream_key,
            endpoint,
            token,
        },
    })
}

pub(super) fn parse_stream_delete(data: &Value) -> Option<AppEvent> {
    Some(AppEvent::StreamDelete {
        stream: StreamDeleteInfo {
            stream_key: required_string(data, "stream_key")?,
            reason: data
                .get("reason")
                .and_then(Value::as_str)
                .unwrap_or_default()
                .to_owned(),
            unavailable: data
                .get("unavailable")
                .and_then(Value::as_bool)
                .unwrap_or(false),
        },
    })
}

fn required_string(data: &Value, field: &str) -> Option<String> {
    data.get(field)
        .and_then(Value::as_str)
        .filter(|value| !value.is_empty())
        .map(str::to_owned)
}

fn parse_viewer_ids(data: &Value) -> Option<Vec<Id<UserMarker>>> {
    data.get("viewer_ids")
        .and_then(Value::as_array)
        .map(|viewer_ids| {
            viewer_ids
                .iter()
                .filter_map(parse_id::<UserMarker>)
                .collect()
        })
}