Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

[Slack] 👻 Add Ephemeral Message Support #958

Merged
merged 2 commits into from
Aug 29, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions docs/readme-slack.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,77 @@ bot.startRTM(function(err, bot, payload) {
setTimeout(bot.destroy.bind(bot), 10000)
```

### Ephemeral Messages

Using the Web API, messages can be sent to a user "ephemerally" which will only show to them, and no one else. Learn more about ephemeral messages at the [Slack API Documentation](https://api.slack.com/methods/chat.postEphemeral). When sending an ephemeral message, you must specify a valid `user` and `channel` id. Valid meaning the specified user is in the specified channel. Currently, updating interactive messages are not supported by ephemeral messages, but you can still create them and listen to the events. They will not have a reference to the original message, however.

#### Ephemeral Message Authorship

Slack allows you to post an ephemeral message as either the user you have an auth token for (would be your bot user in most cases), or as your app. The display name and icon will be different accordingly. The default is set to `as_user: true` for all functions except `bot.sendEphemeral()`. Override the default of any message by explicitly setting `as_user` on the outgoing message.


#### bot.whisper()
| Argument | Description
|--- |---
| src | Message object to reply to, **src.user is required**
| message | _String_ or _Object_ Outgoing response
| callback | _Optional_ Callback in the form function(err,response) { ... }

Functions the same as `bot.reply()` but sends the message ephemerally. Note, src message must have a user field set in addition to a channel


`bot.whisper()` defaults to `as_user: true` unless otherwise specified on the message object. This means messages will be attributed to your bot user, or whichever user who's token you are making the API call with.


#### bot.sendEphemeral()
| Argument | Description
|--- |---
| message | _String_ or _Object_ Outgoing response, **message.user is required**
| callback | _Optional_ Callback in the form function(err,response) { ... }

To send a spontaneous ephemeral message (which slack discourages you from doing) use `bot.sendEphemeral` which functions similarly as `bot.say()` and `bot.send()`


```javascript
controller.hears(['^spooky$'], function(bot, message) {
// default behavior, post as the bot user
bot.whisper(message, 'Booo! This message is ephemeral and private to you')
})

controller.hears(['^spaghetti$'], function(bot, message) {
// attribute slack message to app, not bot user
bot.whisper(message, {as_user: false, text: 'I may be a humble App, but I too love a good noodle'})
})

controller.on('custom_triggered_event', function(bot, trigger) {
// pretend to get a list of user ids from out analytics api...
fetch('users/champions', function(err, userArr) {
userArr.map(function(user) {
// iterate over every user and send them a message
bot.sendEphemeral({
channel: 'general',
user: user.id,
text: "Pssst! You my friend, are a true Bot Champion!"})
})
})
})
```

#### Ephemeral Conversations

To reply to a user ephemerally in a conversation, pass a message object to `convo.say()` `convo.sayFirst()` `convo.ask()` `convo.addMessage()` `convo.addQuestion()` that sets ephemeral to true.

When using interactive message attachments with ephemeral messaging, Slack does not send the original message as part of the payload. With non-ephemeral interactive messages Slack sends a copy of the original message for you to edit and send back. To respond with an edited message when updating ephemeral interactive messages, you must construct a new message to send as the response, containing at least a text field.

```javascript
controller.hears(['^tell me a secret$'], 'direct_mention, ambient, mention', function(bot, message) {
bot.startConversation(message, function(err, convo) {
convo.say('Better take this private...')
convo.say({ ephemeral: true, text: 'These violent delights have violent ends' })
})
})

```

### Slack Threads

Expand Down
6 changes: 6 additions & 0 deletions lib/Slack_web_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ module.exports = function(bot, config) {
'channels.unarchive',
'chat.delete',
'chat.postMessage',
'chat.postEphemeral',
'chat.update',
'chat.unfurl',
'dnd.endDnd',
Expand Down Expand Up @@ -177,6 +178,11 @@ module.exports = function(bot, config) {
slack_api.callAPI('chat.postMessage', options, cb);
};

slack_api.chat.postEphemeral = function(options, cb) {
sanitizeOptions(options);
slack_api.callAPI('chat.postEphemeral', options, cb);
};

slack_api.chat.update = function(options, cb) {
sanitizeOptions(options);
slack_api.callAPI('chat.update', options, cb);
Expand Down
59 changes: 58 additions & 1 deletion lib/Slackbot_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ var slackWebApi = require(__dirname + '/Slack_web_api.js');
var HttpsProxyAgent = require('https-proxy-agent');
var Back = require('back');


module.exports = function(botkit, config) {
var bot = {
type: 'slack',
Expand Down Expand Up @@ -338,6 +337,10 @@ module.exports = function(botkit, config) {
};

bot.send = function(message, cb) {
if (message.ephemeral) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I chose to retain a single send function, and just check if the message should be ephemeral or not

Using send like this also preserves access to these messages from send middlewares!

bot.sendEphemeral(message, cb)
return
}
botkit.debug('SAY', message);

/**
Expand Down Expand Up @@ -413,6 +416,39 @@ module.exports = function(botkit, config) {
cb && cb(err);
}
}
}
bot.sendEphemeral = function (message, cb) {
botkit.debug('SAY EPHEMERAL', message);

/**
* Construct a valid slack message.
*/
var slack_message = {
type: message.type || 'message',
channel: message.channel,
text: message.text || null,
user: message.user,
as_user: message.as_user || false,
parse: message.parse || null,
link_names: message.link_names || null,
attachments: message.attachments ?
JSON.stringify(message.attachments) : null,
};
console.log({message})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use botkit.debug ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No good reason, ty for catching it

console.log({slack_message})
Copy link

@k001 k001 Aug 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, is better use botkit.debug

bot.msgcount++;

if (!bot.config.token) {
throw new Error('Cannot use web API to send messages.');
}

bot.api.chat.postEphemeral(slack_message, function(err, res) {
if (err) {
cb && cb(err);
} else {
cb && cb(null, res);
}
});
};

/**
Expand Down Expand Up @@ -615,6 +651,27 @@ module.exports = function(botkit, config) {
if (src.thread_ts) {
msg.thread_ts = src.thread_ts;
}
if (msg.ephemeral && ! msg.user) {
msg.user = src.user
msg.as_user = true
}

bot.say(msg, cb);
};

bot.whisper = function(src, resp, cb) {
var msg = {};

if (typeof(resp) == 'string') {
msg.text = resp;
} else {
msg = resp;
}

msg.channel = src.channel;
msg.user = src.user;
msg.as_user = true
msg.ephemeral = true

bot.say(msg, cb);
};
Expand Down