From 54ca5eb3155d1cfcadced7c0a3a405ce1d51ecf6 Mon Sep 17 00:00:00 2001
From: cel 🌸 <cel@bunny.garden>
Date: Mon, 24 Mar 2025 12:22:08 +0000
Subject: feat(stanza): xep-0203

---
 stanza/src/client/message.rs  | 19 +++++++++++++++++--
 stanza/src/client/presence.rs | 19 +++++++++++++++++--
 stanza/src/lib.rs             |  2 ++
 stanza/src/xep_0203.rs        | 34 ++++++++++++++++++++++++++++++++++
 4 files changed, 70 insertions(+), 4 deletions(-)
 create mode 100644 stanza/src/xep_0203.rs

(limited to 'stanza/src')

diff --git a/stanza/src/client/message.rs b/stanza/src/client/message.rs
index 893e7cf..192390b 100644
--- a/stanza/src/client/message.rs
+++ b/stanza/src/client/message.rs
@@ -6,6 +6,9 @@ use peanuts::{
     DeserializeError, Element, XML_NS,
 };
 
+#[cfg(feature = "xep_0203")]
+use crate::xep_0203::Delay;
+
 use super::XMLNS;
 
 #[derive(Debug, Clone)]
@@ -20,6 +23,8 @@ pub struct Message {
     pub subject: Option<Subject>,
     pub body: Option<Body>,
     pub thread: Option<Thread>,
+    #[cfg(feature = "xep_0203")]
+    pub delay: Option<Delay>,
 }
 
 impl FromElement for Message {
@@ -37,6 +42,9 @@ impl FromElement for Message {
         let body = element.child_opt()?;
         let thread = element.child_opt()?;
 
+        #[cfg(feature = "xep_0203")]
+        let delay = element.child_opt()?;
+
         Ok(Message {
             from,
             id,
@@ -46,13 +54,15 @@ impl FromElement for Message {
             subject,
             body,
             thread,
+            #[cfg(feature = "xep_0203")]
+            delay,
         })
     }
 }
 
 impl IntoElement for Message {
     fn builder(&self) -> peanuts::element::ElementBuilder {
-        Element::builder("message", Some(XMLNS))
+        let builder = Element::builder("message", Some(XMLNS))
             .push_attribute_opt("from", self.from.clone())
             .push_attribute_opt("id", self.id.clone())
             .push_attribute_opt("to", self.to.clone())
@@ -66,7 +76,12 @@ impl IntoElement for Message {
             .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone())
             .push_child_opt(self.subject.clone())
             .push_child_opt(self.body.clone())
-            .push_child_opt(self.thread.clone())
+            .push_child_opt(self.thread.clone());
+
+        #[cfg(feature = "xep_0203")]
+        let builder = builder.push_child_opt(self.delay.clone());
+
+        builder
     }
 }
 
diff --git a/stanza/src/client/presence.rs b/stanza/src/client/presence.rs
index 1603ace..ae38756 100644
--- a/stanza/src/client/presence.rs
+++ b/stanza/src/client/presence.rs
@@ -6,6 +6,9 @@ use peanuts::{
     DeserializeError, Element, XML_NS,
 };
 
+#[cfg(feature = "xep_0203")]
+use crate::xep_0203::Delay;
+
 use super::{error::Error, XMLNS};
 
 #[derive(Debug, Clone)]
@@ -19,6 +22,8 @@ pub struct Presence {
     pub show: Option<Show>,
     pub status: Option<Status>,
     pub priority: Option<Priority>,
+    #[cfg(feature = "xep_0203")]
+    pub delay: Option<Delay>,
     // TODO: ##other
     // other: Vec<Other>,
     pub errors: Vec<Error>,
@@ -40,6 +45,9 @@ impl FromElement for Presence {
         let priority = element.child_opt()?;
         let errors = element.children()?;
 
+        #[cfg(feature = "xep_0203")]
+        let delay = element.child_opt()?;
+
         Ok(Presence {
             from,
             id,
@@ -50,13 +58,15 @@ impl FromElement for Presence {
             status,
             priority,
             errors,
+            #[cfg(feature = "xep_0203")]
+            delay,
         })
     }
 }
 
 impl IntoElement for Presence {
     fn builder(&self) -> peanuts::element::ElementBuilder {
-        Element::builder("presence", Some(XMLNS))
+        let builder = Element::builder("presence", Some(XMLNS))
             .push_attribute_opt("from", self.from.clone())
             .push_attribute_opt("id", self.id.clone())
             .push_attribute_opt("to", self.to.clone())
@@ -65,7 +75,12 @@ impl IntoElement for Presence {
             .push_child_opt(self.show)
             .push_child_opt(self.status.clone())
             .push_child_opt(self.priority)
-            .push_children(self.errors.clone())
+            .push_children(self.errors.clone());
+
+        #[cfg(feature = "xep_0203")]
+        let builder = builder.push_child_opt(self.delay.clone());
+
+        builder
     }
 }
 
diff --git a/stanza/src/lib.rs b/stanza/src/lib.rs
index 4120207..4629c07 100644
--- a/stanza/src/lib.rs
+++ b/stanza/src/lib.rs
@@ -9,5 +9,7 @@ pub mod starttls;
 pub mod stream;
 pub mod stream_error;
 pub mod xep_0199;
+#[cfg(feature = "xep_0203")]
+pub mod xep_0203;
 
 pub static XML_VERSION: VersionInfo = VersionInfo::One;
diff --git a/stanza/src/xep_0203.rs b/stanza/src/xep_0203.rs
new file mode 100644
index 0000000..b8f9239
--- /dev/null
+++ b/stanza/src/xep_0203.rs
@@ -0,0 +1,34 @@
+use chrono::{DateTime, Utc};
+use jid::JID;
+use peanuts::{
+    element::{FromElement, IntoElement},
+    Element,
+};
+
+pub const XMLNS: &str = "urn:xmpp:delay";
+
+#[derive(Debug, Clone)]
+pub struct Delay {
+    pub from: Option<JID>,
+    pub stamp: DateTime<Utc>,
+}
+
+impl FromElement for Delay {
+    fn from_element(mut element: Element) -> peanuts::element::DeserializeResult<Self> {
+        element.check_name("delay")?;
+        element.check_namespace(XMLNS)?;
+
+        let from = element.attribute_opt("from")?;
+        let stamp = element.attribute("stamp")?;
+
+        Ok(Delay { from, stamp })
+    }
+}
+
+impl IntoElement for Delay {
+    fn builder(&self) -> peanuts::element::ElementBuilder {
+        Element::builder("delay", Some(XMLNS))
+            .push_attribute_opt("from", self.from.clone())
+            .push_attribute("stamp", self.stamp.format("%C%y-%m-%dT%H:%M:%S%.3f%:z"))
+    }
+}
-- 
cgit