Skip to main content

tgbot/types/definitions/rich/
block.rs

1use serde::{Deserialize, Serialize};
2
3use super::text::RichText;
4use crate::types::{Animation, Audio, Integer, Location, PhotoSize, Video, Voice};
5
6/// Represents a block in a rich formatted message.
7#[derive(Debug, Clone, Deserialize, Serialize)]
8#[serde(from = "RawRichBlock", into = "RawRichBlock")]
9pub enum RichBlock {
10    /// An anchor (`<a>` with the `name` attribute).
11    Anchor(String),
12    /// An animation (`<video>`).
13    Animation(RichBlockAnimation),
14    /// An audio (`<audio>`).
15    Audio(RichBlockAudio),
16    /// A block quotation (`<blockquote>`).
17    BlockQuotation(RichBlockBlockQuotation),
18    /// A collage (`<tg-collage>`).
19    Collage(RichBlockCollage),
20    /// An expandable block for details disclosure (`<details>`).
21    Details(RichBlockDetails),
22    /// A divider (`<hr />`).
23    Divider,
24    /// A footer (`<footer>`).
25    Footer(RichText),
26    /// A list (`<ul>` or `<ol>`).
27    List(Vec<RichBlockListItem>),
28    /// A map (`<tg-map>`).
29    Map(RichBlockMap),
30    /// A mathematical expression (`<tg-math-block>`).
31    MathematicalExpression(String),
32    /// A paragraph (`<p>`).
33    Paragraph(RichText),
34    /// A Photo (`<photo>`).
35    Photo(RichBlockPhoto),
36    /// A preformatted text (`<pre>` or `<code>`).
37    Preformatted(RichBlockPreformatted),
38    /// A quotation with centered text (`<aside>`).
39    PullQuotation(RichBlockPullQuotation),
40    /// A section heading (`<h[1-6]>` text, size).
41    SectionHeading(RichText, Integer),
42    /// A slideshow (`<tg-slideshow>`).
43    Slideshow(RichBlockSlideshow),
44    /// A table (`<table>`).
45    Table(RichBlockTable),
46    /// A "Thinking ..." placeholder (`<tg-thinking>`).
47    ///
48    /// The block may be used only in `[crate::types::SendRichMessageDraft]`.
49    Thinking(RichText),
50    /// A video (`<video>`).
51    Video(RichBlockVideo),
52    /// A voice note (`<audio>`).
53    VoiceNote(RichBlockVoiceNote),
54}
55
56impl RichBlock {
57    /// Creates an anchor `RichBlock`.
58    ///
59    /// # Arguments
60    ///
61    /// * `value` - The name of the anchor.
62    pub fn anchor<T>(value: T) -> Self
63    where
64        T: Into<String>,
65    {
66        Self::Anchor(value.into())
67    }
68
69    /// Creates an animation `RichBlock`.
70    ///
71    /// # Arguments
72    ///
73    /// * `value` - The animation.
74    pub fn animation<T>(value: T) -> Self
75    where
76        T: Into<RichBlockAnimation>,
77    {
78        Self::Animation(value.into())
79    }
80
81    /// Creates an audio `RichBlock`.
82    ///
83    /// # Arguments
84    ///
85    /// * `value` - The audio.
86    pub fn audio<T>(value: T) -> Self
87    where
88        T: Into<RichBlockAudio>,
89    {
90        Self::Audio(value.into())
91    }
92
93    /// Creates a block quotation `RichBlock`.
94    ///
95    /// # Arguments
96    ///
97    /// * `value` - The block quotation.
98    pub fn block_quotation<T>(value: T) -> Self
99    where
100        T: Into<RichBlockBlockQuotation>,
101    {
102        Self::BlockQuotation(value.into())
103    }
104
105    /// Creates a collage `RichBlock`.
106    ///
107    /// # Arguments
108    ///
109    /// * `value` - The collage.
110    pub fn collage<T>(value: T) -> Self
111    where
112        T: Into<RichBlockCollage>,
113    {
114        Self::Collage(value.into())
115    }
116
117    /// Creates a details `RichBlock`.
118    ///
119    /// # Arguments
120    ///
121    /// * `value` - The details.
122    pub fn details<T>(value: T) -> Self
123    where
124        T: Into<RichBlockDetails>,
125    {
126        Self::Details(value.into())
127    }
128
129    /// Creates a footer `RichBlock`.
130    ///
131    /// # Arguments
132    ///
133    /// * `value` - The footer.
134    pub fn footer<T>(value: T) -> Self
135    where
136        T: Into<RichText>,
137    {
138        Self::Footer(value.into())
139    }
140
141    /// Creates a list `RichBlock`.
142    ///
143    /// # Arguments
144    ///
145    /// * `value` - The list items.
146    pub fn list<A, B>(value: A) -> Self
147    where
148        A: IntoIterator<Item = B>,
149        B: Into<RichBlockListItem>,
150    {
151        Self::List(value.into_iter().map(Into::into).collect())
152    }
153
154    /// Creates a map `RichBlock`.
155    ///
156    /// # Arguments
157    ///
158    /// * `value` - The map.
159    pub fn map<T>(value: T) -> Self
160    where
161        T: Into<RichBlockMap>,
162    {
163        Self::Map(value.into())
164    }
165
166    /// Creates a mathematical expression `RichBlock`.
167    ///
168    /// # Arguments
169    ///
170    /// * `value` - The expression.
171    pub fn mathematical_expression<T>(value: T) -> Self
172    where
173        T: Into<String>,
174    {
175        Self::MathematicalExpression(value.into())
176    }
177
178    /// Creates a paragraph `RichBlock`.
179    ///
180    /// # Arguments
181    ///
182    /// * `value` - The paragraph.
183    pub fn paragraph<T>(value: T) -> Self
184    where
185        T: Into<RichText>,
186    {
187        Self::Paragraph(value.into())
188    }
189
190    /// Creates a photo `RichBlock`.
191    ///
192    /// # Arguments
193    ///
194    /// * `value` - The photo.
195    pub fn photo<T>(value: T) -> Self
196    where
197        T: Into<RichBlockPhoto>,
198    {
199        Self::Photo(value.into())
200    }
201
202    /// Creates a preformatted `RichBlock`.
203    ///
204    /// # Arguments
205    ///
206    /// * `value` - The text.
207    pub fn preformatted<T>(value: T) -> Self
208    where
209        T: Into<RichBlockPreformatted>,
210    {
211        Self::Preformatted(value.into())
212    }
213
214    /// Creates a pull quotation `RichBlock`.
215    ///
216    /// # Arguments
217    ///
218    /// * `value` - The quotation.
219    pub fn pull_quotation<T>(value: T) -> Self
220    where
221        T: Into<RichBlockPullQuotation>,
222    {
223        Self::PullQuotation(value.into())
224    }
225
226    /// Creates a section heading `RichBlock`.
227    ///
228    /// # Arguments
229    ///
230    /// * `text` - The heading.
231    /// * `size` - Size of the heading.
232    pub fn section_heading<T>(text: T, size: Integer) -> Self
233    where
234        T: Into<RichText>,
235    {
236        Self::SectionHeading(text.into(), size)
237    }
238
239    /// Creates a slideshow `RichBlock`.
240    ///
241    /// # Arguments
242    ///
243    /// * `value` - Thge slideshow.
244    pub fn slideshow<T>(value: T) -> Self
245    where
246        T: Into<RichBlockSlideshow>,
247    {
248        Self::Slideshow(value.into())
249    }
250
251    /// Creates a table `RichBlock`.
252    ///
253    /// # Arguments
254    ///
255    /// * `value` - The table.
256    pub fn table<T>(value: T) -> Self
257    where
258        T: Into<RichBlockTable>,
259    {
260        Self::Table(value.into())
261    }
262
263    /// Creates a thinking `RichBlock`.
264    ///
265    /// # Arguments
266    ///
267    /// * `value` - The content.
268    pub fn thinking<T>(value: T) -> Self
269    where
270        T: Into<RichText>,
271    {
272        Self::Thinking(value.into())
273    }
274
275    /// Creates a video `RichBlock`.
276    ///
277    /// # Arguments
278    ///
279    /// * `value` - The video.
280    pub fn video<T>(value: T) -> Self
281    where
282        T: Into<RichBlockVideo>,
283    {
284        Self::Video(value.into())
285    }
286
287    /// Creates a voice note `RichBlock`.
288    ///
289    /// # Arguments
290    ///
291    /// * `value` - The voice note.
292    pub fn voice_note<T>(value: T) -> Self
293    where
294        T: Into<RichBlockVoiceNote>,
295    {
296        Self::VoiceNote(value.into())
297    }
298}
299
300impl From<&str> for RichBlock {
301    fn from(value: &str) -> Self {
302        Self::paragraph(value)
303    }
304}
305
306impl From<String> for RichBlock {
307    fn from(value: String) -> Self {
308        Self::paragraph(value)
309    }
310}
311
312/// A block with an animation (`<video>`).
313#[serde_with::skip_serializing_none]
314#[derive(Debug, Clone, Deserialize, Serialize)]
315pub struct RichBlockAnimation {
316    /// The animation.
317    pub animation: Animation,
318    /// Caption of the block.
319    pub caption: Option<RichBlockCaption>,
320    /// Whether the media preview is covered by a spoiler animation.
321    pub has_spoiler: Option<bool>,
322}
323
324impl From<Animation> for RichBlockAnimation {
325    fn from(value: Animation) -> Self {
326        Self {
327            animation: value,
328            has_spoiler: None,
329            caption: None,
330        }
331    }
332}
333
334impl<T> From<(Animation, T)> for RichBlockAnimation
335where
336    T: Into<RichBlockCaption>,
337{
338    fn from((animation, caption): (Animation, T)) -> Self {
339        Self::from(animation).with_caption(caption)
340    }
341}
342
343impl<T> From<(Animation, T, bool)> for RichBlockAnimation
344where
345    T: Into<RichBlockCaption>,
346{
347    fn from((animation, caption, has_spoiler): (Animation, T, bool)) -> Self {
348        Self::from(animation)
349            .with_caption(caption)
350            .with_has_spoiler(has_spoiler)
351    }
352}
353
354impl RichBlockAnimation {
355    /// Sets a new caption
356    ///
357    /// # Arguments
358    ///
359    /// * `value` - Caption of the block.
360    pub fn with_caption<T>(mut self, value: T) -> Self
361    where
362        T: Into<RichBlockCaption>,
363    {
364        self.caption = Some(value.into());
365        self
366    }
367
368    /// Sets a new value for the `has_spoiler` flag.
369    ///
370    /// # Arguments
371    ///
372    /// * `value` - Whether the media preview is covered by a spoiler animation.
373    pub fn with_has_spoiler(mut self, value: bool) -> Self {
374        self.has_spoiler = Some(value);
375        self
376    }
377}
378
379/// A block with a music file (`<audio>`)
380#[serde_with::skip_serializing_none]
381#[derive(Debug, Clone, Deserialize, Serialize)]
382pub struct RichBlockAudio {
383    /// The audio.
384    pub audio: Audio,
385    /// Caption of the block.
386    pub caption: Option<RichBlockCaption>,
387}
388
389impl From<Audio> for RichBlockAudio {
390    fn from(value: Audio) -> Self {
391        Self {
392            audio: value,
393            caption: None,
394        }
395    }
396}
397
398impl<T> From<(Audio, T)> for RichBlockAudio
399where
400    T: Into<RichBlockCaption>,
401{
402    fn from((audio, caption): (Audio, T)) -> Self {
403        Self::from(audio).with_caption(caption)
404    }
405}
406
407impl RichBlockAudio {
408    /// Sets a new caption.
409    ///
410    /// # Arguments
411    ///
412    /// * `value` - Caption of the block.
413    pub fn with_caption<T>(mut self, value: T) -> Self
414    where
415        T: Into<RichBlockCaption>,
416    {
417        self.caption = Some(value.into());
418        self
419    }
420}
421
422/// A block quotation (`<blockquote>`).
423#[serde_with::skip_serializing_none]
424#[derive(Debug, Clone, Deserialize, Serialize)]
425pub struct RichBlockBlockQuotation {
426    /// Content of the block.
427    pub blocks: Vec<RichBlock>,
428    /// Credit of the block.
429    pub credit: Option<RichText>,
430}
431
432impl From<RichBlock> for RichBlockBlockQuotation {
433    fn from(value: RichBlock) -> Self {
434        Self {
435            blocks: vec![value],
436            credit: None,
437        }
438    }
439}
440
441impl<T> From<(RichBlock, T)> for RichBlockBlockQuotation
442where
443    T: Into<RichText>,
444{
445    fn from((block, credit): (RichBlock, T)) -> Self {
446        Self {
447            blocks: vec![block],
448            credit: Some(credit.into()),
449        }
450    }
451}
452
453impl<I> FromIterator<I> for RichBlockBlockQuotation
454where
455    I: Into<RichBlock>,
456{
457    fn from_iter<T>(value: T) -> Self
458    where
459        T: IntoIterator<Item = I>,
460    {
461        Self {
462            blocks: value.into_iter().map(Into::into).collect(),
463            credit: None,
464        }
465    }
466}
467
468impl RichBlockBlockQuotation {
469    /// Sets a new credit.
470    ///
471    /// # Arguments
472    ///
473    /// * `value` - Credit of the block.
474    pub fn with_credit<T>(mut self, value: T) -> Self
475    where
476        T: Into<RichText>,
477    {
478        self.credit = Some(value.into());
479        self
480    }
481}
482
483/// Caption of a rich formatted block.
484#[serde_with::skip_serializing_none]
485#[derive(Debug, Clone, Deserialize, Serialize)]
486pub struct RichBlockCaption {
487    /// Block caption.
488    pub text: RichText,
489    /// Block credit which corresponds to the HTML tag `<cite>`.
490    pub credit: Option<RichText>,
491}
492
493impl<T> From<T> for RichBlockCaption
494where
495    T: Into<RichText>,
496{
497    fn from(value: T) -> Self {
498        Self {
499            text: value.into(),
500            credit: None,
501        }
502    }
503}
504
505impl<A, B> From<(A, B)> for RichBlockCaption
506where
507    A: Into<RichText>,
508    B: Into<RichText>,
509{
510    fn from((text, credit): (A, B)) -> Self {
511        Self::from(text).with_credit(credit)
512    }
513}
514
515impl RichBlockCaption {
516    /// Sets a new credit.
517    ///
518    /// # Arguments
519    ///
520    /// * `value` - Block credit.
521    pub fn with_credit<T>(mut self, value: T) -> Self
522    where
523        T: Into<RichText>,
524    {
525        self.credit = Some(value.into());
526        self
527    }
528}
529
530/// A collage (`<tg-collage>`).
531#[serde_with::skip_serializing_none]
532#[derive(Debug, Clone, Deserialize, Serialize)]
533pub struct RichBlockCollage {
534    /// Elements of the collage.
535    pub blocks: Vec<RichBlock>,
536    /// Caption of the block.
537    pub caption: Option<RichBlockCaption>,
538}
539
540impl<A, B> From<A> for RichBlockCollage
541where
542    A: IntoIterator<Item = B>,
543    B: Into<RichBlock>,
544{
545    fn from(value: A) -> Self {
546        value.into_iter().collect()
547    }
548}
549
550impl<I> FromIterator<I> for RichBlockCollage
551where
552    I: Into<RichBlock>,
553{
554    fn from_iter<T>(value: T) -> Self
555    where
556        T: IntoIterator<Item = I>,
557    {
558        Self {
559            blocks: value.into_iter().map(Into::into).collect(),
560            caption: None,
561        }
562    }
563}
564
565impl RichBlockCollage {
566    /// Sets a new caption.
567    ///
568    /// # Arguments
569    ///
570    /// * `value` - Caption of the block.
571    pub fn with_caption<T>(mut self, value: T) -> Self
572    where
573        T: Into<RichBlockCaption>,
574    {
575        self.caption = Some(value.into());
576        self
577    }
578}
579
580/// An expandable block for details disclosure (`<details>`).
581#[serde_with::skip_serializing_none]
582#[derive(Debug, Clone, Deserialize, Serialize)]
583pub struct RichBlockDetails {
584    /// Content of the block.
585    pub blocks: Vec<RichBlock>,
586    /// Always shown summary of the block.
587    pub summary: RichText,
588    /// Whether the content of the block is visible by default.
589    pub is_open: Option<bool>,
590}
591
592impl<A, B, C> From<(A, C)> for RichBlockDetails
593where
594    A: IntoIterator<Item = B>,
595    B: Into<RichBlock>,
596    C: Into<RichText>,
597{
598    fn from((blocks, summary): (A, C)) -> Self {
599        Self::new(blocks, summary)
600    }
601}
602
603impl<A, B, C> From<(A, C, bool)> for RichBlockDetails
604where
605    A: IntoIterator<Item = B>,
606    B: Into<RichBlock>,
607    C: Into<RichText>,
608{
609    fn from((blocks, summary, is_open): (A, C, bool)) -> Self {
610        Self::new(blocks, summary).with_is_open(is_open)
611    }
612}
613
614impl RichBlockDetails {
615    /// Creates a new `RichBlockDetails`.
616    ///
617    /// # Arguments
618    ///
619    /// * `blocks` - Content of the block.
620    /// * `summary` - Always shown summary of the block.
621    pub fn new<A, B, C>(blocks: A, summary: C) -> Self
622    where
623        A: IntoIterator<Item = B>,
624        B: Into<RichBlock>,
625        C: Into<RichText>,
626    {
627        Self {
628            blocks: blocks.into_iter().map(Into::into).collect(),
629            summary: summary.into(),
630            is_open: None,
631        }
632    }
633
634    /// Sets a new value for the `is_open` flag.
635    ///
636    /// # Arguments
637    ///
638    /// * `value` - Whether the content of the block is visible by default.
639    pub fn with_is_open(mut self, value: bool) -> Self {
640        self.is_open = Some(value);
641        self
642    }
643}
644
645/// An item of a list.
646#[serde_with::skip_serializing_none]
647#[derive(Clone, Debug, Deserialize, Serialize)]
648pub struct RichBlockListItem {
649    /// The content of the item.
650    pub blocks: Vec<RichBlock>,
651    /// Label of the item.
652    pub label: String,
653    /// Whether the item has a checkbox.
654    pub has_checkbox: Option<bool>,
655    /// Whether the item has a checked checkbox.
656    pub is_checked: Option<bool>,
657    /// For ordered lists, the type of the item label.
658    #[serde(rename = "type")]
659    pub item_type: Option<RichBlockListItemType>,
660    /// For ordered lists, the numberic value of the item label.
661    pub value: Option<Integer>,
662}
663
664impl<A, B, C> From<(A, B)> for RichBlockListItem
665where
666    A: Into<String>,
667    B: IntoIterator<Item = C>,
668    C: Into<RichBlock>,
669{
670    fn from((label, blocks): (A, B)) -> Self {
671        Self::new(label, blocks)
672    }
673}
674
675impl RichBlockListItem {
676    /// Creates a new `RichBlockListItem`.
677    ///
678    /// # Arguments
679    ///
680    /// * `label` - Label of the item.
681    /// * `blocks` - The content of the item.
682    pub fn new<A, B, C>(label: A, blocks: B) -> Self
683    where
684        A: Into<String>,
685        B: IntoIterator<Item = C>,
686        C: Into<RichBlock>,
687    {
688        Self {
689            blocks: blocks.into_iter().map(Into::into).collect(),
690            label: label.into(),
691            has_checkbox: None,
692            is_checked: None,
693            item_type: None,
694            value: None,
695        }
696    }
697
698    /// Sets a new value for the `has_checkbox` flag.
699    ///
700    /// # Arguments
701    ///
702    /// * `value` - Whether the item has a checkbox.
703    pub fn with_has_checkbox(mut self, value: bool) -> Self {
704        self.has_checkbox = Some(value);
705        self
706    }
707
708    /// Sets a new value for the `is_checked` flag.
709    ///
710    /// # Arguments
711    ///
712    /// * `value` - Whether the item has a checked checkbox.
713    pub fn with_is_checked(mut self, value: bool) -> Self {
714        self.is_checked = Some(value);
715        self
716    }
717
718    /// Sets a new item type.
719    ///
720    /// # Arguments
721    ///
722    /// * `value` - The type of the item label.
723    pub fn with_item_type(mut self, value: RichBlockListItemType) -> Self {
724        self.item_type = Some(value);
725        self
726    }
727
728    /// Sets a new value.
729    ///
730    /// # Arguments
731    ///
732    /// * `value` - The numeric value of the item label.
733    pub fn with_value(mut self, value: Integer) -> Self {
734        self.value = Some(value);
735        self
736    }
737}
738
739/// Represents the type of the item label.
740#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
741pub enum RichBlockListItemType {
742    /// Decimal numbers.
743    #[serde(rename = "1")]
744    Decimal,
745    /// Lowercase letters.
746    #[serde(rename = "a")]
747    LowercaseLetters,
748    /// Lowercase roman numerals.
749    #[serde(rename = "i")]
750    LowercaseRoman,
751    /// Uppercase letters.
752    #[serde(rename = "A")]
753    UppercaseLetters,
754    /// Uppercase roman numerals.
755    #[serde(rename = "I")]
756    UppercaseRoman,
757}
758
759/// Ablock with a map (`<tg-map>`).
760#[serde_with::skip_serializing_none]
761#[derive(Debug, Clone, Deserialize, Serialize)]
762pub struct RichBlockMap {
763    /// Location of the center of the map.
764    pub location: Location,
765    /// Expected height of the map.
766    pub height: Integer,
767    /// Expected width of the map.
768    pub width: Integer,
769    /// Map zoom level; 13-20.
770    pub zoom: Integer,
771    /// Caption of the block.
772    pub caption: Option<RichBlockCaption>,
773}
774
775impl From<Location> for RichBlockMap {
776    fn from(value: Location) -> Self {
777        Self {
778            location: value,
779            zoom: 13,
780            width: 200,
781            height: 200,
782            caption: None,
783        }
784    }
785}
786
787impl RichBlockMap {
788    /// Sets a new caption.
789    ///
790    /// # Arguments
791    ///
792    /// * `value` - Caption of the block.
793    pub fn with_caption<T>(mut self, value: T) -> Self
794    where
795        T: Into<RichBlockCaption>,
796    {
797        self.caption = Some(value.into());
798        self
799    }
800
801    /// Sets a new expected height of the map.
802    ///
803    /// # Arguments
804    ///
805    /// * `value` - The expected height.
806    pub fn with_height(mut self, value: Integer) -> Self {
807        self.height = value;
808        self
809    }
810
811    /// Sets a new expected width of the map.
812    ///
813    /// # Arguments
814    ///
815    /// * `value` - The expected width.
816    pub fn with_width(mut self, value: Integer) -> Self {
817        self.width = value;
818        self
819    }
820
821    /// Sets a new zoom level.
822    ///
823    /// # Arguments
824    ///
825    /// * `value` - Map zoom level; 13-20.
826    pub fn with_zoom(mut self, value: Integer) -> Self {
827        self.zoom = value;
828        self
829    }
830}
831
832/// A block with a photo (`<photo>`).
833#[serde_with::skip_serializing_none]
834#[derive(Debug, Clone, Deserialize, Serialize)]
835pub struct RichBlockPhoto {
836    /// Available sizes of the photo.
837    pub photo: Vec<PhotoSize>,
838    /// Caption of the block.
839    pub caption: Option<RichBlockCaption>,
840    /// Whether the media preview is covered by a spoiler animation.
841    pub has_spoiler: Option<bool>,
842}
843
844impl<T> From<T> for RichBlockPhoto
845where
846    T: IntoIterator<Item = PhotoSize>,
847{
848    fn from(value: T) -> Self {
849        Self::from_iter(value)
850    }
851}
852
853impl FromIterator<PhotoSize> for RichBlockPhoto {
854    fn from_iter<T>(value: T) -> Self
855    where
856        T: IntoIterator<Item = PhotoSize>,
857    {
858        Self {
859            photo: value.into_iter().collect(),
860            caption: None,
861            has_spoiler: None,
862        }
863    }
864}
865
866impl RichBlockPhoto {
867    /// Sets a new caption.
868    ///
869    ///  # Arguments
870    ///
871    /// * `value` - Caption of the block.
872    pub fn with_caption<T>(mut self, value: T) -> Self
873    where
874        T: Into<RichBlockCaption>,
875    {
876        self.caption = Some(value.into());
877        self
878    }
879
880    /// Sets a new value for the `has_spoiler` flag.
881    ///
882    /// # Arguments
883    ///
884    /// * `value` - Whether the media preview is covered by a spoiler animation.
885    pub fn with_has_spoiler(mut self, value: bool) -> Self {
886        self.has_spoiler = Some(value);
887        self
888    }
889}
890
891/// A preformatted text block (`<pre>` or `<code>`).
892#[serde_with::skip_serializing_none]
893#[derive(Debug, Clone, Deserialize, Serialize)]
894pub struct RichBlockPreformatted {
895    /// Text of the block.
896    pub text: RichText,
897    /// The programming language of the text.
898    pub language: Option<String>,
899}
900
901impl<T> From<T> for RichBlockPreformatted
902where
903    T: Into<RichText>,
904{
905    fn from(value: T) -> Self {
906        Self {
907            text: value.into(),
908            language: None,
909        }
910    }
911}
912
913impl<A, B> From<(A, B)> for RichBlockPreformatted
914where
915    A: Into<String>,
916    B: Into<RichText>,
917{
918    fn from((language, text): (A, B)) -> Self {
919        Self {
920            text: text.into(),
921            language: Some(language.into()),
922        }
923    }
924}
925
926/// A quotation with centered text (`<aside>`).
927#[serde_with::skip_serializing_none]
928#[derive(Debug, Clone, Deserialize, Serialize)]
929pub struct RichBlockPullQuotation {
930    /// Text of the block.
931    pub text: RichText,
932    /// Credit of the block.
933    pub credit: Option<RichText>,
934}
935
936impl<T> From<T> for RichBlockPullQuotation
937where
938    T: Into<RichText>,
939{
940    fn from(value: T) -> Self {
941        Self {
942            text: value.into(),
943            credit: None,
944        }
945    }
946}
947
948impl<A, B> From<(A, B)> for RichBlockPullQuotation
949where
950    A: Into<RichText>,
951    B: Into<RichText>,
952{
953    fn from((text, credit): (A, B)) -> Self {
954        Self {
955            text: text.into(),
956            credit: Some(credit.into()),
957        }
958    }
959}
960
961/// A slideshow (`<tg-slideshow>`).
962#[serde_with::skip_serializing_none]
963#[derive(Debug, Clone, Deserialize, Serialize)]
964pub struct RichBlockSlideshow {
965    /// Elements of the slideshow.
966    pub blocks: Vec<RichBlock>,
967    /// Caption of the block.
968    pub caption: Option<RichBlockCaption>,
969}
970
971impl<A, B> From<A> for RichBlockSlideshow
972where
973    A: IntoIterator<Item = B>,
974    B: Into<RichBlock>,
975{
976    fn from(value: A) -> Self {
977        value.into_iter().collect()
978    }
979}
980
981impl<I> FromIterator<I> for RichBlockSlideshow
982where
983    I: Into<RichBlock>,
984{
985    fn from_iter<T>(value: T) -> Self
986    where
987        T: IntoIterator<Item = I>,
988    {
989        Self {
990            blocks: value.into_iter().map(Into::into).collect(),
991            caption: None,
992        }
993    }
994}
995
996impl RichBlockSlideshow {
997    /// Sets a new caption
998    ///
999    /// # Arguments
1000    ///
1001    /// * `value` - Caption of the block.
1002    pub fn with_caption<T>(mut self, value: T) -> Self
1003    where
1004        T: Into<RichBlockCaption>,
1005    {
1006        self.caption = Some(value.into());
1007        self
1008    }
1009}
1010
1011/// A table (`<table>`).
1012#[serde_with::skip_serializing_none]
1013#[derive(Debug, Clone, Deserialize, Serialize)]
1014pub struct RichBlockTable {
1015    /// Cells of the table.
1016    pub cells: Vec<Vec<RichBlockTableCell>>,
1017    /// Caption of the table.
1018    pub caption: Option<RichText>,
1019    /// Whether the table has borders.
1020    pub is_bordered: Option<bool>,
1021    /// Whether the table is striped.
1022    pub is_striped: Option<bool>,
1023}
1024
1025impl<A, B, C> From<A> for RichBlockTable
1026where
1027    A: IntoIterator<Item = B>,
1028    B: IntoIterator<Item = C>,
1029    C: Into<RichBlockTableCell>,
1030{
1031    fn from(value: A) -> Self {
1032        value.into_iter().collect()
1033    }
1034}
1035
1036impl<A, B> FromIterator<A> for RichBlockTable
1037where
1038    A: IntoIterator<Item = B>,
1039    B: Into<RichBlockTableCell>,
1040{
1041    fn from_iter<T>(value: T) -> Self
1042    where
1043        T: IntoIterator<Item = A>,
1044    {
1045        Self {
1046            cells: value
1047                .into_iter()
1048                .map(|x| x.into_iter().map(Into::into).collect())
1049                .collect(),
1050            caption: None,
1051            is_bordered: None,
1052            is_striped: None,
1053        }
1054    }
1055}
1056
1057impl RichBlockTable {
1058    /// Sets a new caption.
1059    ///
1060    /// # Arguments
1061    ///
1062    /// * `value` - Caption of the table.
1063    pub fn with_caption<T>(mut self, value: T) -> Self
1064    where
1065        T: Into<RichText>,
1066    {
1067        self.caption = Some(value.into());
1068        self
1069    }
1070
1071    /// Sets a new value for the `is_bordered` flag.
1072    ///
1073    /// # Arguments
1074    ///
1075    /// * `value` - Whether the table has borders.
1076    pub fn with_is_bordered(mut self, value: bool) -> Self {
1077        self.is_bordered = Some(value);
1078        self
1079    }
1080
1081    /// Sets a new value for the `is_striped` flag.
1082    ///
1083    /// # Arguments
1084    ///
1085    /// * `value` - Whether the table is striped.
1086    pub fn with_is_striped(mut self, value: bool) -> Self {
1087        self.is_striped = Some(value);
1088        self
1089    }
1090}
1091
1092/// Cell in a table.
1093#[serde_with::skip_serializing_none]
1094#[derive(Debug, Default, Clone, Deserialize, Serialize)]
1095pub struct RichBlockTableCell {
1096    /// Horizontal cell content alignment.
1097    pub align: RichBlockTableCellAlign,
1098    /// Vertical cell content alignment.
1099    pub valign: RichBlockTableCellValign,
1100    /// The number of columns the cell spans if it is bigger than 1.
1101    pub colspan: Option<Integer>,
1102    /// Whether the cell is a header cell.
1103    pub is_header: Option<bool>,
1104    /// The number of rows the cell spans if it is bigger than 1.
1105    pub rowspan: Option<Integer>,
1106    /// Text in the cell.
1107    ///
1108    /// If omitted, the the cell is invisible.
1109    pub text: Option<RichText>,
1110}
1111
1112impl<T> From<T> for RichBlockTableCell
1113where
1114    T: Into<RichText>,
1115{
1116    fn from(value: T) -> Self {
1117        Self::default().with_text(value)
1118    }
1119}
1120
1121impl RichBlockTableCell {
1122    /// Sets a new horizontal alignment.
1123    ///
1124    /// # Arguments
1125    ///
1126    /// * `value` - The horizontal alignment.
1127    pub fn with_align(mut self, value: RichBlockTableCellAlign) -> Self {
1128        self.align = value;
1129        self
1130    }
1131
1132    /// Sets a new colspan.
1133    ///
1134    /// # Arguments
1135    ///
1136    /// * `value` - The number of columns the cell span.
1137    pub fn with_colspan(mut self, value: Integer) -> Self {
1138        self.colspan = Some(value);
1139        self
1140    }
1141
1142    /// Sets a new value for the `is_header` flag.
1143    ///
1144    /// # Arguments
1145    ///
1146    /// * `value` - Whether the cell is a header cell.
1147    pub fn with_is_header(mut self, value: bool) -> Self {
1148        self.is_header = Some(value);
1149        self
1150    }
1151
1152    /// Sets a new rowspan.
1153    ///
1154    /// # Arguments
1155    ///
1156    /// * `value` - The number of rows the cell spans.
1157    pub fn with_rowspan(mut self, value: Integer) -> Self {
1158        self.rowspan = Some(value);
1159        self
1160    }
1161
1162    /// Sets a new text.
1163    ///
1164    /// # Arguments
1165    ///
1166    /// * `value` - The text.
1167    pub fn with_text<T>(mut self, value: T) -> Self
1168    where
1169        T: Into<RichText>,
1170    {
1171        self.text = Some(value.into());
1172        self
1173    }
1174
1175    /// Sets a new vertical alignment.
1176    ///
1177    /// # Arguments
1178    ///
1179    /// * `value` - The vertical alignment.
1180    pub fn with_valign(mut self, value: RichBlockTableCellValign) -> Self {
1181        self.valign = value;
1182        self
1183    }
1184}
1185
1186/// Horizontall cell content alignment.
1187#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
1188#[serde(rename_all = "snake_case")]
1189pub enum RichBlockTableCellAlign {
1190    /// Left.
1191    #[default]
1192    Left,
1193    /// Center.
1194    Center,
1195    /// Right.
1196    Right,
1197}
1198
1199/// Vertical cell content alignment.
1200#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
1201#[serde(rename_all = "snake_case")]
1202pub enum RichBlockTableCellValign {
1203    /// Top.
1204    Top,
1205    /// Middle.
1206    #[default]
1207    Middle,
1208    /// Bottom.
1209    Bottom,
1210}
1211
1212/// A block with a video (`<video`).
1213#[serde_with::skip_serializing_none]
1214#[derive(Debug, Clone, Deserialize, Serialize)]
1215pub struct RichBlockVideo {
1216    /// The video.
1217    pub video: Video,
1218    /// Caption of the block.
1219    pub caption: Option<RichBlockCaption>,
1220    /// Whether the media preview is covered by a spoiler animation.
1221    pub has_spoiler: Option<bool>,
1222}
1223
1224impl From<Video> for RichBlockVideo {
1225    fn from(value: Video) -> Self {
1226        Self {
1227            video: value,
1228            caption: None,
1229            has_spoiler: None,
1230        }
1231    }
1232}
1233
1234impl<T> From<(Video, T)> for RichBlockVideo
1235where
1236    T: Into<RichBlockCaption>,
1237{
1238    fn from((video, caption): (Video, T)) -> Self {
1239        Self {
1240            video,
1241            caption: Some(caption.into()),
1242            has_spoiler: None,
1243        }
1244    }
1245}
1246
1247impl<T> From<(Video, T, bool)> for RichBlockVideo
1248where
1249    T: Into<RichBlockCaption>,
1250{
1251    fn from((video, caption, has_spoiler): (Video, T, bool)) -> Self {
1252        Self {
1253            video,
1254            caption: Some(caption.into()),
1255            has_spoiler: Some(has_spoiler),
1256        }
1257    }
1258}
1259
1260/// A block with a voice note (`<audio>`).
1261#[serde_with::skip_serializing_none]
1262#[derive(Debug, Clone, Deserialize, Serialize)]
1263pub struct RichBlockVoiceNote {
1264    /// The voice note.
1265    pub voice_note: Voice,
1266    /// Caption of the block.
1267    pub caption: Option<RichBlockCaption>,
1268}
1269
1270impl From<Voice> for RichBlockVoiceNote {
1271    fn from(value: Voice) -> Self {
1272        Self {
1273            voice_note: value,
1274            caption: None,
1275        }
1276    }
1277}
1278
1279impl<T> From<(Voice, T)> for RichBlockVoiceNote
1280where
1281    T: Into<RichBlockCaption>,
1282{
1283    fn from((voice_note, caption): (Voice, T)) -> Self {
1284        Self {
1285            voice_note,
1286            caption: Some(caption.into()),
1287        }
1288    }
1289}
1290
1291#[serde_with::skip_serializing_none]
1292#[derive(Debug, Clone, Deserialize, Serialize)]
1293#[serde(rename_all = "snake_case", tag = "type")]
1294enum RawRichBlock {
1295    Anchor { name: String },
1296    Animation(RichBlockAnimation),
1297    Audio(RichBlockAudio),
1298    BlockQuotation(RichBlockBlockQuotation),
1299    Collage(RichBlockCollage),
1300    Details(RichBlockDetails),
1301    Divider,
1302    Footer { text: RichText },
1303    List { items: Vec<RichBlockListItem> },
1304    Map(RichBlockMap),
1305    MathematicalExpression { expression: String },
1306    Paragraph { text: RichText },
1307    Photo(RichBlockPhoto),
1308    Preformatted(RichBlockPreformatted),
1309    PullQuotation(RichBlockPullQuotation),
1310    SectionHeading { text: RichText, size: Integer },
1311    Slideshow(RichBlockSlideshow),
1312    Table(RichBlockTable),
1313    Thinking { text: RichText },
1314    Video(RichBlockVideo),
1315    VoiceNote(RichBlockVoiceNote),
1316}
1317
1318impl From<RichBlock> for RawRichBlock {
1319    fn from(value: RichBlock) -> Self {
1320        match value {
1321            RichBlock::Anchor(name) => Self::Anchor { name },
1322            RichBlock::Animation(value) => Self::Animation(value),
1323            RichBlock::Audio(value) => Self::Audio(value),
1324            RichBlock::BlockQuotation(value) => Self::BlockQuotation(value),
1325            RichBlock::Collage(value) => Self::Collage(value),
1326            RichBlock::Details(value) => Self::Details(value),
1327            RichBlock::Divider => Self::Divider,
1328            RichBlock::Footer(text) => Self::Footer { text },
1329            RichBlock::List(items) => Self::List { items },
1330            RichBlock::Map(value) => Self::Map(value),
1331            RichBlock::MathematicalExpression(expression) => Self::MathematicalExpression { expression },
1332            RichBlock::Paragraph(text) => Self::Paragraph { text },
1333            RichBlock::Photo(value) => Self::Photo(value),
1334            RichBlock::Preformatted(value) => Self::Preformatted(value),
1335            RichBlock::PullQuotation(value) => Self::PullQuotation(value),
1336            RichBlock::SectionHeading(text, size) => Self::SectionHeading { text, size },
1337            RichBlock::Slideshow(value) => Self::Slideshow(value),
1338            RichBlock::Table(value) => Self::Table(value),
1339            RichBlock::Thinking(text) => Self::Thinking { text },
1340            RichBlock::Video(value) => Self::Video(value),
1341            RichBlock::VoiceNote(value) => Self::VoiceNote(value),
1342        }
1343    }
1344}
1345
1346impl From<RawRichBlock> for RichBlock {
1347    fn from(value: RawRichBlock) -> Self {
1348        match value {
1349            RawRichBlock::Anchor { name } => Self::Anchor(name),
1350            RawRichBlock::Animation(value) => Self::Animation(value),
1351            RawRichBlock::Audio(value) => Self::Audio(value),
1352            RawRichBlock::BlockQuotation(value) => Self::BlockQuotation(value),
1353            RawRichBlock::Collage(value) => Self::Collage(value),
1354            RawRichBlock::Details(value) => Self::Details(value),
1355            RawRichBlock::Divider => Self::Divider,
1356            RawRichBlock::Footer { text } => Self::Footer(text),
1357            RawRichBlock::List { items } => Self::List(items),
1358            RawRichBlock::Map(value) => Self::Map(value),
1359            RawRichBlock::MathematicalExpression { expression } => Self::MathematicalExpression(expression),
1360            RawRichBlock::Paragraph { text } => Self::Paragraph(text),
1361            RawRichBlock::Photo(value) => Self::Photo(value),
1362            RawRichBlock::Preformatted(value) => Self::Preformatted(value),
1363            RawRichBlock::PullQuotation(value) => Self::PullQuotation(value),
1364            RawRichBlock::SectionHeading { text, size } => Self::SectionHeading(text, size),
1365            RawRichBlock::Slideshow(value) => Self::Slideshow(value),
1366            RawRichBlock::Table(value) => Self::Table(value),
1367            RawRichBlock::Thinking { text } => Self::Thinking(text),
1368            RawRichBlock::Video(value) => Self::Video(value),
1369            RawRichBlock::VoiceNote(value) => Self::VoiceNote(value),
1370        }
1371    }
1372}