1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
use std::collections::HashMap;
use std::fmt::Debug;

use serde::{Deserialize, Serialize};
use serde::de::DeserializeOwned;
use serde_json::Value;

pub trait MessageTrait: Serialize + DeserializeOwned {
    const MESSAGE_TYPE: &'static str;

    fn message_type(&self) -> &'static str;
}

/// 文本 text
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct TextMessage {
    text: String,
}

impl TextMessage {
    pub fn new(text: impl ToString) -> Self {
        Self {
            text: text.to_string(),
        }
    }
}

impl MessageTrait for TextMessage {
    const MESSAGE_TYPE: &'static str = "text";

    fn message_type(&self) -> &'static str {
        Self::MESSAGE_TYPE
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, Hash, Eq, PartialEq)]
pub enum Language {
    #[serde(rename = "zh_cn")]
    ZhCn,
    #[serde(rename = "en_us")]
    EnUs,
    #[serde(rename = "ja_jp")]
    JaJp,
    #[serde(rename = "zh_hk")]
    ZhHk,
    #[serde(rename = "zh_tw")]
    ZhTw,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum LanguageContent {
    #[serde(rename = "zh_cn")]
    ZhCn(RichTextContent),
    #[serde(rename = "en_us")]
    EnUs(Value),
}

impl Default for LanguageContent {
    fn default() -> Self {
        Self::ZhCn(RichTextContent::default())
    }
}

/// 自定义机器人用的富文本消息
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct CustomRichTextMessage {
    post: LanguageContent,
}

impl MessageTrait for CustomRichTextMessage {
    const MESSAGE_TYPE: &'static str = "post";

    fn message_type(&self) -> &'static str {
        Self::MESSAGE_TYPE
    }
}

