botbuilder-adapter-slack
Version:
Connect Botkit or BotBuilder to Slack
368 lines • 15.6 kB
JavaScript
"use strict";
/**
* @module botbuilder-adapter-slack
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SlackBotWorker = void 0;
const botkit_1 = require("botkit");
const request = require("request");
/**
* This is a specialized version of [Botkit's core BotWorker class](core.md#BotWorker) that includes additional methods for interacting with Slack.
* It includes all functionality from the base class, as well as the extension methods below.
*
* When using the SlackAdapter with Botkit, all `bot` objects passed to handler functions will include these extensions.
*/
class SlackBotWorker extends botkit_1.BotWorker {
/**
* Reserved for use internally by Botkit's `controller.spawn()`, this class is used to create a BotWorker instance that can send messages, replies, and make other API calls.
*
* It is possible to spawn a bot instance by passing in the Slack workspace ID of a team that has installed the app.
* Use this in concert with [startPrivateConversation()](#startPrivateConversation) and [changeContext()](core.md#changecontext) to start conversations
* or send proactive alerts to users on a schedule or in response to external events.
*
*
* ```javascript
* // spawn a bot for a given team.
* let bot = await controller.spawn('T0123456');
*
* // start a 1:1 with a specific user
* await bot.startPrivateConversation('U0123456');
*
* // send a message
* await bot.say('Hi user');
* ```
*
* @param botkit The Botkit controller object responsible for spawning this bot worker
* @param config Normally, a DialogContext object. Can also be the id of a team.
*/
constructor(botkit, config) {
// allow a teamid to be passed in
if (typeof config === 'string') {
const team_id = config;
config = {
// an activity is required to spawn the bot via the api
activity: {
conversation: {
team: team_id
}
},
// a reference is used to spawn an api instance inside the adapter...
reference: {
conversation: {
team: team_id
}
}
};
}
super(botkit, config);
}
/**
* Switch a bot's context to a 1:1 private message channel with a specific user.
* After calling this method, messages sent with `bot.say` and any dialogs started with `bot.beginDialog` will occur in this new context.
*
* ```javascript
* controller.hears('dm me', 'message', async(bot, message) => {
*
* // switch to a 1:1 conversation in a DM
* await bot.startPrivateConversation(message.user);
*
* // say hello
* await bot.say('We are in private now...');
* await bot.beginDialog(MY_PRIVATE_DIALOG);
*
* });
* ```
*
* Also useful when sending pro-active messages such as those sent on a schedule or in response to external events:
* ```javascript
* // Spawn a worker with a Slack team id.
* let bot = await controller.spawn(SLACK_TEAM_ID);
*
* // Set the context for the bot's next action...
* await bot.startPrivateConversation(SLACK_ADMIN_USER);
*
* // Begin a dialog in the 1:1 context
* await bot.beginDialog(ALERT_DIALOG);
* ```
*
* @param userId A Slack user id, like one found in `message.user` or in a `<@mention>`
*/
startPrivateConversation(userId) {
return __awaiter(this, void 0, void 0, function* () {
// create the new IM channel
const channel = yield this.api.conversations.open({ users: userId });
if (channel.ok === true) {
// now, switch contexts
return this.changeContext({
conversation: {
id: channel.channel.id,
// @ts-ignore this field is required for slack
team: this.getConfig('activity').conversation.team
},
user: { id: userId, name: null },
channelId: 'slack'
});
}
else {
console.error(channel);
throw new Error('Error creating IM channel');
}
});
}
/**
* Switch a bot's context into a different channel.
* After calling this method, messages sent with `bot.say` and any dialogs started with `bot.beginDialog` will occur in this new context.
*
* ```javascript
* controller.hears('dm me', 'message', async(bot, message) => {
*
* // switch to the channel specified in SLACK_CHANNEL_ID
* // if just using bot.say and not starting a dialog, can use a fake value for user id.
* await bot.startConversationInChannel(SLACK_CHANNEL_ID, message.user);
*
* // say hello
* await bot.say('Shall we discuss this matter over here?');
* // ... continue...
* await bot.beginDialog(ANOTHER_DIALOG);
*
* });
* ```
* @param channelId A Slack channel id, like one found in `message.channel`
* @param userId A Slack user id, like one found in `message.user` or in a `<@mention>`
*/
startConversationInChannel(channelId, userId) {
return __awaiter(this, void 0, void 0, function* () {
return this.changeContext({
conversation: {
id: channelId,
// @ts-ignore this field is required for slack
team: this.getConfig('activity').conversation.team
},
user: { id: userId, name: null },
channelId: 'slack'
});
});
}
/**
* Switch a bot's context into a specific sub-thread within a channel.
* After calling this method, messages sent with `bot.say` and any dialogs started with `bot.beginDialog` will occur in this new context.
*
* ```javascript
* controller.hears('in a thread', 'message', async(bot, message) => {
*
* // branch from the main channel into a side thread associated with this message
* await bot.startConversationInThread(message.channel, message.user, message.ts);
*
* // say hello
* await bot.say(`Let's handle this offline...`);
* // ... continue...
* await bot.beginDialog(OFFLINE_DIALOG);
*
* });
* ```
* @param channelId A Slack channel id, like one found in `message.channel`
* @param userId A Slack user id, like one found in `message.user` or in a `<@mention>`
* @param thread_ts A thread_ts value found in the `message.thread_ts` or `message.ts` field.
*/
startConversationInThread(channelId, userId, thread_ts) {
return __awaiter(this, void 0, void 0, function* () {
return this.changeContext({
conversation: {
id: channelId,
// @ts-ignore this field is required for slack
thread_ts: thread_ts,
team: this.getConfig('activity').conversation.team
},
user: { id: userId, name: null },
channelId: 'slack'
});
});
}
/**
* Like bot.reply, but as a threaded response to the incoming message rather than a new message in the main channel.
* @param src an incoming message object
* @param resp an outgoing message object (or part of one or just reply text)
*/
replyInThread(src, resp) {
return __awaiter(this, void 0, void 0, function* () {
// make sure the thread_ts setting is set
// this will be included in the conversation reference
src.incoming_message.conversation.thread_ts = src.incoming_message.channelData.thread_ts ? src.incoming_message.channelData.thread_ts : src.incoming_message.channelData.ts;
return this.reply(src, resp);
});
}
/**
* Like bot.reply, but sent as an "ephemeral" message meaning only the recipient can see it.
* Uses [chat.postEphemeral](https://api.slack.com/methods/chat.postEphemeral)
* @param src an incoming message object
* @param resp an outgoing message object (or part of one or just reply text)
*/
replyEphemeral(src, resp) {
return __awaiter(this, void 0, void 0, function* () {
// make rure resp is in an object format.
resp = this.ensureMessageFormat(resp);
// make sure ephemeral is set
// fields set in channelData will end up in the final message to slack
resp.channelData = Object.assign(Object.assign({}, resp.channelData), { ephemeral: true });
return this.reply(src, resp);
});
}
/**
* Like bot.reply, but used to send an immediate public reply to a /slash command.
* The message in `resp` will be displayed to everyone in the channel.
* @param src an incoming message object of type `slash_command`
* @param resp an outgoing message object (or part of one or just reply text)
*/
replyPublic(src, resp) {
return __awaiter(this, void 0, void 0, function* () {
const msg = this.ensureMessageFormat(resp);
msg.channelData.response_type = 'in_channel';
return this.replyInteractive(src, msg);
});
}
;
/**
* Like bot.reply, but used to send an immediate private reply to a /slash command.
* The message in `resp` will be displayed only to the person who executed the slash command.
* @param src an incoming message object of type `slash_command`
* @param resp an outgoing message object (or part of one or just reply text)
*/
replyPrivate(src, resp) {
return __awaiter(this, void 0, void 0, function* () {
const msg = this.ensureMessageFormat(resp);
msg.channelData.response_type = 'ephemeral';
msg.channelData.to = src.user;
return this.replyInteractive(src, msg);
});
}
;
/**
* Like bot.reply, but used to respond to an `interactive_message` event and cause the original message to be replaced with a new one.
* @param src an incoming message object of type `interactive_message`
* @param resp a new or modified message that will replace the original one
*/
replyInteractive(src, resp) {
return __awaiter(this, void 0, void 0, function* () {
if (!src.incoming_message.channelData.response_url) {
throw Error('No response_url found in incoming message');
}
else {
let msg = this.ensureMessageFormat(resp);
// @ts-ignore
msg.conversation = {
id: src.channel
};
msg.channelData.to = src.user;
// if source message is in a thread, reply should also be in the thread
if (src.incoming_message.channelData.thread_ts) {
// @ts-ignore
msg.conversation.thread_ts = src.incoming_message.channelData.thread_ts;
}
msg = this.getConfig('adapter').activityToSlack(msg);
const requestOptions = {
uri: src.incoming_message.channelData.response_url,
method: 'POST',
json: msg
};
return new Promise(function (resolve, reject) {
request(requestOptions, function (err, res, body) {
if (err) {
reject(err);
}
else {
resolve(body);
}
});
});
}
});
}
;
/**
* Return 1 or more error to a `dialog_submission` event that will be displayed as form validation errors.
* Each error must be mapped to the name of an input in the dialog.
* @param errors 1 or more objects in form {name: string, error: string}
*/
dialogError(errors) {
if (!errors) {
errors = [];
}
if (!Array.isArray(errors)) {
errors = [errors];
}
this.httpBody(JSON.stringify({ errors }));
}
;
/**
* Reply to a button click with a request to open a dialog.
* @param src An incoming `interactive_callback` event containing a `trigger_id` field
* @param dialog_obj A dialog, as created using [SlackDialog](#SlackDialog) or [authored to this spec](https://api.slack.com/dialogs).
*/
replyWithDialog(src, dialog_obj) {
return __awaiter(this, void 0, void 0, function* () {
const msg = {
trigger_id: src.trigger_id,
dialog: dialog_obj
};
return this.api.dialog.open(msg);
});
}
;
/**
* Update an existing message with new content.
*
* ```javascript
* // send a reply, capture the results
* let sent = await bot.reply(message,'this is my original reply...');
*
* // update the sent message using the sent.id field
* await bot.updateMessage({
* text: 'this is an update!',
* ...sent
* })
* ```
*
* @param update An object in the form `{id: <id of message to update>, conversation: { id: <channel> }, text: <new text>, card: <array of card objects>}`
*/
updateMessage(update) {
return __awaiter(this, void 0, void 0, function* () {
return this.getConfig('adapter').updateActivity(this.getConfig('context'), update);
});
}
/**
* Delete an existing message.
*
* ```javascript
* // send a reply, capture the results
* let sent = await bot.reply(message,'this is my original reply...');
*
* // delete the sent message using the sent.id field
* await bot.deleteMessage(sent);
* ```
*
* @param update An object in the form of `{id: <id of message to delete>, conversation: { id: <channel of message> }}`
*/
deleteMessage(update) {
return __awaiter(this, void 0, void 0, function* () {
return this.getConfig('adapter').deleteActivity(this.getConfig('context'), {
activityId: update.id,
conversation: update.conversation
});
});
}
}
exports.SlackBotWorker = SlackBotWorker;
//# sourceMappingURL=botworker.js.map