1use reqwest::{
2 header::{self, HeaderValue},
3 multipart::{Form, Part},
4};
5use serde::{Deserialize, Serialize};
6
7use crate::{AssetTypeId, DateTime, Error, client::Client, endpoint};
8
9pub const URL: &str = "https://apis.roblox.com/assets/user-auth/v1";
10
11#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
12#[serde(rename_all = "camelCase")]
13pub enum Creator {
14 UserId(String), GroupId(String),
17}
18
19#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
20#[serde(rename_all = "camelCase")]
21pub struct CreationContext {
22 pub creator: Creator,
23 pub expected_price: Option<u64>,
24}
25
26#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
27pub struct ModerationResult {
28 #[serde(rename = "moderationState")]
29 pub state: String,
30}
31
32#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
33#[serde(rename_all = "camelCase")]
34pub struct AssetInfo {
35 #[serde(rename = "assetId")]
36 pub id: String,
37 pub icon: Option<String>,
38 #[serde(rename = "displayName")]
39 pub name: String,
40 pub description: String,
41
42 pub path: String,
43 pub state: String,
44 pub asset_type: AssetTypeId,
45
46 pub revision_id: String,
47 #[serde(rename = "revisionCreateTime")]
48 pub revision_creation_time: DateTime,
49
50 pub creation_context: CreationContext,
51 pub moderation_result: ModerationResult,
52}
53
54#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
55#[serde(rename_all = "camelCase")]
56pub struct AssetUploadResponse {
57 pub path: String,
58 #[serde(rename = "assetId")]
60 pub id: String,
61 #[serde(rename = "displayName")]
62 pub name: String,
63
64 pub state: String,
65 pub asset_type: AssetTypeId,
66
67 pub revision_id: String,
68 #[serde(rename = "revisionCreateTime")]
69 pub revision_creation_time: DateTime,
70
71 pub creation_context: CreationContext,
72 pub moderation_result: ModerationResult,
73}
74
75#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
76#[serde(rename_all = "camelCase")]
77pub struct AssetUploadStatus {
78 pub path: String,
79 pub operation_id: String,
80 #[serde(rename = "done")]
81 pub complete: bool,
82 pub response: Option<AssetUploadResponse>,
83}
84
85endpoint! {
86 asset(id: u64) -> AssetInfo {
87 GET "{URL}/assets/{id}";
88 }
89
90 status(operation_id: &str) -> AssetUploadStatus {
91 GET "{URL}/operations/{operation_id}";
92 }
93}
94
95pub async fn upload(
97 client: &mut Client,
98 bytes: &[u8],
99 title: &str,
100 description: &str,
101 asset_type: AssetTypeId,
102 creation_context: CreationContext,
103) -> Result<AssetUploadStatus, Error> {
104 let mut headers = client.requestor.default_headers.clone();
105 headers.insert(header::ACCEPT, HeaderValue::from_str("*/*").unwrap());
106
107 #[derive(Clone, Debug, Deserialize, Serialize)]
108 #[serde(rename_all = "camelCase")]
109 struct Request<'a> {
110 #[serde(rename = "displayName")]
111 title: &'a str,
112 description: &'a str,
113 asset_type: AssetTypeId,
114 creation_context: CreationContext,
115 }
116
117 let request = serde_json::to_string(&Request {
118 title,
119 description,
120 asset_type,
121 creation_context,
122 })
123 .unwrap();
124
125 let mime = match asset_type {
126 AssetTypeId::Model => "model/x-rbxm",
127 _ => infer::get(bytes)
128 .map(|t| t.mime_type())
129 .unwrap_or("application/octet-stream"),
130 };
131
132 let file_part = Part::bytes(bytes.to_owned())
133 .file_name("instance")
134 .mime_str(mime)?;
135
136 let result = client
137 .requestor
138 .client
139 .post(format!("{URL}/assets"))
140 .headers(headers)
141 .multipart(
142 Form::new()
143 .text("request", request)
144 .part("fileContent", file_part),
145 )
146 .send()
147 .await;
148
149 let response = client.requestor.validate_response(result).await?;
150 client
151 .requestor
152 .parse_json::<AssetUploadStatus>(response)
153 .await
154}
155
156pub async fn update(
157 client: &mut Client,
158 id: u64,
159 bytes: &[u8],
160 title: &str,
161 description: &str,
162 asset_type: AssetTypeId,
163) -> Result<AssetUploadStatus, Error> {
164 let mut headers = client.requestor.default_headers.clone();
165 headers.insert(header::ACCEPT, HeaderValue::from_str("*/*").unwrap());
166
167 #[derive(Clone, Debug, Deserialize, Serialize)]
168 #[serde(rename_all = "camelCase")]
169 struct Request<'a> {
170 #[serde(rename = "assetId")]
171 id: u64,
172 #[serde(rename = "displayName")]
173 title: &'a str,
174 description: &'a str,
175 }
176
177 let request = serde_json::to_string(&Request {
178 id,
179 title,
180 description,
181 })
182 .unwrap();
183
184 let mime = match asset_type {
185 AssetTypeId::Model => "model/x-rbxm",
186 _ => infer::get(bytes)
187 .map(|t| t.mime_type())
188 .unwrap_or("application/octet-stream"),
189 };
190
191 let file_part = Part::bytes(bytes.to_owned())
192 .file_name("instance")
193 .mime_str(mime)?;
194
195 let result = client
196 .requestor
197 .client
198 .patch(format!("{URL}/assets/{id}"))
199 .headers(headers)
200 .multipart(
201 Form::new()
202 .text("request", request)
203 .part("fileContent", file_part),
204 )
205 .send()
206 .await;
207
208 let response = client.requestor.validate_response(result).await?;
209 client
210 .requestor
211 .parse_json::<AssetUploadStatus>(response)
212 .await
213}