use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::error::{ChannelError, Result};
use crate::ids::{ChannelId, PeerId, ThreadId};
use crate::spec::Capabilities;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum MessageContent {
Text { text: String },
Attachment {
media_ref: String,
mime: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
caption: Option<String>,
},
Mixed { parts: Vec<MessageContent> },
}
impl MessageContent {
pub fn text(s: impl Into<String>) -> Self {
Self::Text { text: s.into() }
}
pub fn as_text(&self) -> String {
match self {
Self::Text { text } => text.clone(),
Self::Attachment { caption, .. } => caption.clone().unwrap_or_default(),
Self::Mixed { parts } => parts
.iter()
.map(|p| p.as_text())
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("\n"),
}
}
pub fn summary(&self) -> String {
let t = self.as_text();
if t.is_empty() {
match self {
Self::Attachment { mime, .. } => format!("<attachment: {mime}>"),
Self::Mixed { .. } => "<mixed>".into(),
Self::Text { .. } => String::new(),
}
} else if t.len() <= 80 {
t
} else {
let mut s: String = t.chars().take(77).collect();
s.push_str("...");
s
}
}
pub fn check_capabilities(&self, caps: &Capabilities) -> Result<()> {
match self {
Self::Text { .. } => {
if !caps.text {
return Err(ChannelError::CapabilityDenied("text"));
}
Ok(())
}
Self::Attachment { .. } => {
if !caps.attachments {
return Err(ChannelError::CapabilityDenied("attachments"));
}
Ok(())
}
Self::Mixed { parts } => {
for p in parts {
p.check_capabilities(caps)?;
}
Ok(())
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InboundMessage {
pub channel_id: ChannelId,
pub thread_id: ThreadId,
pub peer: PeerId,
pub provider_msg_id: String,
pub content: MessageContent,
pub received_at: DateTime<Utc>,
#[serde(default)]
pub raw: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutboundMessage {
pub channel_id: ChannelId,
pub thread_id: ThreadId,
pub peer: PeerId,
pub content: MessageContent,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reply_to: Option<String>,
pub idempotency_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderAck {
pub provider_msg_id: String,
pub sent_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelMessageRecord {
pub thread_id: ThreadId,
pub id: String,
pub direction: Direction,
pub content: MessageContent,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provider_msg_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub idempotency_key: Option<String>,
pub at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Direction {
Inbound,
Outbound,
}