diff --git a/examples/hello_bot.rs b/examples/hello_bot.rs index 2d8aae6d..e374bf68 100644 --- a/examples/hello_bot.rs +++ b/examples/hello_bot.rs @@ -7,32 +7,30 @@ use futures::prelude::*; use std::env::args; use std::process::exit; -use std::str::FromStr; use tokio::runtime::current_thread::Runtime; -use xmpp_parsers::{Jid, message::MessageType}; +use xmpp_parsers::message::MessageType; use xmpp::{ClientBuilder, ClientType, ClientFeature, Event}; fn main() { let args: Vec = args().collect(); - if args.len() != 5 { - println!("Usage: {} ", args[0]); + if args.len() != 3 { + println!("Usage: {} ", args[0]); exit(1); } let jid = &args[1]; let password = &args[2]; - let room_jid = &args[3]; - let nick: &str = &args[4]; // tokio_core context let mut rt = Runtime::new().unwrap(); - // Client instance let (mut agent, stream) = ClientBuilder::new(jid, password) .set_client(ClientType::Bot, "xmpp-rs") .set_website("https://gitlab.com/xmpp-rs/xmpp-rs") + .set_default_nick("bot") .enable_feature(ClientFeature::Avatars) .enable_feature(ClientFeature::ContactList) + .enable_feature(ClientFeature::JoinRooms) .build() .unwrap(); @@ -42,26 +40,31 @@ fn main() { match evt { Event::Online => { println!("Online."); - let room_jid = Jid::from_str(room_jid).unwrap().with_resource(nick); - agent.join_room(room_jid, "en", "Yet another bot!"); }, Event::Disconnected => { println!("Disconnected."); return Err(None); }, Event::ContactAdded(contact) => { - println!("Contact {:?} added.", contact); + println!("Contact {} added.", contact.jid); }, Event::ContactRemoved(contact) => { - println!("Contact {:?} removed.", contact); + println!("Contact {} removed.", contact.jid); }, Event::ContactChanged(contact) => { - println!("Contact {:?} changed.", 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::RoomJoined(jid) => { println!("Joined room {}.", jid); agent.send_message(jid.into_bare_jid(), MessageType::Groupchat, "en", "Hello world!"); }, + Event::RoomLeft(jid) => { + println!("Left room {}.", jid); + }, Event::AvatarRetrieved(jid, path) => { println!("Received avatar for {} in {}.", jid, path); }, diff --git a/src/lib.rs b/src/lib.rs index 8a4454b0..93089b67 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,8 @@ #![deny(bare_trait_objects)] use std::str::FromStr; +use std::rc::Rc; +use std::cell::RefCell; use futures::{Future,Stream, Sink, sync::mpsc}; use tokio_xmpp::{ Client as TokioXmppClient, @@ -14,6 +16,11 @@ use tokio_xmpp::{ Packet, }; use xmpp_parsers::{ + bookmarks::{ + Autojoin, + Conference as ConferenceBookmark, + Storage as Bookmarks, + }, caps::{compute_disco, hash_caps, Caps}, disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity}, hashes::Algo, @@ -65,8 +72,10 @@ impl ToString for ClientType { pub enum ClientFeature { Avatars, ContactList, + JoinRooms, } +#[derive(Debug)] pub enum Event { Online, Disconnected, @@ -74,7 +83,9 @@ pub enum Event { ContactRemoved(RosterItem), ContactChanged(RosterItem), AvatarRetrieved(Jid, String), + OpenRoomBookmark(ConferenceBookmark), RoomJoined(Jid), + RoomLeft(Jid), } #[derive(Default)] @@ -82,6 +93,7 @@ pub struct ClientBuilder<'a> { jid: &'a str, password: &'a str, website: String, + default_nick: String, disco: (ClientType, String), features: Vec, } @@ -92,6 +104,7 @@ impl ClientBuilder<'_> { jid, password, website: String::from("https://gitlab.com/xmpp-rs/tokio-xmpp"), + default_nick: String::from("xmpp-rs"), disco: (ClientType::default(), String::from("tokio-xmpp")), features: vec![], } @@ -107,6 +120,11 @@ impl ClientBuilder<'_> { self } + pub fn set_default_nick(mut self, nick: &str) -> Self { + self.default_nick = String::from(nick); + self + } + pub fn enable_feature(mut self, feature: ClientFeature) -> Self { self.features.push(feature); self @@ -121,6 +139,9 @@ impl ClientBuilder<'_> { if self.features.contains(&ClientFeature::Avatars) { features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA))); } + if self.features.contains(&ClientFeature::JoinRooms) { + features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS))); + } DiscoInfoResult { node: None, identities, @@ -225,7 +246,7 @@ impl ClientBuilder<'_> { } else if payload.is("pubsub", ns::PUBSUB) { let pubsub = PubSub::try_from(payload).unwrap(); let from = - iq.from.clone().unwrap_or(Jid::from_str(&jid).unwrap()); + 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); @@ -246,6 +267,21 @@ impl ClientBuilder<'_> { if let PubSubEvent::PublishedItems { node, items } = event { if node.0 == ns::AVATAR_METADATA { avatar::handle_metadata_pubsub_event(&from, &mut sender_tx, items); + } 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)); + } + } } } } @@ -295,38 +331,36 @@ impl ClientBuilder<'_> { .select(sender.into_stream()) .filter_map(|x| x); - let agent = Agent { sender_tx }; + let agent = Agent { + sender_tx, + default_nick: Rc::new(RefCell::new(self.default_nick)), + }; (agent, future) } } -pub struct Client { - sender_tx: mpsc::UnboundedSender, - stream: Box>, -} - -impl Client { - pub fn get_agent(&self) -> Agent { - Agent { - sender_tx: self.sender_tx.clone(), - } - } - - pub fn listen(self) -> Box> { - self.stream - } -} - pub struct Agent { sender_tx: mpsc::UnboundedSender, + default_nick: Rc>, } impl Agent { - pub fn join_room(&mut self, room: Jid, lang: &str, status: &str) { + pub fn join_room(&mut self, room: Jid, nick: Option, password: Option, + lang: &str, status: &str) { + let mut muc = Muc::new(); + if let Some(password) = password { + muc = muc.with_password(password); + } + // TODO: change room into a BareJid, which requires an update of jid, which requires an + // update of xmpp-parsers, which requires an update of tokio-xmpp… + assert_eq!(room.resource, None); + + 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(room)) - .with_payloads(vec![Muc::new().into()]); + .with_to(Some(room_jid)); + presence.add_payload(muc); presence.set_status(String::from(lang), String::from(status)); let presence = presence.into(); self.sender_tx.unbounded_send(Packet::Stanza(presence))