diff --git a/crates/projection-xmpp/src/message.rs b/crates/projection-xmpp/src/message.rs index 15a3e0d..f490ace 100644 --- a/crates/projection-xmpp/src/message.rs +++ b/crates/projection-xmpp/src/message.rs @@ -20,7 +20,8 @@ impl<'a> XmppConnection<'a> { }) = m.to { if server.0.as_ref() == &*self.hostname_rooms && m.r#type == MessageType::Groupchat { - self.user_handle.send_message(RoomId::from(name.0.clone())?, m.body.clone().into()).await?; + let Some(body) = &m.body else { return Ok(()) }; + self.user_handle.send_message(RoomId::from(name.0.clone())?, body.clone()).await?; Message::<()> { to: Some(Jid { name: Some(self.user.xmpp_name.clone()), @@ -42,7 +43,8 @@ impl<'a> XmppConnection<'a> { .serialize(output); Ok(()) } else if server.0.as_ref() == &*self.hostname && m.r#type == MessageType::Chat { - self.user_handle.send_dialog_message(PlayerId::from(name.0.clone())?, m.body.clone()).await?; + let Some(body) = &m.body else { return Ok(()) }; + self.user_handle.send_dialog_message(PlayerId::from(name.0.clone())?, body.clone()).await?; Ok(()) } else { todo!() diff --git a/crates/projection-xmpp/src/presence.rs b/crates/projection-xmpp/src/presence.rs index 3712baa..7e3ff35 100644 --- a/crates/projection-xmpp/src/presence.rs +++ b/crates/projection-xmpp/src/presence.rs @@ -5,8 +5,8 @@ use quick_xml::events::Event; use lavina_core::room::RoomId; use proto_xmpp::bind::{Jid, Name, Server}; -use proto_xmpp::client::Presence; -use proto_xmpp::muc::XUser; +use proto_xmpp::client::{Message, MessageType, Presence, Subject}; +use proto_xmpp::muc::{Affiliation, Role, XUser, XUserItem}; use proto_xmpp::xml::{Ignore, ToXml}; use crate::XmppConnection; @@ -23,8 +23,28 @@ impl<'a> XmppConnection<'a> { // resources in MUCs are members' personas – not implemented (yet?) resource: Some(_), }) if server.0 == self.hostname_rooms => { - let response = self.muc_presence(&name).await?; + let mut response = self.muc_presence(&name).await?; + response.id = p.id; + let subject = Message::<()> { + from: Some(Jid { + name: Some(name), + server: Server(self.hostname_rooms.clone()), + resource: None, + }), + id: None, + to: Some(Jid { + name: Some(self.user.xmpp_name.clone()), + server: Server(self.hostname.clone()), + resource: Some(self.user.xmpp_resource.clone()), + }), + r#type: MessageType::Groupchat, + lang: None, + subject: Some(Subject(None)), + body: None, + custom: vec![], + }; response.serialize(output); + subject.serialize(output); } _ => { // TODO other presence cases @@ -56,7 +76,9 @@ impl<'a> XmppConnection<'a> { }; response.serialize(output); } - _ => todo!(), + e => { + tracing::error!("TODO: unknown presence type: {e:?}"); + } } } @@ -75,7 +97,17 @@ impl<'a> XmppConnection<'a> { server: Server(self.hostname_rooms.clone()), resource: Some(self.user.xmpp_muc_name.clone()), }), - custom: vec![XUser], + custom: vec![XUser { + item: XUserItem { + affiliation: Affiliation::Member, + role: Role::Participant, + jid: Jid { + name: Some(self.user.xmpp_name.clone()), + server: Server(self.hostname.clone()), + resource: Some(self.user.xmpp_resource.clone()), + }, + }, + }], ..Default::default() }; Ok(response) diff --git a/crates/proto-xmpp/src/client.rs b/crates/proto-xmpp/src/client.rs index c9fa8db..ef7d517 100644 --- a/crates/proto-xmpp/src/client.rs +++ b/crates/proto-xmpp/src/client.rs @@ -20,8 +20,8 @@ pub struct Message { // default is Normal pub r#type: MessageType, pub lang: Option, - pub subject: Option, - pub body: Str, + pub subject: Option, + pub body: Option, pub custom: Vec, } @@ -38,6 +38,20 @@ impl FromXml for Message { } } +#[derive(PartialEq, Eq, Debug)] +pub struct Subject(pub Option); +impl ToXml for Subject { + fn serialize(&self, events: &mut Vec>) { + if let Some(ref s) = self.0 { + events.push(Event::Start(BytesStart::new("subject"))); + events.push(Event::Text(BytesText::new(s).into_owned())); + events.push(Event::End(BytesEnd::new("subject"))); + } else { + events.push(Event::Empty(BytesStart::new("subject"))); + } + } +} + #[derive(From)] struct MessageParser(MessageParserInner); @@ -57,7 +71,7 @@ struct MessageParserState { to: Option, r#type: MessageType, lang: Option, - subject: Option, + subject: Option, body: Option, custom: Vec, } @@ -130,7 +144,7 @@ impl Parser for MessageParser { r#type: state.r#type, lang: state.lang, subject: state.subject, - body, + body: Some(body), custom: state.custom, })) } else { @@ -153,7 +167,7 @@ impl Parser for MessageParser { InSubject(mut state) => match event { Event::Text(ref bytes) => { let subject = fail_fast!(std::str::from_utf8(&*bytes)); - state.subject = Some(subject.into()); + state.subject = Some(Subject(Some(subject.into()))); Continuation::Continue(InSubject(state).into()) } Event::End(_) => Continuation::Continue(Outer(state).into()), @@ -208,9 +222,14 @@ impl ToXml for Message { value: self.r#type.as_str().as_bytes().into(), }); events.push(Event::Start(bytes)); - events.push(Event::Start(BytesStart::new("body"))); - events.push(Event::Text(BytesText::new(&self.body).into_owned())); - events.push(Event::End(BytesEnd::new("body"))); + if let Some(subject) = &self.subject { + subject.serialize(events); + } + if let Some(body) = &self.body { + events.push(Event::Start(BytesStart::new("body"))); + events.push(Event::Text(BytesText::new(body).into_owned())); + events.push(Event::End(BytesEnd::new("body"))); + } events.push(Event::End(BytesEnd::new("message"))); } } @@ -577,7 +596,7 @@ impl FromXml for Presence { } b"id" => { let s = std::str::from_utf8(&attr.value)?; - p.r#type = Option::from(s.to_string()); + p.id = Option::from(s.to_string()); } _ => {} } diff --git a/crates/proto-xmpp/src/muc/mod.rs b/crates/proto-xmpp/src/muc/mod.rs index 8ae70d5..2402786 100644 --- a/crates/proto-xmpp/src/muc/mod.rs +++ b/crates/proto-xmpp/src/muc/mod.rs @@ -3,6 +3,7 @@ use quick_xml::events::{BytesEnd, BytesStart, Event}; use quick_xml::name::ResolveResult; +use crate::bind::Jid; use crate::xml::*; use anyhow::{anyhow, Result}; @@ -145,31 +146,94 @@ impl FromXml for X { } #[derive(Debug, PartialEq, Eq)] -pub struct XUser; +pub struct XUser { + pub item: XUserItem, +} impl ToXml for XUser { fn serialize(&self, output: &mut Vec>) { let mut tag = BytesStart::new("x"); tag.push_attribute(("xmlns", XMLNS_USER)); output.push(Event::Start(tag)); - - let mut meg = BytesStart::new("item"); - meg.push_attribute(("affiliation", "owner")); - meg.push_attribute(("role", "moderator")); - meg.push_attribute(("jid", "sauer@localhost")); - output.push(Event::Empty(meg)); - - let mut veg = BytesStart::new("status"); - veg.push_attribute(("code", "100")); - output.push(Event::Empty(veg)); - - let mut veg = BytesStart::new("status"); - veg.push_attribute(("code", "110")); - output.push(Event::Empty(veg)); - + self.item.serialize(output); output.push(Event::End(BytesEnd::new("x"))); } } +#[derive(Debug, PartialEq, Eq)] +pub struct XUserItem { + pub affiliation: Affiliation, + pub jid: Jid, + pub role: Role, +} +impl ToXml for XUserItem { + fn serialize(&self, output: &mut Vec>) { + let mut meg = BytesStart::new("item"); + meg.push_attribute(("affiliation", self.affiliation.to_str())); + meg.push_attribute(("role", self.role.to_str())); + meg.push_attribute(("jid", self.jid.to_string().as_str())); + output.push(Event::Empty(meg)); + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Affiliation { + Owner, + Admin, + Member, + Outcast, + None, +} +impl Affiliation { + pub fn from_str(s: &str) -> Option { + match s { + "owner" => Some(Self::Owner), + "admin" => Some(Self::Admin), + "member" => Some(Self::Member), + "outcast" => Some(Self::Outcast), + "none" => Some(Self::None), + _ => None, + } + } + + pub fn to_str(&self) -> &str { + match self { + Self::Owner => "owner", + Self::Admin => "admin", + Self::Member => "member", + Self::Outcast => "outcast", + Self::None => "none", + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Role { + Moderator, + Participant, + Visitor, + None, +} +impl Role { + pub fn from_str(s: &str) -> Option { + match s { + "moderator" => Some(Self::Moderator), + "participant" => Some(Self::Participant), + "visitor" => Some(Self::Visitor), + "none" => Some(Self::None), + _ => None, + } + } + + pub fn to_str(&self) -> &str { + match self { + Self::Moderator => "moderator", + Self::Participant => "participant", + Self::Visitor => "visitor", + Self::None => "none", + } + } +} + #[cfg(test)] mod test { use super::*;