use crate::id::{MessageId, Uin};
use crate::resource::Media;
use crate::segment::Segment;
pub type Message = Vec<Segment>;
pub trait MessageExt {
fn extract_text(&self) -> String;
fn images(&self) -> Vec<&Media>;
fn first_image(&self) -> Option<&Media>;
fn has_image(&self) -> bool;
fn faces(&self) -> Vec<&str>;
fn has_face(&self) -> bool;
fn reply_quoted(&self) -> Option<&[Segment]>;
fn mentions(&self) -> Vec<Uin>;
fn mentions_all(&self) -> bool;
fn mentions_user(&self, user: Uin) -> bool;
fn reply_to(&self) -> Option<&MessageId>;
fn is_text_only(&self) -> bool;
fn merge_text(&self) -> Message;
fn split_text(&self, sep: char) -> Vec<String>;
fn strip_text_prefix(&self, prefix: &str) -> Option<Message>;
}
impl MessageExt for [Segment] {
fn extract_text(&self) -> String {
self.iter().filter_map(Segment::as_text).collect()
}
fn faces(&self) -> Vec<&str> {
self.iter()
.filter_map(|s| match s {
Segment::Face { id, .. } => Some(id.as_str()),
_ => None,
})
.collect()
}
fn has_face(&self) -> bool {
self.iter().any(|s| matches!(s, Segment::Face { .. }))
}
fn reply_quoted(&self) -> Option<&[Segment]> {
self.iter().find_map(|s| match s {
Segment::Reply { quoted, .. } => Some(quoted.as_slice()),
_ => None,
})
}
fn images(&self) -> Vec<&Media> {
self.iter()
.filter_map(|s| match s {
Segment::Image { res, .. } => Some(res),
_ => None,
})
.collect()
}
fn first_image(&self) -> Option<&Media> {
self.iter().find_map(|s| match s {
Segment::Image { res, .. } => Some(res),
_ => None,
})
}
fn has_image(&self) -> bool {
self.iter().any(|s| matches!(s, Segment::Image { .. }))
}
fn mentions(&self) -> Vec<Uin> {
self.iter()
.filter_map(|s| match s {
Segment::Mention { user, .. } => Some(*user),
_ => None,
})
.collect()
}
fn mentions_all(&self) -> bool {
self.iter().any(|s| matches!(s, Segment::MentionAll))
}
fn mentions_user(&self, user: Uin) -> bool {
self.iter().any(|s| matches!(s, Segment::Mention { user: u, .. } if *u == user))
}
fn reply_to(&self) -> Option<&MessageId> {
self.iter().find_map(|s| match s {
Segment::Reply { id, .. } => Some(id),
_ => None,
})
}
fn is_text_only(&self) -> bool {
!self.is_empty() && self.iter().all(|s| matches!(s, Segment::Text(_)))
}
fn merge_text(&self) -> Message {
let mut out: Message = Vec::with_capacity(self.len());
for seg in self {
match (seg, out.last_mut()) {
(Segment::Text(t), Some(Segment::Text(prev))) => prev.push_str(t),
_ => out.push(seg.clone()),
}
}
out
}
fn split_text(&self, sep: char) -> Vec<String> {
self.extract_text().split(sep).filter(|s| !s.is_empty()).map(str::to_string).collect()
}
fn strip_text_prefix(&self, prefix: &str) -> Option<Message> {
let idx = self.iter().position(|s| matches!(s, Segment::Text(_)))?;
let Segment::Text(t) = &self[idx] else { return None };
let rest = t.strip_prefix(prefix)?;
let mut out: Message = self.to_vec();
if rest.is_empty() {
out.remove(idx);
} else {
out[idx] = Segment::Text(rest.to_string());
}
Some(out)
}
}
#[derive(Clone, Debug, Default)]
pub struct Msg(Message);
impl Msg {
pub fn new() -> Self {
Msg(Vec::new())
}
pub fn text(mut self, s: impl Into<String>) -> Self {
self.0.push(Segment::Text(s.into()));
self
}
pub fn at(mut self, user: impl Into<Uin>) -> Self {
self.0.push(Segment::at(user));
self
}
pub fn at_all(mut self) -> Self {
self.0.push(Segment::MentionAll);
self
}
pub fn face(mut self, id: impl Into<String>) -> Self {
self.0.push(Segment::face(id));
self
}
pub fn image_url(mut self, url: impl Into<String>) -> Self {
self.0.push(Segment::image_url(url));
self
}
pub fn image_bytes(mut self, bytes: impl Into<bytes::Bytes>) -> Self {
self.0.push(Segment::image_bytes(bytes));
self
}
pub fn image_path(mut self, path: impl Into<std::path::PathBuf>) -> Self {
self.0.push(Segment::image_path(path));
self
}
pub fn reply(mut self, id: MessageId) -> Self {
self.0.push(Segment::reply(id));
self
}
pub fn push(mut self, seg: Segment) -> Self {
self.0.push(seg);
self
}
pub fn build(self) -> Message {
self.0
}
}
impl From<Msg> for Message {
fn from(m: Msg) -> Message {
m.0
}
}