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}