1use crate::api::{
6 SlackApiUsersConversationsRequest, SlackApiUsersConversationsResponse,
7 SlackApiUsersProfileSetRequest, SlackApiUsersProfileSetResponse,
8};
9use crate::blocks::*;
10use crate::models::*;
11use crate::multipart_form::FileMultipartData;
12use crate::ratectl::*;
13use crate::{ClientResult, SlackClientHttpConnector};
14use crate::{SlackApiScrollableRequest, SlackApiScrollableResponse, SlackClientSession};
15use futures_util::future::BoxFuture;
16use futures_util::FutureExt;
17use rsb_derive::Builder;
18use rvstruct::ValueStruct;
19use serde::{Deserialize, Serialize, Serializer};
20use serde_with::skip_serializing_none;
21use url::Url;
22
23impl<'a, SCHC> SlackClientSession<'a, SCHC>
24where
25 SCHC: SlackClientHttpConnector + Send,
26{
27 pub async fn files_info(
31 &self,
32 req: &SlackApiFilesInfoRequest,
33 ) -> ClientResult<SlackApiFilesInfoResponse> {
34 self.http_session_api
35 .http_get(
36 "files.info",
37 &vec![("file", Some(req.file.value()))],
38 Some(&SLACK_TIER4_METHOD_CONFIG),
39 )
40 .await
41 }
42
43 pub async fn files_list(
47 &self,
48 req: &SlackApiFilesListRequest,
49 ) -> ClientResult<SlackApiFilesListResponse> {
50 self.http_session_api
51 .http_get(
52 "files.list",
53 &vec![
54 ("channel", req.channel.as_ref().map(|x| x.value())),
55 ("user", req.user.as_ref().map(|x| x.value())),
56 ("types", req.types.as_ref()),
57 ("count", req.count.map(|x| x.to_string()).as_ref()),
58 ("page", req.page.map(|x| x.to_string()).as_ref()),
59 ("ts_from", req.ts_from.map(|x| x.to_string()).as_ref()),
60 ("ts_to", req.ts_to.map(|x| x.to_string()).as_ref()),
61 (
62 "show_files_hidden_by_limit",
63 req.show_files_hidden_by_limit
64 .map(|x| x.to_string())
65 .as_ref(),
66 ),
67 ],
68 Some(&SLACK_TIER3_METHOD_CONFIG),
69 )
70 .await
71 }
72
73 #[deprecated(
77 note = "Deprecated by Slack. Use `getUploadURLExternal/files_upload_via_url/completeUploadExternal` instead."
78 )]
79 pub async fn files_upload(
80 &self,
81 req: &SlackApiFilesUploadRequest,
82 ) -> ClientResult<SlackApiFilesUploadResponse> {
83 let maybe_file = req.binary_content.as_ref().map(|file_data| {
84 let filename = req.filename.clone().unwrap_or("file".to_string());
85 let file_content_type = req.file_content_type.clone().unwrap_or_else(|| {
86 let file_mime = mime_guess::MimeGuess::from_path(&filename).first_or_octet_stream();
87 file_mime.to_string()
88 });
89 FileMultipartData {
90 name: filename,
91 content_type: file_content_type,
92 data: file_data.as_slice(),
93 }
94 });
95 self.http_session_api
96 .http_post_multipart_form(
97 "files.upload",
98 maybe_file,
99 &vec![
100 (
101 "channels",
102 req.channels
103 .as_ref()
104 .map(|xs| {
105 xs.iter()
106 .map(|x| x.to_string())
107 .collect::<Vec<String>>()
108 .join(",")
109 })
110 .as_ref(),
111 ),
112 ("content", req.content.as_ref()),
113 ("filename", req.filename.as_ref()),
114 ("filetype", req.filetype.as_ref().map(|x| x.value())),
115 ("initial_comment", req.initial_comment.as_ref()),
116 ("thread_ts", req.thread_ts.as_ref().map(|x| x.value())),
117 ("title", req.title.as_ref()),
118 ],
119 Some(&SLACK_TIER2_METHOD_CONFIG),
120 )
121 .await
122 }
123
124 pub async fn get_upload_url_external(
128 &self,
129 req: &SlackApiFilesGetUploadUrlExternalRequest,
130 ) -> ClientResult<SlackApiFilesGetUploadUrlExternalResponse> {
131 self.http_session_api
132 .http_get(
133 "files.getUploadURLExternal",
134 &vec![
135 ("filename", Some(&req.filename)),
136 ("length", Some(&req.length.to_string())),
137 ("alt_txt", req.alt_txt.as_ref()),
138 ("snippet_type", req.snippet_type.as_ref().map(|v| v.value())),
139 ],
140 Some(&SLACK_TIER4_METHOD_CONFIG),
141 )
142 .await
143 }
144
145 pub async fn files_upload_via_url(
146 &self,
147 req: &SlackApiFilesUploadViaUrlRequest,
148 ) -> ClientResult<SlackApiFilesUploadViaUrlResponse> {
149 self.http_session_api
150 .http_post_uri_binary(
151 req.upload_url.value().clone(),
152 req.content_type.clone(),
153 &req.content,
154 Some(&SLACK_TIER4_METHOD_CONFIG),
155 )
156 .await
157 }
158
159 pub async fn files_complete_upload_external(
163 &self,
164 req: &SlackApiFilesCompleteUploadExternalRequest,
165 ) -> ClientResult<SlackApiFilesCompleteUploadExternalResponse> {
166 self.http_session_api
167 .http_post(
168 "files.completeUploadExternal",
169 req,
170 Some(&SLACK_TIER4_METHOD_CONFIG),
171 )
172 .await
173 }
174
175 pub async fn files_delete(
179 &self,
180 req: &SlackApiFilesDeleteRequest,
181 ) -> ClientResult<SlackApiFilesDeleteResponse> {
182 self.http_session_api
183 .http_post("files.delete", req, Some(&SLACK_TIER3_METHOD_CONFIG))
184 .await
185 }
186}
187
188#[skip_serializing_none]
189#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
190pub struct SlackApiFilesInfoRequest {
191 pub file: SlackFileId,
192}
193
194#[skip_serializing_none]
195#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
196pub struct SlackApiFilesInfoResponse {
197 pub file: SlackFile,
198}
199
200#[skip_serializing_none]
201#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
202pub struct SlackApiFilesListRequest {
203 pub channel: Option<SlackChannelId>,
204 pub user: Option<SlackUserId>,
205 pub types: Option<String>,
206 pub count: Option<u32>,
207 pub page: Option<u32>,
208 pub ts_from: Option<i64>,
209 pub ts_to: Option<i64>,
210 pub show_files_hidden_by_limit: Option<bool>,
211}
212
213#[skip_serializing_none]
214#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
215pub struct SlackApiFilesListResponse {
216 pub files: Vec<SlackFile>,
217 pub paging: Option<SlackApiFilesListPaging>,
218}
219
220#[skip_serializing_none]
221#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
222pub struct SlackApiFilesListPaging {
223 pub count: Option<u32>,
224 pub total: Option<u32>,
225 pub page: Option<u32>,
226 pub pages: Option<u32>,
227}
228
229impl<SCHC> SlackApiScrollableRequest<SCHC> for SlackApiFilesListRequest
230where
231 SCHC: SlackClientHttpConnector + Send + Sync + Clone + 'static,
232{
233 type ResponseType = SlackApiFilesListResponse;
234 type CursorType = u32;
235 type ResponseItemType = SlackFile;
236
237 fn with_new_cursor(&self, new_cursor: Option<&Self::CursorType>) -> Self {
238 self.clone().opt_page(new_cursor.cloned())
239 }
240
241 fn scroll<'a, 's>(
242 &'a self,
243 session: &'a SlackClientSession<'s, SCHC>,
244 ) -> BoxFuture<'a, ClientResult<Self::ResponseType>> {
245 async move { session.files_list(self).await }.boxed()
246 }
247}
248
249impl SlackApiScrollableResponse for SlackApiFilesListResponse {
250 type CursorType = u32;
251 type ResponseItemType = SlackFile;
252
253 fn next_cursor(&self) -> Option<Self::CursorType> {
254 self.paging
255 .as_ref()
256 .into_iter()
257 .filter_map(|paging| match (paging.page, paging.pages) {
258 (Some(page), Some(pages)) if page < pages => Some(page + 1),
259 _ => None,
260 })
261 .next()
262 }
263
264 fn scrollable_items<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Self::ResponseItemType> + 'a> {
265 Box::new(self.files.iter())
266 }
267}
268
269#[skip_serializing_none]
270#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
271pub struct SlackApiFilesUploadRequest {
272 #[serde(serialize_with = "to_csv")]
273 pub channels: Option<Vec<SlackChannelId>>,
274 pub content: Option<String>,
275 pub binary_content: Option<Vec<u8>>,
276 pub filename: Option<String>,
277 pub filetype: Option<SlackFileType>,
278 pub initial_comment: Option<String>,
279 pub thread_ts: Option<SlackTs>,
280 pub title: Option<String>,
281 pub file_content_type: Option<String>,
282}
283
284#[skip_serializing_none]
285#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
286pub struct SlackApiFilesUploadResponse {
287 pub file: SlackFile,
288}
289
290#[skip_serializing_none]
291#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
292pub struct SlackApiFilesGetUploadUrlExternalRequest {
293 pub filename: String,
294 pub length: usize,
295 pub alt_txt: Option<String>,
296 pub snippet_type: Option<SlackFileSnippetType>,
297}
298
299#[skip_serializing_none]
300#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
301pub struct SlackApiFilesGetUploadUrlExternalResponse {
302 pub upload_url: SlackFileUploadUrl,
303 pub file_id: SlackFileId,
304}
305
306#[skip_serializing_none]
307#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
308pub struct SlackApiFilesUploadViaUrlRequest {
309 pub upload_url: SlackFileUploadUrl,
310 pub content: Vec<u8>,
311 pub content_type: String,
312}
313
314#[skip_serializing_none]
315#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
316pub struct SlackApiFilesUploadViaUrlResponse {}
317
318#[skip_serializing_none]
325#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
326pub struct SlackApiFilesCompleteUploadExternalRequest {
327 pub files: Vec<SlackApiFilesComplete>,
328 pub channel_id: Option<SlackChannelId>,
329 #[serde(serialize_with = "to_csv")]
330 pub channels: Option<Vec<SlackChannelId>>,
331 pub initial_comment: Option<String>,
332 pub blocks: Option<Vec<SlackBlock>>,
339 pub thread_ts: Option<SlackTs>,
340 pub username: Option<String>,
341 pub icon_url: Option<Url>,
342 pub icon_emoji: Option<SlackEmoji>,
343}
344
345#[skip_serializing_none]
346#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
347pub struct SlackApiFilesCompleteUploadExternalResponse {
348 pub files: Vec<SlackFile>,
349}
350
351#[skip_serializing_none]
352#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
353pub struct SlackApiFilesComplete {
354 pub id: SlackFileId,
355 pub title: Option<String>,
356}
357
358#[skip_serializing_none]
359#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
360pub struct SlackApiFilesDeleteRequest {
361 pub file: SlackFileId,
362}
363
364#[skip_serializing_none]
365#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Builder)]
366pub struct SlackApiFilesDeleteResponse {}
367
368fn to_csv<S: Serializer>(x: &Option<Vec<SlackChannelId>>, s: S) -> Result<S::Ok, S::Error> {
369 match x {
370 None => s.serialize_none(),
371 Some(ids) => {
372 let y: Vec<String> = ids.iter().map(|v| v.0.clone()).collect();
373 y.join(",").serialize(s)
374 }
375 }
376}
377
378#[cfg(test)]
379mod test {
380 use super::*;
381
382 #[test]
383 fn test_slack_api_files_complete_upload_external_request_serialization() {
384 let payload =
385 include_str!("./fixtures/slack_api_files_complete_upload_external_request.json");
386 let expected: serde_json::Value = serde_json::from_str(payload).unwrap();
387
388 let request =
389 SlackApiFilesCompleteUploadExternalRequest::new(vec![SlackApiFilesComplete::new(
390 SlackFileId("F123456".into()),
391 )])
392 .opt_channel_id(Some(SlackChannelId("C123456".into())))
393 .with_channels(vec![
394 SlackChannelId("C1".into()),
395 SlackChannelId("C2".into()),
396 ])
397 .with_blocks(vec![SlackMarkdownBlock::new("*bold* text".into()).into()])
398 .with_thread_ts(SlackTs("1234567890.123456".into()))
399 .with_username("test-bot".into())
400 .with_icon_url(Url::parse("https://example.com/icon.png").unwrap())
401 .with_icon_emoji(SlackEmoji::new(":tada:".into()));
402
403 let actual = serde_json::to_value(&request).unwrap();
404 assert_eq!(actual, expected);
405 }
406
407 #[test]
408 fn test_slack_api_files_complete_upload_external_request_minimal() {
409 let request =
410 SlackApiFilesCompleteUploadExternalRequest::new(vec![SlackApiFilesComplete::new(
411 SlackFileId("F123456".into()),
412 )]);
413
414 let actual = serde_json::to_value(&request).unwrap();
415 let expected: serde_json::Value =
416 serde_json::from_str(r#"{"files":[{"id":"F123456"}]}"#).unwrap();
417 assert_eq!(actual, expected);
418 }
419
420 #[test]
421 fn test_slack_api_files_complete_upload_external_response() {
422 let payload =
423 include_str!("./fixtures/slack_api_files_complete_upload_external_response.json");
424 let model: SlackApiFilesCompleteUploadExternalResponse =
425 serde_json::from_str(payload).unwrap();
426
427 assert_eq!(model.files.len(), 1);
428 assert_eq!(model.files[0].id, SlackFileId("F123456".into()));
429 assert_eq!(model.files[0].title, Some("test-file".into()));
430 }
431}