1use serde::Deserialize;
2
3use crate::{DateTime, Error, Paging, client::Client};
4
5pub const URL: &str = "https://games.roblox.com/v1";
6
7#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
8#[serde(rename_all = "camelCase")]
9pub struct PlaceDetails {
10 #[serde(rename = "placeId")]
11 pub id: u64,
12 pub name: String,
13 pub description: String,
14
15 pub source_name: String,
16 pub source_description: String,
17
18 pub universe_id: u64,
19 pub universe_root_place_id: u64,
20
21 pub url: String,
22 pub image_token: String,
23 pub reason_prohibited: String,
24
25 pub builder: String,
27 pub builder_id: u64,
28
29 pub price: u64,
30
31 pub is_playable: bool,
32 #[serde(rename = "hasVerifiedBadge")]
33 pub is_verified: bool,
34}
35
36#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
37pub struct PrivateServerInfoGameRootPlace {
38 pub id: u64,
39 pub name: String,
40}
41
42#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
43#[serde(rename_all = "camelCase")]
44pub struct PrivateServerInfoGame {
45 pub id: u64,
46 pub name: String,
47 pub root_place: PrivateServerInfoGameRootPlace,
48}
49
50#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
51#[serde(rename_all = "camelCase")]
52pub struct PrivateServerInfoSubscription {
53 pub price: u64,
54
55 pub active: bool,
56 pub expired: bool,
57 pub can_renew: bool,
58 pub has_price_changed: bool,
59 pub has_recurring_profile: bool,
60 pub has_insufficient_funds: bool,
61
62 pub expiration_date: DateTime,
63}
64
65#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
66#[serde(rename_all = "camelCase")]
67pub struct PrivateServerInfoPermissions {
68 pub enemy_clan_id: Option<u64>,
73
74 pub clan_allowed: bool,
75 pub friends_allowed: bool,
76}
77
78#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
79pub struct PrivateServerInfoVoiceSettings {
80 pub enabled: bool,
81}
82
83#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
84#[serde(rename_all = "camelCase")]
85pub struct PrivateServerInfo {
86 pub id: u64,
87 pub name: String,
88
89 pub active: bool,
90
91 #[serde(rename = "link")]
92 pub link_url: String,
93 pub join_code: String,
94
95 pub game: PrivateServerInfoGame,
96 pub subscription: PrivateServerInfoSubscription,
97 pub permissions: PrivateServerInfoPermissions,
98 pub voice_settings: PrivateServerInfoVoiceSettings,
99}
100
101#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
102#[serde(rename_all = "camelCase")]
103pub struct PrivateServerOwner {
104 pub id: u64,
105 pub name: String,
106 pub display_name: String,
107 #[serde(rename = "hasVerifiedBadge")]
108 pub is_verified: bool,
109}
110
111#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
112#[serde(rename_all = "camelCase")]
113pub struct PrivateServer {
114 pub name: String,
115 #[serde(rename = "vipServerId")]
116 pub id: u64,
117 pub max_players: u16,
118
119 pub owner: PrivateServerOwner,
120
121 pub player_tokens: Vec<String>,
123}
124
125#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
126pub struct PrivateServersResponse {
127 #[serde(rename = "data")]
128 pub servers: Vec<PrivateServer>,
129 #[serde(rename = "gameJoinRestricted")]
130 pub join_restricted: bool,
131 #[serde(rename = "nextPageCursor")]
132 pub next_cursor: Option<String>,
133 #[serde(rename = "previousPageCursor")]
134 pub previous_cursor: Option<String>,
135}
136
137#[derive(Clone, Debug, Deserialize, PartialEq)]
138#[serde(rename_all = "camelCase")]
139pub struct Server {
140 #[serde(rename = "id")]
141 pub job_id: String,
142
143 pub playing: u16,
144 pub max_players: u16,
145 pub fps: f32,
146 pub ping: u16,
147
148 pub player_tokens: Vec<String>,
150}
151
152#[derive(Clone, Debug, Deserialize, PartialEq)]
153pub struct ServersResponse {
154 #[serde(rename = "data")]
155 pub servers: Vec<Server>,
156 #[serde(rename = "nextPageCursor")]
157 pub next_cursor: Option<String>,
158 #[serde(rename = "previousPageCursor")]
159 pub previous_cursor: Option<String>,
160}
161
162#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
163pub struct UniverseVotes {
164 pub id: u64,
165 #[serde(rename = "upVotes")]
166 pub likes: u32,
167 #[serde(rename = "downVotes")]
168 pub dislikes: u32,
169}
170
171#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
172#[serde(rename_all = "camelCase")]
173pub struct UniverseGamepass {
174 pub id: u64,
175 pub name: String,
176 pub display_name: String,
177
178 pub price: Option<u64>,
179 pub product_id: Option<u64>,
180
181 #[serde(rename = "isOwned")]
182 pub owned: bool,
183
184 pub seller_id: Option<u64>,
185 pub seller_name: String,
186}
187
188#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
189pub struct UniverseGamepassesResponse {
190 #[serde(rename = "data")]
191 pub servers: Vec<UniverseGamepass>,
192 #[serde(rename = "nextPageCursor")]
193 pub next_cursor: Option<String>,
194 #[serde(rename = "previousPageCursor")]
195 pub previous_cursor: Option<String>,
196}
197
198pub async fn batch_place_details(
199 client: &mut Client,
200 ids: &[u64],
201) -> Result<Vec<PlaceDetails>, Error> {
202 let ids = ids
203 .iter()
204 .map(|x| x.to_string())
205 .collect::<Vec<String>>()
206 .join(",");
207
208 let result = client
209 .requestor
210 .client
211 .get(format!("{URL}/games/multiget-place-details"))
212 .query(&[("placeIds", ids)])
213 .headers(client.requestor.default_headers.clone())
214 .send()
215 .await;
216
217 let response = client.validate_response(result).await?;
218 client
219 .requestor
220 .parse_json::<Vec<PlaceDetails>>(response)
221 .await
222}
223
224pub async fn servers(
226 client: &mut Client,
227 id: u64,
228 server_kind: u8,
229 exclude_full_games: bool,
230 paging: Paging<'_>,
231) -> Result<ServersResponse, Error> {
232 let limit = paging.limit.unwrap_or(10).to_string();
233 let sort_order = paging.order.unwrap_or_default().to_string();
234 let cursor = match paging.cursor {
235 Some(cursor) => cursor.to_string(),
236 None => String::new(),
237 };
238
239 let result = client
240 .requestor
241 .client
242 .get(format!("{URL}/games/{id}/servers/{server_kind}"))
243 .query(&[
244 ("excludeFullGames", exclude_full_games.to_string()),
245 ("limit", limit),
246 ("sortOrder", sort_order),
247 ("cursor", cursor),
248 ])
249 .headers(client.requestor.default_headers.clone())
250 .send()
251 .await;
252
253 let response = client.validate_response(result).await?;
254 client
255 .requestor
256 .parse_json::<ServersResponse>(response)
257 .await
258}
259
260pub async fn private_servers(
261 client: &mut Client,
262 id: u64,
263 exclude_friend_servers: bool,
264 paging: Paging<'_>,
265) -> Result<PrivateServersResponse, Error> {
266 let limit = paging.limit.unwrap_or(10).to_string();
267 let sort_order = paging.order.unwrap_or_default().to_string();
268 let cursor = match paging.cursor {
269 Some(cursor) => cursor.to_string(),
270 None => String::new(),
271 };
272
273 let result = client
274 .requestor
275 .client
276 .get(format!("{URL}/games/{id}/private-servers"))
277 .query(&[
278 ("excludeFriendServers", exclude_friend_servers.to_string()),
279 ("limit", limit),
280 ("sortOrder", sort_order),
281 ("cursor", cursor),
282 ])
283 .headers(client.requestor.default_headers.clone())
284 .send()
285 .await;
286
287 let response = client.validate_response(result).await?;
288 client
289 .requestor
290 .parse_json::<PrivateServersResponse>(response)
291 .await
292}
293
294pub async fn private_server_info(client: &mut Client, id: u64) -> Result<PrivateServerInfo, Error> {
295 let result = client
296 .requestor
297 .client
298 .get(format!("{URL}/vip-servers/{id}"))
299 .headers(client.requestor.default_headers.clone())
300 .send()
301 .await;
302
303 let response = client.validate_response(result).await?;
304 client
305 .requestor
306 .parse_json::<PrivateServerInfo>(response)
307 .await
308}
309
310pub async fn universe_favorite_count(client: &mut Client, id: u64) -> Result<u64, Error> {
311 let result = client
312 .requestor
313 .client
314 .get(format!("{URL}/games/{id}/favorites/count"))
315 .headers(client.requestor.default_headers.clone())
316 .send()
317 .await;
318
319 #[derive(Debug, Deserialize)]
320 struct Response {
321 #[serde(rename = "favoritesCount")]
322 favorites: u64,
323 }
324
325 let response = client.validate_response(result).await?;
326 Ok(client
327 .requestor
328 .parse_json::<Response>(response)
329 .await?
330 .favorites)
331}
332
333pub async fn universe_votes(client: &mut Client, ids: &[u64]) -> Result<Vec<UniverseVotes>, Error> {
334 let ids = ids
335 .iter()
336 .map(|x| x.to_string())
337 .collect::<Vec<String>>()
338 .join(",");
339
340 let result = client
341 .requestor
342 .client
343 .get(format!("{URL}/games/votes"))
344 .query(&[("universeIds", ids)])
345 .headers(client.requestor.default_headers.clone())
346 .send()
347 .await;
348
349 #[derive(Debug, Deserialize)]
350 struct Response {
351 #[serde(rename = "data")]
352 votes: Vec<UniverseVotes>,
353 }
354
355 let response = client.validate_response(result).await?;
356 Ok(client
357 .requestor
358 .parse_json::<Response>(response)
359 .await?
360 .votes)
361}
362
363pub async fn universe_gamepasses(
364 client: &mut Client,
365 id: u64,
366 paging: Paging<'_>,
367) -> Result<UniverseGamepassesResponse, Error> {
368 let limit = paging.limit.unwrap_or(10).to_string();
369 let sort_order = paging.order.unwrap_or_default().to_string();
370 let cursor = match paging.cursor {
371 Some(cursor) => cursor.to_string(),
372 None => String::new(),
373 };
374
375 let result = client
376 .requestor
377 .client
378 .get(format!("{URL}/games/{id}/game-passes"))
379 .query(&[
380 ("limit", limit),
381 ("sortOrder", sort_order),
382 ("cursor", cursor),
383 ])
384 .headers(client.requestor.default_headers.clone())
385 .send()
386 .await;
387
388 let response = client.validate_response(result).await?;
389 client
390 .requestor
391 .parse_json::<UniverseGamepassesResponse>(response)
392 .await
393}