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