Skip to main content

slack/
events.rs

1//
2// Copyright 2015-2016 the slack-rs authors.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17use crate::api::{
18    reactions, stars, Bot, Channel, File, FileComment, Message, MessagePinnedItem,
19    MessageUnpinnedItem, User,
20};
21
22/// Represents Slack [rtm event](https://api.slack.com/rtm) types.
23#[derive(Clone, Debug, Deserialize)]
24#[serde(tag = "type")]
25#[serde(rename_all = "snake_case")]
26pub enum Event {
27    /// Represents the slack [`hello`](https://api.slack.com/events/hello) event.
28    Hello,
29    /// Represents the slack [`message`](https://api.slack.com/events/message)
30    /// event.
31    Message(Box<Message>),
32    /// Represents the slack
33    /// [`user_typing`](https://api.slack.com/events/user_typing) event.
34    UserTyping { channel: String, user: String },
35    /// Represents the slack
36    /// [`channel_marked`](https://api.slack.com/events/channel_marked) event.
37    ChannelMarked { channel: String, ts: String },
38    /// Represents the slack
39    /// [`channel_created`](https://api.slack.com/events/channel_created) event.
40    ChannelCreated { channel: Box<Channel> },
41    /// Represents the slack
42    /// [`channel_joined`](https://api.slack.com/events/channel_joined) event.
43    ChannelJoined { channel: Box<Channel> },
44    /// Represents the slack
45    /// [`channel_left`](https://api.slack.com/events/channel_left) event.
46    ChannelLeft { channel: String },
47    /// Represents the slack
48    /// [`channel_deleted`](https://api.slack.com/events/channel_deleted) event.
49    ChannelDeleted { channel: String },
50    /// Represents the slack
51    /// [`channel_rename`](https://api.slack.com/events/channel_rename) event.
52    ChannelRename { channel: Box<Channel> },
53    /// Represents the slack
54    /// [`channel_archive`](https://api.slack.com/events/channel_archive) event.
55    ChannelArchive { channel: String, user: String },
56    /// Represents the slack
57    /// [`channel_unarchive`](https://api.slack.com/events/channel_unarchive) event.
58    ChannelUnArchive { channel: String, user: String },
59    /// Represents the slack
60    /// [`channel_history_changed`](https://api.slack.com/events/channel_history_changed) event.
61    ChannelHistoryChanged {
62        latest: String,
63        ts: String,
64        event_ts: String,
65    },
66    /// Represents the slack
67    /// [`im_created`](https://api.slack.com/events/im_created) event.
68    ImCreated { user: String, channel: Box<Channel> },
69    /// Represents the slack [`im_open`](https://api.slack.com/events/im_open)
70    /// event.
71    ImOpen { user: String, channel: String },
72    /// Represents the slack [`im_close`](https://api.slack.com/events/im_close)
73    /// event.
74    ImClose { user: String, channel: String },
75    /// Represents the slack [`im_marked`](https://api.slack.com/events/im_marked)
76    /// event.
77    ImMarked { channel: String, ts: String },
78    /// Represents the slack
79    /// [`im_history_changed`](https://api.slack.com/events/im_history_changed)
80    /// event.
81    ImHistoryChanged {
82        latest: String,
83        ts: String,
84        event_ts: String,
85    },
86    /// Represents the slack [`goodbye`](https://api.slack.com/events/goodbye) event.
87    Goodbye,
88    /// Represents the slack
89    /// [`group_joined`](https://api.slack.com/events/group_joined) event.
90    GroupJoined { channel: Box<Channel> },
91    /// Represents the slack
92    /// [`group_left`](https://api.slack.com/events/group_left) event.
93    GroupLeft { channel: Box<Channel> },
94    /// Represents the slack
95    /// [`group_open`](https://api.slack.com/events/group_open) event.
96    GroupOpen { user: String, channel: String },
97    /// Represents the slack
98    /// [`group_close`](https://api.slack.com/events/group_close) event.
99    GroupClose { user: String, channel: String },
100    /// Represents the slack
101    /// [`group_archive`](https://api.slack.com/events/group_archive) event.
102    GroupArchive { channel: String },
103    /// Represents the slack
104    /// [`group_unarchive`](https://api.slack.com/events/group_unarchive) event.
105    GroupUnArchive { channel: String },
106    /// Represents the slack
107    /// [`group_rename`](https://api.slack.com/events/group_rename) event.
108    GroupRename { channel: Box<Channel> },
109    /// Represents the slack
110    /// [`group_marked`](https://api.slack.com/events/group_marked) event.
111    GroupMarked { channel: String, ts: String },
112    /// Represents the slack
113    /// [`group_history_changed`](https://api.slack.com/events/group_history_changed) event.
114    GroupHistoryChanged {
115        latest: String,
116        ts: String,
117        event_ts: String,
118    },
119    /// Represents the slack
120    /// [`file_created`](https://api.slack.com/events/file_created) event.
121    FileCreated { file: Box<File> },
122    /// Represents the slack
123    /// [`file_shared`](https://api.slack.com/events/file_shared) event.
124    FileShared { file: Box<File> },
125    /// Represents the slack
126    /// [`file_unshared`](https://api.slack.com/events/file_unshared) event.
127    FileUnShared { file: Box<File> },
128    /// Represents the slack
129    /// [`file_public`](https://api.slack.com/events/file_public) event.
130    FilePublic { file: Box<File> },
131    /// Represents the slack
132    /// [`file_private`](https://api.slack.com/events/file_private) event.
133    FilePrivate { file: String },
134    /// Represents the slack
135    /// [`file_change`](https://api.slack.com/events/file_change) event.
136    FileChange { file: Box<File> },
137    /// Represents the slack
138    /// [`file_deleted`](https://api.slack.com/events/file_deleted) event.
139    FileDeleted { file_id: String, event_ts: String },
140    /// Represents the slack
141    /// [`file_comment_added`](https://api.slack.com/events/file_comment_added)
142    /// event.
143    FileCommentAdded {
144        file: Box<File>,
145        comment: FileComment,
146    },
147    /// Represents the slack
148    /// [`file_comment_edited`](https://api.slack.com/events/file_comment_edited)
149    /// event.
150    FileCommentEdited {
151        file: Box<File>,
152        comment: FileComment,
153    },
154    /// Represents the slack
155    /// [`file_comment_deleted`](https://api.slack.com/events/file_comment_deleted)
156    /// event.
157    FileCommentDeleted { file: Box<File>, comment: String },
158    /// Represents the slack [`pin_added`](https://api.slack.com/events/pin_added)
159    /// event.
160    PinAdded {
161        user: String,
162        channel_id: String,
163        item: Box<MessagePinnedItem>,
164        event_ts: String,
165    },
166    /// Represents the slack
167    /// [`pin_removed`](https://api.slack.com/events/pin_removed) event.
168    PinRemoved {
169        user: String,
170        channel_id: String,
171        item: Box<MessageUnpinnedItem>,
172        has_pins: bool,
173        event_ts: String,
174    },
175    /// Represents the slack
176    /// [`presence_change`](https://api.slack.com/events/presence_change) event.
177    PresenceChange {
178        user: Option<String>,
179        users: Option<Vec<String>>,
180        presence: String,
181    },
182    /// Represents the slack
183    /// [`manual_presence_change`](https://api.slack.com/events/manual_presence_change) event.
184    ManualPresenceChange { presence: String },
185    /// Represents the slack
186    /// [`pref_change`](https://api.slack.com/events/pref_change) event.
187    PrefChange { name: String, value: String },
188    /// Represents the slack
189    /// [`user_change`](https://api.slack.com/events/user_change) event.
190    UserChange { user: Box<User> },
191    /// Represents the slack [`team_join`](https://api.slack.com/events/team_join)
192    /// event.
193    TeamJoin { user: Box<User> },
194    /// Represents the slack
195    /// [`star_added`](https://api.slack.com/events/star_added) event.
196    StarAdded {
197        user: String,
198        item: Box<stars::ListResponseItem>,
199        event_ts: String,
200    },
201    /// Represents the slack
202    /// [`star_removed`](https://api.slack.com/events/star_removed) event.
203    StarRemoved {
204        user: String,
205        item: Box<stars::ListResponseItem>,
206        event_ts: String,
207    },
208    /// Represents the slack
209    /// [`reaction_added`](https://api.slack.com/events/reaction_added) event.
210    ReactionAdded {
211        user: String,
212        reaction: String,
213        item: Box<reactions::ListResponseItem>,
214        item_user: String,
215        event_ts: String,
216    },
217    /// Represents the slack
218    /// [`reaction_removed`](https://api.slack.com/events/reaction_removed) event.
219    ReactionRemoved {
220        user: String,
221        reaction: String,
222        item: Box<reactions::ListResponseItem>,
223        item_user: String,
224        event_ts: String,
225    },
226    /// Represents the slack
227    /// [`emoji_changed`](https://api.slack.com/events/emoji_changed) event.
228    EmojiChanged { event_ts: String },
229    /// Represents the slack
230    /// [`commands_changed`](https://api.slack.com/events/commands_changed) event.
231    CommandsChanged { event_ts: String },
232    /// Represents the slack
233    /// [`team_plan_change`](https://api.slack.com/events/team_plan_change) event.
234    TeamPlanChange { plan: String },
235    /// Represents the slack
236    /// [`team_pref_change`](https://api.slack.com/events/team_pref_change) event.
237    TeamPrefChange { name: String, value: bool },
238    /// Represents the slack
239    /// [`team_rename`](https://api.slack.com/events/team_rename) event.
240    TeamRename { name: String },
241    /// Represents the slack
242    /// [`team_domain_change`](https://api.slack.com/events/team_domain_change)
243    /// event.
244    TeamDomainChange { url: String, domain: String },
245    /// Represents the slack
246    /// [`email_domain_changed`](https://api.slack.com/events/email_domain_changed) event.
247    EmailDomainChanged {
248        email_domain: String,
249        event_ts: String,
250    },
251    /// Represents the slack [`bot_added`](https://api.slack.com/events/bot_added)
252    /// event.
253    BotAdded { bot: Bot },
254    /// Represents the slack
255    /// [`bot_changed`](https://api.slack.com/events/bot_changed) event.
256    BotChanged { bot: Bot },
257    /// Represents the slack
258    /// [`accounts_changed`](https://api.slack.com/events/accounts_changed) event.
259    AccountsChanged,
260    /// Represents the slack
261    /// [`team_migration_started`](https://api.slack.com/events/team_migration_started) event.
262    TeamMigrationStarted,
263    /// Represents the slack
264    /// [`reconnect_url`](https://api.slack.com/events/reconnect_url)
265    /// event.
266    ReconnectUrl { url: String },
267    /// Represents a confirmation of a message sent
268    MessageSent(MessageSent),
269    /// Represents an error sending a message
270    MessageError(MessageError),
271    /// Represents a request to display a desktop notification
272    DesktopNotification {
273        #[serde(rename = "avatarImage")]
274        avatar_image: Option<String>,
275        channel: Option<String>,
276        content: Option<String>,
277        event_ts: Option<String>,
278        #[serde(rename = "imageUri")]
279        image_uri: Option<String>,
280        is_shared: Option<bool>,
281        #[serde(rename = "launchUri")]
282        launch_uri: Option<String>,
283        msg: Option<String>,
284        #[serde(rename = "ssbFilename")]
285        ssb_filename: Option<String>,
286        subtitle: Option<String>,
287        thread_ts: Option<String>,
288        title: Option<String>,
289    },
290}
291
292/// Represents a confirmation of a message sent
293#[derive(Debug, Clone, Deserialize)]
294pub struct MessageSent {
295    pub ok: bool,
296    pub reply_to: isize,
297    pub text: String,
298    pub ts: String,
299}
300
301/// Represents an error sending a message
302#[derive(Debug, Clone, Deserialize)]
303pub struct MessageError {
304    pub ok: bool,
305    pub reply_to: isize,
306    pub error: MessageErrorDetail,
307}
308
309/// Details of an error sending a message
310#[derive(Debug, Clone, Deserialize)]
311pub struct MessageErrorDetail {
312    pub code: isize,
313    pub msg: String,
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319    use crate::api::{Message, MessageStandard};
320
321    #[test]
322    fn decode_short_standard_message() {
323        let event: Event = Event::from_json(
324            r#"{
325            "type": "message",
326            "ts": "1234567890.218332",
327            "user": "U12345678",
328            "text": "Hello world",
329            "channel": "C12345678"
330        }"#,
331        )
332        .unwrap();
333        match event {
334            Event::Message(message) => match *message {
335                Message::Standard(MessageStandard {
336                    ref ts,
337                    ref user,
338                    ref text,
339                    ..
340                }) => {
341                    assert_eq!(ts.unwrap().to_string(), "1234567890.218332");
342                    assert_eq!(text.as_ref().unwrap(), "Hello world");
343                    assert_eq!(user.as_ref().unwrap(), "U12345678");
344                }
345                _ => panic!("Message decoded into incorrect variant."),
346            },
347            _ => panic!("Event decoded into incorrect variant."),
348        }
349    }
350
351    #[test]
352    fn decode_sent_ok() {
353        let event: Event = Event::from_json(
354            r#"{
355            "ok": true,
356            "reply_to": 1,
357            "ts": "1234567890.218332",
358            "text": "Hello world"
359        }"#,
360        )
361        .unwrap();
362        match event {
363            Event::MessageSent(MessageSent {
364                reply_to, ts, text, ..
365            }) => {
366                assert_eq!(reply_to, 1);
367                assert_eq!(ts, "1234567890.218332");
368                assert_eq!(text, "Hello world");
369            }
370            _ => panic!("Event decoded into incorrect variant."),
371        }
372    }
373
374    #[test]
375    fn decode_sent_not_ok() {
376        let event: Event = Event::from_json(
377            r#"{
378            "ok": false,
379            "reply_to": 1,
380            "error": {
381                "code": 2,
382                "msg": "message text is missing"
383            }
384        }"#,
385        )
386        .unwrap();
387        match event {
388            Event::MessageError(MessageError {
389                reply_to,
390                error: MessageErrorDetail { code, msg, .. },
391                ..
392            }) => {
393                assert_eq!(reply_to, 1);
394                assert_eq!(code, 2);
395                assert_eq!(msg, "message text is missing");
396            }
397            _ => panic!("Event decoded into incorrect variant."),
398        }
399    }
400
401    #[test]
402    fn decode_presence_change_event() {
403        let event: Event = Event::from_json(
404            r##"{
405            "type": "presence_change",
406            "users": ["U024BE7LH", "U012EA2U1"],
407            "presence": "away"
408        }"##,
409        )
410        .unwrap();
411        match event {
412            Event::PresenceChange {
413                users, presence, ..
414            } => {
415                assert_eq!(presence, "away");
416                match users {
417                    Some(u) => {
418                        assert_eq!(u.len(), 2);
419                        assert_eq!(u[0], "U024BE7LH");
420                        assert_eq!(u[1], "U012EA2U1");
421                    }
422                    _ => panic!("Presence change does not contain users elem"),
423                }
424            }
425            _ => panic!("Event decoded into incorrect variant."),
426        }
427    }
428
429    #[test]
430    fn decode_legacy_presence_change_event() {
431        let event: Event = Event::from_json(
432            r##"{
433            "type": "presence_change",
434            "user": "U024BE7LH",
435            "presence": "away"
436        }"##,
437        )
438        .unwrap();
439        match event {
440            Event::PresenceChange { user, presence, .. } => {
441                assert_eq!(presence, "away");
442                match user {
443                    Some(u) => {
444                        assert_eq!(u, "U024BE7LH");
445                    }
446                    _ => panic!("Presence change does not contain users elem"),
447                }
448            }
449            _ => panic!("Event decoded into incorrect variant."),
450        }
451    }
452
453    #[test]
454    fn decode_extended_standard_message() {
455        let event: Event = Event::from_json(
456            r##"{
457            "type": "message",
458            "ts": "1234567890.218332",
459            "user": "U12345678",
460            "text": "Hello world",
461            "channel": "C12345678",
462            "pinned_to": [ "C12345678" ],
463            "reactions": [
464                {
465                    "name": "astonished",
466                    "count": 5,
467                    "users": [ "U12345678", "U87654321" ]
468                }
469            ],
470            "edited": {
471                "user": "U12345678",
472                "ts": "1234567890.218332"
473            },
474            "attachments": [
475                {
476                    "fallback": "Required plain-text summary of the attachment.",
477                    "color": "#36a64f",
478                    "pretext": "Optional text that appears above the attachment block",
479                    "author_name": "Bobby Tables",
480                    "author_link": "http://flickr.com/bobby/",
481                    "author_icon": "http://flickr.com/icons/bobby.jpg",
482                    "title": "Slack API Documentation",
483                    "title_link": "https://api.slack.com/",
484                    "text": "Optional text that appears within the attachment",
485                    "fields": [
486                        {
487                            "title": "Priority",
488                            "value": "High",
489                            "short": false
490                        }
491                    ],
492                    "image_url": "http://my-website.com/path/to/image.jpg",
493                    "thumb_url": "http://example.com/path/to/thumb.png"
494                }
495            ]
496        }"##,
497        )
498        .unwrap();
499        match event {
500            Event::Message(message) => match *message {
501                Message::Standard(MessageStandard { attachments, .. }) => {
502                    assert_eq!(attachments.unwrap()[0].color.as_ref().unwrap(), "#36a64f");
503                }
504                _ => panic!("Message decoded into incorrect variant."),
505            },
506            _ => panic!("Event decoded into incorrect variant."),
507        }
508    }
509}