flawless_slack/
lib.rs

1#![doc = include_str!("../Readme.md")]
2
3pub mod http_client;
4
5use http_client::{HttpClient, HttpClientError};
6
7use serde::Deserialize;
8
9/// Represents a Slack client that interacts with the Slack API using the provided HTTP client.
10pub struct SlackClient<T: HttpClient> {
11    token: String,
12    client: T,
13}
14
15/// Represents errors that can occur during interactions with the Slack API.
16#[derive(Debug)]
17pub enum SlackClientError {
18    /// Error related to HTTP client operations.
19    HttpClientError(HttpClientError),
20    /// Error related to JSON deserialization.
21    JsonError(serde_json::Error),
22    /// Generic error with a descriptive string.
23    Error(String),
24}
25
26impl From<serde_json::Error> for SlackClientError {
27    fn from(err: serde_json::Error) -> Self {
28        SlackClientError::JsonError(err)
29    }
30}
31
32impl From<HttpClientError> for SlackClientError {
33    fn from(err: HttpClientError) -> Self {
34        SlackClientError::HttpClientError(err)
35    }
36}
37
38/// Represents an element in a Slack message block.
39#[derive(Debug, Deserialize)]
40pub struct Element {
41    /// The type of the element.
42    #[serde(rename = "type")]
43    pub element_type: String,
44    /// The text content of the element.
45    pub text: Option<String>,
46}
47
48/// Represents a block in a Slack message.
49#[derive(Debug, Deserialize)]
50pub struct Block {
51    /// The type of the block.
52    #[serde(rename = "type")]
53    pub block_type: String,
54    /// The unique identifier of the block.
55    pub block_id: String,
56    /// Elements within the block.
57    pub elements: Vec<Element>,
58}
59
60/// Represents a Slack message.
61#[derive(Debug, Deserialize)]
62pub struct Message {
63    #[serde(default)]
64    /// The ID of the bot that sent the message.
65    pub bot_id: Option<String>,
66    /// The user who sent the message.
67    pub user: String,
68    /// The text content of the message.
69    pub text: Option<String>,
70    /// The timestamp of the message.
71    pub ts: Option<String>,
72}
73
74/// Represents the response structure when getting messages from Slack.
75#[derive(Debug, Deserialize)]
76struct GetMessagesResponse {
77    /// The list of messages retrieved from Slack.
78    messages: Vec<Message>,
79}
80
81/// Represents a Slack channel.
82#[derive(Debug, Deserialize)]
83pub struct Channel {
84    /// The unique identifier of the channel.
85    id: String,
86    /// The name of the channel.
87    name: String,
88}
89
90#[derive(Debug, Deserialize)]
91struct ListConversationsResponse {
92    channels: Vec<Channel>,
93}
94
95impl<T: HttpClient> SlackClient<T> {
96    /// Creates a new instance of `SlackClient` with the specified token and HTTP client.
97    ///
98    /// # Arguments
99    ///
100    /// * `token` - A String containing the Slack API token.
101    /// * `client` - An HTTP client implementing the `HttpClient` trait.
102    ///
103    /// # Returns
104    ///
105    /// A new instance of `SlackClient`.
106    pub fn new(token: String, client: T) -> SlackClient<T> {
107        SlackClient { token, client }
108    }
109
110    /// Sends a message to a Slack channel.
111    ///
112    /// # Arguments
113    ///
114    /// * `channel` - The name or ID of the channel where the message will be sent.
115    /// * `text` - The text of the message to be sent.
116    ///
117    /// # Returns
118    ///
119    /// Returns `Ok(())` on success, or an error of type `SlackClientError` on failure.
120    pub fn send_message(&self, channel: &str, text: &str) -> Result<(), SlackClientError> {
121        let url = "https://slack.com/api/chat.postMessage";
122
123        let token = format!("Bearer {}", self.token);
124        let params = [("Authorization", token.as_str()), ("Content-Type", "application/json")];
125
126        let payload = format!("{{\"channel\": \"{}\", \"text\": \"{}\"}}", channel, text);
127
128        let result = self.client.post(url, &params, &payload);
129
130        result.map_err(SlackClientError::HttpClientError).map(|response| {
131            if response.contains("\"ok\":false") {
132                Err(SlackClientError::Error(response))
133            } else {
134                Ok(())
135            }
136        })?
137    }
138
139    /// Retrieves messages from a Slack channel.
140    ///
141    /// # Arguments
142    ///
143    /// * `channel` - The name or ID of the channel from which to retrieve messages.
144    ///
145    /// # Returns
146    ///
147    /// Returns a vector of `Message` on success, or an error of type `SlackClientError` on failure.
148    pub fn get_messages(&self, channel: &str) -> Result<Vec<Message>, SlackClientError> {
149        let url = "https://slack.com/api/conversations.history";
150
151        let token = format!("Bearer {}", self.token);
152        let params = [("Authorization", token.as_str()), ("Content-Type", "application/json")];
153
154        let mut channel_id = channel.to_string();
155        if channel.starts_with('#') {
156            let channels = self.get_channels()?;
157            channel_id = channels.iter().find(|c| c.name == channel[1..]).unwrap().id.clone();
158        }
159
160        let queries = [("channel", channel_id.as_str())];
161
162        let response = self.client.get(url, &params, &queries)?;
163
164        let result: GetMessagesResponse = serde_json::from_str(&response)?;
165
166        Ok(result.messages)
167    }
168
169    /// Retrieves a list of Slack channels.
170    ///
171    /// # Returns
172    ///
173    /// Returns a vector of `Channel` on success, or an error of type `SlackClientError` on failure.
174    pub fn get_channels(&self) -> Result<Vec<Channel>, SlackClientError> {
175        let url = "https://slack.com/api/conversations.list";
176
177        let token = format!("Bearer {}", self.token);
178        let params = [("Authorization", token.as_str()), ("Content-Type", "application/json")];
179
180        let response = self.client.get(url, &params, &[])?;
181
182        let result: ListConversationsResponse = serde_json::from_str(&response)?;
183
184        Ok(result.channels)
185    }
186}