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