UNPKG

18.3 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11Object.defineProperty(exports, "__esModule", { value: true });
12const botbuilder_1 = require("botbuilder");
13/**
14 * A base class for a `bot` instance, an object that contains the information and functionality for taking action in response to an incoming message.
15 * Note that adapters are likely to extend this class with additional platform-specific methods - refer to the adapter documentation for these extensions.
16 */
17class BotWorker {
18 /**
19 * Create a new BotWorker instance. Do not call this directly - instead, use [controller.spawn()](#spawn).
20 * @param controller A pointer to the main Botkit controller
21 * @param config An object typically containing { dialogContext, reference, context, activity }
22 */
23 constructor(controller, config) {
24 this._controller = controller;
25 this._config = Object.assign({}, config);
26 }
27 /**
28 * Get a reference to the main Botkit controller.
29 */
30 get controller() {
31 return this._controller;
32 }
33 /**
34 * Get a value from the BotWorker's configuration.
35 *
36 * ```javascript
37 * let original_context = bot.getConfig('context');
38 * await original_context.sendActivity('send directly using the adapter instead of Botkit');
39 * ```
40 *
41 * @param {string} key The name of a value stored in the configuration
42 * @returns {any} The value stored in the configuration (or null if absent)
43 */
44 getConfig(key) {
45 if (key) {
46 return this._config[key];
47 }
48 else {
49 return this._config;
50 }
51 }
52 /**
53 * Send a message using whatever context the `bot` was spawned in or set using [changeContext()](#changecontext) --
54 * or more likely, one of the platform-specific helpers like
55 * [startPrivateConversation()](../reference/slack.md#startprivateconversation) (Slack),
56 * [startConversationWithUser()](../reference/twilio-sms.md#startconversationwithuser) (Twilio SMS),
57 * and [startConversationWithUser()](../reference/facebook.md#startconversationwithuser) (Facebook Messenger).
58 * Be sure to check the platform documentation for others - most adapters include at least one.
59 *
60 * Simple use in event handler (acts the same as bot.reply)
61 * ```javascript
62 * controller.on('event', async(bot, message) => {
63 *
64 * await bot.say('I received an event!');
65 *
66 * });
67 * ```
68 *
69 * Use with a freshly spawned bot and bot.changeContext:
70 * ```javascript
71 * let bot = controller.spawn(OPTIONS);
72 * bot.changeContext(REFERENCE);
73 * bot.say('ALERT! I have some news.');
74 * ```
75 *
76 * Use with multi-field message object:
77 * ```javascript
78 * controller.on('event', async(bot, message) => {
79 * bot.say({
80 * text: 'I heard an event',
81 * attachments: [
82 * title: message.type,
83 * text: `The message was of type ${ message.type }`,
84 * // ...
85 * ]
86 * });
87 * });
88 * ```
89 *
90 * @param message A string containing the text of a reply, or more fully formed message object
91 * @returns Return value will contain the results of the send action, typically `{id: <id of message>}`
92 */
93 say(message) {
94 return __awaiter(this, void 0, void 0, function* () {
95 return new Promise((resolve, reject) => {
96 const activity = this.ensureMessageFormat(message);
97 this._controller.middleware.send.run(this, activity, (err, bot, activity) => __awaiter(this, void 0, void 0, function* () {
98 if (err) {
99 return reject(err);
100 }
101 resolve(yield this.getConfig('context').sendActivity(activity));
102 }));
103 });
104 });
105 }
106 ;
107 /**
108 * Reply to an incoming message.
109 * Message will be sent using the context of the source message, which may in some cases be different than the context used to spawn the bot.
110 *
111 * Note that like [bot.say()](#say), `reply()` can take a string or a message object.
112 *
113 * ```javascript
114 * controller.on('event', async(bot, message) => {
115 *
116 * await bot.reply(message, 'I received an event and am replying to it.');
117 *
118 * });
119 * ```
120 *
121 * @param src An incoming message, usually passed in to a handler function
122 * @param resp A string containing the text of a reply, or more fully formed message object
123 * @returns Return value will contain the results of the send action, typically `{id: <id of message>}`
124 */
125 reply(src, resp) {
126 return __awaiter(this, void 0, void 0, function* () {
127 let activity = this.ensureMessageFormat(resp);
128 // Get conversation reference from src
129 const reference = botbuilder_1.TurnContext.getConversationReference(src.incoming_message);
130 activity = botbuilder_1.TurnContext.applyConversationReference(activity, reference);
131 return this.say(activity);
132 });
133 }
134 /**
135 * Begin a pre-defined dialog by specifying its id. The dialog will be started in the same context (same user, same channel) in which the original incoming message was received.
136 * [See "Using Dialogs" in the core documentation.](../index.md#using-dialogs)
137 *
138 * ```javascript
139 * controller.hears('hello', 'message', async(bot, message) => {
140 * await bot.beginDialog(GREETINGS_DIALOG);
141 * });
142 * ```
143 * @param id id of dialog
144 * @param options object containing options to be passed into the dialog
145 */
146 beginDialog(id, options) {
147 return __awaiter(this, void 0, void 0, function* () {
148 if (this._config.dialogContext) {
149 yield this._config.dialogContext.beginDialog(id + ':botkit-wrapper', Object.assign({ user: this.getConfig('context').activity.from.id, channel: this.getConfig('context').activity.conversation.id }, options));
150 // make sure we save the state change caused by the dialog.
151 // this may also get saved again at end of turn
152 yield this._controller.saveState(this);
153 }
154 else {
155 throw new Error('Call to beginDialog on a bot that did not receive a dialogContext during spawn');
156 }
157 });
158 }
159 /**
160 * Cancel any and all active dialogs for the current user/context.
161 */
162 cancelAllDialogs() {
163 return __awaiter(this, void 0, void 0, function* () {
164 if (this._config.dialogContext) {
165 return this._config.dialogContext.cancelAllDialogs();
166 }
167 });
168 }
169 /**
170 * Get a reference to the active dialog
171 * @returns a reference to the active dialog or undefined if no dialog is active
172 */
173 getActiveDialog() {
174 return this.getConfig('dialogContext').activeDialog;
175 }
176 /**
177 * Check if any dialog is active or not
178 * @returns true if there is an active dialog, otherwise false
179 */
180 hasActiveDialog() {
181 return !!this.getActiveDialog();
182 }
183 /**
184 * Check to see if a given dialog is currently active in the stack
185 * @param id The id of a dialog to look for in the dialog stack
186 * @returns true if dialog with id is located anywhere in the dialog stack
187 */
188 isDialogActive(id) {
189 if (this.getConfig('dialogContext').stack.length) {
190 return (this.getConfig('dialogContext').stack.filter((d) => d.id === id).length > 0);
191 }
192 return false;
193 }
194 /**
195 * Replace any active dialogs with a new a pre-defined dialog by specifying its id. The dialog will be started in the same context (same user, same channel) in which the original incoming message was received.
196 * [See "Using Dialogs" in the core documentation.](../index.md#using-dialogs)
197 *
198 * ```javascript
199 * controller.hears('hello', 'message', async(bot, message) => {
200 * await bot.replaceDialog(GREETINGS_DIALOG);
201 * });
202 * ```
203 * @param id id of dialog
204 * @param options object containing options to be passed into the dialog
205 */
206 replaceDialog(id, options) {
207 return __awaiter(this, void 0, void 0, function* () {
208 if (this._config.dialogContext) {
209 yield this._config.dialogContext.replaceDialog(id + ':botkit-wrapper', Object.assign({ user: this.getConfig('context').activity.from.id, channel: this.getConfig('context').activity.conversation.id }, options));
210 // make sure we save the state change caused by the dialog.
211 // this may also get saved again at end of turn
212 yield this._controller.saveState(this);
213 }
214 else {
215 throw new Error('Call to beginDialog on a bot that did not receive a dialogContext during spawn');
216 }
217 });
218 }
219 /**
220 * Alter the context in which a bot instance will send messages.
221 * Use this method to create or adjust a bot instance so that it can send messages to a predefined user/channel combination.
222 *
223 * ```javascript
224 * // get the reference field and store it.
225 * const saved_reference = message.reference;
226 *
227 * // later on...
228 * let bot = await controller.spawn();
229 * bot.changeContext(saved_reference);
230 * bot.say('Hello!');
231 * ```
232 *
233 * @param reference A [ConversationReference](https://docs.microsoft.com/en-us/javascript/api/botframework-schema/conversationreference?view=botbuilder-ts-latest), most likely captured from an incoming message and stored for use in proactive messaging scenarios.
234 */
235 changeContext(reference) {
236 return __awaiter(this, void 0, void 0, function* () {
237 // change context of outbound activities to use this new address
238 this._config.reference = reference;
239 // Create an activity using this reference
240 const activity = botbuilder_1.TurnContext.applyConversationReference({ type: 'message' }, reference, true);
241 // create a turn context
242 const turnContext = new botbuilder_1.TurnContext(this.getConfig('adapter'), activity);
243 // create a new dialogContext so beginDialog works.
244 const dialogContext = yield this._controller.dialogSet.createContext(turnContext);
245 this._config.context = turnContext;
246 this._config.dialogContext = dialogContext;
247 this._config.activity = activity;
248 return this;
249 });
250 }
251 startConversationWithUser(reference) {
252 return __awaiter(this, void 0, void 0, function* () {
253 // this code is mostly copied from BotFrameworkAdapter.createConversation
254 if (!reference.serviceUrl) {
255 throw new Error('bot.startConversationWithUser(): missing serviceUrl.');
256 }
257 // Create conversation
258 const parameters = { bot: reference.bot, members: [reference.user], isGroup: false, activity: null, channelData: null };
259 const client = this.getConfig('adapter').createConnectorClient(reference.serviceUrl);
260 // Mix in the tenant ID if specified. This is required for MS Teams.
261 if (reference.conversation && reference.conversation.tenantId) {
262 // Putting tenantId in channelData is a temporary solution while we wait for the Teams API to be updated
263 parameters.channelData = { tenant: { id: reference.conversation.tenantId } };
264 // Permanent solution is to put tenantId in parameters.tenantId
265 parameters.tenantId = reference.conversation.tenantId;
266 }
267 const response = yield client.conversations.createConversation(parameters);
268 // Initialize request and copy over new conversation ID and updated serviceUrl.
269 const request = botbuilder_1.TurnContext.applyConversationReference({ type: 'event', name: 'createConversation' }, reference, true);
270 const conversation = {
271 // fallback to existing conversation id because Emulator will respond without a response.id AND needs to stay in same channel.
272 // This should be fixed by Emulator. https://github.com/microsoft/BotFramework-Emulator/issues/2097
273 id: response.id || reference.conversation.id,
274 isGroup: false,
275 conversationType: null,
276 tenantId: null,
277 name: null
278 };
279 request.conversation = conversation;
280 if (response.serviceUrl) {
281 request.serviceUrl = response.serviceUrl;
282 }
283 // Create context and run middleware
284 const turnContext = this.getConfig('adapter').createContext(request);
285 // create a new dialogContext so beginDialog works.
286 const dialogContext = yield this._controller.dialogSet.createContext(turnContext);
287 this._config.context = turnContext;
288 this._config.dialogContext = dialogContext;
289 this._config.activity = request;
290 });
291 }
292 /**
293 * Take a crudely-formed Botkit message with any sort of field (may just be a string, may be a partial message object)
294 * and map it into a beautiful BotFramework Activity.
295 * Any fields not found in the Activity definition will be moved to activity.channelData.
296 * @params message a string or partial outgoing message object
297 * @returns a properly formed Activity object
298 */
299 ensureMessageFormat(message) {
300 if (typeof (message) === 'string') {
301 return {
302 type: 'message',
303 text: message,
304 channelData: {}
305 };
306 }
307 else {
308 // set up a base message activity
309 // https://docs.microsoft.com/en-us/javascript/api/botframework-schema/activity?view=botbuilder-ts-latest
310 const activity = {
311 type: message.type || 'message',
312 text: message.text,
313 action: message.action,
314 attachmentLayout: message.attachmentLayout,
315 attachments: message.attachments,
316 channelData: Object.assign({}, message.channelData),
317 channelId: message.channelId,
318 code: message.code,
319 conversation: message.conversation,
320 deliveryMode: message.deliveryMode,
321 entities: message.entities,
322 expiration: message.expiration,
323 from: message.from,
324 historyDisclosed: message.historyDisclosed,
325 id: message.id,
326 importance: message.importance,
327 inputHint: message.inputHint,
328 label: message.label,
329 listenFor: message.listenFor,
330 locale: message.locale,
331 localTimestamp: message.localTimestamp,
332 localTimezone: message.localTimezone,
333 membersAdded: message.membersAdded,
334 membersRemoved: message.membersRemoved,
335 name: message.name,
336 reactionsAdded: message.reactionsAdded,
337 reactionsRemoved: message.reactionsRemoved,
338 recipient: message.recipient,
339 relatesTo: message.relatesTo,
340 replyToId: message.replyToId,
341 semanticAction: message.semanticAction,
342 serviceUrl: message.serviceUrl,
343 speak: message.speak,
344 suggestedActions: message.suggestedActions,
345 summary: message.summary,
346 textFormat: message.textFormat,
347 textHighlights: message.textHighlights,
348 timestamp: message.timestamp,
349 topicName: message.topicName,
350 value: message.value,
351 valueType: message.valueType
352 };
353 // Now, copy any additional fields not in the activity into channelData
354 // This way, any fields added by the developer to the root object
355 // end up in the approved channelData location.
356 for (const key in message) {
357 if (key !== 'channelData' && !Object.prototype.hasOwnProperty.call(activity, key)) {
358 activity.channelData[key] = message[key];
359 }
360 }
361 return activity;
362 }
363 }
364 /**
365 * Set the http response status code for this turn
366 *
367 * ```javascript
368 * controller.on('event', async(bot, message) => {
369 * // respond with a 500 error code for some reason!
370 * bot.httpStatus(500);
371 * });
372 * ```
373 *
374 * @param status {number} a valid http status code like 200 202 301 500 etc
375 */
376 httpStatus(status) {
377 this.getConfig('context').turnState.set('httpStatus', status);
378 }
379 /**
380 * Set the http response body for this turn.
381 * Use this to define the response value when the platform requires a synchronous response to the incoming webhook.
382 *
383 * Example handling of a /slash command from Slack:
384 * ```javascript
385 * controller.on('slash_command', async(bot, message) => {
386 * bot.httpBody('This is a reply to the slash command.');
387 * })
388 * ```
389 *
390 * @param body (any) a value that will be returned as the http response body
391 */
392 httpBody(body) {
393 this.getConfig('context').turnState.set('httpBody', body);
394 }
395}
396exports.BotWorker = BotWorker;
397//# sourceMappingURL=botworker.js.map
\No newline at end of file