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