Move from XEP-0048 to Bookmarks 2 (This Time it’s Serious).

Emmanuel Gil Peyrot created

Change summary

examples/hello_bot.rs |  12 +++-
src/lib.rs            |  62 +++++++------------------
src/pubsub/avatar.rs  |   0 
src/pubsub/mod.rs     | 106 +++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 133 insertions(+), 47 deletions(-)

Detailed changes

examples/hello_bot.rs 🔗

@@ -54,9 +54,15 @@ fn main() {
             Event::ContactChanged(contact) => {
                 println!("Contact {} changed.", contact.jid);
             },
-            Event::OpenRoomBookmark(bookmark) => {
-                println!("Joining room “{}” ({})…", bookmark.name, bookmark.jid);
-                agent.join_room(bookmark.jid, bookmark.nick, bookmark.password, "en", "Yet another bot!");
+            Event::JoinRoom(jid, conference) => {
+                println!("Joining room {} ({:?})…", jid, conference.name);
+                agent.join_room(jid, conference.nick, conference.password, "en", "Yet another bot!");
+            },
+            Event::LeaveRoom(jid) => {
+                println!("Leaving room {}…", jid);
+            },
+            Event::LeaveAllRooms => {
+                println!("Leaving all rooms…");
             },
             Event::RoomJoined(jid) => {
                 println!("Joined room {}.", jid);

src/lib.rs 🔗

@@ -17,11 +17,7 @@ use tokio_xmpp::{
     Packet,
 };
 use xmpp_parsers::{
-    bookmarks::{
-        Autojoin,
-        Conference as ConferenceBookmark,
-        Storage as Bookmarks,
-    },
+    bookmarks2::Conference,
     caps::{compute_disco, hash_caps, Caps},
     disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity},
     hashes::Algo,
@@ -33,16 +29,13 @@ use xmpp_parsers::{
     },
     ns,
     presence::{Presence, Type as PresenceType},
-    pubsub::{
-        event::PubSubEvent,
-        pubsub::PubSub,
-    },
+    pubsub::pubsub::{PubSub, Items},
     roster::{Roster, Item as RosterItem},
     stanza_error::{StanzaError, ErrorType, DefinedCondition},
     Jid, BareJid, FullJid, JidParseError,
 };
 
-mod avatar;
+mod pubsub;
 
 pub type Error = tokio_xmpp::Error;
 
@@ -84,7 +77,9 @@ pub enum Event {
     ContactRemoved(RosterItem),
     ContactChanged(RosterItem),
     AvatarRetrieved(Jid, String),
-    OpenRoomBookmark(ConferenceBookmark),
+    JoinRoom(BareJid, Conference),
+    LeaveRoom(BareJid),
+    LeaveAllRooms,
     RoomJoined(BareJid),
     RoomLeft(BareJid),
 }
@@ -141,7 +136,7 @@ impl ClientBuilder<'_> {
             features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA)));
         }
         if self.features.contains(&ClientFeature::JoinRooms) {
-            features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS)));
+            features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS2)));
         }
         DiscoInfoResult {
             node: None,
@@ -209,6 +204,10 @@ impl ClientBuilder<'_> {
                         let iq = Iq::from_get("roster", Roster { ver: None, items: vec![] })
                             .into();
                         sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
+                        // TODO: only send this when the JoinRooms feature is enabled.
+                        let iq = Iq::from_get("bookmarks", PubSub::Items(Items::new(ns::BOOKMARKS2)))
+                            .into();
+                        sender_tx.unbounded_send(Packet::Stanza(iq)).unwrap();
                     }
                     TokioXmppEvent::Disconnected => {
                         events.push(Event::Disconnected);
@@ -216,6 +215,8 @@ impl ClientBuilder<'_> {
                     TokioXmppEvent::Stanza(stanza) => {
                         if stanza.is("iq", "jabber:client") {
                             let iq = Iq::try_from(stanza).unwrap();
+                            let from =
+                                iq.from.clone().unwrap_or_else(|| Jid::from_str(&jid).unwrap());
                             if let IqType::Get(payload) = iq.payload {
                                 if payload.is("query", ns::DISCO_INFO) {
                                     let query = DiscoInfoQuery::try_from(payload);
@@ -245,15 +246,8 @@ impl ClientBuilder<'_> {
                                         events.push(Event::ContactAdded(item));
                                     }
                                 } else if payload.is("pubsub", ns::PUBSUB) {
-                                    let pubsub = PubSub::try_from(payload).unwrap();
-                                    let from =
-                                        iq.from.clone().unwrap_or_else(|| Jid::from_str(&jid).unwrap());
-                                    if let PubSub::Items(items) = pubsub {
-                                        if items.node.0 == ns::AVATAR_DATA {
-                                            let new_events = avatar::handle_data_pubsub_iq(&from, &items);
-                                            events.extend(new_events);
-                                        }
-                                    }
+                                    let new_events = pubsub::handle_iq_result(&from, payload);
+                                    events.extend(new_events);
                                 }
                             } else if let IqType::Set(_) = iq.payload {
                                 // We MUST answer unhandled set iqs with a service-unavailable error.
@@ -264,28 +258,8 @@ impl ClientBuilder<'_> {
                             let from = message.from.clone().unwrap();
                             for child in message.payloads {
                                 if child.is("event", ns::PUBSUB_EVENT) {
-                                    let event = PubSubEvent::try_from(child).unwrap();
-                                    if let PubSubEvent::PublishedItems { node, items } = event {
-                                        if node.0 == ns::AVATAR_METADATA {
-                                            let new_events = avatar::handle_metadata_pubsub_event(&from, &mut sender_tx, items);
-                                            events.extend(new_events);
-                                        } else if node.0 == ns::BOOKMARKS {
-                                            // TODO: Check that our bare JID is the sender.
-                                            assert_eq!(items.len(), 1);
-                                            let item = items.clone().pop().unwrap();
-                                            let payload = item.payload.clone().unwrap();
-                                            let bookmarks = match Bookmarks::try_from(payload) {
-                                                Ok(bookmarks) => bookmarks,
-                                                // XXX: Don’t panic…
-                                                Err(err) => panic!("… {}", err),
-                                            };
-                                            for bookmark in bookmarks.conferences {
-                                                if bookmark.autojoin == Autojoin::True {
-                                                    events.push(Event::OpenRoomBookmark(bookmark));
-                                                }
-                                            }
-                                        }
-                                    }
+                                    let new_events = pubsub::handle_event(&from, child, &mut sender_tx);
+                                    events.extend(new_events);
                                 }
                             }
                         } else if stanza.is("presence", "jabber:client") {
@@ -362,7 +336,7 @@ impl Agent {
         let nick = nick.unwrap_or_else(|| self.default_nick.borrow().clone());
         let room_jid = room.with_resource(nick);
         let mut presence = Presence::new(PresenceType::None)
-            .with_to(Some(Jid::Full(room_jid)));
+            .with_to(Jid::Full(room_jid));
         presence.add_payload(muc);
         presence.set_status(String::from(lang), String::from(status));
         let presence = presence.into();

src/pubsub/mod.rs 🔗

@@ -0,0 +1,106 @@
+// Copyright (c) 2019 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use crate::Event;
+use futures::sync::mpsc;
+use std::convert::TryFrom;
+use std::str::FromStr;
+use tokio_xmpp::Packet;
+use xmpp_parsers::{
+    Jid, BareJid,
+    Element,
+    ns,
+    bookmarks2::{
+        Autojoin,
+        Conference,
+    },
+    pubsub::event::PubSubEvent,
+    pubsub::pubsub::PubSub,
+};
+
+pub(crate) mod avatar;
+
+pub(crate) fn handle_event(from: &Jid, elem: Element, mut tx: &mut mpsc::UnboundedSender<Packet>) -> impl IntoIterator<Item = Event> {
+    let mut events = Vec::new();
+    match PubSubEvent::try_from(elem) {
+        Ok(PubSubEvent::PublishedItems { node, items }) => {
+            match node.0 {
+                node if node == ns::AVATAR_METADATA => {
+                    let new_events = avatar::handle_metadata_pubsub_event(&from, &mut tx, items);
+                    events.extend(new_events);
+                },
+                node if node == ns::BOOKMARKS2 => {
+                    // 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.id.clone().unwrap().0).unwrap();
+                    let payload = item.payload.clone().unwrap();
+                    match Conference::try_from(payload) {
+                        Ok(conference) => {
+                            if conference.autojoin == Autojoin::True {
+                                events.push(Event::JoinRoom(jid, conference));
+                            } else {
+                                events.push(Event::LeaveRoom(jid));
+                            }
+                        },
+                        Err(err) => println!("not bookmark: {}", err)
+                    }
+                },
+                node => unimplemented!("node {}", node)
+            }
+        }
+        Ok(PubSubEvent::RetractedItems { node, items }) => {
+            match node.0 {
+                node if node == ns::BOOKMARKS2 => {
+                    // 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();
+                    events.push(Event::LeaveRoom(jid));
+                },
+                node => unimplemented!("node {}", node)
+            }
+        }
+        Ok(PubSubEvent::Purge { node }) => {
+            match node.0 {
+                node if node == ns::BOOKMARKS2 => {
+                    // TODO: Check that our bare JID is the sender.
+                    events.push(Event::LeaveAllRooms);
+                },
+                node => unimplemented!("node {}", node)
+            }
+        }
+        _ => unimplemented!()
+    }
+    events
+}
+
+pub(crate) fn handle_iq_result(from: &Jid, elem: Element) -> impl IntoIterator<Item = Event> {
+    let mut events = Vec::new();
+    let pubsub = PubSub::try_from(elem).unwrap();
+    if let PubSub::Items(items) = pubsub {
+        if items.node.0 == ns::AVATAR_DATA {
+            let new_events = avatar::handle_data_pubsub_iq(&from, &items);
+            events.extend(new_events);
+        } else if items.node.0 == ns::BOOKMARKS2 {
+            events.push(Event::LeaveAllRooms);
+            for item in items.items {
+                let item = item.0;
+                let jid = BareJid::from_str(&item.id.clone().unwrap().0).unwrap();
+                let payload = item.payload.clone().unwrap();
+                match Conference::try_from(payload) {
+                    Ok(conference) => {
+                        if let Autojoin::True = conference.autojoin {
+                            events.push(Event::JoinRoom(jid, conference));
+                        }
+                    },
+                    Err(err) => panic!("Wrong payload type in bookmarks 2 item: {}", err),
+                }
+            }
+        }
+    }
+    events
+}