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;
9use tokio_xmpp::connect::ServerConnector;
10use tokio_xmpp::{
11 jid::{BareJid, Jid},
12 parsers::{
13 disco::{DiscoInfoResult, Feature, Identity},
14 ns,
15 },
16 AsyncClient as TokioXmppClient, AsyncConfig,
17};
18
19use crate::{Agent, ClientFeature};
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: String,
48 lang: Vec<String>,
49 disco: (ClientType, String),
50 features: Vec<ClientFeature>,
51 resource: Option<String>,
52}
53
54#[cfg(any(feature = "starttls-rust", feature = "starttls-native"))]
55impl ClientBuilder<'_, tokio_xmpp::starttls::ServerConfig> {
56 pub fn new<'a>(
57 jid: BareJid,
58 password: &'a str,
59 ) -> ClientBuilder<'a, tokio_xmpp::starttls::ServerConfig> {
60 Self::new_with_connector(jid, password, tokio_xmpp::starttls::ServerConfig::UseSrv)
61 }
62}
63
64impl<C: ServerConnector> ClientBuilder<'_, C> {
65 pub fn new_with_connector<'a>(
66 jid: BareJid,
67 password: &'a str,
68 server_connector: C,
69 ) -> ClientBuilder<'a, C> {
70 ClientBuilder {
71 jid,
72 password,
73 server_connector,
74 website: String::from("https://gitlab.com/xmpp-rs/tokio-xmpp"),
75 default_nick: String::from("xmpp-rs"),
76 lang: vec![String::from("en")],
77 disco: (ClientType::default(), String::from("tokio-xmpp")),
78 features: vec![],
79 resource: None,
80 }
81 }
82
83 /// Optionally set a resource associated to this device on the client
84 pub fn set_resource(mut self, resource: &str) -> Self {
85 self.resource = Some(resource.to_string());
86 self
87 }
88
89 pub fn set_client(mut self, type_: ClientType, name: &str) -> Self {
90 self.disco = (type_, String::from(name));
91 self
92 }
93
94 pub fn set_website(mut self, url: &str) -> Self {
95 self.website = String::from(url);
96 self
97 }
98
99 pub fn set_default_nick(mut self, nick: &str) -> Self {
100 self.default_nick = String::from(nick);
101 self
102 }
103
104 pub fn set_lang(mut self, lang: Vec<String>) -> Self {
105 self.lang = lang;
106 self
107 }
108
109 pub fn enable_feature(mut self, feature: ClientFeature) -> Self {
110 self.features.push(feature);
111 self
112 }
113
114 fn make_disco(&self) -> DiscoInfoResult {
115 let identities = vec![Identity::new(
116 "client",
117 self.disco.0.to_string(),
118 "en",
119 self.disco.1.to_string(),
120 )];
121 let mut features = vec![Feature::new(ns::DISCO_INFO)];
122 #[cfg(feature = "avatars")]
123 {
124 if self.features.contains(&ClientFeature::Avatars) {
125 features.push(Feature::new(format!("{}+notify", ns::AVATAR_METADATA)));
126 }
127 }
128 if self.features.contains(&ClientFeature::JoinRooms) {
129 features.push(Feature::new(format!("{}+notify", ns::BOOKMARKS2)));
130 }
131 DiscoInfoResult {
132 node: None,
133 identities,
134 features,
135 extensions: vec![],
136 }
137 }
138
139 pub fn build(self) -> Agent<C> {
140 let jid: Jid = if let Some(resource) = &self.resource {
141 self.jid.with_resource_str(resource).unwrap().into()
142 } else {
143 self.jid.clone().into()
144 };
145
146 let config = AsyncConfig {
147 jid,
148 password: self.password.into(),
149 server: self.server_connector.clone(),
150 };
151 let mut client = TokioXmppClient::new_with_config(config);
152 client.set_reconnect(true);
153 self.build_impl(client)
154 }
155
156 // This function is meant to be used for testing build
157 pub(crate) fn build_impl(self, client: TokioXmppClient<C>) -> Agent<C> {
158 let disco = self.make_disco();
159 let node = self.website;
160
161 Agent {
162 client,
163 default_nick: Arc::new(RwLock::new(self.default_nick)),
164 lang: Arc::new(self.lang),
165 disco,
166 node,
167 uploads: Vec::new(),
168 awaiting_disco_bookmarks_type: false,
169 }
170 }
171}