use std::any::Any;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use nu_protocol::{CustomValue, Record, ShellError, Span, Value, record};
use twitch_irc::message::{UserNoticeEvent, UserNoticeMessage};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct TwitchNotice {
pub channel: String,
pub sender: String,
pub message: Option<String>,
pub event_id: String,
pub event_type: String,
pub event: Value,
pub timestamp: DateTime<Utc>,
}
impl From<UserNoticeMessage> for TwitchNotice {
fn from(value: UserNoticeMessage) -> Self {
let span = Span::unknown();
let (event_type, event) = TwitchNotice::event_record(
value.sender.login.clone(),
value.event_id.clone(),
value.event,
);
Self {
channel: value.channel_login,
sender: value.sender.login.clone(),
message: value.message_text,
event_id: value.event_id.clone(),
event_type,
event: if !event.is_empty() {
Value::record(event, span)
} else {
Value::nothing(span)
},
timestamp: value.server_timestamp,
}
}
}
impl TwitchNotice {
fn event_record(sender: String, event_id: String, event: UserNoticeEvent) -> (String, Record) {
let span = Span::unknown();
match event {
UserNoticeEvent::Raid {
viewer_count,
profile_image_url,
} => (
"RAID".into(),
record!(
"from" => Value::string(sender, span),
"viewers" => Value::int(viewer_count as i64, span),
"profile_image_url" => Value::string(profile_image_url, span),
),
),
UserNoticeEvent::SubOrResub {
is_resub,
cumulative_months,
streak_months,
sub_plan,
..
} => (
"SUB".into(),
record!(
"is_resub" => Value::bool(is_resub, span),
"streak" => streak_months.map(|s| Value::int(s as i64, span)).unwrap_or(Value::nothing(span)),
"total" => Value::int(cumulative_months as i64, span),
"tier" => Value::string(sub_plan, span),
),
),
UserNoticeEvent::GiftPaidUpgrade { gifter_login, .. } => (
"SUB_GIFT_UPGRADE".into(),
record!(
"from" => Value::string(gifter_login, span),
"to" => Value::string(sender, span),
"tier" => Value::nothing(span),
"months" => Value::int(1, span),
),
),
UserNoticeEvent::AnonGiftPaidUpgrade { .. } => (
"SUB_GIFT_UPGRADE".into(),
record!(
"from" => Value::nothing(span),
"to" => Value::string(sender, span),
"tier" => Value::nothing(span),
"months" => Value::int(1, span),
),
),
UserNoticeEvent::SubGift {
is_sender_anonymous,
cumulative_months,
recipient,
sub_plan,
num_gifted_months,
..
} => (
"GIFT_SUB".into(),
record!(
"from" => if !is_sender_anonymous { Value::string(sender, span) } else { Value::nothing(span) },
"to" => Value::string(recipient.login, span),
"tier" => Value::string(sub_plan, span),
"total" => Value::int(cumulative_months as i64, span),
"months" => Value::int(num_gifted_months as i64, span),
),
),
UserNoticeEvent::SubMysteryGift {
mass_gift_count,
sender_total_gifts,
sub_plan,
} => (
"GIFT_SUBS".into(),
record!(
"from" => Value::string(sender, span),
"to" => Value::nothing(span),
"gifted" => Value::int(mass_gift_count as i64, span),
"total" => Value::int(sender_total_gifts as i64, span),
"tier" => Value::string(sub_plan, span),
),
),
_ => match event_id.as_str() {
"announcement" => ("ANNOUNCEMENT".into(), record!()),
_ => (
"UNKNOWN".into(),
record!(
"raw" => Value::string(event_id, span),
),
),
},
}
}
}
#[typetag::serde]
impl CustomValue for TwitchNotice {
fn clone_value(&self, span: nu_protocol::Span) -> Value {
Value::custom(Box::new(self.clone()), span)
}
fn type_name(&self) -> String {
"TwitchNotice".into()
}
fn to_base_value(&self, span: Span) -> Result<Value, ShellError> {
Ok(Value::record(
record! {
"channel" => Value::string(&self.channel, span),
"sender" => Value::string(&self.sender, span),
"message" => self.message.clone()
.map(|msg| Value::string(msg, span))
.unwrap_or(Value::nothing(span)),
"event_id" => Value::string(&self.event_id, span),
"event_type" => Value::string(&self.event_type, span),
"event" => self.event.clone().with_span(span),
"timestamp" => Value::date(self.timestamp.into(), span),
},
span,
))
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_mut_any(&mut self) -> &mut dyn Any {
self
}
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,
})
}
}