Autojoin MUCs from bookmarks.

When the JoinRooms ClientFeature is enabled, we want to automatically
receive bookmarks and join them when they are added.
This commit is contained in:
Emmanuel Gil Peyrot 2019-07-25 15:11:39 +02:00
parent 9df465d940
commit 8e0bcaed14
2 changed files with 71 additions and 34 deletions

View file

@ -7,32 +7,30 @@
use futures::prelude::*; use futures::prelude::*;
use std::env::args; use std::env::args;
use std::process::exit; use std::process::exit;
use std::str::FromStr;
use tokio::runtime::current_thread::Runtime; use tokio::runtime::current_thread::Runtime;
use xmpp_parsers::{Jid, message::MessageType}; use xmpp_parsers::message::MessageType;
use xmpp::{ClientBuilder, ClientType, ClientFeature, Event}; use xmpp::{ClientBuilder, ClientType, ClientFeature, Event};
fn main() { fn main() {
let args: Vec<String> = args().collect(); let args: Vec<String> = args().collect();
if args.len() != 5 { if args.len() != 3 {
println!("Usage: {} <jid> <password> <room JID> <nick>", args[0]); println!("Usage: {} <jid> <password>", args[0]);
exit(1); exit(1);
} }
let jid = &args[1]; let jid = &args[1];
let password = &args[2]; let password = &args[2];
let room_jid = &args[3];
let nick: &str = &args[4];
// tokio_core context // tokio_core context
let mut rt = Runtime::new().unwrap(); let mut rt = Runtime::new().unwrap();
// Client instance // Client instance
let (mut agent, stream) = ClientBuilder::new(jid, password) let (mut agent, stream) = ClientBuilder::new(jid, password)
.set_client(ClientType::Bot, "xmpp-rs") .set_client(ClientType::Bot, "xmpp-rs")
.set_website("https://gitlab.com/xmpp-rs/xmpp-rs") .set_website("https://gitlab.com/xmpp-rs/xmpp-rs")
.set_default_nick("bot")
.enable_feature(ClientFeature::Avatars) .enable_feature(ClientFeature::Avatars)
.enable_feature(ClientFeature::ContactList) .enable_feature(ClientFeature::ContactList)
.enable_feature(ClientFeature::JoinRooms)
.build() .build()
.unwrap(); .unwrap();
@ -42,26 +40,31 @@ fn main() {
match evt { match evt {
Event::Online => { Event::Online => {
println!("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 => { Event::Disconnected => {
println!("Disconnected."); println!("Disconnected.");
return Err(None); return Err(None);
}, },
Event::ContactAdded(contact) => { Event::ContactAdded(contact) => {
println!("Contact {:?} added.", contact); println!("Contact {} added.", contact.jid);
}, },
Event::ContactRemoved(contact) => { Event::ContactRemoved(contact) => {
println!("Contact {:?} removed.", contact); println!("Contact {} removed.", contact.jid);
}, },
Event::ContactChanged(contact) => { 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) => { Event::RoomJoined(jid) => {
println!("Joined room {}.", jid); println!("Joined room {}.", jid);
agent.send_message(jid.into_bare_jid(), MessageType::Groupchat, "en", "Hello world!"); agent.send_message(jid.into_bare_jid(), MessageType::Groupchat, "en", "Hello world!");
}, },
Event::RoomLeft(jid) => {
println!("Left room {}.", jid);
},
Event::AvatarRetrieved(jid, path) => { Event::AvatarRetrieved(jid, path) => {
println!("Received avatar for {} in {}.", jid, path); println!("Received avatar for {} in {}.", jid, path);
}, },

View file

@ -7,6 +7,8 @@
#![deny(bare_trait_objects)] #![deny(bare_trait_objects)]
use std::str::FromStr; use std::str::FromStr;
use std::rc::Rc;
use std::cell::RefCell;
use futures::{Future,Stream, Sink, sync::mpsc}; use futures::{Future,Stream, Sink, sync::mpsc};
use tokio_xmpp::{ use tokio_xmpp::{
Client as TokioXmppClient, Client as TokioXmppClient,
@ -14,6 +16,11 @@ use tokio_xmpp::{
Packet, Packet,
}; };
use xmpp_parsers::{ use xmpp_parsers::{
bookmarks::{
Autojoin,
Conference as ConferenceBookmark,
Storage as Bookmarks,
},
caps::{compute_disco, hash_caps, Caps}, caps::{compute_disco, hash_caps, Caps},
disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity}, disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity},
hashes::Algo, hashes::Algo,
@ -65,8 +72,10 @@ impl ToString for ClientType {
pub enum ClientFeature { pub enum ClientFeature {
Avatars, Avatars,
ContactList, ContactList,
JoinRooms,
} }
#[derive(Debug)]
pub enum Event { pub enum Event {
Online, Online,
Disconnected, Disconnected,
@ -74,7 +83,9 @@ pub enum Event {
ContactRemoved(RosterItem), ContactRemoved(RosterItem),
ContactChanged(RosterItem), ContactChanged(RosterItem),
AvatarRetrieved(Jid, String), AvatarRetrieved(Jid, String),
OpenRoomBookmark(ConferenceBookmark),
RoomJoined(Jid), RoomJoined(Jid),
RoomLeft(Jid),
} }
#[derive(Default)] #[derive(Default)]
@ -82,6 +93,7 @@ pub struct ClientBuilder<'a> {
jid: &'a str, jid: &'a str,
password: &'a str, password: &'a str,
website: String, website: String,
default_nick: String,
disco: (ClientType, String), disco: (ClientType, String),
features: Vec<ClientFeature>, features: Vec<ClientFeature>,
} }
@ -92,6 +104,7 @@ impl ClientBuilder<'_> {
jid, jid,
password, password,
website: String::from("https://gitlab.com/xmpp-rs/tokio-xmpp"), website: String::from("https://gitlab.com/xmpp-rs/tokio-xmpp"),
default_nick: String::from("xmpp-rs"),
disco: (ClientType::default(), String::from("tokio-xmpp")), disco: (ClientType::default(), String::from("tokio-xmpp")),
features: vec![], features: vec![],
} }
@ -107,6 +120,11 @@ impl ClientBuilder<'_> {
self 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 { pub fn enable_feature(mut self, feature: ClientFeature) -> Self {
self.features.push(feature); self.features.push(feature);
self self
@ -121,6 +139,9 @@ impl ClientBuilder<'_> {
if self.features.contains(&ClientFeature::Avatars) { if self.features.contains(&ClientFeature::Avatars) {
features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA))); features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA)));
} }
if self.features.contains(&ClientFeature::JoinRooms) {
features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS)));
}
DiscoInfoResult { DiscoInfoResult {
node: None, node: None,
identities, identities,
@ -225,7 +246,7 @@ impl ClientBuilder<'_> {
} else if payload.is("pubsub", ns::PUBSUB) { } else if payload.is("pubsub", ns::PUBSUB) {
let pubsub = PubSub::try_from(payload).unwrap(); let pubsub = PubSub::try_from(payload).unwrap();
let from = 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 let PubSub::Items(items) = pubsub {
if items.node.0 == ns::AVATAR_DATA { if items.node.0 == ns::AVATAR_DATA {
let new_events = avatar::handle_data_pubsub_iq(&from, &items); let new_events = avatar::handle_data_pubsub_iq(&from, &items);
@ -246,6 +267,21 @@ impl ClientBuilder<'_> {
if let PubSubEvent::PublishedItems { node, items } = event { if let PubSubEvent::PublishedItems { node, items } = event {
if node.0 == ns::AVATAR_METADATA { if node.0 == ns::AVATAR_METADATA {
avatar::handle_metadata_pubsub_event(&from, &mut sender_tx, items); 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: Dont 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()) .select(sender.into_stream())
.filter_map(|x| x); .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) (agent, future)
} }
} }
pub struct Client {
sender_tx: mpsc::UnboundedSender<Packet>,
stream: Box<dyn Stream<Item = Event, Error = Error>>,
}
impl Client {
pub fn get_agent(&self) -> Agent {
Agent {
sender_tx: self.sender_tx.clone(),
}
}
pub fn listen(self) -> Box<dyn Stream<Item = Event, Error = Error>> {
self.stream
}
}
pub struct Agent { pub struct Agent {
sender_tx: mpsc::UnboundedSender<Packet>, sender_tx: mpsc::UnboundedSender<Packet>,
default_nick: Rc<RefCell<String>>,
} }
impl Agent { impl Agent {
pub fn join_room(&mut self, room: Jid, lang: &str, status: &str) { pub fn join_room(&mut self, room: Jid, nick: Option<String>, password: Option<String>,
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) let mut presence = Presence::new(PresenceType::None)
.with_to(Some(room)) .with_to(Some(room_jid));
.with_payloads(vec![Muc::new().into()]); presence.add_payload(muc);
presence.set_status(String::from(lang), String::from(status)); presence.set_status(String::from(lang), String::from(status));
let presence = presence.into(); let presence = presence.into();
self.sender_tx.unbounded_send(Packet::Stanza(presence)) self.sender_tx.unbounded_send(Packet::Stanza(presence))