open_lark/service/cloud_docs/comments/
list_replies.rs

1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    core::{
6        api_req::ApiRequest,
7        api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
8        config::Config,
9        constants::AccessTokenType,
10        endpoints::cloud_docs::*,
11        http::Transport,
12        req_option::RequestOption,
13        SDKResult,
14    },
15    impl_executable_builder_owned,
16};
17
18use super::list::Reply;
19
20/// 获取回复信息请求
21#[derive(Debug, Serialize, Default, Clone)]
22pub struct ListRepliesRequest {
23    #[serde(skip)]
24    api_request: ApiRequest,
25    /// 文档token
26    #[serde(skip)]
27    file_token: String,
28    /// 文档类型:doc、docx、sheet、bitable
29    #[serde(skip)]
30    file_type: String,
31    /// 评论ID
32    #[serde(skip)]
33    comment_id: String,
34    /// 分页大小
35    #[serde(skip_serializing_if = "Option::is_none")]
36    page_size: Option<i32>,
37    /// 分页标记
38    #[serde(skip_serializing_if = "Option::is_none")]
39    page_token: Option<String>,
40    /// 用户ID类型
41    #[serde(skip_serializing_if = "Option::is_none")]
42    user_id_type: Option<String>,
43}
44
45impl ListRepliesRequest {
46    pub fn builder() -> ListRepliesRequestBuilder {
47        ListRepliesRequestBuilder::default()
48    }
49
50    pub fn new(
51        file_token: impl ToString,
52        file_type: impl ToString,
53        comment_id: impl ToString,
54    ) -> Self {
55        Self {
56            file_token: file_token.to_string(),
57            file_type: file_type.to_string(),
58            comment_id: comment_id.to_string(),
59            ..Default::default()
60        }
61    }
62}
63
64#[derive(Default)]
65pub struct ListRepliesRequestBuilder {
66    request: ListRepliesRequest,
67}
68
69impl ListRepliesRequestBuilder {
70    /// 文档token
71    pub fn file_token(mut self, file_token: impl ToString) -> Self {
72        self.request.file_token = file_token.to_string();
73        self
74    }
75
76    /// 文档类型
77    pub fn file_type(mut self, file_type: impl ToString) -> Self {
78        self.request.file_type = file_type.to_string();
79        self
80    }
81
82    /// 设置为文档类型
83    pub fn with_doc_type(mut self) -> Self {
84        self.request.file_type = "doc".to_string();
85        self
86    }
87
88    /// 设置为docx类型
89    pub fn with_docx_type(mut self) -> Self {
90        self.request.file_type = "docx".to_string();
91        self
92    }
93
94    /// 设置为电子表格类型
95    pub fn with_sheet_type(mut self) -> Self {
96        self.request.file_type = "sheet".to_string();
97        self
98    }
99
100    /// 设置为多维表格类型
101    pub fn with_bitable_type(mut self) -> Self {
102        self.request.file_type = "bitable".to_string();
103        self
104    }
105
106    /// 评论ID
107    pub fn comment_id(mut self, comment_id: impl ToString) -> Self {
108        self.request.comment_id = comment_id.to_string();
109        self
110    }
111
112    /// 分页大小
113    pub fn page_size(mut self, page_size: i32) -> Self {
114        self.request.page_size = Some(page_size);
115        self
116    }
117
118    /// 分页标记
119    pub fn page_token(mut self, page_token: impl ToString) -> Self {
120        self.request.page_token = Some(page_token.to_string());
121        self
122    }
123
124    /// 用户ID类型
125    pub fn user_id_type(mut self, user_id_type: impl ToString) -> Self {
126        self.request.user_id_type = Some(user_id_type.to_string());
127        self
128    }
129
130    /// 使用OpenID
131    pub fn with_open_id(mut self) -> Self {
132        self.request.user_id_type = Some("open_id".to_string());
133        self
134    }
135
136    /// 使用UserID
137    pub fn with_user_id(mut self) -> Self {
138        self.request.user_id_type = Some("user_id".to_string());
139        self
140    }
141
142    /// 使用UnionID
143    pub fn with_union_id(mut self) -> Self {
144        self.request.user_id_type = Some("union_id".to_string());
145        self
146    }
147
148    pub fn build(mut self) -> ListRepliesRequest {
149        self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
150        self.request
151    }
152}
153
154// 应用ExecutableBuilder trait到ListRepliesRequestBuilder
155impl_executable_builder_owned!(
156    ListRepliesRequestBuilder,
157    super::CommentsService,
158    ListRepliesRequest,
159    BaseResponse<ListRepliesResponse>,
160    list_replies
161);
162
163/// 获取回复信息响应
164#[derive(Debug, Deserialize)]
165pub struct ListRepliesResponse {
166    /// 回复列表
167    pub items: Vec<Reply>,
168    /// 是否还有更多项
169    pub has_more: bool,
170    /// 分页标记
171    pub page_token: Option<String>,
172}
173
174impl ApiResponseTrait for ListRepliesResponse {
175    fn data_format() -> ResponseFormat {
176        ResponseFormat::Data
177    }
178}
179
180/// 获取回复信息
181pub async fn list_replies(
182    request: ListRepliesRequest,
183    config: &Config,
184    option: Option<RequestOption>,
185) -> SDKResult<BaseResponse<ListRepliesResponse>> {
186    let mut api_req = request.api_request;
187    api_req.http_method = Method::GET;
188    api_req.api_path = format!(
189        "{}?file_type={}&file_token={}",
190        COMMENT_V1_COMMENT_REPLIES.replace("{}", &request.comment_id),
191        request.file_type,
192        request.file_token
193    );
194
195    // 构建查询参数
196    let mut query_params = Vec::new();
197    if let Some(page_size) = request.page_size {
198        query_params.push(format!("page_size={page_size}"));
199    }
200    if let Some(page_token) = request.page_token {
201        query_params.push(format!("page_token={page_token}"));
202    }
203    if let Some(user_id_type) = request.user_id_type {
204        query_params.push(format!("user_id_type={user_id_type}"));
205    }
206
207    if !query_params.is_empty() {
208        api_req.api_path = format!("{}&{}", api_req.api_path, query_params.join("&"));
209    }
210
211    api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
212
213    let api_resp = Transport::request(api_req, config, option).await?;
214    Ok(api_resp)
215}
216
217impl ListRepliesResponse {
218    /// 获取回复数量
219    pub fn count(&self) -> usize {
220        self.items.len()
221    }
222
223    /// 是否为空
224    pub fn is_empty(&self) -> bool {
225        self.items.is_empty()
226    }
227
228    /// 根据用户ID筛选回复
229    pub fn replies_by_user(&self, user_id: &str) -> Vec<&Reply> {
230        self.items
231            .iter()
232            .filter(|reply| reply.user_id == user_id)
233            .collect()
234    }
235
236    /// 获取最新的回复
237    pub fn latest_reply(&self) -> Option<&Reply> {
238        self.items.iter().max_by_key(|reply| reply.create_time)
239    }
240
241    /// 获取最早的回复
242    pub fn earliest_reply(&self) -> Option<&Reply> {
243        self.items.iter().min_by_key(|reply| reply.create_time)
244    }
245
246    /// 按创建时间排序的回复列表
247    pub fn sorted_by_time(&self) -> Vec<&Reply> {
248        let mut replies: Vec<&Reply> = self.items.iter().collect();
249        replies.sort_by_key(|reply| reply.create_time);
250        replies
251    }
252
253    /// 按创建时间倒序排序的回复列表
254    pub fn sorted_by_time_desc(&self) -> Vec<&Reply> {
255        let mut replies: Vec<&Reply> = self.items.iter().collect();
256        replies.sort_by_key(|reply| std::cmp::Reverse(reply.create_time));
257        replies
258    }
259
260    /// 获取所有回复的文本内容
261    pub fn get_all_text_content(&self) -> Vec<String> {
262        self.items
263            .iter()
264            .map(|reply| reply.get_text_content())
265            .collect()
266    }
267
268    /// 获取回复摘要信息
269    pub fn summary(&self) -> String {
270        format!(
271            "回复总数: {}, 是否有更多: {}, 最新回复时间: {}",
272            self.count(),
273            self.has_more,
274            self.latest_reply()
275                .map(|r| r.create_time.to_string())
276                .unwrap_or_else(|| "无".to_string())
277        )
278    }
279}
280
281#[cfg(test)]
282#[allow(unused_variables, unused_unsafe)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn test_list_replies_request_builder() {
288        let request = ListRepliesRequest::builder()
289            .file_token("doccnxxxxxx")
290            .with_doc_type()
291            .comment_id("comment123")
292            .page_size(20)
293            .with_open_id()
294            .build();
295
296        assert_eq!(request.file_token, "doccnxxxxxx");
297        assert_eq!(request.file_type, "doc");
298        assert_eq!(request.comment_id, "comment123");
299        assert_eq!(request.page_size, Some(20));
300        assert_eq!(request.user_id_type, Some("open_id".to_string()));
301    }
302
303    #[test]
304    fn test_list_replies_new() {
305        let request = ListRepliesRequest::new("doccnxxxxxx", "doc", "comment123");
306        assert_eq!(request.file_token, "doccnxxxxxx");
307        assert_eq!(request.file_type, "doc");
308        assert_eq!(request.comment_id, "comment123");
309    }
310}