diff --git a/docs/readme-slack.md b/docs/readme-slack.md index bb5ba9f0f..159e9231d 100644 --- a/docs/readme-slack.md +++ b/docs/readme-slack.md @@ -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 diff --git a/lib/Slack_web_api.js b/lib/Slack_web_api.js index 030197237..789897239 100755 --- a/lib/Slack_web_api.js +++ b/lib/Slack_web_api.js @@ -41,6 +41,7 @@ module.exports = function(bot, config) { 'channels.unarchive', 'chat.delete', 'chat.postMessage', + 'chat.postEphemeral', 'chat.update', 'chat.unfurl', 'dnd.endDnd', @@ -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); diff --git a/lib/Slackbot_worker.js b/lib/Slackbot_worker.js index 25f1512ab..23094818b 100755 --- a/lib/Slackbot_worker.js +++ b/lib/Slackbot_worker.js @@ -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', @@ -338,6 +337,10 @@ module.exports = function(botkit, config) { }; bot.send = function(message, cb) { + if (message.ephemeral) { + bot.sendEphemeral(message, cb) + return + } botkit.debug('SAY', message); /** @@ -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}) + console.log({slack_message}) + 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); + } + }); }; /** @@ -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); };