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
use crate::{errors::ApiError, errors::RobloxApiErrorResponse, ApiResult, Client};
use reqwest::{header, Method, RequestBuilder, Response};
use serde::de::{self, DeserializeOwned};

#[derive(Debug, Clone)]
pub struct Https {
    pub client: reqwest::Client,
}

impl Default for Https {
    fn default() -> Self {
        Self::new()
    }
}

impl Client {
    /// # setCookie
    /// Set the cookie for the client; This function is needed to execute specific API requests such as `.create_developer_product()`
    ///
    /// # Example
    /// ```
    ///
    /// let COOKIE: &str = "_|WARNING:-DO-NOT-SHARE-THIS.--Sharing-this-will-allow-someone-to-log-in-as-you-and-to-steal-your-ROBUX-and-items.|_8B1028";
    ///
    /// #[tokio::main]
    /// async fn main() {
    ///     let mut client = robloxapi::Client();
    ///     client.set_cookie(COOKIE).await;
    /// }
    ///
    /// ```
    pub async fn set_cookie(&mut self, cookie: &str) -> &mut Self {
        let mut headers = header::HeaderMap::new();

        headers.insert(
            header::COOKIE,
            header::HeaderValue::from_str(&(".ROBLOSECURITY=".to_owned() + cookie)).unwrap(),
        );
        headers.insert(
            header::CONTENT_LENGTH,
            header::HeaderValue::from_static("0"),
        );

        // Add the x-csrf-token to the headers
        headers.insert(
            header::HeaderName::from_static("x-csrf-token"),
            header::HeaderValue::from(
                reqwest::Client::new()
                    .post("https://auth.roblox.com/v2/logout")
                    .header("content-length", "0")
                    .send()
                    .await
                    .expect("Failed to get X-CSRF-TOKEN")
                    .headers()
                    .get("x-csrf-token")
                    .unwrap_or(&header::HeaderValue::from_static("")),
            ),
        );

        // Create a new session with the cookie and token
        self.session.client = reqwest::Client::builder()
            .cookie_store(true)
            .user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.152 Safari/537.36")
            .default_headers(headers)
            .build()
            .expect("Failed to build new client from headers");

        // Validate Cookie before continuing
        self.session.validate_cookie().await;

        self
    }
}

impl Https {
    /// Create a new client instance
    pub fn new() -> Self {
        Self {
            client: reqwest::Client::builder()
                .cookie_store(true)
                .build()
                .unwrap(),
        }
    }

    async fn de_to_result<T>(req: Response) -> ApiResult<T>
    where
        T: DeserializeOwned,
    {
        let status_code = req.status();
        let data = req.bytes().await?;

        if let Ok(error) = serde_json::from_slice::<RobloxApiErrorResponse>(&data) {
            if !error.is_empty() {
                return Err(ApiError::Roblox {
                    status_code,
                    reason: error.reason().unwrap_or_else(|| "Unknown error".to_owned()),
                });
            }
        }
        Ok(serde_json::from_slice::<T>(&data)?)
    }

    // Send a get_request. Automatically handles the x-csrf token regeneration
    pub async fn request<T>(&mut self, method: Method, request_url: &str) -> ApiResult<T>
    where
        T: de::DeserializeOwned,
    {
        println!("{}", request_url);
        let response = self
            .client
            .request(method.clone(), request_url)
            .send()
            .await
            .expect("Request failed");

        return Https::de_to_result::<T>(response).await;
    }

    pub async fn post(&mut self, request_url: &str) -> RequestBuilder {
        self.client.post(request_url)
    }

    // Validate the cookie
    async fn validate_cookie(&mut self) {
        let req = self
            .client
            .request(Method::GET, "https://www.roblox.com/mobileapi/userinfo")
            .send()
            .await
            .expect("Failed to get user info");

        let _: serde_json::Value = req.json().await.expect("Failed to validate cookie");
    }
}