open_lark/service/im/v1/
message.rs

1use std::collections::HashMap;
2
3use log::error;
4use reqwest::Method;
5use serde::{Deserialize, Serialize};
6use serde_json::{json, Value};
7
8use crate::core::{
9    api_req::ApiRequest,
10    api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
11    config::Config,
12    constants::AccessTokenType,
13    http::Transport,
14    req_option::RequestOption,
15    standard_response::StandardResponse,
16    SDKResult,
17};
18
19/// 消息服务
20///
21/// 提供消息的创建、发送、获取等核心功能。支持多种消息类型和格式。
22///
23/// # 支持的消息类型
24///
25/// - **text**: 纯文本消息
26/// - **post**: 富文本消息
27/// - **image**: 图片消息
28/// - **file**: 文件消息
29/// - **audio**: 音频消息
30/// - **media**: 视频消息
31/// - **sticker**: 表情包消息
32/// - **interactive**: 交互式卡片消息
33/// - **share_chat**: 群名片消息
34/// - **share_user**: 个人名片消息
35///
36/// # 示例
37///
38/// ```rust
39/// use open_lark::prelude::*;
40///
41/// // 通过客户端访问消息服务
42/// let client = LarkClient::builder("app_id", "app_secret").build();
43/// let message_service = &client.im.v1.message;
44///
45/// // 创建文本消息
46/// let request = CreateMessageRequest::builder()
47///     .receive_id_type("open_id")
48///     .request_body(
49///         CreateMessageRequestBody::builder()
50///             .receive_id("ou_xxx")
51///             .msg_type("text")
52///             .content("{\"text\":\"Hello!\"}")
53///             .build()
54///     )
55///     .build();
56/// ```
57pub struct MessageService {
58    /// 服务配置
59    pub config: Config,
60}
61
62impl MessageService {
63    /// 发送消息
64    ///
65    /// 给指定用户或者会话发送消息,支持文本、富文本、可交互的消息卡片、群名片、个人名片、图片、
66    /// 视频、音频、文件、表情包。
67    ///
68    /// <https://open.feishu.cn/document/server-docs/im-v1/message/create>
69    pub async fn create(
70        &self,
71        create_message_request: CreateMessageRequest,
72        option: Option<RequestOption>,
73    ) -> SDKResult<Message> {
74        let mut api_req = create_message_request.api_req;
75        api_req.http_method = Method::POST;
76        api_req.api_path = "/open-apis/im/v1/messages".to_string();
77        api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
78
79        let api_resp: BaseResponse<Message> =
80            Transport::request(api_req, &self.config, option).await?;
81
82        api_resp.into_result()
83    }
84
85    /// 获取会话历史消息
86    ///
87    /// 获取会话(包括单聊、群组)的历史消息(聊天记录)
88    ///
89    /// <https://open.feishu.cn/document/server-docs/im-v1/message/list>
90    pub async fn list(
91        &self,
92        list_message_request: ListMessageRequest,
93        option: Option<RequestOption>,
94    ) -> SDKResult<ListMessageRespData> {
95        let mut api_req = list_message_request.api_req;
96        api_req.http_method = Method::GET;
97        api_req.api_path = "/open-apis/im/v1/messages".to_string();
98        api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::User];
99
100        let api_resp: BaseResponse<ListMessageRespData> =
101            Transport::request(api_req, &self.config, option).await?;
102
103        api_resp.into_result()
104    }
105
106    pub fn list_iter(
107        &self,
108        list_message_request: ListMessageRequest,
109        option: Option<RequestOption>,
110    ) -> ListMessageIterator<'_> {
111        ListMessageIterator {
112            service: self,
113            req: list_message_request,
114            option,
115            has_more: true,
116        }
117    }
118}
119
120pub struct ListMessageIterator<'a> {
121    service: &'a MessageService,
122    req: ListMessageRequest,
123    option: Option<RequestOption>,
124    has_more: bool,
125}
126
127impl ListMessageIterator<'_> {
128    pub async fn next(&mut self) -> Option<Vec<Message>> {
129        if !self.has_more {
130            return None;
131        }
132        match self
133            .service
134            .list(self.req.clone(), self.option.clone())
135            .await
136        {
137            Ok(data) => {
138                self.has_more = data.has_more;
139                if data.has_more {
140                    self.req
141                        .api_req
142                        .query_params
143                        .insert("page_token".to_string(), data.page_token.unwrap());
144                    Some(data.items)
145                } else if data.items.is_empty() {
146                    None
147                } else {
148                    Some(data.items)
149                }
150            }
151            Err(e) => {
152                error!("Error: {e:?}");
153                None
154            }
155        }
156    }
157}
158
159/// 创建消息请求
160///
161/// 用于构建发送消息的请求参数,包含消息接收者类型和消息内容。
162///
163/// # 示例
164///
165/// ```rust
166/// use open_lark::service::im::v1::message::{CreateMessageRequest, CreateMessageRequestBody};
167///
168/// let request = CreateMessageRequest::builder()
169///     .receive_id_type("open_id")
170///     .request_body(
171///         CreateMessageRequestBody::builder()
172///             .receive_id("ou_xxx")
173///             .msg_type("text")
174///             .content("{\"text\":\"Hello!\"}")
175///             .build()
176///     )
177///     .build();
178/// ```
179#[derive(Default, Clone)]
180pub struct CreateMessageRequest {
181    api_req: ApiRequest,
182}
183
184impl CreateMessageRequest {
185    /// 创建请求构建器
186    pub fn builder() -> CreateMessageRequestBuilder {
187        CreateMessageRequestBuilder::default()
188    }
189}
190
191/// 创建消息请求构建器
192///
193/// 用于逐步构建创建消息的请求参数。
194#[derive(Default)]
195pub struct CreateMessageRequestBuilder {
196    request: CreateMessageRequest,
197}
198
199impl CreateMessageRequestBuilder {
200    /// 设置消息接收者ID类型
201    ///
202    /// # 参数
203    /// - `receive_id_type`: 接收者ID类型,可选值:
204    ///   - `"open_id"`: Open ID(推荐)
205    ///   - `"user_id"`: User ID
206    ///   - `"union_id"`: Union ID
207    ///   - `"email"`: 邮箱地址
208    ///   - `"chat_id"`: 群聊ID
209    pub fn receive_id_type(mut self, receive_id_type: impl ToString) -> Self {
210        self.request
211            .api_req
212            .query_params
213            .insert("receive_id_type".to_string(), receive_id_type.to_string());
214        self
215    }
216
217    /// 设置消息请求体
218    ///
219    /// # 参数
220    /// - `body`: 包含接收者ID、消息类型和内容的请求体
221    pub fn request_body(mut self, body: CreateMessageRequestBody) -> Self {
222        self.request.api_req.body = serde_json::to_vec(&body).unwrap();
223        self
224    }
225
226    /// 构建最终的请求对象
227    pub fn build(self) -> CreateMessageRequest {
228        self.request
229    }
230}
231
232// 应用ExecutableBuilder trait到CreateMessageRequestBuilder
233crate::impl_executable_builder_owned!(
234    CreateMessageRequestBuilder,
235    MessageService,
236    CreateMessageRequest,
237    Message,
238    create
239);
240
241/// 发送消息 请求体
242#[derive(Debug, Default, Clone, Serialize, Deserialize)]
243pub struct CreateMessageRequestBody {
244    /// 消息接收者的ID,ID类型应与查询参数receive_id_type 对应;
245    /// 推荐使用 OpenID,获取方式可参考文档如何获取 Open ID?
246    ///
247    /// 示例值:"ou_7d8a6e6df7621556ce0d21922b676706ccs"
248    receive_id: String,
249    /// 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、
250    /// share_user等,类型定义请参考发送消息内容
251    ///
252    /// 示例值:"text"
253    msg_type: String,
254    /// 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容,具体格式说明参考:
255    /// 发送消息内容
256    ///
257    /// 注意:
258    /// JSON字符串需进行转义,如换行符转义后为\\n
259    /// 文本消息请求体最大不能超过150KB
260    /// 卡片及富文本消息请求体最大不能超过30KB
261    /// 示例值:"{\"text\":\"test content\"}"
262    content: String,
263    /// 由开发者生成的唯一字符串序列,用于发送消息请求去重;
264    /// 持有相同uuid的请求1小时内至多成功发送一条消息
265    ///
266    /// 示例值:"选填,每次调用前请更换,如a0d69e20-1dd1-458b-k525-dfeca4015204"
267    ///
268    /// 数据校验规则:
269    ///
270    /// 最大长度:50 字符
271    uuid: Option<String>,
272}
273
274impl CreateMessageRequestBody {
275    pub fn builder() -> CreateMessageRequestBodyBuilder {
276        CreateMessageRequestBodyBuilder::default()
277    }
278}
279
280#[derive(Default)]
281pub struct CreateMessageRequestBodyBuilder {
282    request: CreateMessageRequestBody,
283}
284
285impl CreateMessageRequestBodyBuilder {
286    /// 消息接收者的ID,ID类型应与查询参数receive_id_type 对应;
287    /// 推荐使用 OpenID,获取方式可参考文档如何获取 Open ID?
288    ///
289    /// 示例值:"ou_7d8a6e6df7621556ce0d21922b676706ccs"
290    pub fn receive_id(mut self, receive_id: impl ToString) -> Self {
291        self.request.receive_id = receive_id.to_string();
292        self
293    }
294
295    /// 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、
296    /// share_user等,类型定义请参考发送消息内容
297    ///
298    /// 示例值:"text"
299    pub fn msg_type(mut self, msg_type: impl ToString) -> Self {
300        self.request.msg_type = msg_type.to_string();
301        self
302    }
303
304    /// 消息内容,JSON结构序列化后的字符串。不同msg_type对应不同内容,具体格式说明参考:
305    /// 发送消息内容
306    ///
307    /// 注意:
308    /// JSON字符串需进行转义,如换行符转义后为\\n
309    /// 文本消息请求体最大不能超过150KB
310    /// 卡片及富文本消息请求体最大不能超过30KB
311    /// 示例值:"{\"text\":\"test content\"}"
312    pub fn content(mut self, content: impl ToString) -> Self {
313        self.request.content = content.to_string();
314        self
315    }
316
317    /// 由开发者生成的唯一字符串序列,用于发送消息请求去重;
318    /// 持有相同uuid的请求1小时内至多成功发送一条消息
319    ///
320    /// 示例值:"选填,每次调用前请更换,如a0d69e20-1dd1-458b-k525-dfeca4015204"
321    ///
322    /// 数据校验规则:
323    ///
324    /// 最大长度:50 字符
325    pub fn uuid(mut self, uuid: impl ToString) -> Self {
326        self.request.uuid = Some(uuid.to_string());
327        self
328    }
329
330    pub fn build(self) -> CreateMessageRequestBody {
331        self.request
332    }
333}
334
335#[derive(Debug, Serialize, Deserialize)]
336pub struct CreateMessageResp {
337    pub data: Message,
338}
339
340/// 飞书消息
341///
342/// 表示一条完整的飞书消息,包含消息ID、类型、内容、发送者等所有信息。
343///
344/// # 字段说明
345///
346/// - `message_id`: 消息的唯一标识符
347/// - `msg_type`: 消息类型(text、post、image等)
348/// - `sender`: 消息发送者信息
349/// - `body`: 消息具体内容
350/// - `chat_id`: 所属会话ID
351/// - `create_time`/`update_time`: 创建和更新时间戳
352///
353/// # 示例
354///
355/// ```ignore
356/// // 通常通过API调用获得Message实例
357/// let message = client.im.v1.message.create(request, None).await?;
358/// println!("消息ID: {}", message.message_id);
359/// println!("消息类型: {}", message.msg_type);
360/// ```
361#[derive(Debug, Serialize, Deserialize)]
362pub struct Message {
363    /// 消息id
364    pub message_id: String,
365    /// 根消息id,用于回复消息场景
366    pub root_id: Option<String>,
367    /// 父消息的id,用于回复消息场景
368    pub parent_id: Option<String>,
369    /// 消息所属的话题 ID(不返回说明该消息非话题消息)
370    pub thread_id: Option<String>,
371    /// 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、
372    /// share_user等
373    pub msg_type: String,
374    /// 消息生成的时间戳(毫秒)
375    pub create_time: String,
376    /// 消息更新的时间戳(毫秒)
377    pub update_time: String,
378    /// 消息是否被撤回
379    pub deleted: bool,
380    /// 消息是否被更新
381    pub updated: bool,
382    /// 所属的群
383    pub chat_id: String,
384    /// 发送者,可以是用户或应用
385    pub sender: Sender,
386    /// 消息内容
387    pub body: MessageBody,
388    /// 被@的用户或机器人的id列表
389    pub mentions: Option<Vec<Mention>>,
390}
391
392impl ApiResponseTrait for Message {
393    fn data_format() -> ResponseFormat {
394        ResponseFormat::Data
395    }
396}
397
398/// 发送者,可以是用户或应用
399#[derive(Debug, Serialize, Deserialize)]
400pub struct Sender {
401    /// 该字段标识发送者的id
402    id: String,
403    /// 该字段标识发送者的id类型
404    ///
405    /// 可选值有:
406    /// - open_id
407    /// - app_id
408    id_type: String,
409    /// 该字段标识发送者的类型
410    ///
411    /// 可选值有:
412    /// - user: 用户
413    /// - app: 应用
414    /// - anonymous: 匿名
415    /// - unknown: 未知
416    sender_type: String,
417    /// 为租户在飞书上的唯一标识,用来换取对应的tenant_access_token,
418    /// 也可以用作租户在应用里面的唯一标识
419    tenant_key: String,
420}
421
422/// 消息内容
423#[derive(Debug, Serialize, Deserialize)]
424pub struct MessageBody {
425    /// 消息内容,json结构序列化后的字符串。不同msg_type对应不同内容。
426    ///
427    /// 消息类型 包括:text、post、image、file、audio、media、sticker、interactive、share_chat、
428    /// share_user等,
429    pub content: String,
430}
431
432/// 被@的用户或机器人的id列表
433#[derive(Debug, Serialize, Deserialize)]
434pub struct Mention {
435    /// 被@的用户或机器人的序号。例如,第3个被@到的成员,值为“@_user_3”
436    pub key: String,
437    /// 被@的用户或者机器人的open_id
438    pub id: String,
439    /// 被@的用户或机器人 id 类型,目前仅支持 open_id
440    pub id_type: String,
441    /// 被@的用户或机器人的姓名
442    pub name: String,
443    /// 为租户在飞书上的唯一标识,用来换取对应的tenant_access_token,
444    /// 也可以用作租户在应用里面的唯一标识
445    pub tenant_key: String,
446    /// 合并转发消息中,上一层级的消息id message_id
447    pub upper_message_id: String,
448}
449
450#[derive(Default, Clone)]
451pub struct ListMessageRequest {
452    api_req: ApiRequest,
453}
454
455impl ListMessageRequest {
456    pub fn builder() -> ListMessageRequestBuilder {
457        ListMessageRequestBuilder::default()
458    }
459}
460
461#[derive(Default)]
462pub struct ListMessageRequestBuilder {
463    request: ListMessageRequest,
464}
465
466impl ListMessageRequestBuilder {
467    /// 容器类型 ,目前可选值仅有"chat",包含单聊(p2p)和群聊(group)
468    ///
469    /// 示例值:chat
470    pub fn container_id_type(mut self, container_id_type: impl ToString) -> Self {
471        self.request.api_req.query_params.insert(
472            "container_id_type".to_string(),
473            container_id_type.to_string(),
474        );
475        self
476    }
477
478    /// 容器的id,即chat的id,详情参见[群ID 说明](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/chat-id-description)
479    ///
480    /// 示例值:oc_234jsi43d3ssi993d43545f
481    pub fn container_id(mut self, container_id: impl ToString) -> Self {
482        self.request
483            .api_req
484            .query_params
485            .insert("container_id".to_string(), container_id.to_string());
486        self
487    }
488
489    /// 历史信息的起始时间(秒级时间戳)
490    ///
491    /// 示例值:1609296809
492    pub fn start_time(mut self, start_time: i64) -> Self {
493        self.request
494            .api_req
495            .query_params
496            .insert("start_time".to_string(), start_time.to_string());
497        self
498    }
499
500    /// 历史信息的结束时间(秒级时间戳)
501    ///
502    /// 示例值:1608594809
503    pub fn end_time(mut self, end_time: i64) -> Self {
504        self.request
505            .api_req
506            .query_params
507            .insert("end_time".to_string(), end_time.to_string());
508        self
509    }
510
511    /// 消息排序方式
512    ///
513    /// 示例值:ByCreateTimeAsc
514    pub fn sort_type(mut self, sort_type: impl ToString) -> Self {
515        self.request
516            .api_req
517            .query_params
518            .insert("sort_type".to_string(), sort_type.to_string());
519        self
520    }
521
522    /// 分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的
523    /// page_token,下次遍历可采用该 page_token 获取查询结果
524    pub fn page_token(mut self, page_token: impl ToString) -> Self {
525        self.request
526            .api_req
527            .query_params
528            .insert("page_token".to_string(), page_token.to_string());
529        self
530    }
531
532    /// 分页大小
533    ///
534    /// 示例值:20
535    pub fn page_size(mut self, page_size: i32) -> Self {
536        self.request
537            .api_req
538            .query_params
539            .insert("page_size".to_string(), page_size.to_string());
540        self
541    }
542
543    pub fn build(self) -> ListMessageRequest {
544        self.request
545    }
546}
547
548crate::impl_executable_builder_owned!(
549    ListMessageRequestBuilder,
550    MessageService,
551    ListMessageRequest,
552    ListMessageRespData,
553    list
554);
555
556#[derive(Debug, Serialize, Deserialize)]
557pub struct ListMessageRespData {
558    /// 是否还有更多项
559    pub has_more: bool,
560    /// 分页标记,当 has_more 为 true 时,会同时返回新的 page_token,否则不返回 page_token
561    pub page_token: Option<String>,
562    pub items: Vec<Message>,
563}
564
565impl ApiResponseTrait for ListMessageRespData {
566    fn data_format() -> ResponseFormat {
567        ResponseFormat::Data
568    }
569}
570
571/// 发送消息的通用trait
572///
573/// 所有可以作为消息内容发送的类型都应该实现此trait。
574/// 它定义了获取消息类型和内容的标准接口。
575///
576/// # 实现
577///
578/// - `MessageText`: 文本消息
579/// - `MessagePost`: 富文本消息  
580/// - `MessageImage`: 图片消息
581/// - `MessageCardTemplate`: 卡片模板消息
582///
583/// # 示例
584///
585/// ```rust
586/// use open_lark::service::im::v1::message::{MessageText, SendMessageTrait};
587///
588/// let text_msg = MessageText::new("Hello, World!");
589/// assert_eq!(text_msg.msg_type(), "text");
590/// assert_eq!(text_msg.content(), "{\"text\":\"Hello, World!\"}");
591/// ```
592pub trait SendMessageTrait {
593    /// 获取消息类型
594    ///
595    /// 返回消息的类型标识,如 "text"、"post"、"image" 等
596    fn msg_type(&self) -> String;
597
598    /// 获取消息内容
599    ///
600    /// 返回序列化后的消息内容JSON字符串
601    fn content(&self) -> String;
602}
603
604/// 文本消息
605///
606/// 用于发送纯文本消息,支持@用户、换行等功能。
607/// 是最常用的消息类型之一。
608///
609/// # 特殊功能
610///
611/// - 支持@用户:`<at user_id="xxx"></at>`
612/// - 支持@所有人:`<at user_id="all">name="全体成员"</at>`
613/// - 支持换行:使用`\n`或调用`line()`方法
614///
615/// # 示例
616///
617/// ```rust
618/// use open_lark::service::im::v1::message::{MessageText, SendMessageTrait};
619///
620/// // 创建简单文本消息
621/// let msg = MessageText::new("Hello, World!");
622/// assert_eq!(msg.msg_type(), "text");
623///
624/// // 创建包含@用户和换行的消息
625/// let msg = MessageText::new("")
626///     .add_text("欢迎新成员 ")
627///     .at_user("ou_xxx")
628///     .text_line("!")
629///     .add_text("请大家多多关照")
630///     .build();
631/// ```
632pub struct MessageText {
633    text: String,
634}
635
636impl MessageText {
637    /// 创建新的文本消息
638    ///
639    /// # 参数
640    /// - `text`: 初始文本内容
641    pub fn new(text: &str) -> Self {
642        Self {
643            text: text.to_string(),
644        }
645    }
646
647    /// 追加文本内容
648    ///
649    /// # 参数
650    /// - `text`: 要追加的文本
651    pub fn add_text(mut self, text: &str) -> Self {
652        self.text += text;
653        self
654    }
655
656    /// 添加一行文本(自动添加换行符)
657    ///
658    /// # 参数
659    /// - `text`: 要添加的文本行
660    pub fn text_line(mut self, text: &str) -> Self {
661        self.text = self.text + text + "\n";
662        self
663    }
664
665    /// 添加换行符
666    pub fn line(mut self) -> Self {
667        self.text += "\n";
668        self
669    }
670
671    /// @指定用户
672    ///
673    /// # 参数
674    /// - `user_id`: 用户的ID(open_id、user_id或union_id)
675    pub fn at_user(mut self, user_id: &str) -> Self {
676        self.text = self.text + &format!("<at user_id=\"{user_id}\"></at>");
677        self
678    }
679
680    /// @所有人
681    pub fn at_all(mut self) -> Self {
682        self.text += "<at user_id=\"all\">name=\"全体成员\"</at>";
683        self
684    }
685
686    /// 构建最终的文本消息
687    pub fn build(self) -> MessageText {
688        MessageText { text: self.text }
689    }
690}
691
692impl SendMessageTrait for MessageText {
693    fn msg_type(&self) -> String {
694        "text".to_string()
695    }
696
697    fn content(&self) -> String {
698        json!({"text": self.text}).to_string()
699    }
700}
701
702/// 富文本参数
703#[derive(Debug, Serialize, Deserialize)]
704pub struct MessagePost {
705    /// 默认的语言
706    #[serde(skip)]
707    default_language: String,
708    post: HashMap<String, MessagePostContent>,
709}
710
711impl SendMessageTrait for MessagePost {
712    fn msg_type(&self) -> String {
713        "post".to_string()
714    }
715
716    fn content(&self) -> String {
717        json!(self).to_string()
718    }
719}
720
721impl MessagePost {
722    pub fn new(lng: &str) -> Self {
723        let post = HashMap::new();
724        Self {
725            default_language: lng.to_string(),
726            post,
727        }
728    }
729
730    pub fn title(mut self, title: impl ToString) -> Self {
731        let post = self
732            .post
733            .entry(self.default_language.clone())
734            .or_insert(MessagePostContent {
735                title: title.to_string(),
736                content: vec![],
737            });
738        post.title = title.to_string();
739        self
740    }
741
742    /// 追加富文本内容
743    pub fn append_content(mut self, contents: Vec<MessagePostNode>) -> Self {
744        let post = self
745            .post
746            .entry(self.default_language.clone())
747            .or_insert(MessagePostContent {
748                title: "".to_string(),
749                content: vec![],
750            });
751        post.content.push(contents);
752        self
753    }
754}
755
756#[derive(Debug, Serialize, Deserialize, Default)]
757pub struct MessagePostContent {
758    /// 富文本消息的标题。
759    pub title: String,
760    /// 富文本消息内容,由多个段落组成,每个段落为一个 node 列表。支持的 node 标签类型及对应参数
761    pub content: Vec<Vec<MessagePostNode>>,
762}
763
764/// 富文本消息内容
765#[derive(Debug, Serialize, Deserialize)]
766#[serde(tag = "tag")]
767pub enum MessagePostNode {
768    /// 文本内容。
769    #[serde(rename = "text")]
770    Text(TextNode),
771    #[serde(rename = "a")]
772    A(ANode),
773    #[serde(rename = "at")]
774    At(AtNode),
775    #[serde(rename = "img")]
776    Img(ImgNode),
777    #[serde(rename = "media")]
778    Media(MediaNode),
779    #[serde(rename = "emotion")]
780    Emotion(EmotionNode),
781}
782
783/// 文本node
784#[derive(Debug, Serialize, Deserialize)]
785pub struct TextNode {
786    text: String,
787    /// 表示是不是 unescape 解码,默认为 false ,不用可以不填。
788    #[serde(skip_serializing_if = "Option::is_none")]
789    un_escape: Option<bool>,
790    /// 用于配置文本内容加粗、下划线、删除线和斜体样式,可选值分别为bold、underline、
791    /// lineThrough与italic,非可选值将被忽略。
792    #[serde(skip_serializing_if = "Option::is_none")]
793    style: Option<Vec<String>>,
794}
795
796impl TextNode {
797    pub fn new(text: &str) -> Self {
798        Self {
799            text: text.to_string(),
800            un_escape: None,
801            style: None,
802        }
803    }
804
805    pub fn un_escape(mut self, un_escape: bool) -> Self {
806        self.un_escape = Some(un_escape);
807        self
808    }
809
810    pub fn style(mut self, style: Vec<&str>) -> Self {
811        self.style = Some(style.iter().map(|s| s.to_string()).collect());
812        self
813    }
814}
815
816/// a Node
817#[derive(Debug, Serialize, Deserialize)]
818pub struct ANode {
819    /// 文本内容
820    text: String,
821    /// 默认的链接地址,请确保链接地址的合法性,否则消息会发送失败。
822    href: String,
823    /// 用于配置文本内容加粗、下划线、删除线和斜体样式,可选值分别为bold、underline、
824    /// lineThrough与italic,非可选值将被忽略。
825    #[serde(skip_serializing_if = "Option::is_none")]
826    style: Option<Vec<String>>,
827}
828
829impl ANode {
830    pub fn new(text: &str, href: &str) -> Self {
831        Self {
832            text: text.to_string(),
833            href: href.to_string(),
834            style: None,
835        }
836    }
837
838    pub fn style(mut self, style: Vec<&str>) -> Self {
839        self.style = Some(style.iter().map(|s| s.to_string()).collect());
840        self
841    }
842}
843
844#[derive(Debug, Serialize, Deserialize)]
845pub struct AtNode {
846    /// 用户的open_id,union_id 或 user_id,请参考如何获取 User ID、Open ID 和 Union ID?
847    /// 注意: @单个用户时,user_id字段必须是有效值;@所有人填"all"。
848    user_id: String,
849    /// 用于配置文本内容加粗、下划线、删除线和斜体样式,可选值分别为bold、underline、
850    /// lineThrough与italic,非可选值将被忽略。
851    #[serde(skip_serializing_if = "Option::is_none")]
852    style: Option<Vec<String>>,
853}
854
855impl AtNode {
856    pub fn new(user_id: &str) -> Self {
857        Self {
858            user_id: user_id.to_string(),
859            style: None,
860        }
861    }
862
863    pub fn style(mut self, style: Vec<&str>) -> Self {
864        self.style = Some(style.iter().map(|s| s.to_string()).collect());
865        self
866    }
867}
868
869#[derive(Debug, Serialize, Deserialize)]
870pub struct ImgNode {
871    /// 图片的唯一标识,可通过 上传图片 接口获取image_key。
872    image_key: String,
873}
874
875impl ImgNode {
876    pub fn new(image_key: &str) -> Self {
877        Self {
878            image_key: image_key.to_string(),
879        }
880    }
881}
882
883#[derive(Debug, Serialize, Deserialize)]
884pub struct MediaNode {
885    /// 视频文件的唯一标识,可通过 上传文件 接口获取file_key
886    file_key: String,
887    /// 视频封面图片的唯一标识,可通过 上传图片 接口获取image_key。
888    #[serde(skip_serializing_if = "Option::is_none")]
889    image_key: Option<String>,
890}
891
892impl MediaNode {
893    pub fn new(file_key: &str, image_key: Option<&str>) -> Self {
894        Self {
895            file_key: file_key.to_string(),
896            image_key: image_key.map(|s| s.to_string()),
897        }
898    }
899}
900
901/// 表情类型
902#[derive(Debug, Serialize, Deserialize)]
903pub struct EmotionNode {
904    /// 表情类型,部分可选值请参见表情文案。
905    emoji_type: String,
906}
907
908impl EmotionNode {
909    pub fn new(emoji_type: &str) -> Self {
910        Self {
911            emoji_type: emoji_type.to_string(),
912        }
913    }
914}
915
916/// 图片消息
917pub struct MessageImage {
918    pub image_key: String,
919}
920
921impl SendMessageTrait for MessageImage {
922    fn msg_type(&self) -> String {
923        "image".to_string()
924    }
925
926    fn content(&self) -> String {
927        json!({"image_key": self.image_key}).to_string()
928    }
929}
930
931/// 卡片模板
932#[derive(Debug, Serialize, Deserialize)]
933pub struct MessageCardTemplate {
934    /// 固定值:template
935    r#type: String,
936    /// 卡片模板数据
937    data: CardTemplate,
938}
939
940impl SendMessageTrait for MessageCardTemplate {
941    fn msg_type(&self) -> String {
942        "interactive".to_string()
943    }
944
945    fn content(&self) -> String {
946        serde_json::to_string(self).unwrap()
947    }
948}
949
950impl MessageCardTemplate {
951    pub fn new(template_id: impl ToString, template_variable: Value) -> Self {
952        Self {
953            r#type: "template".to_string(),
954            data: CardTemplate {
955                template_id: template_id.to_string(),
956                template_variable,
957            },
958        }
959    }
960}
961
962/// 卡片模板数据
963#[derive(Debug, Serialize, Deserialize)]
964struct CardTemplate {
965    /// 卡片模板 ID,可在消息卡片搭建工具,我的卡片中,通过复制卡片 ID 获取
966    template_id: String,
967    /// 卡片中的变量数据,值为{key:value}形式,其中 key 表示变量名称。value 值表示变量的值
968    template_variable: Value,
969}
970
971#[cfg(test)]
972mod test {
973    use serde_json::json;
974
975    use crate::service::im::v1::message::{
976        ANode, AtNode, EmotionNode, ImgNode, MediaNode, MessageText, SendMessageTrait, TextNode,
977    };
978
979    #[test]
980    fn test_message_text() {
981        let t1 = MessageText::new("").add_text(" test content").build();
982        assert_eq!(t1.text, " test content");
983        let t2 = MessageText::new("").text_line(" test content").build();
984        assert_eq!(t2.text, " test content\n");
985        let t3 = MessageText::new("")
986            .add_text(" test content")
987            .line()
988            .build();
989        assert_eq!(t3.text, " test content\n");
990        let t4 = MessageText::new("")
991            .add_text(" test content")
992            .at_user("user_id")
993            .build();
994        assert_eq!(t4.text, " test content<at user_id=\"user_id\"></at>");
995        let t5 = MessageText::new("").at_all().build();
996        assert_eq!(t5.text, "<at user_id=\"all\">name=\"全体成员\"</at>");
997    }
998
999    #[test]
1000    fn test_message_post() {
1001        use crate::service::im::v1::message::{MessagePost, MessagePostNode};
1002        let post = MessagePost::new("zh_cn")
1003            .title("title")
1004            .append_content(vec![
1005                MessagePostNode::Text(TextNode::new("text")),
1006                MessagePostNode::A(ANode::new("text", "https://www.feishu.cn")),
1007                MessagePostNode::At(AtNode::new("user_id")),
1008                MessagePostNode::Img(ImgNode::new("image_key")),
1009                MessagePostNode::Media(MediaNode::new("file_key", Some("image_key"))),
1010                MessagePostNode::Emotion(EmotionNode::new("SMILE")),
1011            ]);
1012        assert_eq!(post.msg_type(), "post");
1013        assert_eq!(
1014            json!(post),
1015            json!({
1016            "post": {
1017            "zh_cn": {
1018                "title":"title",
1019                "content": [[{"tag":"text","text":"text"},{"tag":"a","text":"text","href":"https://www.feishu.cn"},{"tag":"at","user_id":"user_id"},{"tag":"img","image_key":"image_key"},{"tag":"media","file_key":"file_key","image_key":"image_key"},{"tag":"emotion","emoji_type":"SMILE"}
1020                ]]
1021            }}})
1022        );
1023    }
1024
1025    #[test]
1026    fn test_message_image() {
1027        use crate::service::im::v1::message::MessageImage;
1028        let image = MessageImage {
1029            image_key: "image_key".to_string(),
1030        };
1031        assert_eq!(image.msg_type(), "image");
1032        assert_eq!(
1033            image.content(),
1034            json!({"image_key": "image_key"}).to_string()
1035        );
1036    }
1037}