open_lark/service/cloud_docs/comments/
list.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        http::Transport,
11        req_option::RequestOption,
12        SDKResult,
13    },
14    impl_executable_builder_owned,
15};
16
17/// 获取云文档所有评论请求
18#[derive(Debug, Serialize, Default, Clone)]
19pub struct ListCommentsRequest {
20    #[serde(skip)]
21    api_request: ApiRequest,
22    /// 文档token
23    #[serde(skip)]
24    file_token: String,
25    /// 文档类型:doc、docx、sheet、bitable
26    #[serde(skip)]
27    file_type: String,
28    /// 是否是全文评论,不传该参数则返回全部评论
29    #[serde(skip_serializing_if = "Option::is_none")]
30    is_whole: Option<bool>,
31    /// 是否获取已解决的评论
32    #[serde(skip_serializing_if = "Option::is_none")]
33    is_solved: Option<bool>,
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 ListCommentsRequest {
46    pub fn builder() -> ListCommentsRequestBuilder {
47        ListCommentsRequestBuilder::default()
48    }
49
50    pub fn new(file_token: impl ToString, file_type: impl ToString) -> Self {
51        Self {
52            file_token: file_token.to_string(),
53            file_type: file_type.to_string(),
54            ..Default::default()
55        }
56    }
57}
58
59#[derive(Default)]
60pub struct ListCommentsRequestBuilder {
61    request: ListCommentsRequest,
62}
63
64impl ListCommentsRequestBuilder {
65    /// 文档token
66    pub fn file_token(mut self, file_token: impl ToString) -> Self {
67        self.request.file_token = file_token.to_string();
68        self
69    }
70
71    /// 文档类型
72    pub fn file_type(mut self, file_type: impl ToString) -> Self {
73        self.request.file_type = file_type.to_string();
74        self
75    }
76
77    /// 设置为文档类型
78    pub fn with_doc_type(mut self) -> Self {
79        self.request.file_type = "doc".to_string();
80        self
81    }
82
83    /// 设置为docx类型
84    pub fn with_docx_type(mut self) -> Self {
85        self.request.file_type = "docx".to_string();
86        self
87    }
88
89    /// 设置为电子表格类型
90    pub fn with_sheet_type(mut self) -> Self {
91        self.request.file_type = "sheet".to_string();
92        self
93    }
94
95    /// 设置为多维表格类型
96    pub fn with_bitable_type(mut self) -> Self {
97        self.request.file_type = "bitable".to_string();
98        self
99    }
100
101    /// 是否是全文评论
102    pub fn set_whole(mut self, is_whole: bool) -> Self {
103        self.request.is_whole = Some(is_whole);
104        self
105    }
106
107    /// 只获取全文评论
108    pub fn whole_comments_only(mut self) -> Self {
109        self.request.is_whole = Some(true);
110        self
111    }
112
113    /// 获取所有类型评论
114    pub fn all_comment_types(mut self) -> Self {
115        self.request.is_whole = None;
116        self
117    }
118
119    /// 是否获取已解决的评论
120    pub fn set_solved(mut self, is_solved: bool) -> Self {
121        self.request.is_solved = Some(is_solved);
122        self
123    }
124
125    /// 只获取已解决的评论
126    pub fn solved_comments_only(mut self) -> Self {
127        self.request.is_solved = Some(true);
128        self
129    }
130
131    /// 只获取未解决的评论
132    pub fn unsolved_comments_only(mut self) -> Self {
133        self.request.is_solved = Some(false);
134        self
135    }
136
137    /// 获取所有评论(无论是否解决)
138    pub fn all_comments(mut self) -> Self {
139        self.request.is_solved = None;
140        self
141    }
142
143    /// 分页大小
144    pub fn page_size(mut self, page_size: i32) -> Self {
145        self.request.page_size = Some(page_size);
146        self
147    }
148
149    /// 分页标记
150    pub fn page_token(mut self, page_token: impl ToString) -> Self {
151        self.request.page_token = Some(page_token.to_string());
152        self
153    }
154
155    /// 用户ID类型
156    pub fn user_id_type(mut self, user_id_type: impl ToString) -> Self {
157        self.request.user_id_type = Some(user_id_type.to_string());
158        self
159    }
160
161    pub fn build(mut self) -> ListCommentsRequest {
162        self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
163        self.request
164    }
165}
166
167// 应用ExecutableBuilder trait到ListCommentsRequestBuilder
168impl_executable_builder_owned!(
169    ListCommentsRequestBuilder,
170    super::CommentsService,
171    ListCommentsRequest,
172    BaseResponse<ListCommentsResponse>,
173    list
174);
175
176/// 评论信息
177#[derive(Debug, Deserialize)]
178pub struct Comment {
179    /// 评论ID
180    pub comment_id: String,
181    /// 用户ID
182    pub user_id: String,
183    /// 创建时间(毫秒时间戳)
184    pub create_time: i64,
185    /// 更新时间(毫秒时间戳)
186    pub update_time: i64,
187    /// 是否解决
188    pub is_solved: bool,
189    /// 已解决时间(毫秒时间戳)
190    pub solved_time: Option<i64>,
191    /// 解决者用户ID
192    pub solver_user_id: Option<String>,
193    /// 是否有更多回复
194    pub has_more: bool,
195    /// 分页标记
196    pub page_token: Option<String>,
197    /// 回复列表
198    pub reply_list: Option<Vec<Reply>>,
199    /// 是否是全文评论
200    pub is_whole: Option<bool>,
201    /// 引用内容
202    pub quote: Option<String>,
203}
204
205/// 回复信息
206#[derive(Debug, Deserialize)]
207pub struct Reply {
208    /// 回复ID
209    pub reply_id: String,
210    /// 用户ID
211    pub user_id: String,
212    /// 创建时间(毫秒时间戳)
213    pub create_time: i64,
214    /// 更新时间(毫秒时间戳)
215    pub update_time: i64,
216    /// 回复内容
217    pub content: ReplyContent,
218    /// 额外字段
219    pub extra: Option<serde_json::Value>,
220}
221
222/// 回复内容
223#[derive(Debug, Serialize, Deserialize, Default, Clone)]
224pub struct ReplyContent {
225    /// 元素列表
226    pub elements: Vec<ContentElement>,
227}
228
229/// 内容元素
230#[derive(Debug, Serialize, Deserialize, Clone)]
231pub struct ContentElement {
232    /// 元素类型
233    #[serde(rename = "type")]
234    pub element_type: String,
235    /// 文本内容
236    pub text_run: Option<TextRun>,
237}
238
239/// 文本内容
240#[derive(Debug, Serialize, Deserialize, Clone)]
241pub struct TextRun {
242    /// 文本内容
243    pub text: String,
244    /// 样式
245    pub style: Option<serde_json::Value>,
246}
247
248/// 获取云文档所有评论响应
249#[derive(Debug, Deserialize)]
250pub struct ListCommentsResponse {
251    /// 评论列表
252    pub items: Vec<Comment>,
253    /// 是否还有更多项
254    pub has_more: bool,
255    /// 分页标记
256    pub page_token: Option<String>,
257}
258
259impl ApiResponseTrait for ListCommentsResponse {
260    fn data_format() -> ResponseFormat {
261        ResponseFormat::Data
262    }
263}
264
265/// 获取云文档所有评论
266pub async fn list_comments(
267    request: ListCommentsRequest,
268    config: &Config,
269    option: Option<RequestOption>,
270) -> SDKResult<BaseResponse<ListCommentsResponse>> {
271    let mut api_req = request.api_request;
272    api_req.http_method = Method::GET;
273    api_req.api_path = format!(
274        "/open-apis/comment/v1/comments?file_type={}&file_token={}",
275        request.file_type, request.file_token
276    );
277
278    // 构建查询参数
279    let mut query_params = Vec::new();
280    if let Some(is_whole) = request.is_whole {
281        query_params.push(format!("is_whole={is_whole}"));
282    }
283    if let Some(is_solved) = request.is_solved {
284        query_params.push(format!("is_solved={is_solved}"));
285    }
286    if let Some(page_size) = request.page_size {
287        query_params.push(format!("page_size={page_size}"));
288    }
289    if let Some(page_token) = request.page_token {
290        query_params.push(format!("page_token={page_token}"));
291    }
292    if let Some(user_id_type) = request.user_id_type {
293        query_params.push(format!("user_id_type={user_id_type}"));
294    }
295
296    if !query_params.is_empty() {
297        api_req.api_path = format!("{}&{}", api_req.api_path, query_params.join("&"));
298    }
299
300    api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
301
302    let api_resp = Transport::request(api_req, config, option).await?;
303    Ok(api_resp)
304}
305
306impl Comment {
307    /// 获取评论的文本内容
308    pub fn get_text_content(&self) -> String {
309        if let Some(replies) = &self.reply_list {
310            replies
311                .iter()
312                .map(|reply| reply.get_text_content())
313                .collect::<Vec<_>>()
314                .join("\n")
315        } else {
316            String::new()
317        }
318    }
319
320    /// 是否有回复
321    pub fn has_replies(&self) -> bool {
322        self.reply_list
323            .as_ref()
324            .is_some_and(|replies| !replies.is_empty())
325    }
326
327    /// 获取回复数量
328    pub fn reply_count(&self) -> usize {
329        self.reply_list.as_ref().map_or(0, |replies| replies.len())
330    }
331}
332
333impl Reply {
334    /// 获取回复的文本内容
335    pub fn get_text_content(&self) -> String {
336        self.content
337            .elements
338            .iter()
339            .filter_map(|element| {
340                element
341                    .text_run
342                    .as_ref()
343                    .map(|text_run| text_run.text.clone())
344            })
345            .collect::<Vec<_>>()
346            .join("")
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353
354    #[test]
355    fn test_list_comments_request_builder() {
356        let request = ListCommentsRequest::builder()
357            .file_token("doccnxxxxxx")
358            .with_doc_type()
359            .whole_comments_only()
360            .unsolved_comments_only()
361            .page_size(20)
362            .user_id_type("open_id")
363            .build();
364
365        assert_eq!(request.file_token, "doccnxxxxxx");
366        assert_eq!(request.file_type, "doc");
367        assert_eq!(request.is_whole, Some(true));
368        assert_eq!(request.is_solved, Some(false));
369        assert_eq!(request.page_size, Some(20));
370        assert_eq!(request.user_id_type, Some("open_id".to_string()));
371    }
372}