nu_plugin_twitch 0.1.1

A Nu Shell plugin to interact with Twitch IRC and API, making scripting easier.
Documentation
use std::{any::Any, collections::HashMap};

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use nu_protocol::{CustomValue, ShellError, Span, Value, record};
use twitch_irc::message::PrivmsgMessage;

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TwitchMessage {
    pub channel: String,
    pub sender: String,
    pub message: String,
    pub badges: HashMap<String, String>,
    pub timestamp: DateTime<Utc>,
}

impl From<PrivmsgMessage> for TwitchMessage {
    fn from(value: PrivmsgMessage) -> Self {
        Self {
            channel: value.channel_login,
            sender: value.sender.login,
            message: value.message_text,
            badges: value
                .badges
                .iter()
                .cloned()
                .map(|b| (b.name, b.version))
                .collect(),
            timestamp: value.server_timestamp,
        }
    }
}

#[typetag::serde]
impl CustomValue for TwitchMessage {
    fn clone_value(&self, span: nu_protocol::Span) -> Value {
        Value::custom(Box::new(self.clone()), span)
    }

    fn type_name(&self) -> String {
        "TwitchMessage".into()
    }

    fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
        let badges = self
            .badges
            .iter()
            .map(|(k, v)| {
                Value::record(
                    record! {
                        "id" => Value::string(k, span),
                        "version" => Value::string(v, span),
                    },
                    span,
                )
            })
            .collect::<Vec<_>>();
        Ok(Value::record(
            record! {
                "channel" => Value::string(&self.channel, span),
                "sender" => Value::string(&self.sender, span),
                "message" => Value::string(&self.message, span),
                "badges" => Value::list(badges, span),
                "timestamp" => Value::date(self.timestamp.fixed_offset(), span)
            },
            span,
        ))
    }

    fn as_any(&self) -> &dyn Any {
        self
    }

    fn as_mut_any(&mut self) -> &mut dyn Any {
        self
    }

    /// Follow cell path by string key (e.g. columns)
    fn follow_path_string(
        &self,
        self_span: Span,
        column_name: String,
        path_span: Span,
    ) -> Result<Value, ShellError> {
        self.to_base_value(self_span)
            .unwrap()
            .with_span(path_span)
            .get_data_by_key(&column_name)
            .ok_or(ShellError::IncompatiblePathAccess {
                type_name: self.type_name(),
                span: path_span,
            })
    }
}