diff --git a/examples/client.rs b/examples/client.rs index e5c4a9b..cf6692d 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -6,6 +6,7 @@ use xmpp::plugins::stanza::StanzaPlugin; use xmpp::plugins::unhandled_iq::UnhandledIqPlugin; use xmpp::plugins::messaging::{MessagingPlugin, MessageEvent}; use xmpp::plugins::presence::{PresencePlugin, Type}; +use xmpp::plugins::disco::DiscoPlugin; use xmpp::plugins::ibb::IbbPlugin; use xmpp::plugins::ping::PingPlugin; use xmpp::event::{Priority, Propagation}; @@ -23,6 +24,7 @@ fn main() { client.register_plugin(UnhandledIqPlugin::new()); client.register_plugin(MessagingPlugin::new()); client.register_plugin(PresencePlugin::new()); + client.register_plugin(DiscoPlugin::new("client", "bot", "en", "xmpp-rs")); client.register_plugin(IbbPlugin::new()); client.register_plugin(PingPlugin::new()); client.register_handler(Priority::Max, |e: &MessageEvent| { diff --git a/src/plugins/disco.rs b/src/plugins/disco.rs new file mode 100644 index 0000000..2ad839d --- /dev/null +++ b/src/plugins/disco.rs @@ -0,0 +1,125 @@ +use std::convert::TryFrom; +use std::sync::{Mutex, Arc}; + +use plugin::PluginProxy; +use event::{Event, Priority, Propagation}; +use jid::Jid; + +use plugins::stanza::Iq; +use xmpp_parsers::iq::{IqType, IqPayload}; +use xmpp_parsers::disco::{Disco, Identity, Feature}; +use xmpp_parsers::data_forms::DataForm; +use xmpp_parsers::ns; + +#[derive(Debug)] +pub struct DiscoInfoRequest { + pub from: Jid, + pub id: String, + pub node: Option, +} + +impl Event for DiscoInfoRequest {} + +pub struct DiscoPlugin { + proxy: PluginProxy, + cached_disco: Arc>, +} + +impl DiscoPlugin { + pub fn new(category: &str, type_: &str, lang: &str, name: &str) -> DiscoPlugin { + DiscoPlugin { + proxy: PluginProxy::new(), + cached_disco: Arc::new(Mutex::new(Disco { + node: None, + identities: vec!(Identity { + category: category.to_owned(), + type_: type_.to_owned(), + lang: Some(lang.to_owned()), + name: Some(name.to_owned()) + }), + features: vec!(Feature { var: String::from(ns::DISCO_INFO) }), + extensions: vec!(), + })), + } + } + + pub fn add_identity(&self, category: &str, type_: &str, lang: Option<&str>, name: Option<&str>) { + let mut cached_disco = self.cached_disco.lock().unwrap(); + cached_disco.identities.push(Identity { + category: category.to_owned(), + type_: type_.to_owned(), + lang: lang.and_then(|lang| Some(lang.to_owned())), + name: name.and_then(|name| Some(name.to_owned())), + }); + } + + pub fn remove_identity(&self, category: &str, type_: &str, lang: Option<&str>, name: Option<&str>) { + let mut cached_disco = self.cached_disco.lock().unwrap(); + cached_disco.identities.retain(|identity| { + identity.category != category || + identity.type_ != type_ || + identity.lang != lang.and_then(|lang| Some(lang.to_owned())) || + identity.name != name.and_then(|name| Some(name.to_owned())) + }); + } + + pub fn add_feature(&self, var: &str) { + let mut cached_disco = self.cached_disco.lock().unwrap(); + cached_disco.features.push(Feature { var: String::from(var) }); + } + + pub fn remove_feature(&self, var: &str) { + let mut cached_disco = self.cached_disco.lock().unwrap(); + cached_disco.features.retain(|feature| feature.var != var); + } + + pub fn add_extension(&self, extension: DataForm) { + let mut cached_disco = self.cached_disco.lock().unwrap(); + cached_disco.extensions.push(extension); + } + + pub fn remove_extension(&self, form_type: &str) { + let mut cached_disco = self.cached_disco.lock().unwrap(); + cached_disco.extensions.retain(|extension| { + extension.form_type != Some(form_type.to_owned()) + }); + } + + fn handle_iq(&self, iq: &Iq) -> Propagation { + let iq = iq.clone(); + if let IqType::Get(payload) = iq.payload { + // TODO: use an intermediate plugin to parse this payload. + if let Ok(IqPayload::Disco(disco)) = IqPayload::try_from(payload) { + self.proxy.dispatch(DiscoInfoRequest { // TODO: safety!!! + from: iq.from.unwrap(), + id: iq.id.unwrap(), + node: disco.node, + }); + return Propagation::Stop; + } + } + Propagation::Continue + } + + fn reply_disco_info(&self, request: &DiscoInfoRequest) -> Propagation { + let payload = if request.node.is_none() { + let cached_disco = self.cached_disco.lock().unwrap().clone(); + IqType::Result(Some(cached_disco.into())) + } else { + // TODO: handle the requests on nodes too. + return Propagation::Continue; + }; + self.proxy.send(Iq { + from: None, + to: Some(request.from.to_owned()), + id: Some(request.id.to_owned()), + payload, + }.into()); + Propagation::Stop + } +} + +impl_plugin!(DiscoPlugin, proxy, [ + (Iq, Priority::Default) => handle_iq, + (DiscoInfoRequest, Priority::Default) => reply_disco_info, +]); diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index d2846f5..fca357f 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,5 +1,6 @@ pub mod messaging; pub mod presence; +pub mod disco; pub mod ping; pub mod ibb; pub mod stanza;