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