builder.rs

  1// Copyright (c) 2023 xmpp-rs contributors.
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use std::collections::HashMap;
  8use std::sync::Arc;
  9use tokio::sync::RwLock;
 10#[cfg(any(feature = "starttls-rust", feature = "starttls-native"))]
 11use tokio_xmpp::connect::{DnsConfig, StartTlsServerConnector};
 12use tokio_xmpp::{
 13    connect::ServerConnector,
 14    jid::{BareJid, Jid},
 15    parsers::{
 16        disco::{DiscoInfoResult, Feature, Identity},
 17        ns,
 18    },
 19    xmlstream::Timeouts,
 20    Client as TokioXmppClient,
 21};
 22
 23use crate::{Agent, ClientFeature};
 24
 25#[derive(Debug)]
 26pub enum ClientType {
 27    Bot,
 28    Pc,
 29}
 30
 31impl Default for ClientType {
 32    fn default() -> Self {
 33        ClientType::Bot
 34    }
 35}
 36
 37impl ToString for ClientType {
 38    fn to_string(&self) -> String {
 39        String::from(match self {
 40            ClientType::Bot => "bot",
 41            ClientType::Pc => "pc",
 42        })
 43    }
 44}
 45
 46pub struct ClientBuilder<'a, C: ServerConnector> {
 47    jid: BareJid,
 48    password: &'a str,
 49    server_connector: C,
 50    website: String,
 51    default_nick: String,
 52    lang: Vec<String>,
 53    disco: (ClientType, String),
 54    features: Vec<ClientFeature>,
 55    resource: Option<String>,
 56    timeouts: Timeouts,
 57}
 58
 59#[cfg(any(feature = "starttls-rust", feature = "starttls-native"))]
 60impl ClientBuilder<'_, StartTlsServerConnector> {
 61    pub fn new<'a>(jid: BareJid, password: &'a str) -> ClientBuilder<'a, StartTlsServerConnector> {
 62        Self::new_with_connector(
 63            jid.clone(),
 64            password,
 65            StartTlsServerConnector(DnsConfig::srv_default_client(jid.domain())),
 66        )
 67    }
 68}
 69
 70impl<C: ServerConnector> ClientBuilder<'_, C> {
 71    pub fn new_with_connector<'a>(
 72        jid: BareJid,
 73        password: &'a str,
 74        server_connector: C,
 75    ) -> ClientBuilder<'a, C> {
 76        ClientBuilder {
 77            jid,
 78            password,
 79            server_connector,
 80            website: String::from("https://gitlab.com/xmpp-rs/tokio-xmpp"),
 81            default_nick: String::from("xmpp-rs"),
 82            lang: vec![String::from("en")],
 83            disco: (ClientType::default(), String::from("tokio-xmpp")),
 84            features: vec![],
 85            resource: None,
 86            timeouts: Timeouts::default(),
 87        }
 88    }
 89
 90    /// Optionally set a resource associated to this device on the client
 91    pub fn set_resource(mut self, resource: &str) -> Self {
 92        self.resource = Some(resource.to_string());
 93        self
 94    }
 95
 96    pub fn set_client(mut self, type_: ClientType, name: &str) -> Self {
 97        self.disco = (type_, String::from(name));
 98        self
 99    }
100
101    pub fn set_website(mut self, url: &str) -> Self {
102        self.website = String::from(url);
103        self
104    }
105
106    pub fn set_default_nick(mut self, nick: &str) -> Self {
107        self.default_nick = String::from(nick);
108        self
109    }
110
111    pub fn set_lang(mut self, lang: Vec<String>) -> Self {
112        self.lang = lang;
113        self
114    }
115
116    /// Configure the timeouts used.
117    ///
118    /// See [`Timeouts`] for more information on the semantics and the
119    /// defaults (which are used unless you call this method).
120    pub fn set_timeouts(mut self, timeouts: Timeouts) -> Self {
121        self.timeouts = timeouts;
122        self
123    }
124
125    pub fn enable_feature(mut self, feature: ClientFeature) -> Self {
126        self.features.push(feature);
127        self
128    }
129
130    fn make_disco(&self) -> DiscoInfoResult {
131        let identities = vec![Identity::new(
132            "client",
133            self.disco.0.to_string(),
134            "en",
135            self.disco.1.to_string(),
136        )];
137        let mut features = vec![Feature::new(ns::DISCO_INFO)];
138        #[cfg(feature = "avatars")]
139        {
140            if self.features.contains(&ClientFeature::Avatars) {
141                features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA)));
142            }
143        }
144        if self.features.contains(&ClientFeature::JoinRooms) {
145            features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS2)));
146        }
147        DiscoInfoResult {
148            node: None,
149            identities,
150            features,
151            extensions: vec![],
152        }
153    }
154
155    pub fn build(self) -> Agent {
156        let jid: Jid = if let Some(resource) = &self.resource {
157            self.jid.with_resource_str(resource).unwrap().into()
158        } else {
159            self.jid.clone().into()
160        };
161
162        let client = TokioXmppClient::new_with_connector(
163            jid,
164            self.password,
165            self.server_connector.clone(),
166            self.timeouts,
167        );
168        self.build_impl(client)
169    }
170
171    // This function is meant to be used for testing build
172    pub(crate) fn build_impl(self, client: TokioXmppClient) -> Agent {
173        let disco = self.make_disco();
174        let node = self.website;
175
176        Agent {
177            client,
178            default_nick: Arc::new(RwLock::new(self.default_nick)),
179            lang: Arc::new(self.lang),
180            disco,
181            node,
182            uploads: Vec::new(),
183            awaiting_disco_bookmarks_type: false,
184            rooms_joined: HashMap::new(),
185            rooms_joining: HashMap::new(),
186            rooms_leaving: HashMap::new(),
187        }
188    }
189}