@@ -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);
@@ -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();
@@ -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
+}