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}