Remove the -rs suffix of jid, minidom and xmpp
We know those are Rust libraries, no need to add it to the path. This synchronises their directory with the crate name, hopefully reducing confusion.
This commit is contained in:
parent
e501addb96
commit
714d850e69
28 changed files with 6 additions and 6 deletions
427
xmpp/src/lib.rs
Normal file
427
xmpp/src/lib.rs
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
// 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/.
|
||||
|
||||
#![deny(bare_trait_objects)]
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryFrom;
|
||||
use std::rc::Rc;
|
||||
use tokio_xmpp::{AsyncClient as TokioXmppClient, Event as TokioXmppEvent};
|
||||
use xmpp_parsers::{
|
||||
bookmarks2::Conference,
|
||||
caps::{compute_disco, hash_caps, Caps},
|
||||
disco::{DiscoInfoQuery, DiscoInfoResult, Feature, Identity},
|
||||
hashes::Algo,
|
||||
iq::{Iq, IqType},
|
||||
message::{Body, Message, MessageType},
|
||||
muc::{
|
||||
user::{MucUser, Status},
|
||||
Muc,
|
||||
},
|
||||
ns,
|
||||
presence::{Presence, Type as PresenceType},
|
||||
pubsub::pubsub::{Items, PubSub},
|
||||
roster::{Item as RosterItem, Roster},
|
||||
stanza_error::{DefinedCondition, ErrorType, StanzaError},
|
||||
BareJid, FullJid, Jid,
|
||||
};
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod pubsub;
|
||||
|
||||
pub type Error = tokio_xmpp::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClientType {
|
||||
Bot,
|
||||
Pc,
|
||||
}
|
||||
|
||||
impl Default for ClientType {
|
||||
fn default() -> Self {
|
||||
ClientType::Bot
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for ClientType {
|
||||
fn to_string(&self) -> String {
|
||||
String::from(match self {
|
||||
ClientType::Bot => "bot",
|
||||
ClientType::Pc => "pc",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum ClientFeature {
|
||||
#[cfg(feature = "avatars")]
|
||||
Avatars,
|
||||
ContactList,
|
||||
JoinRooms,
|
||||
}
|
||||
|
||||
pub type RoomNick = String;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Online,
|
||||
Disconnected,
|
||||
ContactAdded(RosterItem),
|
||||
ContactRemoved(RosterItem),
|
||||
ContactChanged(RosterItem),
|
||||
#[cfg(feature = "avatars")]
|
||||
AvatarRetrieved(Jid, String),
|
||||
ChatMessage(BareJid, Body),
|
||||
JoinRoom(BareJid, Conference),
|
||||
LeaveRoom(BareJid),
|
||||
LeaveAllRooms,
|
||||
RoomJoined(BareJid),
|
||||
RoomLeft(BareJid),
|
||||
RoomMessage(BareJid, RoomNick, Body),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClientBuilder<'a> {
|
||||
jid: &'a str,
|
||||
password: &'a str,
|
||||
website: String,
|
||||
default_nick: String,
|
||||
lang: Vec<String>,
|
||||
disco: (ClientType, String),
|
||||
features: Vec<ClientFeature>,
|
||||
}
|
||||
|
||||
impl ClientBuilder<'_> {
|
||||
pub fn new<'a>(jid: &'a str, password: &'a str) -> ClientBuilder<'a> {
|
||||
ClientBuilder {
|
||||
jid,
|
||||
password,
|
||||
website: String::from("https://gitlab.com/xmpp-rs/tokio-xmpp"),
|
||||
default_nick: String::from("xmpp-rs"),
|
||||
lang: vec![String::from("en")],
|
||||
disco: (ClientType::default(), String::from("tokio-xmpp")),
|
||||
features: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_client(mut self, type_: ClientType, name: &str) -> Self {
|
||||
self.disco = (type_, String::from(name));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_website(mut self, url: &str) -> Self {
|
||||
self.website = String::from(url);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_default_nick(mut self, nick: &str) -> Self {
|
||||
self.default_nick = String::from(nick);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_lang(mut self, lang: Vec<String>) -> Self {
|
||||
self.lang = lang;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn enable_feature(mut self, feature: ClientFeature) -> Self {
|
||||
self.features.push(feature);
|
||||
self
|
||||
}
|
||||
|
||||
fn make_disco(&self) -> DiscoInfoResult {
|
||||
let identities = vec![Identity::new(
|
||||
"client",
|
||||
self.disco.0.to_string(),
|
||||
"en",
|
||||
self.disco.1.to_string(),
|
||||
)];
|
||||
let mut features = vec![Feature::new(ns::DISCO_INFO)];
|
||||
#[cfg(feature = "avatars")]
|
||||
{
|
||||
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::BOOKMARKS2)));
|
||||
}
|
||||
DiscoInfoResult {
|
||||
node: None,
|
||||
identities,
|
||||
features,
|
||||
extensions: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build(self) -> Result<Agent, Error> {
|
||||
let client = TokioXmppClient::new(self.jid, self.password)?;
|
||||
Ok(self.build_impl(client)?)
|
||||
}
|
||||
|
||||
// This function is meant to be used for testing build
|
||||
pub(crate) fn build_impl(self, client: TokioXmppClient) -> Result<Agent, Error> {
|
||||
let disco = self.make_disco();
|
||||
let node = self.website;
|
||||
|
||||
let agent = Agent {
|
||||
client,
|
||||
default_nick: Rc::new(RefCell::new(self.default_nick)),
|
||||
lang: Rc::new(self.lang),
|
||||
disco,
|
||||
node,
|
||||
};
|
||||
|
||||
Ok(agent)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Agent {
|
||||
client: TokioXmppClient,
|
||||
default_nick: Rc<RefCell<String>>,
|
||||
lang: Rc<Vec<String>>,
|
||||
disco: DiscoInfoResult,
|
||||
node: String,
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
pub async fn join_room(
|
||||
&mut self,
|
||||
room: BareJid,
|
||||
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);
|
||||
}
|
||||
|
||||
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(Jid::Full(room_jid));
|
||||
presence.add_payload(muc);
|
||||
presence.set_status(String::from(lang), String::from(status));
|
||||
let _ = self.client.send_stanza(presence.into()).await;
|
||||
}
|
||||
|
||||
pub async fn send_message(
|
||||
&mut self,
|
||||
recipient: Jid,
|
||||
type_: MessageType,
|
||||
lang: &str,
|
||||
text: &str,
|
||||
) {
|
||||
let mut message = Message::new(Some(recipient));
|
||||
message.type_ = type_;
|
||||
message
|
||||
.bodies
|
||||
.insert(String::from(lang), Body(String::from(text)));
|
||||
let _ = self.client.send_stanza(message.into()).await;
|
||||
}
|
||||
|
||||
fn make_initial_presence(disco: &DiscoInfoResult, node: &str) -> Presence {
|
||||
let caps_data = compute_disco(disco);
|
||||
let hash = hash_caps(&caps_data, Algo::Sha_1).unwrap();
|
||||
let caps = Caps::new(node, hash);
|
||||
|
||||
let mut presence = Presence::new(PresenceType::None);
|
||||
presence.add_payload(caps);
|
||||
presence
|
||||
}
|
||||
|
||||
pub async fn wait_for_events(&mut self) -> Option<Vec<Event>> {
|
||||
if let Some(event) = self.client.next().await {
|
||||
let mut events = Vec::new();
|
||||
|
||||
match event {
|
||||
TokioXmppEvent::Online { resumed: false, .. } => {
|
||||
let presence = Self::make_initial_presence(&self.disco, &self.node).into();
|
||||
let _ = self.client.send_stanza(presence).await;
|
||||
events.push(Event::Online);
|
||||
// TODO: only send this when the ContactList feature is enabled.
|
||||
let iq = Iq::from_get(
|
||||
"roster",
|
||||
Roster {
|
||||
ver: None,
|
||||
items: vec![],
|
||||
},
|
||||
)
|
||||
.into();
|
||||
let _ = self.client.send_stanza(iq).await;
|
||||
// TODO: only send this when the JoinRooms feature is enabled.
|
||||
let iq =
|
||||
Iq::from_get("bookmarks", PubSub::Items(Items::new(ns::BOOKMARKS2))).into();
|
||||
let _ = self.client.send_stanza(iq).await;
|
||||
}
|
||||
TokioXmppEvent::Online { resumed: true, .. } => {}
|
||||
TokioXmppEvent::Disconnected(_) => {
|
||||
events.push(Event::Disconnected);
|
||||
}
|
||||
TokioXmppEvent::Stanza(stanza) => {
|
||||
if stanza.is("iq", "jabber:client") {
|
||||
let iq = Iq::try_from(stanza).unwrap();
|
||||
let from = iq
|
||||
.from
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.client.bound_jid().unwrap().clone());
|
||||
if let IqType::Get(payload) = iq.payload {
|
||||
if payload.is("query", ns::DISCO_INFO) {
|
||||
let query = DiscoInfoQuery::try_from(payload);
|
||||
match query {
|
||||
Ok(query) => {
|
||||
let mut disco_info = self.disco.clone();
|
||||
disco_info.node = query.node;
|
||||
let iq = Iq::from_result(iq.id, Some(disco_info))
|
||||
.with_to(iq.from.unwrap())
|
||||
.into();
|
||||
let _ = self.client.send_stanza(iq).await;
|
||||
}
|
||||
Err(err) => {
|
||||
let error = StanzaError::new(
|
||||
ErrorType::Modify,
|
||||
DefinedCondition::BadRequest,
|
||||
"en",
|
||||
&format!("{}", err),
|
||||
);
|
||||
let iq = Iq::from_error(iq.id, error)
|
||||
.with_to(iq.from.unwrap())
|
||||
.into();
|
||||
let _ = self.client.send_stanza(iq).await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We MUST answer unhandled get iqs with a service-unavailable error.
|
||||
let error = StanzaError::new(
|
||||
ErrorType::Cancel,
|
||||
DefinedCondition::ServiceUnavailable,
|
||||
"en",
|
||||
"No handler defined for this kind of iq.",
|
||||
);
|
||||
let iq = Iq::from_error(iq.id, error)
|
||||
.with_to(iq.from.unwrap())
|
||||
.into();
|
||||
let _ = self.client.send_stanza(iq).await;
|
||||
}
|
||||
} else if let IqType::Result(Some(payload)) = iq.payload {
|
||||
// TODO: move private iqs like this one somewhere else, for
|
||||
// security reasons.
|
||||
if payload.is("query", ns::ROSTER) && iq.from.is_none() {
|
||||
let roster = Roster::try_from(payload).unwrap();
|
||||
for item in roster.items.into_iter() {
|
||||
events.push(Event::ContactAdded(item));
|
||||
}
|
||||
} else if payload.is("pubsub", ns::PUBSUB) {
|
||||
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.
|
||||
let error = StanzaError::new(
|
||||
ErrorType::Cancel,
|
||||
DefinedCondition::ServiceUnavailable,
|
||||
"en",
|
||||
"No handler defined for this kind of iq.",
|
||||
);
|
||||
let iq = Iq::from_error(iq.id, error)
|
||||
.with_to(iq.from.unwrap())
|
||||
.into();
|
||||
let _ = self.client.send_stanza(iq).await;
|
||||
}
|
||||
} else if stanza.is("message", "jabber:client") {
|
||||
let message = Message::try_from(stanza).unwrap();
|
||||
let from = message.from.clone().unwrap();
|
||||
let langs: Vec<&str> = self.lang.iter().map(String::as_str).collect();
|
||||
match message.get_best_body(langs) {
|
||||
Some((_lang, body)) => match message.type_ {
|
||||
MessageType::Groupchat => {
|
||||
let event = Event::RoomMessage(
|
||||
from.clone().into(),
|
||||
FullJid::try_from(from.clone()).unwrap().resource,
|
||||
body.clone(),
|
||||
);
|
||||
events.push(event)
|
||||
}
|
||||
MessageType::Chat | MessageType::Normal => {
|
||||
let event =
|
||||
Event::ChatMessage(from.clone().into(), body.clone());
|
||||
events.push(event)
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
for child in message.payloads {
|
||||
if child.is("event", ns::PUBSUB_EVENT) {
|
||||
let new_events = pubsub::handle_event(&from, child, self).await;
|
||||
events.extend(new_events);
|
||||
}
|
||||
}
|
||||
} else if stanza.is("presence", "jabber:client") {
|
||||
let presence = Presence::try_from(stanza).unwrap();
|
||||
let from: BareJid = match presence.from.clone().unwrap() {
|
||||
Jid::Full(FullJid { node, domain, .. }) => BareJid { node, domain },
|
||||
Jid::Bare(bare) => bare,
|
||||
};
|
||||
for payload in presence.payloads.into_iter() {
|
||||
let muc_user = match MucUser::try_from(payload) {
|
||||
Ok(muc_user) => muc_user,
|
||||
_ => continue,
|
||||
};
|
||||
for status in muc_user.status.into_iter() {
|
||||
if status == Status::SelfPresence {
|
||||
events.push(Event::RoomJoined(from.clone()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if stanza.is("error", "http://etherx.jabber.org/streams") {
|
||||
println!("Received a fatal stream error: {}", String::from(&stanza));
|
||||
} else {
|
||||
panic!("Unknown stanza: {}", String::from(&stanza));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(events)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Agent, ClientBuilder, ClientFeature, ClientType, Event};
|
||||
use tokio_xmpp::AsyncClient as TokioXmppClient;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_simple() {
|
||||
let client = TokioXmppClient::new("foo@bar", "meh").unwrap();
|
||||
|
||||
// Client instance
|
||||
let client_builder = ClientBuilder::new("foo@bar", "meh")
|
||||
.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);
|
||||
|
||||
let mut agent: Agent = client_builder.build_impl(client).unwrap();
|
||||
|
||||
while let Some(events) = agent.wait_for_events().await {
|
||||
assert!(match events[0] {
|
||||
Event::Disconnected => true,
|
||||
_ => false,
|
||||
});
|
||||
assert_eq!(events.len(), 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
xmpp/src/pubsub/avatar.rs
Normal file
93
xmpp/src/pubsub/avatar.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// 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 super::Agent;
|
||||
use crate::Event;
|
||||
use std::convert::TryFrom;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, Write};
|
||||
use xmpp_parsers::{
|
||||
avatar::{Data, Metadata},
|
||||
iq::Iq,
|
||||
ns,
|
||||
pubsub::{
|
||||
event::Item,
|
||||
pubsub::{Items, PubSub},
|
||||
NodeName,
|
||||
},
|
||||
Jid,
|
||||
};
|
||||
|
||||
pub(crate) async fn handle_metadata_pubsub_event(
|
||||
from: &Jid,
|
||||
agent: &mut Agent,
|
||||
items: Vec<Item>,
|
||||
) -> Vec<Event> {
|
||||
let mut events = Vec::new();
|
||||
for item in items {
|
||||
let payload = item.payload.clone().unwrap();
|
||||
if payload.is("metadata", ns::AVATAR_METADATA) {
|
||||
let metadata = Metadata::try_from(payload).unwrap();
|
||||
for info in metadata.infos {
|
||||
let filename = format!("data/{}/{}", from, &*info.id.to_hex());
|
||||
let file_length = match fs::metadata(filename.clone()) {
|
||||
Ok(metadata) => metadata.len(),
|
||||
Err(_) => 0,
|
||||
};
|
||||
// TODO: Also check the hash.
|
||||
if info.bytes as u64 == file_length {
|
||||
events.push(Event::AvatarRetrieved(from.clone(), filename));
|
||||
} else {
|
||||
let iq = download_avatar(from);
|
||||
let _ = agent.client.send_stanza(iq.into()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
events
|
||||
}
|
||||
|
||||
fn download_avatar(from: &Jid) -> Iq {
|
||||
Iq::from_get(
|
||||
"coucou",
|
||||
PubSub::Items(Items {
|
||||
max_items: None,
|
||||
node: NodeName(String::from(ns::AVATAR_DATA)),
|
||||
subid: None,
|
||||
items: Vec::new(),
|
||||
}),
|
||||
)
|
||||
.with_to(from.clone())
|
||||
}
|
||||
|
||||
// The return value of this function will be simply pushed to a Vec in the caller function,
|
||||
// so it makes no sense to allocate a Vec here - we're lazy instead
|
||||
pub(crate) fn handle_data_pubsub_iq<'a>(
|
||||
from: &'a Jid,
|
||||
items: &'a Items,
|
||||
) -> impl IntoIterator<Item = Event> + 'a {
|
||||
let from = from.clone();
|
||||
items
|
||||
.items
|
||||
.iter()
|
||||
.filter_map(move |item| match (&item.id, &item.payload) {
|
||||
(Some(id), Some(payload)) => {
|
||||
let data = Data::try_from(payload.clone()).unwrap();
|
||||
let filename = save_avatar(&from, id.0.clone(), &data.data).unwrap();
|
||||
Some(Event::AvatarRetrieved(from.clone(), filename))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn save_avatar(from: &Jid, id: String, data: &[u8]) -> io::Result<String> {
|
||||
let directory = format!("data/{}", from);
|
||||
let filename = format!("data/{}/{}", from, id);
|
||||
fs::create_dir_all(directory)?;
|
||||
let mut file = File::create(&filename)?;
|
||||
file.write_all(data)?;
|
||||
Ok(filename)
|
||||
}
|
||||
112
xmpp/src/pubsub/mod.rs
Normal file
112
xmpp/src/pubsub/mod.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// 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 super::Agent;
|
||||
use crate::Event;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use xmpp_parsers::{
|
||||
bookmarks2::{Autojoin, Conference},
|
||||
ns,
|
||||
pubsub::event::PubSubEvent,
|
||||
pubsub::pubsub::PubSub,
|
||||
BareJid, Element, Jid,
|
||||
};
|
||||
|
||||
#[cfg(feature = "avatars")]
|
||||
pub(crate) mod avatar;
|
||||
|
||||
pub(crate) async fn handle_event(from: &Jid, elem: Element, agent: &mut Agent) -> Vec<Event> {
|
||||
let mut events = Vec::new();
|
||||
let event = PubSubEvent::try_from(elem);
|
||||
trace!("PubSub event: {:#?}", event);
|
||||
match event {
|
||||
Ok(PubSubEvent::PublishedItems { node, items }) => {
|
||||
match node.0 {
|
||||
#[cfg(feature = "avatars")]
|
||||
ref node if node == ns::AVATAR_METADATA => {
|
||||
let new_events =
|
||||
avatar::handle_metadata_pubsub_event(&from, agent, items).await;
|
||||
events.extend(new_events);
|
||||
}
|
||||
ref 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),
|
||||
}
|
||||
}
|
||||
ref node => unimplemented!("node {}", node),
|
||||
}
|
||||
}
|
||||
Ok(PubSubEvent::RetractedItems { node, items }) => {
|
||||
match node.0 {
|
||||
ref 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));
|
||||
}
|
||||
ref node => unimplemented!("node {}", node),
|
||||
}
|
||||
}
|
||||
Ok(PubSubEvent::Purge { node }) => {
|
||||
match node.0 {
|
||||
ref node if node == ns::BOOKMARKS2 => {
|
||||
// TODO: Check that our bare JID is the sender.
|
||||
events.push(Event::LeaveAllRooms);
|
||||
}
|
||||
ref 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();
|
||||
trace!("PubSub: {:#?}", pubsub);
|
||||
if let PubSub::Items(items) = pubsub {
|
||||
match items.node.0.clone() {
|
||||
#[cfg(feature = "avatars")]
|
||||
ref node if node == ns::AVATAR_DATA => {
|
||||
let new_events = avatar::handle_data_pubsub_iq(&from, &items);
|
||||
events.extend(new_events);
|
||||
}
|
||||
ref node if node == 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
events
|
||||
}
|
||||
Loading…
Reference in a new issue