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:
parent
9df465d940
commit
8e0bcaed14
2 changed files with 71 additions and 34 deletions
|
@ -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);
|
||||||
},
|
},
|
||||||
|
|
78
src/lib.rs
78
src/lib.rs
|
@ -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: 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())
|
.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))
|
||||||
|
|
Loading…
Reference in a new issue