/// Normalised chat message — protocol-agnostic representation. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum MessageKind { Text, Action, Notice, Private, FileTransfer { filename: String, size_bytes: u64, source: String }, Error, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ChatMessage { pub id: String, pub protocol: crate::core::protocol::ProtocolType, pub kind: MessageKind, pub source: String, pub sender: String, pub body: String, pub timestamp: DateTime, pub is_own: bool, /// `true` if the timestamp was provided by the remote server (e.g. IRCv3 /// `server-time`, Matrix `origin_server_ts`) rather than the local clock. /// The TUI uses this to render the timestamp in a distinct style so the /// user can see which messages have server-confirmed times. #[serde(default)] pub remote_ts: bool, } impl ChatMessage { pub fn new_id() -> String { use std::time::{SystemTime, UNIX_EPOCH}; let nanos = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_nanos(); format!("{nanos:x}") } pub fn text(protocol: crate::core::protocol::ProtocolType, source: &str, sender: &str, body: &str, is_own: bool) -> Self { Self { id: Self::new_id(), protocol, kind: MessageKind::Text, source: source.to_owned(), sender: sender.to_owned(), body: body.to_owned(), timestamp: Utc::now(), is_own, remote_ts: false } } pub fn action(protocol: crate::core::protocol::ProtocolType, source: &str, sender: &str, body: &str, is_own: bool) -> Self { Self { id: Self::new_id(), protocol, kind: MessageKind::Action, source: source.to_owned(), sender: sender.to_owned(), body: body.to_owned(), timestamp: Utc::now(), is_own, remote_ts: false } } pub fn notice(protocol: crate::core::protocol::ProtocolType, source: &str, body: &str) -> Self { Self { id: Self::new_id(), protocol, kind: MessageKind::Notice, source: source.to_owned(), sender: String::new(), body: body.to_owned(), timestamp: Utc::now(), is_own: false, remote_ts: false } } pub fn error(protocol: crate::core::protocol::ProtocolType, source: &str, body: &str) -> Self { Self { id: Self::new_id(), protocol, kind: MessageKind::Error, source: source.to_owned(), sender: String::new(), body: body.to_owned(), timestamp: Utc::now(), is_own: false, remote_ts: false } } /// Private / direct message. pub fn private(protocol: crate::core::protocol::ProtocolType, source: &str, sender: &str, body: &str, is_own: bool) -> Self { Self { id: Self::new_id(), protocol, kind: MessageKind::Private, source: source.to_owned(), sender: sender.to_owned(), body: body.to_owned(), timestamp: Utc::now(), is_own, remote_ts: false } } /// Override the timestamp (used by IRCv3 server-time, Matrix event /// origin_server_ts, etc.). Returns `self` for chaining. pub fn with_timestamp(mut self, ts: DateTime) -> Self { self.timestamp = ts; self } /// Mark that this message's timestamp came from the remote server rather /// than the local clock. The TUI renders these with a distinct style. pub fn with_remote_ts(mut self) -> Self { self.remote_ts = true; self } /// Conditionally mark the message as having a remote timestamp. pub fn with_remote_ts_if(mut self, flag: bool) -> Self { self.remote_ts = flag; self } }