1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Serialize};
3use strum_macros::{Display, EnumString};
4
5use crate::{DateTime, Error, Paging, client::Client};
6
7pub const URL: &str = "https://badges.roblox.com/v1";
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq, Display, EnumString)]
10pub enum BadgeSortBy {
11 Rank,
12 DateCreated,
13}
14
15#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
17pub enum BadgeCreatorType {
18 User,
19 Group,
20}
21
22#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Display, EnumString)]
23pub enum BadgeAwarderType {
24 Place,
25}
26
27#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
28#[serde(rename_all = "camelCase")]
29pub struct BadgeStatistics {
30 #[serde(rename = "pastDayAwardedCount")]
31 pub awarded_today: u32,
32 #[serde(rename = "awardedCount")]
33 pub awarded_total: u32,
34 pub win_rate_percentage: f32,
35}
36
37#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
38pub struct BadgeCreator {
39 pub id: u64,
40 pub name: String,
41 #[serde(rename = "type")]
42 pub kind: BadgeCreatorType,
43}
44
45#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
46pub struct BadgeAwarder {
47 pub id: u64,
48 #[serde(rename = "type")]
49 pub kind: BadgeAwarderType,
50}
51
52#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
53#[serde(rename_all = "camelCase")]
54pub struct BadgeUniverse {
55 pub id: u64,
56 pub name: String,
57 pub root_place_id: u64,
58}
59
60#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
61#[serde(rename_all = "camelCase")]
62pub struct Badge {
63 pub id: u64,
64 pub name: String,
65 pub description: String,
66
67 pub display_name: String,
68 pub display_description: String,
69
70 pub enabled: bool,
71
72 pub created: DateTime,
73 pub updated: DateTime,
74
75 pub icon_image_id: u64,
76 pub display_icon_image_id: u64,
77
78 pub statistics: BadgeStatistics,
79
80 pub creator: Option<BadgeCreator>,
81 pub awarder: Option<BadgeAwarder>,
82 #[serde(rename = "awardingUniverse")]
83 pub universe: Option<BadgeUniverse>,
84}
85
86#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
87pub struct BadgesResponse {
88 #[serde(rename = "data")]
89 pub badges: Vec<Badge>,
90 #[serde(rename = "nextPageCursor")]
91 pub next_cursor: Option<String>,
92 #[serde(rename = "previousPageCursor")]
93 pub previous_cursor: Option<String>,
94}
95
96async fn badges_generic<Response: DeserializeOwned>(
97 client: &mut Client,
98 path: &str,
99 sort_by: Option<BadgeSortBy>,
100 paging: Paging<'_>,
101) -> Result<Response, Error> {
102 let limit = paging.limit.unwrap_or(10).to_string();
103 let sort_order = paging.order.unwrap_or_default().to_string();
104 let cursor = match paging.cursor {
105 Some(cursor) => cursor.to_string(),
106 None => String::new(),
107 };
108
109 let sort_by = match sort_by {
110 Some(sort_by) => sort_by.to_string(),
111 None => String::new(),
112 };
113
114 let result = client
115 .requestor
116 .client
117 .get(format!("{URL}/{path}/badges"))
118 .query(&[
119 ("sortBy", sort_by),
120 ("limit", limit),
121 ("sortOrder", sort_order),
122 ("cursor", cursor),
123 ])
124 .headers(client.requestor.default_headers.clone())
125 .send()
126 .await;
127
128 let response = client.requestor.validate_response(result).await?;
129 client.requestor.parse_json::<Response>(response).await
130}
131
132pub async fn information(client: &mut Client, id: u64) -> Result<Badge, Error> {
133 let result = client
134 .requestor
135 .client
136 .get(format!("{URL}/badges/{id}"))
137 .headers(client.requestor.default_headers.clone())
138 .send()
139 .await;
140
141 let response = client.requestor.validate_response(result).await?;
142 client.requestor.parse_json::<Badge>(response).await
143}
144
145pub async fn universe_badges(
146 client: &mut Client,
147 id: u64,
148 sort_by: Option<BadgeSortBy>,
149 paging: Paging<'_>,
150) -> Result<BadgesResponse, Error> {
151 badges_generic::<BadgesResponse>(client, &format!("universes/{id}"), sort_by, paging).await
152}
153
154pub async fn user_badges(
155 client: &mut Client,
156 id: u64,
157 paging: Paging<'_>,
158) -> Result<BadgesResponse, Error> {
159 badges_generic::<BadgesResponse>(client, &format!("users/{id}"), None, paging).await
160}
161
162pub async fn remove(client: &mut Client, id: u64, user_id: u64) -> Result<(), Error> {
163 let result = client
164 .requestor
165 .client
166 .delete(format!("{URL}/user/{user_id}/badges/{id}"))
167 .headers(client.requestor.default_headers.clone())
168 .send()
169 .await;
170
171 let response = client.requestor.validate_response(result).await?;
172 client.requestor.parse_json::<()>(response).await
173}
174
175pub async fn authenticated_remove(client: &mut Client, id: u64) -> Result<(), Error> {
176 let result = client
177 .requestor
178 .client
179 .delete(format!("{URL}/user/badges/{id}"))
180 .headers(client.requestor.default_headers.clone())
181 .send()
182 .await;
183
184 let response = client.requestor.validate_response(result).await?;
185 client.requestor.parse_json::<()>(response).await
186}