Skip to main content

pickcat_api_collection/
media.rs

1//! 媒体资源章节:文件上传/读取、头像、表情。
2//!
3//! 参见 <https://pickcat-docs.xiaole6324.fun/media.html>。
4
5use crate::dto::media::{FileListDTO, UploadedFileDTO};
6use crate::{Error, PickcatAccound};
7
8/// 媒体资源接口,来自文档的「媒体资源」章节
9/// (<https://pickcat-docs.xiaole6324.fun/media.html>)。
10///
11/// 二进制读取(文件/头像/表情)在浏览器里通常直接由 `<img>` 加载,这里返回
12/// 原始字节,方便调用方自行处理缓存 / MIME。
13pub trait MediaBehavior {
14    /// `POST /api/v1/files` —— 需要会话,`multipart/form-data`。
15    ///
16    /// `idempotency_key` 会作为必填的 `Idempotency-Key` 请求头发送
17    /// (可用 [`crate::topic::generate_idempotency_key`] 生成)。
18    fn upload_file(
19        &self,
20        idempotency_key: &str,
21        ownership: &str,
22        file_name: &str,
23        bytes: Vec<u8>,
24    ) -> impl std::future::Future<Output = Result<UploadedFileDTO, Error>> + Send;
25
26    /// `GET /api/v1/files` —— 需要会话,游标分页。
27    fn list_files(
28        &self,
29        scope: Option<&str>,
30        state: Option<&str>,
31        limit: Option<u32>,
32        cursor: Option<&str>,
33    ) -> impl std::future::Future<Output = Result<FileListDTO, Error>> + Send;
34
35    /// `GET /api/v1/files/{fileId}` —— 公开,返回图片字节。
36    fn get_file(
37        &self,
38        file_id: &str,
39    ) -> impl std::future::Future<Output = Result<Vec<u8>, Error>> + Send;
40
41    /// `GET /api/v1/avatars/{id}` —— 公开,`id` 为预设编号或自定义头像 `fileId`。
42    fn get_avatar(
43        &self,
44        id: &str,
45        version: Option<&str>,
46    ) -> impl std::future::Future<Output = Result<Vec<u8>, Error>> + Send;
47
48    /// `GET /api/v1/emojis/{emojiName}/image` —— 公开,返回表情图片字节。
49    fn get_emoji_image(
50        &self,
51        emoji_name: &str,
52        emoji_read: Option<&str>,
53    ) -> impl std::future::Future<Output = Result<Vec<u8>, Error>> + Send;
54}
55
56impl MediaBehavior for PickcatAccound {
57    async fn upload_file(
58        &self,
59        idempotency_key: &str,
60        ownership: &str,
61        file_name: &str,
62        bytes: Vec<u8>,
63    ) -> Result<UploadedFileDTO, Error> {
64        let part = reqwest::multipart::Part::bytes(bytes).file_name(file_name.to_string());
65        let form = reqwest::multipart::Form::new()
66            .text("ownership", ownership.to_string())
67            .part("file", part);
68
69        Ok(self
70            .client
71            .post(format!("{}/api/v1/files", self.base_url))
72            .header("Idempotency-Key", idempotency_key)
73            .header("origin", self.base_url.clone())
74            .multipart(form)
75            .send()
76            .await?
77            .json::<UploadedFileDTO>()
78            .await?)
79    }
80
81    async fn list_files(
82        &self,
83        scope: Option<&str>,
84        state: Option<&str>,
85        limit: Option<u32>,
86        cursor: Option<&str>,
87    ) -> Result<FileListDTO, Error> {
88        let mut params: Vec<(&str, String)> = Vec::new();
89        if let Some(scope) = scope {
90            params.push(("scope", scope.to_string()));
91        }
92        if let Some(state) = state {
93            params.push(("state", state.to_string()));
94        }
95        if let Some(limit) = limit {
96            params.push(("limit", limit.to_string()));
97        }
98        if let Some(cursor) = cursor {
99            params.push(("cursor", cursor.to_string()));
100        }
101
102        let mut request = self.client.get(format!("{}/api/v1/files", self.base_url));
103        if !params.is_empty() {
104            request = request.query(&params);
105        }
106        Ok(request.send().await?.json::<FileListDTO>().await?)
107    }
108
109    async fn get_file(&self, file_id: &str) -> Result<Vec<u8>, Error> {
110        Ok(self
111            .client
112            .get(format!("{}/api/v1/files/{}", self.base_url, file_id))
113            .send()
114            .await?
115            .error_for_status()?
116            .bytes()
117            .await?
118            .to_vec())
119    }
120
121    async fn get_avatar(&self, id: &str, version: Option<&str>) -> Result<Vec<u8>, Error> {
122        let mut request = self
123            .client
124            .get(format!("{}/api/v1/avatars/{}", self.base_url, id));
125        if let Some(version) = version {
126            request = request.query(&[("v", version)]);
127        }
128        Ok(request
129            .send()
130            .await?
131            .error_for_status()?
132            .bytes()
133            .await?
134            .to_vec())
135    }
136
137    async fn get_emoji_image(
138        &self,
139        emoji_name: &str,
140        emoji_read: Option<&str>,
141    ) -> Result<Vec<u8>, Error> {
142        let mut request = self.client.get(format!(
143            "{}/api/v1/emojis/{}/image",
144            self.base_url, emoji_name
145        ));
146        if let Some(emoji_read) = emoji_read {
147            request = request.query(&[("emoji-read", emoji_read)]);
148        }
149        Ok(request
150            .send()
151            .await?
152            .error_for_status()?
153            .bytes()
154            .await?
155            .to_vec())
156    }
157}