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