1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use crate::auth::o_auth::OAuthInfo;
use crate::auth::{AuthInfo, AuthType};
use crate::request::metadata::MetaData;
use crate::Result;
use crate::{auth, Error, NadeoClient};
use futures::future::join3;
use reqwest::Client;
use thiserror::Error;

type EMail = String;
type Password = String;
type Identifier = String;
type Secret = String;

#[derive(Debug, Clone, Default)]
pub struct NadeoClientBuilder {
    normal_auth: Option<(EMail, Password)>,
    o_auth: Option<(Identifier, Secret)>,
    user_agent: Option<String>,
}

impl NadeoClientBuilder {
    /// Adds credentials for using [`AuthType::NadeoServices`] and [`AuthType::NadeoLiveServices`].
    pub fn with_normal_auth(mut self, email: &str, password: &str) -> Self {
        self.normal_auth = Some((email.to_string(), password.to_string()));

        self
    }

    /// Adds credentials for using [`AuthType::OAuth`].
    pub fn with_oauth(mut self, identifier: &str, secret: &str) -> Self {
        self.o_auth = Some((identifier.to_string(), secret.to_string()));

        self
    }

    /// Adds a UserAgent which is sent along with each [`NadeoRequest`].
    /// This is required because Ubisoft blocks some default UserAgents.
    /// An example of a *good* UserAgent is:
    /// - `My amazing app / my.email.address@gmail.com`
    ///
    /// # Examples
    ///
    /// ```rust
    /// # use nadeo_api::NadeoClient;
    /// let client = NadeoClient::builder()
    ///     .with_normal_auth("my_email", "my_password")
    ///     .user_agent("API Testing / mustermann.max@gmail.com") // not a real email
    ///     .build()
    ///     .await?;
    /// ```
    ///
    /// [`NadeoRequest`]: crate::NadeoRequest
    pub fn user_agent(mut self, user_agent: &str) -> Self {
        self.user_agent = Some(user_agent.to_string());

        self
    }

    /// Trys to build a [`NadeoClient`].
    pub async fn build(self) -> Result<NadeoClient> {
        if self.o_auth.is_none() && self.normal_auth.is_none() {
            return Err(Error::from(NadeoClientBuilderError::MissingCredentials));
        }
        if self.user_agent.is_none() {
            return Err(Error::from(NadeoClientBuilderError::MissingUserAgent));
        }

        let meta_data = MetaData {
            user_agent: self.user_agent.unwrap(),
        };

        let client = Client::new();

        // Ubisoft auth ticket
        let mut ticket = String::new();
        if let Some(ref auth) = self.normal_auth {
            ticket = auth::get_ubi_auth_ticket(&auth.0, &auth.1, &meta_data, &client).await?;
        }

        // NadeoServices
        let normal_auth_future = async {
            if self.normal_auth.is_some() {
                Some(AuthInfo::new(AuthType::NadeoServices, &ticket, &meta_data, &client).await)
            } else {
                None
            }
        };
        // NadeoLiveServices
        let live_auth_future = async {
            if self.normal_auth.is_some() {
                Some(AuthInfo::new(AuthType::NadeoLiveServices, &ticket, &meta_data, &client).await)
            } else {
                None
            }
        };
        // OAuth
        let oauth_future = async {
            if let Some(auth) = self.o_auth {
                Some(OAuthInfo::new(&auth.0, &auth.1, &client).await)
            } else {
                None
            }
        };
        // execute requests
        let (normal_auth_res, live_auth_res, oauth_res) =
            join3(normal_auth_future, live_auth_future, oauth_future).await;

        let mut normal_auth = None;
        let mut live_auth = None;
        let mut o_auth = None;

        // extract results
        if let Some(auth) = normal_auth_res {
            normal_auth = Some(auth?);
        }
        if let Some(auth) = live_auth_res {
            live_auth = Some(auth?);
        }
        if let Some(auth) = oauth_res {
            o_auth = Some(auth?);
        }

        Ok(NadeoClient {
            client,
            normal_auth,
            live_auth,
            o_auth,
            meta_data,
        })
    }
}

#[derive(Error, Debug)]
pub enum NadeoClientBuilderError {
    #[error("No credentials were provided. At least 1 auth method is required")]
    MissingCredentials,
    #[error("No UserAgent was provided")]
    MissingUserAgent,
}