diff options
author | 2025-03-24 13:09:16 +0000 | |
---|---|---|
committer | 2025-03-24 13:09:16 +0000 | |
commit | 5dcef9651acd962971c7b25801222c09239acac8 (patch) | |
tree | cf2c5278dbf6468920e77ce0e5cdc31f3132e8d1 | |
parent | 54ca5eb3155d1cfcadced7c0a3a405ce1d51ecf6 (diff) | |
download | luz-5dcef9651acd962971c7b25801222c09239acac8.tar.gz luz-5dcef9651acd962971c7b25801222c09239acac8.tar.bz2 luz-5dcef9651acd962971c7b25801222c09239acac8.zip |
feat(luz): xep-0203 delayed delivery
-rw-r--r-- | luz/Cargo.toml | 2 | ||||
-rw-r--r-- | luz/src/connection/read.rs | 27 | ||||
-rw-r--r-- | luz/src/lib.rs | 23 | ||||
-rw-r--r-- | luz/src/presence.rs | 60 |
4 files changed, 86 insertions, 26 deletions
diff --git a/luz/Cargo.toml b/luz/Cargo.toml index d6920e9..08a0d6c 100644 --- a/luz/Cargo.toml +++ b/luz/Cargo.toml @@ -9,7 +9,7 @@ jabber = { version = "0.1.0", path = "../jabber" } peanuts = { version = "0.1.0", path = "../../peanuts" } jid = { version = "0.1.0", path = "../jid", features = ["sqlx"] } sqlx = { version = "0.8.3", features = ["sqlite", "runtime-tokio", "uuid", "chrono"] } -stanza = { version = "0.1.0", path = "../stanza" } +stanza = { version = "0.1.0", path = "../stanza", features = ["xep_0203"] } tokio = "1.42.0" tokio-stream = "0.1.17" tokio-util = "0.7.13" diff --git a/luz/src/connection/read.rs b/luz/src/connection/read.rs index 80322ce..d510c5d 100644 --- a/luz/src/connection/read.rs +++ b/luz/src/connection/read.rs @@ -20,7 +20,7 @@ use crate::{ chat::{Body, Message}, db::Db, error::{Error, IqError, MessageRecvError, PresenceError, ReadError, RosterError}, - presence::{Offline, Online, Presence, Show}, + presence::{Offline, Online, Presence, PresenceType, Show}, roster::Contact, UpdateMessage, }; @@ -181,6 +181,11 @@ async fn handle_stanza( match stanza { Stanza::Message(stanza_message) => { if let Some(mut from) = stanza_message.from { + // TODO: don't ignore delay from. xep says SHOULD send error if incorrect. + let timestamp = stanza_message + .delay + .map(|delay| delay.stamp) + .unwrap_or_else(|| Utc::now()); // TODO: group chat messages let mut message = Message { id: stanza_message @@ -189,7 +194,7 @@ async fn handle_stanza( .map(|id| Uuid::from_str(&id).unwrap_or_else(|_| Uuid::new_v4())) .unwrap_or_else(|| Uuid::new_v4()), from: from.clone(), - timestamp: Utc::now(), + timestamp, body: Body { // TODO: should this be an option? body: stanza_message @@ -263,10 +268,17 @@ async fn handle_stanza( let offline = Offline { status: presence.status.map(|status| status.status.0), }; + let timestamp = presence + .delay + .map(|delay| delay.stamp) + .unwrap_or_else(|| Utc::now()); let _ = update_sender .send(UpdateMessage::Presence { from, - presence: Presence::Offline(offline), + presence: Presence { + timestamp, + presence: PresenceType::Offline(offline), + }, }) .await; } @@ -286,10 +298,17 @@ async fn handle_stanza( status: presence.status.map(|status| status.status.0), priority: presence.priority.map(|priority| priority.0), }; + let timestamp = presence + .delay + .map(|delay| delay.stamp) + .unwrap_or_else(|| Utc::now()); let _ = update_sender .send(UpdateMessage::Presence { from, - presence: Presence::Online(online), + presence: Presence { + timestamp, + presence: PresenceType::Online(online), + }, }) .await; } diff --git a/luz/src/lib.rs b/luz/src/lib.rs index b87891d..99c96e0 100644 --- a/luz/src/lib.rs +++ b/luz/src/lib.rs @@ -15,7 +15,7 @@ use error::{ }; use futures::{future::Fuse, FutureExt}; use jabber::JID; -use presence::{Offline, Online, Presence}; +use presence::{Offline, Online, Presence, PresenceType}; use roster::{Contact, ContactUpdate}; use sqlx::SqlitePool; use stanza::client::{ @@ -180,7 +180,7 @@ impl Luz { let (send, recv) = oneshot::channel(); CommandMessage::SendPresence( None, - Presence::Online(online.clone()), + PresenceType::Online(online.clone()), send, ) .handle_online( @@ -264,10 +264,9 @@ impl Luz { .await; } mut c => { - // TODO: send unavailable presence if let Some((write_handle, supervisor_handle)) = c.take() { let offline_presence: stanza::client::presence::Presence = - offline.clone().into(); + offline.clone().into_stanza(None); let stanza = Stanza::Presence(offline_presence); // TODO: timeout and error check write_handle.write(stanza).await; @@ -649,6 +648,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; match result { @@ -666,6 +666,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; let _ = sender.send(result); @@ -684,6 +685,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; let _ = sender.send(result); @@ -699,6 +701,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; match result { @@ -716,6 +719,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; let _ = sender.send(result); @@ -733,6 +737,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; let _ = sender.send(result); @@ -748,6 +753,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; let _ = sender.send(result); @@ -763,6 +769,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; let _ = sender.send(result); @@ -778,6 +785,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; match result { @@ -795,6 +803,7 @@ impl CommandMessage { status: None, priority: None, errors: Vec::new(), + delay: None, }); let result = write_handle.write(presence).await; let _ = sender.send(result); @@ -981,7 +990,7 @@ impl CommandMessage { .await; } let result = write_handle - .write(Stanza::Presence(online.into())) + .write(Stanza::Presence(online.into_stanza(None))) .await .map_err(|e| StatusError::Write(e)); // .map_err(|e| StatusError::Write(e)); @@ -1009,6 +1018,7 @@ impl CommandMessage { body: Some(body.body.clone()), }), thread: None, + delay: None, }); let _ = sender.send(Ok(())); // let _ = sender.send(Ok(message.clone())); @@ -1446,9 +1456,10 @@ pub enum CommandMessage { /// set online status. if disconnected, will be cached so when client connects, will be sent as the initial presence. SetStatus(Online, oneshot::Sender<Result<(), StatusError>>), /// send presence stanza + // TODO: cache presence stanza SendPresence( Option<JID>, - Presence, + PresenceType, oneshot::Sender<Result<(), WriteError>>, ), /// send a directed presence (usually to a non-contact). diff --git a/luz/src/presence.rs b/luz/src/presence.rs index 4bc1993..e35761c 100644 --- a/luz/src/presence.rs +++ b/luz/src/presence.rs @@ -1,5 +1,6 @@ +use chrono::{DateTime, Utc}; use sqlx::Sqlite; -use stanza::client::presence::String1024; +use stanza::{client::presence::String1024, xep_0203::Delay}; #[derive(Debug, Default, sqlx::FromRow, Clone)] pub struct Online { @@ -60,62 +61,91 @@ pub struct Offline { } #[derive(Debug, Clone)] -pub enum Presence { +pub enum PresenceType { Online(Online), Offline(Offline), } -impl From<Online> for stanza::client::presence::Presence { - fn from(value: Online) -> Self { - Self { +#[derive(Debug, Clone)] +pub struct Presence { + pub timestamp: DateTime<Utc>, + pub presence: PresenceType, +} + +impl Online { + pub fn into_stanza( + self, + timestamp: Option<DateTime<Utc>>, + ) -> stanza::client::presence::Presence { + stanza::client::presence::Presence { from: None, id: None, to: None, r#type: None, lang: None, - show: value.show.map(|show| match show { + show: self.show.map(|show| match show { Show::Away => stanza::client::presence::Show::Away, Show::Chat => stanza::client::presence::Show::Chat, Show::DoNotDisturb => stanza::client::presence::Show::Dnd, Show::ExtendedAway => stanza::client::presence::Show::Xa, }), // TODO: enforce message length in status message - status: value.status.map(|status| stanza::client::presence::Status { + status: self.status.map(|status| stanza::client::presence::Status { lang: None, status: String1024(status), }), - priority: value + priority: self .priority .map(|priority| stanza::client::presence::Priority(priority)), errors: Vec::new(), + delay: timestamp.map(|timestamp| Delay { + from: None, + stamp: timestamp, + }), } } } -impl From<Offline> for stanza::client::presence::Presence { - fn from(value: Offline) -> Self { - Self { +impl Offline { + pub fn into_stanza( + self, + timestamp: Option<DateTime<Utc>>, + ) -> stanza::client::presence::Presence { + stanza::client::presence::Presence { from: None, id: None, to: None, r#type: Some(stanza::client::presence::PresenceType::Unavailable), lang: None, show: None, - status: value.status.map(|status| stanza::client::presence::Status { + status: self.status.map(|status| stanza::client::presence::Status { lang: None, status: String1024(status), }), priority: None, errors: Vec::new(), + delay: timestamp.map(|timestamp| Delay { + from: None, + stamp: timestamp, + }), + } + } +} + +impl From<PresenceType> for stanza::client::presence::Presence { + fn from(value: PresenceType) -> Self { + match value { + PresenceType::Online(online) => online.into_stanza(None), + PresenceType::Offline(offline) => offline.into_stanza(None), } } } impl From<Presence> for stanza::client::presence::Presence { fn from(value: Presence) -> Self { - match value { - Presence::Online(online) => online.into(), - Presence::Offline(offline) => offline.into(), + match value.presence { + PresenceType::Online(online) => online.into_stanza(Some(value.timestamp)), + PresenceType::Offline(offline) => offline.into_stanza(Some(value.timestamp)), } } } |