cord-nvim 2.0.0-beta.28

🚀 The most extensible Discord Rich Presence plugin for Neovim, powered by Rust.
use std::str::FromStr;

use crate::protocol::json;
use crate::protocol::msgpack::{self, Value};
use crate::{get_field_or_none, remove_field, remove_field_or_none};

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Activity {
    pub ty: ActivityType,
    pub details: Option<String>,
    pub state: Option<String>,
    pub assets: Option<ActivityAssets>,
    pub timestamps: Option<ActivityTimestamps>,
    pub buttons: Vec<ActivityButton>,
    pub is_idle: bool,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum ActivityType {
    #[default]
    Playing = 0,
    Listening = 2,
    Watching = 3,
    Competing = 5,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActivityAssets {
    pub large_image: Option<String>,
    pub large_text: Option<String>,
    pub small_image: Option<String>,
    pub small_text: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActivityTimestamps {
    pub start: Option<u64>,
    pub end: Option<u64>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ActivityButton {
    pub label: String,
    pub url: String,
}

impl FromStr for ActivityType {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "playing" => Ok(ActivityType::Playing),
            "listening" => Ok(ActivityType::Listening),
            "watching" => Ok(ActivityType::Watching),
            "competing" => Ok(ActivityType::Competing),
            _ => Err(()),
        }
    }
}

impl json::Serialize for Activity {
    fn serialize<'a>(
        &'a self,
        f: json::SerializeFn<'a>,
        state: &mut json::SerializeState,
    ) -> crate::Result<()> {
        f("type", json::ValueRef::Number(self.ty as u8 as f64), state)?;
        if let Some(details) = &self.details {
            f("details", json::ValueRef::String(details), state)?;
        }
        if let Some(state_str) = &self.state {
            f("state", json::ValueRef::String(state_str), state)?;
        }
        if let Some(assets) = &self.assets {
            f("assets", json::ValueRef::Object(assets), state)?;
        }
        if let Some(timestamps) = &self.timestamps {
            f("timestamps", json::ValueRef::Object(timestamps), state)?;
        }
        if !self.buttons.is_empty() {
            f(
                "buttons",
                json::ValueRef::Array(
                    self.buttons
                        .iter()
                        .map(|b| {
                            json::ValueRef::Object(b as &dyn json::Serialize)
                        })
                        .collect(),
                ),
                state,
            )?;
        }

        Ok(())
    }
}

impl json::Serialize for ActivityAssets {
    fn serialize<'a>(
        &'a self,
        f: json::SerializeFn<'a>,
        state: &mut json::SerializeState,
    ) -> crate::Result<()> {
        if let Some(large_image) = &self.large_image {
            f("large_image", json::ValueRef::String(large_image), state)?;
        }
        if let Some(large_text) = &self.large_text {
            f("large_text", json::ValueRef::String(large_text), state)?;
        }
        if let Some(small_image) = &self.small_image {
            f("small_image", json::ValueRef::String(small_image), state)?;
        }
        if let Some(small_text) = &self.small_text {
            f("small_text", json::ValueRef::String(small_text), state)?;
        }

        Ok(())
    }
}

impl json::Serialize for ActivityTimestamps {
    fn serialize<'a>(
        &'a self,
        f: json::SerializeFn<'a>,
        state: &mut json::SerializeState,
    ) -> crate::Result<()> {
        if let Some(start) = self.start {
            f("start", json::ValueRef::Number(start as f64), state)?;
        }
        if let Some(end) = self.end {
            f("end", json::ValueRef::Number(end as f64), state)?;
        }

        Ok(())
    }
}

impl json::Serialize for ActivityButton {
    fn serialize<'a>(
        &'a self,
        f: json::SerializeFn<'a>,
        state: &mut json::SerializeState,
    ) -> crate::Result<()> {
        f("label", json::ValueRef::String(&self.label), state)?;
        f("url", json::ValueRef::String(&self.url), state)?;

        Ok(())
    }
}

impl msgpack::Deserialize for Activity {
    fn deserialize<'a>(input: Value) -> crate::Result<Self> {
        let mut input = input.take_map().ok_or("Invalid activity")?;

        let ty = get_field_or_none!(input, "type", |v| v.as_str())
            .and_then(|type_str| ActivityType::from_str(type_str).ok())
            .unwrap_or_default();
        let details =
            remove_field_or_none!(input, "details", |v| v.take_string());
        let state = remove_field_or_none!(input, "state", |v| v.take_string());
        let assets = remove_field_or_none!(input, "assets", |v| {
            ActivityAssets::deserialize(v).ok()
        });
        let timestamps = remove_field_or_none!(input, "timestamps", |v| {
            ActivityTimestamps::deserialize(v).ok()
        });
        let buttons = remove_field_or_none!(input, "buttons", |v| v
            .take_array()
            .map(|v| v
                .into_iter()
                .map(ActivityButton::deserialize)
                .filter_map(Result::ok)
                .collect()))
        .unwrap_or_default();
        let is_idle = get_field_or_none!(input, "is_idle", |v| v.as_bool())
            .unwrap_or_default();

        Ok(Activity {
            ty,
            details,
            state,
            assets,
            timestamps,
            buttons,
            is_idle,
        })
    }
}

impl msgpack::Deserialize for ActivityAssets {
    fn deserialize<'a>(input: Value) -> crate::Result<Self> {
        let mut input = input.take_map().ok_or("Invalid activity assets")?;

        let large_image =
            remove_field_or_none!(input, "large_image", |v| v.take_string());
        let large_text =
            remove_field_or_none!(input, "large_text", |v| v.take_string());
        let small_image =
            remove_field_or_none!(input, "small_image", |v| v.take_string());
        let small_text =
            remove_field_or_none!(input, "small_text", |v| v.take_string());

        Ok(ActivityAssets {
            large_image,
            large_text,
            small_image,
            small_text,
        })
    }
}

impl msgpack::Deserialize for ActivityTimestamps {
    fn deserialize<'a>(input: Value) -> crate::Result<Self> {
        let mut input =
            input.take_map().ok_or("Invalid activity timestamps")?;

        let start = remove_field_or_none!(input, "start", |v| v.as_uinteger());
        let end = remove_field_or_none!(input, "end", |v| v.as_uinteger());

        Ok(ActivityTimestamps { start, end })
    }
}

impl msgpack::Deserialize for ActivityButton {
    fn deserialize<'a>(input: Value) -> crate::Result<Self> {
        let mut input = input.take_map().ok_or("Invalid activity button")?;

        let label = remove_field!(input, "label", |v| v.take_string());
        let url = remove_field!(input, "url", |v| v.take_string());

        Ok(ActivityButton { label, url })
    }
}