use crate::api::{
reactions, stars, Bot, Channel, File, FileComment, Message, MessagePinnedItem,
MessageUnpinnedItem, User,
};
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum Event {
Hello,
Message(Box<Message>),
UserTyping { channel: String, user: String },
ChannelMarked { channel: String, ts: String },
ChannelCreated { channel: Box<Channel> },
ChannelJoined { channel: Box<Channel> },
ChannelLeft { channel: String },
ChannelDeleted { channel: String },
ChannelRename { channel: Box<Channel> },
ChannelArchive { channel: String, user: String },
ChannelUnArchive { channel: String, user: String },
ChannelHistoryChanged {
latest: String,
ts: String,
event_ts: String,
},
ImCreated { user: String, channel: Box<Channel> },
ImOpen { user: String, channel: String },
ImClose { user: String, channel: String },
ImMarked { channel: String, ts: String },
ImHistoryChanged {
latest: String,
ts: String,
event_ts: String,
},
Goodbye,
GroupJoined { channel: Box<Channel> },
GroupLeft { channel: Box<Channel> },
GroupOpen { user: String, channel: String },
GroupClose { user: String, channel: String },
GroupArchive { channel: String },
GroupUnArchive { channel: String },
GroupRename { channel: Box<Channel> },
GroupMarked { channel: String, ts: String },
GroupHistoryChanged {
latest: String,
ts: String,
event_ts: String,
},
FileCreated { file: Box<File> },
FileShared { file: Box<File> },
FileUnShared { file: Box<File> },
FilePublic { file: Box<File> },
FilePrivate { file: String },
FileChange { file: Box<File> },
FileDeleted { file_id: String, event_ts: String },
FileCommentAdded {
file: Box<File>,
comment: FileComment,
},
FileCommentEdited {
file: Box<File>,
comment: FileComment,
},
FileCommentDeleted { file: Box<File>, comment: String },
PinAdded {
user: String,
channel_id: String,
item: Box<MessagePinnedItem>,
event_ts: String,
},
PinRemoved {
user: String,
channel_id: String,
item: Box<MessageUnpinnedItem>,
has_pins: bool,
event_ts: String,
},
PresenceChange {
user: Option<String>,
users: Option<Vec<String>>,
presence: String,
},
ManualPresenceChange { presence: String },
PrefChange { name: String, value: String },
UserChange { user: Box<User> },
TeamJoin { user: Box<User> },
StarAdded {
user: String,
item: Box<stars::ListResponseItem>,
event_ts: String,
},
StarRemoved {
user: String,
item: Box<stars::ListResponseItem>,
event_ts: String,
},
ReactionAdded {
user: String,
reaction: String,
item: Box<reactions::ListResponseItem>,
item_user: String,
event_ts: String,
},
ReactionRemoved {
user: String,
reaction: String,
item: Box<reactions::ListResponseItem>,
item_user: String,
event_ts: String,
},
EmojiChanged { event_ts: String },
CommandsChanged { event_ts: String },
TeamPlanChange { plan: String },
TeamPrefChange { name: String, value: bool },
TeamRename { name: String },
TeamDomainChange { url: String, domain: String },
EmailDomainChanged {
email_domain: String,
event_ts: String,
},
BotAdded { bot: Bot },
BotChanged { bot: Bot },
AccountsChanged,
TeamMigrationStarted,
ReconnectUrl { url: String },
MessageSent(MessageSent),
MessageError(MessageError),
DesktopNotification {
#[serde(rename = "avatarImage")]
avatar_image: Option<String>,
channel: Option<String>,
content: Option<String>,
event_ts: Option<String>,
#[serde(rename = "imageUri")]
image_uri: Option<String>,
is_shared: Option<bool>,
#[serde(rename = "launchUri")]
launch_uri: Option<String>,
msg: Option<String>,
#[serde(rename = "ssbFilename")]
ssb_filename: Option<String>,
subtitle: Option<String>,
thread_ts: Option<String>,
title: Option<String>,
},
}
#[derive(Debug, Clone, Deserialize)]
pub struct MessageSent {
pub ok: bool,
pub reply_to: isize,
pub text: String,
pub ts: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MessageError {
pub ok: bool,
pub reply_to: isize,
pub error: MessageErrorDetail,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MessageErrorDetail {
pub code: isize,
pub msg: String,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::{Message, MessageStandard};
#[test]
fn decode_short_standard_message() {
let event: Event = Event::from_json(
r#"{
"type": "message",
"ts": "1234567890.218332",
"user": "U12345678",
"text": "Hello world",
"channel": "C12345678"
}"#,
)
.unwrap();
match event {
Event::Message(message) => match *message {
Message::Standard(MessageStandard {
ref ts,
ref user,
ref text,
..
}) => {
assert_eq!(ts.unwrap().to_string(), "1234567890.218332");
assert_eq!(text.as_ref().unwrap(), "Hello world");
assert_eq!(user.as_ref().unwrap(), "U12345678");
}
_ => panic!("Message decoded into incorrect variant."),
},
_ => panic!("Event decoded into incorrect variant."),
}
}
#[test]
fn decode_sent_ok() {
let event: Event = Event::from_json(
r#"{
"ok": true,
"reply_to": 1,
"ts": "1234567890.218332",
"text": "Hello world"
}"#,
)
.unwrap();
match event {
Event::MessageSent(MessageSent {
reply_to, ts, text, ..
}) => {
assert_eq!(reply_to, 1);
assert_eq!(ts, "1234567890.218332");
assert_eq!(text, "Hello world");
}
_ => panic!("Event decoded into incorrect variant."),
}
}
#[test]
fn decode_sent_not_ok() {
let event: Event = Event::from_json(
r#"{
"ok": false,
"reply_to": 1,
"error": {
"code": 2,
"msg": "message text is missing"
}
}"#,
)
.unwrap();
match event {
Event::MessageError(MessageError {
reply_to,
error: MessageErrorDetail { code, msg, .. },
..
}) => {
assert_eq!(reply_to, 1);
assert_eq!(code, 2);
assert_eq!(msg, "message text is missing");
}
_ => panic!("Event decoded into incorrect variant."),
}
}
#[test]
fn decode_presence_change_event() {
let event: Event = Event::from_json(
r##"{
"type": "presence_change",
"users": ["U024BE7LH", "U012EA2U1"],
"presence": "away"
}"##,
)
.unwrap();
match event {
Event::PresenceChange {
users, presence, ..
} => {
assert_eq!(presence, "away");
match users {
Some(u) => {
assert_eq!(u.len(), 2);
assert_eq!(u[0], "U024BE7LH");
assert_eq!(u[1], "U012EA2U1");
}
_ => panic!("Presence change does not contain users elem"),
}
}
_ => panic!("Event decoded into incorrect variant."),
}
}
#[test]
fn decode_legacy_presence_change_event() {
let event: Event = Event::from_json(
r##"{
"type": "presence_change",
"user": "U024BE7LH",
"presence": "away"
}"##,
)
.unwrap();
match event {
Event::PresenceChange { user, presence, .. } => {
assert_eq!(presence, "away");
match user {
Some(u) => {
assert_eq!(u, "U024BE7LH");
}
_ => panic!("Presence change does not contain users elem"),
}
}
_ => panic!("Event decoded into incorrect variant."),
}
}
#[test]
fn decode_extended_standard_message() {
let event: Event = Event::from_json(
r##"{
"type": "message",
"ts": "1234567890.218332",
"user": "U12345678",
"text": "Hello world",
"channel": "C12345678",
"pinned_to": [ "C12345678" ],
"reactions": [
{
"name": "astonished",
"count": 5,
"users": [ "U12345678", "U87654321" ]
}
],
"edited": {
"user": "U12345678",
"ts": "1234567890.218332"
},
"attachments": [
{
"fallback": "Required plain-text summary of the attachment.",
"color": "#36a64f",
"pretext": "Optional text that appears above the attachment block",
"author_name": "Bobby Tables",
"author_link": "http://flickr.com/bobby/",
"author_icon": "http://flickr.com/icons/bobby.jpg",
"title": "Slack API Documentation",
"title_link": "https://api.slack.com/",
"text": "Optional text that appears within the attachment",
"fields": [
{
"title": "Priority",
"value": "High",
"short": false
}
],
"image_url": "http://my-website.com/path/to/image.jpg",
"thumb_url": "http://example.com/path/to/thumb.png"
}
]
}"##,
)
.unwrap();
match event {
Event::Message(message) => match *message {
Message::Standard(MessageStandard { attachments, .. }) => {
assert_eq!(attachments.unwrap()[0].color.as_ref().unwrap(), "#36a64f");
}
_ => panic!("Message decoded into incorrect variant."),
},
_ => panic!("Event decoded into incorrect variant."),
}
}
}