xmpp_client_rs/mods/
disco.rs1use std::collections::{HashMap, HashSet};
5use std::convert::TryFrom;
6use std::fmt;
7use std::str::FromStr;
8
9use anyhow::{anyhow, Result};
10use uuid::Uuid;
11
12use jid::Jid;
13use xmpp_parsers::disco;
14use xmpp_parsers::disco::Feature;
15use xmpp_parsers::iq::{Iq, IqType};
16use xmpp_parsers::ns;
17
18use crate::account::Account;
19use crate::core::{Aparte, AparteAsync, Event, ModTrait};
20use crate::i18n;
21
22pub struct DiscoMod {
23 identity: disco::Identity,
24 client_features: HashSet<Feature>,
25 server_features: HashMap<Account, Vec<String>>,
26}
27
28impl DiscoMod {
29 pub fn new<C: Into<String>, T: Into<String>, L: Into<String>, N: Into<String>>(
30 category: C,
31 type_: T,
32 lang: L,
33 name: N,
34 ) -> Self {
35 Self {
36 identity: disco::Identity::new(category, type_, lang, name),
37 client_features: HashSet::new(),
38 server_features: HashMap::new(),
39 }
40 }
41
42 pub fn add_feature<S: Into<String>>(&mut self, feature: S) {
43 let feature = Feature::new(feature.into());
44 log::debug!("Adding `{}` feature", feature.var);
45 self.client_features.insert(feature);
46 }
47
48 pub fn has_feature(&self, account: &Account, feature: &str) -> bool {
49 self.server_features
50 .get(account)
51 .unwrap()
52 .iter()
53 .any(|i| i == feature)
54 }
55
56 async fn get_server_disco(
57 aparte: &mut AparteAsync,
58 account: &Account,
59 jid: &Jid,
60 ) -> Result<()> {
61 let resp = aparte
62 .iq(
63 account,
64 Self::disco_info_query_iq(&Jid::from_str(jid.domain().as_ref()).unwrap(), None),
65 )
66 .await?;
67
68 match resp.payload {
69 IqType::Result(Some(el)) => {
70 if let Ok(disco) = disco::DiscoInfoResult::try_from(el) {
71 aparte.schedule(Event::Disco(
72 account.clone(),
73 disco.features.iter().map(|i| i.var.clone()).collect(),
74 ));
75
76 Ok(())
77 } else {
78 Err(anyhow!("Cannot get server disco info: invalid response"))
79 }
80 }
81 IqType::Error(err) => Err(anyhow!(
82 "Cannot get server disco info: {}",
83 i18n::xmpp_err_to_string(&err, vec![]).1
84 )),
85 _ => Err(anyhow!("Cannot get server disco info: invalid response")),
86 }
87 }
88
89 fn disco_info_query_iq(jid: &Jid, node: Option<String>) -> Iq {
90 let id = Uuid::new_v4().hyphenated().to_string();
91 let query = disco::DiscoInfoQuery { node };
92 Iq::from_get(id, query).with_to(jid.clone())
93 }
94
95 pub fn get_disco(&self) -> disco::DiscoInfoResult {
96 let identities = vec![self.identity.clone()];
97 disco::DiscoInfoResult {
98 node: None,
99 identities,
100 features: self.client_features.iter().cloned().collect(),
101 extensions: vec![],
102 }
103 }
104}
105
106impl ModTrait for DiscoMod {
107 fn init(&mut self, _aparte: &mut Aparte) -> Result<(), ()> {
108 self.add_feature(ns::DISCO_INFO);
109 Ok(())
111 }
112
113 fn on_event(&mut self, aparte: &mut Aparte, event: &Event) {
114 match event {
115 Event::Connected(account, jid) => {
116 self.server_features.insert(account.clone(), Vec::new());
117
118 Aparte::spawn({
119 let mut aparte = aparte.proxy();
120 let account = account.clone();
121 let jid = jid.clone();
122 async move {
123 if let Err(err) = Self::get_server_disco(&mut aparte, &account, &jid).await
124 {
125 crate::error!(aparte, err, "Cannot get server disco");
126 }
127 }
128 });
129 }
130 Event::Disco(account, features) => {
131 if let Some(server_features) = self.server_features.get_mut(account) {
132 server_features.extend(features.clone());
133 }
134 }
135 Event::Iq(account, iq) => {
136 if let IqType::Get(el) = iq.payload.clone() {
137 if let Ok(_disco) = disco::DiscoInfoQuery::try_from(el) {
138 let id = iq.id.clone();
139 let disco = self.get_disco();
140 let iq = Iq::from_result(id, Some(disco));
141 aparte.send(account, iq);
142 }
143 }
144 }
145 _ => {}
146 }
147 }
148}
149
150impl fmt::Display for DiscoMod {
151 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
152 write!(f, "XEP-0030: Service Discovery")
153 }
154}