add a disco plugin

Emmanuel Gil Peyrot created

Change summary

examples/client.rs   |   2 
src/plugins/disco.rs | 125 ++++++++++++++++++++++++++++++++++++++++++++++
src/plugins/mod.rs   |   1 
3 files changed, 128 insertions(+)

Detailed changes

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| {

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<String>,
+}
+
+impl Event for DiscoInfoRequest {}
+
+pub struct DiscoPlugin {
+    proxy: PluginProxy,
+    cached_disco: Arc<Mutex<Disco>>,
+}
+
+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,
+]);

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;