145 lines
4.9 KiB
Rust
145 lines
4.9 KiB
Rust
use anyhow::anyhow;
|
|
use anyhow::Context;
|
|
use chrono::{DateTime, TimeZone, Utc};
|
|
use schemars::JsonSchema;
|
|
use serde::Serialize;
|
|
use std::{borrow::Cow, collections::HashMap};
|
|
use twitch::{Command, Tag};
|
|
|
|
use super::ResponseMessage;
|
|
|
|
#[derive(Serialize, JsonSchema, Debug, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BasicMessage<'a> {
|
|
pub text: Cow<'a, str>,
|
|
pub display_name: &'a str,
|
|
#[schemars(with = "String")]
|
|
pub timestamp: DateTime<Utc>,
|
|
pub id: &'a str,
|
|
pub tags: HashMap<&'a str, Cow<'a, str>>,
|
|
}
|
|
|
|
impl<'a> ResponseMessage<'a> for BasicMessage<'a> {
|
|
fn from_irc_message(irc_message: &'a twitch::Message) -> anyhow::Result<Self> {
|
|
let raw_timestamp = irc_message
|
|
.tag(Tag::TmiSentTs)
|
|
.context("Missing timestamp tag")?
|
|
.parse::<i64>()
|
|
.context("Invalid timestamp")?;
|
|
let timestamp = Utc
|
|
.timestamp_millis_opt(raw_timestamp)
|
|
.single()
|
|
.context("Invalid timestamp")?;
|
|
|
|
let response_tags = irc_message
|
|
.tags()
|
|
.unwrap_or_default()
|
|
.iter()
|
|
.map(|(key, value)| (key.as_str(), Cow::Borrowed(*value)))
|
|
.collect();
|
|
|
|
match irc_message.command() {
|
|
Command::Privmsg => {
|
|
let raw_text = irc_message.params().context("Privmsg has no params")?;
|
|
let text = extract_message_text(raw_text);
|
|
|
|
let display_name = irc_message
|
|
.tag(Tag::DisplayName)
|
|
.context("Missing display name tag")?;
|
|
let id = irc_message.tag(Tag::Id).unwrap_or_default();
|
|
|
|
Ok(Self {
|
|
text: Cow::Borrowed(text),
|
|
display_name,
|
|
timestamp,
|
|
id,
|
|
tags: response_tags,
|
|
})
|
|
}
|
|
Command::Clearchat => {
|
|
let mut username = None;
|
|
|
|
let text = match irc_message.params() {
|
|
Some(user_login) => {
|
|
let user_login = extract_message_text(user_login);
|
|
username = Some(user_login);
|
|
|
|
match irc_message.tag(Tag::BanDuration) {
|
|
Some(ban_duration) => {
|
|
format!(
|
|
"{user_login} has been timed out for {ban_duration} seconds"
|
|
)
|
|
}
|
|
None => {
|
|
format!("{user_login} has been banned")
|
|
}
|
|
}
|
|
}
|
|
None => "Chat has been cleared".to_owned(),
|
|
};
|
|
|
|
Ok(Self {
|
|
text: Cow::Owned(text),
|
|
display_name: username.unwrap_or_default(),
|
|
timestamp,
|
|
id: "",
|
|
tags: response_tags,
|
|
})
|
|
}
|
|
Command::UserNotice => {
|
|
let system_message = irc_message
|
|
.tag(Tag::SystemMsg)
|
|
.context("System message tag missing")?;
|
|
let system_message = twitch::unescape(system_message);
|
|
|
|
let text = if let Some(user_message) = irc_message.params() {
|
|
let user_message = extract_message_text(user_message);
|
|
Cow::Owned(format!("{system_message} {user_message}"))
|
|
} else {
|
|
Cow::Owned(system_message)
|
|
};
|
|
|
|
let display_name = irc_message
|
|
.tag(Tag::DisplayName)
|
|
.context("Missing display name tag")?;
|
|
let id = irc_message.tag(Tag::Id).context("Missing message id tag")?;
|
|
|
|
let response_tags = response_tags
|
|
.into_iter()
|
|
.map(|(key, value)| (key, Cow::Owned(twitch::unescape(&value))))
|
|
.collect();
|
|
|
|
Ok(Self {
|
|
text,
|
|
display_name,
|
|
timestamp,
|
|
id,
|
|
tags: response_tags,
|
|
})
|
|
}
|
|
other => Err(anyhow!("Unsupported message type: {other:?}")),
|
|
}
|
|
}
|
|
|
|
fn unescape_tags(&mut self) {
|
|
for value in self.tags.values_mut() {
|
|
let new_value = twitch::unescape(value);
|
|
*value = Cow::Owned(new_value);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn extract_message_text(message_text: &str) -> &str {
|
|
let message_text = message_text.trim_start();
|
|
let mut message_text = message_text.strip_prefix(':').unwrap_or(message_text);
|
|
|
|
let is_action =
|
|
message_text.starts_with("\u{0001}ACTION ") && message_text.ends_with('\u{0001}');
|
|
if is_action {
|
|
// remove the prefix and suffix
|
|
message_text = &message_text[8..message_text.len() - 1]
|
|
}
|
|
|
|
message_text
|
|
}
|