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::sync::Arc;
8use tokio::sync::RwLock;
9#[cfg(any(feature = "starttls-rust", feature = "starttls-native"))]
10use tokio_xmpp::connect::{DnsConfig, StartTlsServerConnector};
11use tokio_xmpp::{
12 connect::ServerConnector,
13 jid::{BareJid, Jid},
14 parsers::{
15 disco::{DiscoInfoResult, Feature, Identity},
16 ns,
17 },
18 Client as TokioXmppClient,
19};
20
21use crate::{Agent, ClientFeature};
22
23#[derive(Debug)]
24pub enum ClientType {
25 Bot,
26 Pc,
27}
28
29impl Default for ClientType {
30 fn default() -> Self {
31 ClientType::Bot
32 }
33}
34
35impl ToString for ClientType {
36 fn to_string(&self) -> String {
37 String::from(match self {
38 ClientType::Bot => "bot",
39 ClientType::Pc => "pc",
40 })
41 }
42}
43
44pub struct ClientBuilder<'a, C: ServerConnector> {
45 jid: BareJid,
46 password: &'a str,
47 server_connector: C,
48 website: String,
49 default_nick: String,
50 lang: Vec<String>,
51 disco: (ClientType, String),
52 features: Vec<ClientFeature>,
53 resource: Option<String>,
54}
55
56#[cfg(any(feature = "starttls-rust", feature = "starttls-native"))]
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 website: String::from("https://gitlab.com/xmpp-rs/tokio-xmpp"),
78 default_nick: String::from("xmpp-rs"),
79 lang: vec![String::from("en")],
80 disco: (ClientType::default(), String::from("tokio-xmpp")),
81 features: vec![],
82 resource: None,
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: &str) -> Self {
103 self.default_nick = String::from(nick);
104 self
105 }
106
107 pub fn set_lang(mut self, lang: Vec<String>) -> Self {
108 self.lang = lang;
109 self
110 }
111
112 pub fn enable_feature(mut self, feature: ClientFeature) -> Self {
113 self.features.push(feature);
114 self
115 }
116
117 fn make_disco(&self) -> DiscoInfoResult {
118 let identities = vec![Identity::new(
119 "client",
120 self.disco.0.to_string(),
121 "en",
122 self.disco.1.to_string(),
123 )];
124 let mut features = vec![Feature::new(ns::DISCO_INFO)];
125 #[cfg(feature = "avatars")]
126 {
127 if self.features.contains(&ClientFeature::Avatars) {
128 features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA)));
129 }
130 }
131 if self.features.contains(&ClientFeature::JoinRooms) {
132 features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS2)));
133 }
134 DiscoInfoResult {
135 node: None,
136 identities,
137 features,
138 extensions: vec![],
139 }
140 }
141
142 pub fn build(self) -> Agent<C> {
143 let jid: Jid = if let Some(resource) = &self.resource {
144 self.jid.with_resource_str(resource).unwrap().into()
145 } else {
146 self.jid.clone().into()
147 };
148
149 let mut client =
150 TokioXmppClient::new_with_connector(jid, self.password, self.server_connector.clone());
151 client.set_reconnect(true);
152 self.build_impl(client)
153 }
154
155 // This function is meant to be used for testing build
156 pub(crate) fn build_impl(self, client: TokioXmppClient<C>) -> Agent<C> {
157 let disco = self.make_disco();
158 let node = self.website;
159
160 Agent {
161 client,
162 default_nick: Arc::new(RwLock::new(self.default_nick)),
163 lang: Arc::new(self.lang),
164 disco,
165 node,
166 uploads: Vec::new(),
167 awaiting_disco_bookmarks_type: false,
168 }
169 }
170}