slack.rs

  1use serde::{Deserialize, Serialize};
  2
  3/// https://api.slack.com/reference/messaging/payload
  4#[derive(Default, Clone, Serialize, Deserialize)]
  5pub struct WebhookBody {
  6    text: String,
  7    #[serde(skip_serializing_if = "Vec::is_empty")]
  8    blocks: Vec<Block>,
  9    #[serde(skip_serializing_if = "Option::is_none")]
 10    thread_ts: Option<String>,
 11    #[serde(skip_serializing_if = "Option::is_none")]
 12    mrkdwn: Option<bool>,
 13}
 14
 15impl WebhookBody {
 16    pub fn new(f: impl FnOnce(Self) -> Self) -> Self {
 17        f(Self::default())
 18    }
 19
 20    pub fn add_section(mut self, build: impl FnOnce(Section) -> Section) -> Self {
 21        self.blocks.push(Block::Section(build(Section::default())));
 22        self
 23    }
 24
 25    pub fn add_rich_text(mut self, build: impl FnOnce(RichText) -> RichText) -> Self {
 26        self.blocks
 27            .push(Block::RichText(build(RichText::default())));
 28        self
 29    }
 30}
 31
 32#[derive(Clone, Serialize, Deserialize)]
 33#[serde(tag = "type")]
 34/// https://api.slack.com/reference/block-kit/blocks
 35pub enum Block {
 36    #[serde(rename = "section")]
 37    Section(Section),
 38    #[serde(rename = "rich_text")]
 39    RichText(RichText),
 40    // .... etc.
 41}
 42
 43/// https://api.slack.com/reference/block-kit/blocks#section
 44#[derive(Default, Clone, Serialize, Deserialize)]
 45pub struct Section {
 46    #[serde(skip_serializing_if = "Option::is_none")]
 47    text: Option<Text>,
 48    #[serde(skip_serializing_if = "Vec::is_empty")]
 49    fields: Vec<Text>,
 50    // fields, accessories...
 51}
 52
 53impl Section {
 54    pub fn text(mut self, text: Text) -> Self {
 55        self.text = Some(text);
 56        self
 57    }
 58
 59    pub fn add_field(mut self, field: Text) -> Self {
 60        self.fields.push(field);
 61        self
 62    }
 63}
 64
 65/// https://api.slack.com/reference/block-kit/composition-objects#text
 66#[derive(Clone, Serialize, Deserialize)]
 67#[serde(tag = "type")]
 68pub enum Text {
 69    #[serde(rename = "plain_text")]
 70    PlainText { text: String, emoji: bool },
 71    #[serde(rename = "mrkdwn")]
 72    Markdown { text: String, verbatim: bool },
 73}
 74
 75impl Text {
 76    pub fn plain(s: String) -> Self {
 77        Self::PlainText {
 78            text: s,
 79            emoji: true,
 80        }
 81    }
 82
 83    pub fn markdown(s: String) -> Self {
 84        Self::Markdown {
 85            text: s,
 86            verbatim: false,
 87        }
 88    }
 89}
 90
 91#[derive(Default, Clone, Serialize, Deserialize)]
 92pub struct RichText {
 93    elements: Vec<RichTextObject>,
 94}
 95
 96impl RichText {
 97    pub fn new(f: impl FnOnce(Self) -> Self) -> Self {
 98        f(Self::default())
 99    }
100
101    pub fn add_preformatted(
102        mut self,
103        build: impl FnOnce(RichTextPreformatted) -> RichTextPreformatted,
104    ) -> Self {
105        self.elements.push(RichTextObject::Preformatted(build(
106            RichTextPreformatted::default(),
107        )));
108        self
109    }
110}
111
112/// https://api.slack.com/reference/block-kit/blocks#rich_text
113#[derive(Clone, Serialize, Deserialize)]
114#[serde(tag = "type")]
115pub enum RichTextObject {
116    #[serde(rename = "rich_text_preformatted")]
117    Preformatted(RichTextPreformatted),
118    // etc.
119}
120
121/// https://api.slack.com/reference/block-kit/blocks#rich_text_preformatted
122#[derive(Clone, Default, Serialize, Deserialize)]
123pub struct RichTextPreformatted {
124    #[serde(skip_serializing_if = "Vec::is_empty")]
125    elements: Vec<RichTextElement>,
126    #[serde(skip_serializing_if = "Option::is_none")]
127    border: Option<u8>,
128}
129
130impl RichTextPreformatted {
131    pub fn add_text(mut self, text: String) -> Self {
132        self.elements.push(RichTextElement::Text { text });
133        self
134    }
135}
136
137/// https://api.slack.com/reference/block-kit/blocks#element-types
138#[derive(Clone, Serialize, Deserialize)]
139#[serde(tag = "type")]
140pub enum RichTextElement {
141    #[serde(rename = "text")]
142    Text { text: String },
143    // etc.
144}