xmpp-parsers: Use xso for pubsub events

Also split the PubSubEvent enum into Event and Payload.
This commit is contained in:
Emmanuel Gil Peyrot 2025-01-27 04:24:02 +01:00
commit fa61f421bb
7 changed files with 156 additions and 252 deletions

View file

@ -51,6 +51,9 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
- http_uploads Header type has been split into HeaderName, an enum - http_uploads Header type has been split into HeaderName, an enum
containing only the name of the header, and the Header struct with containing only the name of the header, and the Header struct with
both a name and a value(!530) both a name and a value(!530)
- pubsub::Event is now the wrapper for the pubsub::event::Payload enum,
and the PublishedItems and RetractedItems have been merged into the
Items sub-struct. These replace the previous PubSubEvent enum (!531)
* New parsers/serialisers: * New parsers/serialisers:
- Stream Features (RFC 6120) (!400) - Stream Features (RFC 6120) (!400)
- Spam Reporting (XEP-0377) (!506) - Spam Reporting (XEP-0377) (!506)

View file

@ -62,7 +62,7 @@ impl Conference {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::pubsub::{pubsub::Item as PubSubItem, PubSubEvent}; use crate::pubsub::{self, pubsub::Item as PubSubItem};
#[cfg(target_pointer_width = "32")] #[cfg(target_pointer_width = "32")]
#[test] #[test]
@ -132,10 +132,16 @@ mod tests {
assert_eq!(conference.clone().password.unwrap(), "secret"); assert_eq!(conference.clone().password.unwrap(), "secret");
let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='urn:xmpp:bookmarks:1'><item xmlns='http://jabber.org/protocol/pubsub#event' id='test-muc@muc.localhost'><conference xmlns='urn:xmpp:bookmarks:1' autojoin='true' name='Test MUC'><nick>Coucou</nick><password>secret</password></conference></item></items></event>".parse().unwrap(); let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='urn:xmpp:bookmarks:1'><item xmlns='http://jabber.org/protocol/pubsub#event' id='test-muc@muc.localhost'><conference xmlns='urn:xmpp:bookmarks:1' autojoin='true' name='Test MUC'><nick>Coucou</nick><password>secret</password></conference></item></items></event>".parse().unwrap();
let mut items = match PubSubEvent::try_from(elem) { let event = pubsub::Event::try_from(elem).unwrap();
Ok(PubSubEvent::PublishedItems { node, items }) => { let mut items = match event.payload {
pubsub::event::Payload::Items {
node,
published,
retracted,
} => {
assert_eq!(&node.0, ns::BOOKMARKS2); assert_eq!(&node.0, ns::BOOKMARKS2);
items assert_eq!(retracted.len(), 0);
published
} }
_ => panic!(), _ => panic!(),
}; };

View file

@ -582,7 +582,7 @@ mod tests {
#[test] #[test]
fn test_extract_payload() { fn test_extract_payload() {
use super::super::attention::Attention; use super::super::attention::Attention;
use super::super::pubsub::event::PubSubEvent; use super::super::pubsub;
#[cfg(not(feature = "component"))] #[cfg(not(feature = "component"))]
let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap(); let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
@ -590,7 +590,7 @@ mod tests {
let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap(); let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
let mut message = Message::try_from(elem).unwrap(); let mut message = Message::try_from(elem).unwrap();
assert_eq!(message.payloads.len(), 1); assert_eq!(message.payloads.len(), 1);
match message.extract_payload::<PubSubEvent>() { match message.extract_payload::<pubsub::Event>() {
Ok(None) => (), Ok(None) => (),
other => panic!("unexpected result: {:?}", other), other => panic!("unexpected result: {:?}", other),
}; };

View file

@ -13,7 +13,6 @@ use crate::ns;
use crate::pubsub::{ItemId, NodeName, Subscription, SubscriptionId}; use crate::pubsub::{ItemId, NodeName, Subscription, SubscriptionId};
use jid::Jid; use jid::Jid;
use minidom::Element; use minidom::Element;
use xso::error::{Error, FromElementError};
/// An event item from a PubSub node. /// An event item from a PubSub node.
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)] #[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
@ -33,231 +32,103 @@ pub struct Item {
} }
/// Represents an event happening to a PubSub node. /// Represents an event happening to a PubSub node.
#[derive(Debug, Clone, PartialEq)] #[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
pub enum PubSubEvent { #[xml(namespace = ns::PUBSUB_EVENT, name = "event")]
pub struct Event {
/// The inner child of this event.
#[xml(child)]
pub payload: Payload,
}
impl MessagePayload for Event {}
/// Represents an event happening to a PubSub node.
#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
#[xml(namespace = ns::PUBSUB_EVENT, exhaustive)]
pub enum Payload {
/* /*
Collection { Collection {
}, },
*/ */
/// This nodes configuration changed. /// This nodes configuration changed.
#[xml(name = "configuration")]
Configuration { Configuration {
/// The node affected. /// The node affected.
#[xml(attribute)]
node: NodeName, node: NodeName,
/// The new configuration of this node. /// The new configuration of this node.
#[xml(child(default))]
form: Option<DataForm>, form: Option<DataForm>,
}, },
/// This node has been deleted, with an optional redirect to another node. /// This node has been deleted, with an optional redirect to another node.
#[xml(name = "delete")]
Delete { Delete {
/// The node affected. /// The node affected.
#[xml(attribute)]
node: NodeName, node: NodeName,
/// The xmpp: URI of another node replacing this one. /// The xmpp: URI of another node replacing this one.
#[xml(extract(default, fields(attribute(default, name = "uri"))))]
redirect: Option<String>, redirect: Option<String>,
}, },
/// Some items have been published on this node. /// Some items have been published or retracted on this node.
PublishedItems { #[xml(name = "items")]
Items {
/// The node affected. /// The node affected.
#[xml(attribute)]
node: NodeName, node: NodeName,
/// The list of published items. /// The list of published items.
items: Vec<Item>, #[xml(child(n = ..))]
}, published: Vec<Item>,
/// Some items have been removed from this node.
RetractedItems {
/// The node affected.
node: NodeName,
/// The list of retracted items. /// The list of retracted items.
items: Vec<ItemId>, #[xml(extract(n = .., name = "retract", fields(attribute(name = "id", type_ = ItemId))))]
retracted: Vec<ItemId>,
}, },
/// All items of this node just got removed at once. /// All items of this node just got removed at once.
#[xml(name = "purge")]
Purge { Purge {
/// The node affected. /// The node affected.
#[xml(attribute)]
node: NodeName, node: NodeName,
}, },
/// The users subscription to this node has changed. /// The users subscription to this node has changed.
#[xml(name = "subscription")]
Subscription { Subscription {
/// The node affected. /// The node affected.
#[xml(attribute)]
node: NodeName, node: NodeName,
/// The time at which this subscription will expire. /// The time at which this subscription will expire.
#[xml(attribute(default))]
expiry: Option<DateTime>, expiry: Option<DateTime>,
/// The JID of the user affected. /// The JID of the user affected.
#[xml(attribute(default))]
jid: Option<Jid>, jid: Option<Jid>,
/// An identifier for this subscription. /// An identifier for this subscription.
#[xml(attribute(default))]
subid: Option<SubscriptionId>, subid: Option<SubscriptionId>,
/// The state of this subscription. /// The state of this subscription.
#[xml(attribute(default))]
subscription: Option<Subscription>, subscription: Option<Subscription>,
}, },
} }
fn parse_items(elem: Element, node: NodeName) -> Result<PubSubEvent, Error> { impl Payload {
let mut is_retract = None;
let mut items = vec![];
let mut retracts = vec![];
for child in elem.children() {
if child.is("item", ns::PUBSUB_EVENT) {
match is_retract {
None => is_retract = Some(false),
Some(false) => (),
Some(true) => {
return Err(Error::Other("Mix of item and retract in items element."));
}
}
items.push(Item::try_from(child.clone())?);
} else if child.is("retract", ns::PUBSUB_EVENT) {
match is_retract {
None => is_retract = Some(true),
Some(true) => (),
Some(false) => {
return Err(Error::Other("Mix of item and retract in items element."));
}
}
check_no_children!(child, "retract");
check_no_unknown_attributes!(child, "retract", ["id"]);
let id = get_attr!(child, "id", Required);
retracts.push(id);
} else {
return Err(Error::Other("Invalid child in items element."));
}
}
Ok(match is_retract {
Some(false) => PubSubEvent::PublishedItems { node, items },
Some(true) => PubSubEvent::RetractedItems {
node,
items: retracts,
},
None => return Err(Error::Other("Missing children in items element.")),
})
}
impl TryFrom<Element> for PubSubEvent {
type Error = FromElementError;
fn try_from(elem: Element) -> Result<PubSubEvent, FromElementError> {
check_self!(elem, "event", PUBSUB_EVENT);
check_no_attributes!(elem, "event");
let mut payload = None;
for child in elem.children() {
let node = get_attr!(child, "node", Required);
if child.is("configuration", ns::PUBSUB_EVENT) {
let mut payloads = child.children().cloned().collect::<Vec<_>>();
let item = payloads.pop();
if !payloads.is_empty() {
return Err(Error::Other(
"More than a single payload in configuration element.",
)
.into());
}
let form = match item {
None => None,
Some(payload) => Some(DataForm::try_from(payload)?),
};
payload = Some(PubSubEvent::Configuration { node, form });
} else if child.is("delete", ns::PUBSUB_EVENT) {
let mut redirect = None;
for item in child.children() {
if item.is("redirect", ns::PUBSUB_EVENT) {
if redirect.is_some() {
return Err(
Error::Other("More than one redirect in delete element.").into()
);
}
let uri = get_attr!(item, "uri", Required);
redirect = Some(uri);
} else {
return Err(Error::Other("Unknown child in delete element.").into());
}
}
payload = Some(PubSubEvent::Delete { node, redirect });
} else if child.is("items", ns::PUBSUB_EVENT) {
payload = Some(parse_items(child.clone(), node)?);
} else if child.is("purge", ns::PUBSUB_EVENT) {
check_no_children!(child, "purge");
payload = Some(PubSubEvent::Purge { node });
} else if child.is("subscription", ns::PUBSUB_EVENT) {
check_no_children!(child, "subscription");
payload = Some(PubSubEvent::Subscription {
node,
expiry: get_attr!(child, "expiry", Option),
jid: get_attr!(child, "jid", Option),
subid: get_attr!(child, "subid", Option),
subscription: get_attr!(child, "subscription", Option),
});
} else {
return Err(Error::Other("Unknown child in event element.").into());
}
}
payload.ok_or(Error::Other("No payload in event element.").into())
}
}
impl From<PubSubEvent> for Element {
fn from(event: PubSubEvent) -> Element {
let payload = match event {
PubSubEvent::Configuration { node, form } => {
Element::builder("configuration", ns::PUBSUB_EVENT)
.attr("node", node)
.append_all(form.map(Element::from))
}
PubSubEvent::Delete { node, redirect } => Element::builder("purge", ns::PUBSUB_EVENT)
.attr("node", node)
.append_all(redirect.map(|redirect| {
Element::builder("redirect", ns::PUBSUB_EVENT).attr("uri", redirect)
})),
PubSubEvent::PublishedItems { node, items } => {
Element::builder("items", ns::PUBSUB_EVENT)
.attr("node", node)
.append_all(items)
}
PubSubEvent::RetractedItems { node, items } => {
Element::builder("items", ns::PUBSUB_EVENT)
.attr("node", node)
.append_all(
items
.into_iter()
.map(|id| Element::builder("retract", ns::PUBSUB_EVENT).attr("id", id)),
)
}
PubSubEvent::Purge { node } => {
Element::builder("purge", ns::PUBSUB_EVENT).attr("node", node)
}
PubSubEvent::Subscription {
node,
expiry,
jid,
subid,
subscription,
} => Element::builder("subscription", ns::PUBSUB_EVENT)
.attr("node", node)
.attr("expiry", expiry)
.attr("jid", jid)
.attr("subid", subid)
.attr("subscription", subscription),
};
Element::builder("event", ns::PUBSUB_EVENT)
.append(payload)
.build()
}
}
impl PubSubEvent {
/// Return the name of the node to which this event is related. /// Return the name of the node to which this event is related.
pub fn node_name(&self) -> &NodeName { pub fn node_name(&self) -> &NodeName {
match self { match self {
Self::Purge { node, .. } => node, Self::Purge { node, .. } => node,
Self::PublishedItems { node, .. } => node, Self::Items { node, .. } => node,
Self::RetractedItems { node, .. } => node,
Self::Subscription { node, .. } => node, Self::Subscription { node, .. } => node,
Self::Delete { node, .. } => node, Self::Delete { node, .. } => node,
Self::Configuration { node, .. } => node, Self::Configuration { node, .. } => node,
@ -265,20 +136,21 @@ impl PubSubEvent {
} }
} }
impl MessagePayload for PubSubEvent {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use jid::BareJid; use jid::BareJid;
use xso::error::{Error, FromElementError};
// TODO: Reenable this test once we support asserting that a Vec isnt empty.
#[test] #[test]
#[ignore]
fn missing_items() { fn missing_items() {
let elem: Element = let elem: Element =
"<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='coucou'/></event>" "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='coucou'/></event>"
.parse() .parse()
.unwrap(); .unwrap();
let error = PubSubEvent::try_from(elem).unwrap_err(); let error = Event::try_from(elem).unwrap_err();
let message = match error { let message = match error {
FromElementError::Invalid(Error::Other(string)) => string, FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(), _ => panic!(),
@ -289,16 +161,21 @@ mod tests {
#[test] #[test]
fn test_simple_items() { fn test_simple_items() {
let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='coucou'><item id='test' publisher='test@coucou'/></items></event>".parse().unwrap(); let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='coucou'><item id='test' publisher='test@coucou'/></items></event>".parse().unwrap();
let event = PubSubEvent::try_from(elem).unwrap(); let event = Event::try_from(elem).unwrap();
match event { match event.payload {
PubSubEvent::PublishedItems { node, items } => { Payload::Items {
node,
published,
retracted,
} => {
assert_eq!(node, NodeName(String::from("coucou"))); assert_eq!(node, NodeName(String::from("coucou")));
assert_eq!(items[0].id, Some(ItemId(String::from("test")))); assert_eq!(retracted.len(), 0);
assert_eq!(published[0].id, Some(ItemId(String::from("test"))));
assert_eq!( assert_eq!(
items[0].publisher.clone().unwrap(), published[0].publisher.clone().unwrap(),
BareJid::new("test@coucou").unwrap() BareJid::new("test@coucou").unwrap()
); );
assert_eq!(items[0].payload, None); assert_eq!(published[0].payload, None);
} }
_ => panic!(), _ => panic!(),
} }
@ -307,13 +184,18 @@ mod tests {
#[test] #[test]
fn test_simple_pep() { fn test_simple_pep() {
let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='something'><item><foreign xmlns='example:namespace'/></item></items></event>".parse().unwrap(); let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='something'><item><foreign xmlns='example:namespace'/></item></items></event>".parse().unwrap();
let event = PubSubEvent::try_from(elem).unwrap(); let event = Event::try_from(elem).unwrap();
match event { match event.payload {
PubSubEvent::PublishedItems { node, items } => { Payload::Items {
node,
published,
retracted,
} => {
assert_eq!(node, NodeName(String::from("something"))); assert_eq!(node, NodeName(String::from("something")));
assert_eq!(items[0].id, None); assert_eq!(retracted.len(), 0);
assert_eq!(items[0].publisher, None); assert_eq!(published[0].id, None);
match items[0].payload { assert_eq!(published[0].publisher, None);
match published[0].payload {
Some(ref elem) => assert!(elem.is("foreign", "example:namespace")), Some(ref elem) => assert!(elem.is("foreign", "example:namespace")),
_ => panic!(), _ => panic!(),
} }
@ -325,12 +207,17 @@ mod tests {
#[test] #[test]
fn test_simple_retract() { fn test_simple_retract() {
let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='something'><retract id='coucou'/><retract id='test'/></items></event>".parse().unwrap(); let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='something'><retract id='coucou'/><retract id='test'/></items></event>".parse().unwrap();
let event = PubSubEvent::try_from(elem).unwrap(); let event = Event::try_from(elem).unwrap();
match event { match event.payload {
PubSubEvent::RetractedItems { node, items } => { Payload::Items {
node,
published,
retracted,
} => {
assert_eq!(node, NodeName(String::from("something"))); assert_eq!(node, NodeName(String::from("something")));
assert_eq!(items[0], ItemId(String::from("coucou"))); assert_eq!(published.len(), 0);
assert_eq!(items[1], ItemId(String::from("test"))); assert_eq!(retracted[0], ItemId(String::from("coucou")));
assert_eq!(retracted[1], ItemId(String::from("test")));
} }
_ => panic!(), _ => panic!(),
} }
@ -339,9 +226,9 @@ mod tests {
#[test] #[test]
fn test_simple_delete() { fn test_simple_delete() {
let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><delete node='coucou'><redirect uri='hello'/></delete></event>".parse().unwrap(); let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><delete node='coucou'><redirect uri='hello'/></delete></event>".parse().unwrap();
let event = PubSubEvent::try_from(elem).unwrap(); let event = Event::try_from(elem).unwrap();
match event { match event.payload {
PubSubEvent::Delete { node, redirect } => { Payload::Delete { node, redirect } => {
assert_eq!(node, NodeName(String::from("coucou"))); assert_eq!(node, NodeName(String::from("coucou")));
assert_eq!(redirect, Some(String::from("hello"))); assert_eq!(redirect, Some(String::from("hello")));
} }
@ -355,9 +242,9 @@ mod tests {
"<event xmlns='http://jabber.org/protocol/pubsub#event'><purge node='coucou'/></event>" "<event xmlns='http://jabber.org/protocol/pubsub#event'><purge node='coucou'/></event>"
.parse() .parse()
.unwrap(); .unwrap();
let event = PubSubEvent::try_from(elem).unwrap(); let event = Event::try_from(elem).unwrap();
match event { match event.payload {
PubSubEvent::Purge { node } => { Payload::Purge { node } => {
assert_eq!(node, NodeName(String::from("coucou"))); assert_eq!(node, NodeName(String::from("coucou")));
} }
_ => panic!(), _ => panic!(),
@ -367,9 +254,9 @@ mod tests {
#[test] #[test]
fn test_simple_configure() { fn test_simple_configure() {
let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><configuration node='coucou'><x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field></x></configuration></event>".parse().unwrap(); let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><configuration node='coucou'><x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field></x></configuration></event>".parse().unwrap();
let event = PubSubEvent::try_from(elem).unwrap(); let event = Event::try_from(elem).unwrap();
match event { match event.payload {
PubSubEvent::Configuration { node, form: _ } => { Payload::Configuration { node, form: _ } => {
assert_eq!(node, NodeName(String::from("coucou"))); assert_eq!(node, NodeName(String::from("coucou")));
//assert_eq!(form.type_, Result_); //assert_eq!(form.type_, Result_);
} }
@ -383,12 +270,12 @@ mod tests {
"<event xmlns='http://jabber.org/protocol/pubsub#event'><coucou node='test'/></event>" "<event xmlns='http://jabber.org/protocol/pubsub#event'><coucou node='test'/></event>"
.parse() .parse()
.unwrap(); .unwrap();
let error = PubSubEvent::try_from(elem).unwrap_err(); let error = Event::try_from(elem).unwrap_err();
let message = match error { let message = match error {
FromElementError::Invalid(Error::Other(string)) => string, FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(), _ => panic!(),
}; };
assert_eq!(message, "Unknown child in event element."); assert_eq!(message, "This is not a Payload element.");
} }
#[cfg(not(feature = "disable-validation"))] #[cfg(not(feature = "disable-validation"))]
@ -397,12 +284,12 @@ mod tests {
let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event' coucou=''/>" let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event' coucou=''/>"
.parse() .parse()
.unwrap(); .unwrap();
let error = PubSubEvent::try_from(elem).unwrap_err(); let error = Event::try_from(elem).unwrap_err();
let message = match error { let message = match error {
FromElementError::Invalid(Error::Other(string)) => string, FromElementError::Invalid(Error::Other(string)) => string,
_ => panic!(), _ => panic!(),
}; };
assert_eq!(message, "Unknown attribute in event element."); assert_eq!(message, "Unknown attribute in Event element.");
} }
#[test] #[test]
@ -410,9 +297,9 @@ mod tests {
let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><subscription expiry='2006-02-28T23:59:59+00:00' jid='francisco@denmark.lit' node='princely_musings' subid='ba49252aaa4f5d320c24d3766f0bdcade78c78d3' subscription='subscribed'/></event>" let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><subscription expiry='2006-02-28T23:59:59+00:00' jid='francisco@denmark.lit' node='princely_musings' subid='ba49252aaa4f5d320c24d3766f0bdcade78c78d3' subscription='subscribed'/></event>"
.parse() .parse()
.unwrap(); .unwrap();
let event = PubSubEvent::try_from(elem.clone()).unwrap(); let event = Event::try_from(elem.clone()).unwrap();
match event.clone() { match event.payload.clone() {
PubSubEvent::Subscription { Payload::Subscription {
node, node,
expiry, expiry,
jid, jid,

View file

@ -14,7 +14,7 @@ pub mod owner;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
pub mod pubsub; pub mod pubsub;
pub use self::event::PubSubEvent; pub use self::event::Event;
pub use self::owner::PubSubOwner; pub use self::owner::PubSubOwner;
pub use self::pubsub::PubSub; pub use self::pubsub::PubSub;

View file

@ -15,7 +15,7 @@ use xmpp_parsers::{
ns, ns,
presence::{Presence, Type as PresenceType}, presence::{Presence, Type as PresenceType},
pubsub::{ pubsub::{
event::PubSubEvent, self,
pubsub::{Items, PubSub}, pubsub::{Items, PubSub},
NodeName, NodeName,
}, },
@ -126,10 +126,15 @@ async fn main() {
} }
for child in message.payloads { for child in message.payloads {
if child.is("event", ns::PUBSUB_EVENT) { if child.is("event", ns::PUBSUB_EVENT) {
let event = PubSubEvent::try_from(child).unwrap(); let event = pubsub::Event::try_from(child).unwrap();
if let PubSubEvent::PublishedItems { node, items } = event { if let pubsub::event::Payload::Items {
node,
published,
retracted: _,
} = event.payload
{
if node.0 == ns::AVATAR_METADATA { if node.0 == ns::AVATAR_METADATA {
for item in items.into_iter() { for item in published.into_iter() {
let payload = item.payload.clone().unwrap(); let payload = item.payload.clone().unwrap();
if payload.is("metadata", ns::AVATAR_METADATA) { if payload.is("metadata", ns::AVATAR_METADATA) {
// TODO: do something with these metadata. // TODO: do something with these metadata.

View file

@ -10,7 +10,7 @@ use crate::{
muc::room::{JoinRoomSettings, LeaveRoomSettings}, muc::room::{JoinRoomSettings, LeaveRoomSettings},
parsers::{ parsers::{
bookmarks2, ns, bookmarks2, ns,
pubsub::{event::PubSubEvent, pubsub::PubSub}, pubsub::{self, pubsub::PubSub},
}, },
Agent, Event, RoomNick, Agent, Event, RoomNick,
}; };
@ -30,61 +30,64 @@ pub(crate) async fn handle_event(
#[allow(unused_mut)] #[allow(unused_mut)]
let mut events = Vec::new(); let mut events = Vec::new();
let event = PubSubEvent::try_from(elem); let event = pubsub::Event::try_from(elem);
trace!("PubSub event: {:#?}", event); trace!("PubSub event: {:#?}", event);
match event { match event {
Ok(PubSubEvent::PublishedItems { node, items }) => { Ok(pubsub::Event {
payload:
pubsub::event::Payload::Items {
node,
published,
retracted,
},
}) => {
match node.0 { match node.0 {
#[cfg(feature = "avatars")] #[cfg(feature = "avatars")]
ref node if node == ns::AVATAR_METADATA => { ref node if node == ns::AVATAR_METADATA => {
// TODO: Also handle retracted!
let new_events = let new_events =
avatar::handle_metadata_pubsub_event(&from, agent, items).await; avatar::handle_metadata_pubsub_event(&from, agent, published).await;
events.extend(new_events); events.extend(new_events);
} }
ref node if node == ns::BOOKMARKS2 => { ref node if node == ns::BOOKMARKS2 => {
// TODO: Check that our bare JID is the sender. // TODO: Check that our bare JID is the sender.
assert_eq!(items.len(), 1); if let [item] = &published[..] {
let item = items.clone().pop().unwrap(); let jid = BareJid::from_str(&item.id.clone().unwrap().0).unwrap();
let jid = BareJid::from_str(&item.id.clone().unwrap().0).unwrap(); let payload = item.payload.clone().unwrap();
let payload = item.payload.clone().unwrap(); match bookmarks2::Conference::try_from(payload) {
match bookmarks2::Conference::try_from(payload) { Ok(conference) => {
Ok(conference) => { if conference.autojoin {
if conference.autojoin { if !agent.rooms_joined.contains_key(&jid) {
if !agent.rooms_joined.contains_key(&jid) { agent
agent .join_room(JoinRoomSettings {
.join_room(JoinRoomSettings { room: jid,
room: jid, nick: conference.nick.map(RoomNick::new),
nick: conference.nick.map(RoomNick::new), password: conference.password,
password: conference.password, status: None,
status: None, })
}) .await;
.await; }
} else {
// So maybe another client of ours left the room... let's leave it too
agent.leave_room(LeaveRoomSettings::new(jid)).await;
} }
} else {
// So maybe another client of ours left the room... let's leave it too
agent.leave_room(LeaveRoomSettings::new(jid)).await;
} }
Err(err) => println!("not bookmark: {}", err),
} }
Err(err) => println!("not bookmark: {}", err), } else if let [item] = &retracted[..] {
let jid = BareJid::from_str(&item.0).unwrap();
agent.leave_room(LeaveRoomSettings::new(jid)).await;
} else {
error!("No published or retracted item in pubsub event!");
} }
} }
ref node => unimplemented!("node {}", node), ref node => unimplemented!("node {}", node),
} }
} }
Ok(PubSubEvent::RetractedItems { node, items }) => { Ok(pubsub::Event {
match node.0 { payload: pubsub::event::Payload::Purge { node },
ref node if node == ns::BOOKMARKS2 => { }) => match node.0 {
// TODO: Check that our bare JID is the sender.
assert_eq!(items.len(), 1);
let item = items.clone().pop().unwrap();
let jid = BareJid::from_str(&item.0).unwrap();
agent.leave_room(LeaveRoomSettings::new(jid)).await;
}
ref node => unimplemented!("node {}", node),
}
}
Ok(PubSubEvent::Purge { node }) => match node.0 {
ref node if node == ns::BOOKMARKS2 => { ref node if node == ns::BOOKMARKS2 => {
warn!("The bookmarks2 PEP node was deleted!"); warn!("The bookmarks2 PEP node was deleted!");
} }