1use reqwest::{
2 Method, Response,
3 header::{self, HeaderMap, HeaderValue},
4};
5use serde::{Serialize, de::DeserializeOwned};
6
7use crate::{ApiError, Error, ratelimit::Ratelimit};
8
9#[derive(Default)]
10pub struct Cookie(String);
11
12impl std::fmt::Display for Cookie {
13 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14 write!(f, "{}", self.0)
15 }
16}
17
18impl From<&str> for Cookie {
19 fn from(value: &str) -> Self {
20 Self(format!(".ROBLOSECURITY={}", value))
21 }
22}
23
24#[derive(Default, Debug)]
25pub struct ClientRequestor {
26 pub(crate) client: reqwest::Client,
27 pub(crate) default_headers: HeaderMap,
28 pub(crate) ratelimit: Option<Ratelimit>,
29}
30
31#[derive(Default, Debug)]
32pub struct Client {
33 pub requestor: ClientRequestor,
34}
35
36impl Client {
37 pub fn from_cookie(cookie: Cookie) -> Self {
38 let client = reqwest::Client::new();
39 let mut default_headers = HeaderMap::new();
40
41 default_headers.insert(
42 header::USER_AGENT,
43 HeaderValue::from_str("Roblox/WinInet").unwrap(),
44 );
45
46 default_headers.insert(
47 header::COOKIE,
48 HeaderValue::from_str(&cookie.to_string()).unwrap(),
49 );
50
51 default_headers.append(
53 header::COOKIE,
54 HeaderValue::from_str("RBXEventTrackerV2=&browserid=2").unwrap(),
55 );
56
57 Client {
58 requestor: ClientRequestor {
59 client,
60 default_headers,
61 ratelimit: None,
62 },
63 }
64 }
65
66 #[deprecated = "Failed requests due to token validation are now automatically retried"]
67 pub async fn ensure_token(&mut self) -> Result<(), Error> {
68 Ok(())
69 }
70
71 pub async fn ratelimits(&self) -> Option<Ratelimit> {
72 self.requestor.ratelimits().await
73 }
74
75 }
79
80pub(crate) struct ResponseWrapped(Response);
81impl ResponseWrapped {
82 pub(crate) async fn json<T: DeserializeOwned>(self) -> Result<T, Error> {
83 Ok(self.0.json::<T>().await?)
84 }
85
86 pub(crate) async fn bytes(self) -> Result<Vec<u8>, Error> {
87 let bytes = self.0.bytes().await;
88 match bytes {
89 Ok(bytes) => Ok(bytes.to_vec()),
90 Err(error) => Err(Error::ReqwestError(error)),
91 }
92 }
93}
94
95impl ClientRequestor {
96 pub(crate) async fn parse_json<T: DeserializeOwned>(
97 &self,
98 response: Response,
99 ) -> Result<T, Error> {
100 Ok(response.json::<T>().await?)
101 }
102
103 pub(crate) async fn request<'a, R: Serialize>(
104 &mut self,
105 method: Method,
106 url: &str,
107 request: Option<&'a R>,
108 query: Option<&'a [(&'a str, &'a str)]>,
109 headers: Option<HeaderMap>,
110 ) -> Result<ResponseWrapped, Error> {
111 let mut builder = self
112 .client
113 .request(method.clone(), url)
114 .headers(headers.clone().unwrap_or(self.default_headers.clone()));
115
116 if let Some(request) = request {
118 builder = builder.json(&request);
119 }
120
121 if let Some(query) = query {
122 builder = builder.query(&query);
123 }
124
125 let response = self.validate_response(builder.send().await).await;
126
127 match response {
128 Err(Error::ApiError(ApiError::TokenValidation)) => {
129 let mut builder = self
130 .client
131 .request(method, url)
132 .headers(headers.unwrap_or(self.default_headers.clone()));
133
134 if let Some(request) = request {
135 builder = builder.json(&request);
136 }
137
138 if let Some(query) = query {
139 builder = builder.query(&query);
140 }
141
142 let response = self.validate_response(builder.send().await).await?;
143 Ok(ResponseWrapped(response))
144 }
145
146 response => Ok(ResponseWrapped(response?)),
147 }
148 }
149}