impl CustomRichTextMessage {
    pub fn new(title: impl ToString, content: Vec<Vec<RichTextParagraph>>) -> Self {
        Self {
            post: LanguageContent::ZhCn(RichTextContent {
                title: title.to_string(),
                content,
            }),
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct RichTextContent {
    title: String,
    content: Vec<Vec<RichTextParagraph>>,
}

/// 富文本支持的标签和参数
#[derive(Debug, Serialize, Deserialize, Clone)]
// #[serde(transparent)]
#[serde(tag = "tag")]
pub enum RichTextParagraph {
    #[serde(rename = "text")]
    Text {
        text: String,
        un_escape: Option<bool>,
    },
    #[serde(rename = "a")]
    A { text: String, href: String },
    #[serde(rename = "at")]
    At {
        user_id: String,
        user_name: Option<String>,
    },
    #[serde(rename = "img")]
    Img { image_key: String },
}

impl Default for RichTextParagraph {
    fn default() -> Self {
        Self::Text {
            text: "".to_string(),
            un_escape: None,
        }
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ImageMessage {
    image_key: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ShareChatMessage {
    share_chat_id: String,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum InteractiveMessage {
    TemplateCard(TemplateCard),
    JsonCard(MessageCard),
}

impl Default for InteractiveMessage {
    fn default() -> Self {
        Self::TemplateCard(TemplateCard::default())
    }
}

/// 卡片模板
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct TemplateCard {
    pub template_id: String,
    pub template_variable: HashMap<String, Value>,
}

impl MessageTrait for InteractiveMessage {
    const MESSAGE_TYPE: &'static str = "interactive";

    fn message_type(&self) -> &'static str {
        Self::MESSAGE_TYPE
    }
}

/// 消息卡片
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct MessageCard {
    pub header: Header,
    /// 卡片的正文内容,支持配置多语言。如果需要配置多语言环境时,需要将 elements 替换为 i18n_elements,并且还需要同时对卡片标题进行多语言配置。详情可参见配置多语言内容。
    ///
    /// 在卡片的正文内容中,支持添加以下属性:
    ///
    /// column_set:多列布局,可以横向排布多个列容器,在列内纵向自由组合图文内容,解决多列内容对齐问题,并实现了灵活的图文混排。详情参见多列布局。
    /// div:内容模块,以格式化的文本为主体,支持混合图片、交互组件的富文本内容。详情参见内容模块。
    /// markdown:使用 Markdown 标签构造富文本内容。详情参见 Markdown。
    /// hr:模块之间的分割线。详情参见分割线。
    /// img:用于展示图片的组件。详情参见图片。
    /// note:备注组件,用于展示卡片内的次要信息。详情参见备注。
    /// actions:交互模块。使用交互组件可以实现消息卡片与用户之间的信息交互。详情参见交互模块。
    pub elements: Option<Vec<Elements>>,
    pub i18n_elements: Option<HashMap<Language, Vec<Elements>>>,
    /// 用于配置卡片的属性,包括是否允许被转发、是否为共享卡片等。详情参见配置卡片属性。
    pub config: Option<CardConfig>,
    /// 用于指定卡片整体的跳转链接。详情参见消息卡片跳转链接。
    pub card_link: Option<CarLinkComponent>,
}

/// 标题组件
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Header {
    pub title: Title,
    pub subtitle: Option<Subtitle>,
    pub icon: Option<HeaderIcon>,
    pub template: Option<String>,
    pub text_tag_list: Option<TextTagList>,
    pub i18n_text_tag_list: Option<I18nTextTagList>,
}

/// 配置卡片的主标题信息
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Title {
    tag: String,
    content: Option<String>,
    i18n: Option<Language>,
}

/// 配置卡片的副标题信息。
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Subtitle {
    tag: String,
    content: Option<String>,
    i18n: Option<Language>,
}

/// 该对象用于设置标题的前缀图标。一个卡片仅可配置一个标题图标。
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct HeaderIcon {
    img_key: Option<String>,
}

/// 标题的标签属性。最多可配置 3 个标签内容,如果配置的标签数量超过 3 个,则取前 3 个标签进行展示。标签展示顺序与数组顺序一致。
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct TextTagList {
    tag: Option<String>,
    text: Option<String>,
    color: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct I18nTextTagList {
    language_tags: HashMap<Language, TextTagList>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Elements {
    ColumnSet(Box<ColumnComponent>),
    Div(DivComponent),
    DividerLine(DividerLineComponent),
    Image(ImageComponent),
    Note(NotesComponent),
    Actions(ActionComponent),
}

impl Default for Elements {
    fn default() -> Self {
        Self::ColumnSet(Box::default())
    }
}

/// 内容模块(div)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct DivComponent {
    tag: String,
    text: TextComponent,
    fields: Option<FieldsComponent>,
    extra: Option<DivExtraComponent>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum DivExtraComponent {
    Image(ImageComponent),
    Button(ButtonComponent),
    SelectMenu(SelectMenuComponent),
    Overflow(OverflowComponent),
    DatePicker(DatePickerComponent),
}

impl Default for DivExtraComponent {
    fn default() -> Self {
        Self::Button(ButtonComponent::default())
    }
}

/// 文本组件
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct TextComponent {
    /// 文本元素的标签。两种模式的固定取值:
    ///
    /// plain_text:普通文本内容。
    /// lark_md:支持部分 Markdown 语法的文本内容。关于 Markdown 语法的详细介绍,可参见 Markdown。
    pub tag: String,
    /// 文本内容。
    pub content: String,
    /// 内容显示行数。该字段仅支持 text 的 plain_text 模式,不支持 lark_md 模式。
    pub lines: Option<i32>,
}

/// 链接元素(url)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct UrlComponent {
    /// 默认的跳转链接。
    pub url: Option<String>,
    /// Android 端的跳转链接。
    pub android_url: Option<String>,
    /// iOS 端的跳转链接。
    pub ios_url: Option<String>,
    /// PC 端的跳转链接。
    pub pc_url: Option<String>,
}

/// 消息卡片跳转链接(card_link)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct CarLinkComponent {
    /// 默认的跳转链接。
    pub url: String,
    /// Android 端的跳转链接。
    pub android_url: Option<String>,
    /// iOS 端的跳转链接。
    pub ios_url: Option<String>,
    /// PC 端的跳转链接。
    pub pc_url: Option<String>,
}

/// 按钮(button)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ButtonComponent {
    /// 按钮组件的标识。固定取值:button
    pub tag: String,
    /// 按钮中的文本。基于文本组件的数据结构配置文本内容,详情参见文本组件。
    pub text: TextComponent,
    /// 点击按钮后的跳转链接。该字段与 multi_url 字段不可同时设置。
    pub url: Option<String>,
    /// 基于 url 元素配置多端跳转链接,详情参见url 元素。该字段与 url 字段不可同时设置。
    pub multi_url: Option<UrlComponent>,
    /// 配置按钮样式。取值:
    ///
    /// default:默认样式
    /// primary:强调样式
    /// danger:警示样式
    /// 默认值:default
    pub r#type: Option<String>,
    /// 该字段用于交互组件的回传交互方式,当用户点击交互组件后,会将 value 的值返回给接收回调数据的服务器。后续你可以通过服务器接收的 value 值进行业务处理。
    ///
    /// 该字段值仅支持 key-value 形式的 JSON 结构,且 key 为 String 类型。示例值:
    ///
    /// "value":{
    ///     "key-1":Object-1,
    ///     "key-2":Object-2,
    ///     "key-3":Object-3,
    ///     ······
    /// }
    pub value: Option<Value>,
    /// 设置二次确认弹框。confirm 元素的配置方式可参见 confirm。
    pub confirm: Option<ConfirmComponent>,
}

/// 双列文本
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct FieldsComponent {
    /// 是否并排布局。取值:
    ///
    /// true:并排
    /// false:不并排
    pub is_short: bool,
    /// 国际化文本内容。使用文本组件的数据结构展示内容,详情参见文本组件。
    pub text: TextComponent,
}

/// 图片元素
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ImageComponent {
    /// 元素标签
    pub tag: String,
    /// 图片资源,获取方式:上传图片
    pub img_key: String,
    /// 图片hover说明
    pub alt: TextComponent,
    /// 点击后是否放大图片,缺省为true。在配置 card_link 后可设置为false,使用户点击卡片上的图片也能响应card_link链接跳转
    pub preview: Option<bool>,
}

/// 二次确认(confirm)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ConfirmComponent {
    pub title: TextComponent,
    pub text: TextComponent,
}

/// 列表选择器(selectMenu)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct SelectMenuComponent {
    /// 列表选择器的标签。在两种模式下的固定取值:
    ///
    /// 自定义选项选择器:select_static
    /// 人员选择器:select_person
    pub tag: String,
    /// 选择器的提示文本。基于文本组件的数据结构填写内容,详情参见文本组件
    pub placeholder: Option<TextComponent>,
    /// 为列表选择器配置默认选项。在人员选择器(select_person)模式中不支持设置该字段。
    pub initial_option: Option<String>,
    /// 列表选择器中的选项。基于 option 元素添加选项内容,详情参见 option。
    ///
    /// 在自定义选项选择器中,你可以通过 option 元素的 text 字段配置选项内容。
    /// 在人员选择器中,你可以通过 option 元素的 value 字段设置用户 open_id 来指定人员,如果 value 字段不传值,则系统会自动获取当前回话内的人员列表作为选项。
    /// 注意:不支持设置 option 元素中的 url、multi_url 字段。
    pub options: Vec<OptionComponent>,
    /// 该字段用于交互组件的回传交互方式,当用户点击交互组件的选项后,会将 value 的值返回给接收回调数据的服务器。后续你可以通过服务器接收的 value 值进行业务处理。
    ///
    /// 该字段值仅支持 key-value 形式的 JSON 结构,且 key 为 String 类型。示例值:
    ///
    /// "value":{
    ///     "key-1":Object-1,
    ///     "key-2":Object-2,
    ///     "key-3":Object-3,
    ///     ······
    /// }
    pub value: Option<Value>,
    /// 设置二次确认弹框。confirm 元素的配置方式可参见 confirm。
    pub confirm: Option<ConfirmComponent>,
}

/// 选项元素(option)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct OptionComponent {
    /// 选项显示的内容。
    ///
    /// 基于文本组件的 plain_text 模式设置文本内容,详情参见文本组件。
    /// 当列表选择器(selectMenu)的模式为人员选择器(select_person)时,无需配置 text 字段,除此之外,其他场景中 text 字段必填。
    pub text: Option<TextComponent>,
    /// 回传参数值。当选项选中后,应用会将该值返回至消息卡片请求地址。
    pub value: Option<String>,
    /// 选项的跳转链接,仅支持在折叠按钮组(overflow)中设置。
    ///
    /// 说明:url 和 multi_url 字段必须且仅能填写其中一个。
    pub url: Option<String>,
    /// 选项的跳转链接,仅支持在折叠按钮组(overflow)中设置。支持按操作系统设置不同的链接,参数配置详情参见 链接元素(url)。
    ///
    /// 说明:url 和 multi_url 字段必须且仅能填写其中一个。
    pub multi_url: Option<UrlComponent>,
}

/// 折叠按钮组(overflow)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct OverflowComponent {
    /// 折叠按钮组的标签。固定取值:overflow
    pub tag: String,
    /// 折叠按钮组当中的选项按钮。按钮基于 option 元素进行配置,详情参见 option 元素。
    pub options: Vec<OptionComponent>,
    /// 该字段用于交互组件的回传交互方式,当用户点击交互组件的选项后,会将 value 的值返回给接收回调数据的服务器。后续你可以通过服务器接收的 value 值进行业务处理。
    ///
    /// 该字段值仅支持 key-value 形式的 JSON 结构,且 key 为 String 类型。示例值:
    ///
    /// "value":{
    ///     "key-1":Object-1,
    ///     "key-2":Object-2,
    ///     "key-3":Object-3,
    ///     ······
    /// }
    pub value: Option<Value>,
    /// 设置二次确认弹框。confirm 元素的配置方式可参见 confirm。
    pub confirm: Option<ConfirmComponent>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct DatePickerComponent {
    /// 日期选择器的标签。在三种模式下的固定取值:
    ///
    /// 日期选择器:date_picker
    /// 时间选择器:picker_time
    /// 日期时间选择器:picker_datetime
    pub tag: String,
    /// 设置日期模式的初始值。格式:yyyy-MM-dd
    pub initial_date: Option<String>,
    /// 设置时间模式的初始值。格式:HH:mm
    pub initial_time: Option<String>,
    /// 设置日期时间模式的初始值。格式:yyyy-MM-dd HH:mm
    pub initial_datetime: Option<String>,
    /// 选择器的提示文案,无初始值时必填。只能设置文本组件中的 "tag": "plain_text" 模式以及 content 参数。
    ///
    /// 示例值:
    ///
    /// "placeholder": {
    ///     "tag": "plain_text",
    ///     "content": "请选择日期"
    /// },
    pub placeholder: Option<TextComponent>,
    /// 该字段用于交互组件的回传交互方式,当用户点击交互组件的选项后,会将 value 的值返回给接收回调数据的服务器。后续你可以通过服务器接收的 value 值进行业务处理。
    ///
    /// 该字段值仅支持 key-value 形式的 JSON 结构,且 key 为 String 类型。示例值:
    ///
    /// "value":{
    ///     "key-1":Object-1,
    ///     "key-2":Object-2,
    ///     "key-3":Object-3,
    ///     ······
    /// }
    pub value: Option<Value>,
    /// 设置二次确认弹框。confirm 元素的配置方式可参见 confirm。
    pub confirm: Option<ConfirmComponent>,
}

/// 交互模块(action)
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ActionComponent {
    /// 交互模块的标识。固定取值:action
    pub tag: String,
    /// 添加可交互的组件。支持添加的组件如下,你可以跳转至对应的组件文档查看参数配置详情。
    ///
    /// 按钮(button)
    /// 列表选择器(selectMenu)
    /// 折叠按钮组(overflow)
    /// 日期选择器(datePicker)
    pub actions: Vec<Actions>,
    /// 设置窄屏自适应布局方式。取值:
    ///
    /// bisected:二等分布局,每行两列交互元素。
    /// trisection:三等分布局,每行三列交互元素。
    /// flow:流式布局,元素会按自身大小横向排列并在空间不够的时候折行。
    pub layout: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Actions {
    Button(ButtonComponent),
    SelectMenu(SelectMenuComponent),
    OverFlow(OverflowComponent),
    Datepicker(DatePickerComponent),
}

impl Default for Actions {
    fn default() -> Self {
        Self::Button(ButtonComponent::default())
    }
}

/// 多列布局
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ColumnSetComponent {
    /// 多列布局容器的标识,固定取值:column_set。
    pub tag: String,
    /// 移动端和 PC 端的窄屏幕下,各列的自适应方式。取值:
    ///
    /// none:不做布局上的自适应,在窄屏幕下按比例压缩列宽度。
    /// stretch:列布局变为行布局,且每列(行)宽度强制拉伸为 100%,所有列自适应为上下堆叠排布。
    /// flow:列流式排布(自动换行),当一行展示不下一列时,自动换至下一行展示。
    /// bisect:两列等分布局。
    /// trisect:三列等分布局。
    /// 默认值:none。
    pub flex_mode: String,
    /// 多列布局的背景色样式。取值:
    ///
    /// default:默认的白底样式,dark mode 下为黑底。
    /// grey:灰底样式。
    /// 当存在多列布局的嵌套时,上层多列布局的颜色覆盖下层多列布局的颜色。
    pub background_style: Option<String>,
    ///  多列布局内,各列之间的水平分栏间距。取值:
    ///
    /// default:默认间距。
    /// small:窄间距。
    pub horizontal_spacing: Option<String>,
    /// 多列布局容器内,各个列容器的配置信息。详情参见下文 column 参数说明。
    pub columns: Option<Vec<ColumnComponent>>,
    /// 设置点击布局容器时的交互配置。当前仅支持跳转交互。如果布局容器内有交互组件,则优先响应交互组件定义的交互。
    ///
    /// 示例配置如下,其中支持内嵌 url 元素,该元素说明参见 url。
    ///
    /// {
    /// "action": {
    ///  "multi_url": {
    ///   "url": "https://open.feishu.cn",
    ///   "pc_url": "https://open.feishu.com",
    ///   "ios_url": "https://developer.apple.com/",
    ///   "android_url": "https://developer.android.com/"
    ///   }
    ///  }
    /// }
    pub action: Option<ActionComponent>,
}

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct ColumnComponent {
    /// 列容器标识,固定取值:column。
    pub tag: String,
    /// 列宽度属性。取值:
    ///
    /// auto:列宽度与列内元素宽度一致。
    /// weighted:列宽度按 weight 参数定义的权重分布。
    pub width: Option<String>,
    /// 当 width 取值 weighted 时生效,表示当前列的宽度占比。取值范围:1 ~ 5
    pub weight: Option<u32>,
    /// 列内成员垂直对齐方式。取值:
    ///
    /// top:顶对齐。
    /// center:居中对齐。
    /// bottom:底部对齐。
    pub vertical_align: Option<String>,
    /// 需要在列内展示的卡片元素。
    pub elements: Box<ColumnElements>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ColumnElements {
    Elements(Box<Elements>),
    ColumnSet(ColumnSetComponent),
}

impl Default for ColumnElements {
    fn default() -> Self {
        Self::Elements(Box::default())
    }
}

/// 分割线
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DividerLineComponent {
    /// 分割线模块标识,固定取值:hr。
    pub tag: String,
}

/// 备注模块
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NotesComponent {
    pub tag: String,
    pub elements: Vec<NotesElements>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum NotesElements {
    Text(TextComponent),
    Image(ImageComponent),
}

/// 在消息卡片结构的 config 字段中,支持配置卡片是否可以转发、是否为共享卡片。
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CardConfig {
    /// 是否允许转发卡片。取值:
    ///
    /// true:允许
    /// false:不允许
    /// 默认值为 true,该字段要求飞书客户端的版本为 V3.31.0 及以上。
    ///
    /// 注意:转发后卡片上的回传交互组件将自动置为禁用态,用户不能在转发后的卡片内进行数据交互。
    pub enable_forward: Option<bool>,
    /// 是否为共享卡片。取值:
    ///
    /// true:是共享卡片,更新卡片的内容对所有收到这张卡片的人员可见。
    /// false:非共享卡片,即独享卡片,仅操作用户可见卡片的更新内容。
    /// 默认值为 false。
    pub update_multi: Option<bool>,
}

#[cfg(test)]
mod test {
    use crate::message::CustomRichTextMessage;

    #[test]
    fn test_serialize() {
        let rich_text = CustomRichTextMessage::new("a", vec![]);

        println!("{:?}", serde_json::to_string(&rich_text))
    }
}