Skip to content

Instantly share code, notes, and snippets.

@AskYous

AskYous/bot.ts Secret

Created January 3, 2019 21:58
Show Gist options
  • Save AskYous/5f8da20bccc42ad6d33bce49b73d250a to your computer and use it in GitHub Desktop.
Save AskYous/5f8da20bccc42ad6d33bce49b73d250a to your computer and use it in GitHub Desktop.
import { LuisRecognizer, LuisPredictionOptions } from "botbuilder-ai";
import { ConversationState, TurnContext, StatePropertyAccessor, CardFactory, RecognizerResult } from "botbuilder";
import { OAuthHelpers, LOGIN_PROMPT } from "./oauth-helpers";
import { ActivityTypes } from "botbuilder";
import {
DialogState,
DialogSet,
WaterfallDialog,
WaterfallStepContext,
WaterfallStep,
DialogTurnResult,
TextPrompt,
Prompt,
ActivityPrompt,
ConfirmPrompt,
ChoicePrompt
} from "botbuilder-dialogs";
import { Activity } from "botframework-connector/lib/connectorApi/models/mappers";
const CONNECTION_SETTINGS_NAME = "ms-graph";
const RECEPIENT_PROMPT = "recepiant_prompt";
/**
* A simple bot that responds to utterances with answers from the Language Understanding (LUIS) service.
* If an answer is not found for an utterance, the bot responds with help.
*/
class LuisBot {
private luisRecognizer: LuisRecognizer;
private dialogState: StatePropertyAccessor<DialogState>;
private commandState: StatePropertyAccessor<any>;
private helpMessage: string;
private graphDialogId = "graphDialog";
private dialogs: DialogSet;
/**
* The LuisBot constructor requires one argument (`application`) which is used to create an instance of `LuisRecognizer`.
* @param {LuisApplication} luisApplication The basic configuration needed to call LUIS. In this sample the configuration is retrieved from the .bot file.
* @param {LuisPredictionOptions} luisPredictionOptions (Optional) Contains additional settings for configuring calls to LUIS.
*/
constructor(
application,
luisPredictionOptions: LuisPredictionOptions,
private conversationState: ConversationState
) {
this.luisRecognizer = new LuisRecognizer(application, luisPredictionOptions, true);
this.conversationState = conversationState;
this.dialogState = conversationState.createProperty("dialogState");
this.commandState = conversationState.createProperty("commandState");
this.helpMessage =
`You can type "send <recipient_email>" to send an email, "recent" to view recent unread mail,` +
` "me" to see information about your, or "help" to view the commands` +
` again. Any other text will display your token.`;
// @ts-ignore
this.dialogs = new DialogSet(this.dialogState);
this.dialogs.add(OAuthHelpers.prompt(CONNECTION_SETTINGS_NAME));
this.dialogs.add(
new WaterfallDialog(this.graphDialogId, [this.promptStep.bind(this), this.processStep.bind(this)])
);
// send email
this.dialogs.add(new TextPrompt("tp"));
this.dialogs.add(new TextPrompt("tp2"));
this.dialogs.add(new TextPrompt("tp3"));
this.dialogs.add(
new WaterfallDialog("send_email", [
this.promptStep.bind(this),
async step => await step.prompt("tp", "Who do you want to email?"),
async step => await step.prompt("tp2", "What's the subject line?"),
async step => await step.prompt("tp3", "And what's the message?"),
async step => await OAuthHelpers.sendMail(step.context, step.result, step.result)
])
);
}
/**
* WaterfallDialogStep to process the command sent by the user.
* @param { WaterfallStepContext } step WaterfallStepContext
*/
public async processStep(step: WaterfallStepContext) {
// We do not need to store the token in the bot. When we need the token we can
// send another prompt. If the token is valid the user will not need to log back in.
// The token will be available in the Result property of the task.
const tokenResponse = step.result;
const luisResult = (await this.luisRecognizer.recognize(step.context)).luisResult;
const intent = luisResult.topScoringIntent.intent; // TODO: Check confidence level
// If the user is authenticated the bot can use the token to make API calls.
if (tokenResponse !== undefined) {
let parts = await this.commandState.get(step.context);
if (!parts) parts = step.context.activity.text;
switch (intent) {
case "me":
await OAuthHelpers.listMe(step.context, tokenResponse);
break;
case "sendEmail":
let recipient: string;
if (luisResult.entities.length) recipient = luisResult.entities[0].entity;
else {
const dc = await this.dialogs.createContext(step.context);
await dc.beginDialog("send_email");
}
break;
case "getEmails":
await OAuthHelpers.listRecentMail(step.context, tokenResponse);
break;
default:
await step.context.sendActivity(`Your token is: ${tokenResponse.token}`);
}
} else {
// Ask the user to try logging in later as they are not logged in.
await step.context.sendActivity(`We couldn't log you in. Please try again later.`);
}
return await step.endDialog();
}
/**
* Every conversation turn calls this method.
* @param {TurnContext} turnContext Contains all the data needed for processing the conversation turn.
*/
public async onTurn(turnContext) {
const dc = await this.dialogs.createContext(turnContext);
switch (turnContext.activity.type) {
case ActivityTypes.Message:
await this.processInput(dc);
break;
case ActivityTypes.Event:
break;
case ActivityTypes.Invoke:
// This handles the Microsoft Teams Invoke Activity sent when magic code is not used.
// See: https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/authentication/auth-oauth-card#getting-started-with-oauthcard-in-teams
// Manifest Schema Here: https://docs.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema
// It also handles the Event Activity sent from The Emulator when the magic code is not used.
// See: https://blog.botframework.com/2018/08/28/testing-authentication-to-your-bot-using-the-bot-framework-emulator/
// Sanity check the Activity type and channel Id.
if (
turnContext.activity.type === ActivityTypes.Invoke &&
turnContext.activity.channelId !== "msteams"
) {
throw new Error("The Invoke type is only valid on the MS Teams channel.");
}
await dc.continueDialog();
if (!turnContext.responded) await dc.beginDialog(this.graphDialogId);
break;
case ActivityTypes.ConversationUpdate:
await this.sendWelcomeMessage(turnContext);
break;
default:
await turnContext.sendActivity(`[${turnContext.activity.type}]-type activity detected.`);
}
await this.conversationState.saveChanges(turnContext);
}
/**
* Creates a Hero Card that is sent as a welcome message to the user.
* @param {TurnContext} turnContext A TurnContext instance containing all the data needed for processing this conversation turn.
*/
public async sendWelcomeMessage(turnContext: TurnContext) {
const activity = turnContext.activity;
if (activity && activity.membersAdded) {
const heroCard = CardFactory.heroCard(
"🎉 Welcome to FlexO! 🎉",
CardFactory.images([
"https://chiefexecutive.net/wp-content/uploads/2018/02/GettyImages-870184586-compressor.jpg",
"https://botframeworksamples.blob.core.windows.net/samples/aadlogo.png"
]),
CardFactory.actions([
{
type: "imBack",
title: "Me",
value: "me",
channelData: undefined
},
{
type: "imBack",
title: "Recent",
value: "recent",
channelData: undefined
},
{
type: "imBack",
title: "View Token",
value: "view Token",
channelData: undefined
},
{
type: "imBack",
title: "Help",
value: "help",
channelData: undefined
},
{
type: "imBack",
title: "Signout",
value: "signout",
channelData: undefined
}
])
);
for (const idx in activity.membersAdded) {
if (activity.membersAdded[idx].id !== activity.recipient.id) {
await turnContext.sendActivity({ attachments: [heroCard] });
}
}
}
}
/**
* Processes input and route to the appropriate step.
* @param {DialogContext} dc DialogContext
* @param {RecognizerResult} luisResponse the response from luis
*/
public async processInput(dc) {
switch (dc.context.activity.text.toLowerCase()) {
case "signout":
case "logout":
case "signoff":
case "logoff":
// The bot adapter encapsulates the authentication processes and sends
// activities to from the Bot Connector Service.
const botAdapter = dc.context.adapter;
await botAdapter.signOutUser(dc.context, CONNECTION_SETTINGS_NAME);
// Let the user know they are signed out.
await dc.context.sendActivity("You are now signed out.");
break;
case "help":
await dc.context.sendActivity(this.helpMessage);
break;
default:
// The user has input a command that has not been handled yet,
// begin the waterfall dialog to handle the input.
await dc.continueDialog();
if (!dc.context.responded) {
await dc.beginDialog(this.graphDialogId);
}
}
}
/**
* WaterfallDialogStep for storing commands and beginning the OAuthPrompt.
* Saves the user's message as the command to execute if the message is not
* a magic code.
* @param {WaterfallStepContext} step WaterfallStepContext
*/
public async promptStep(step) {
const activity = step.context.activity;
if (activity.type === ActivityTypes.Message && !/\d{6}/.test(activity.text)) {
await this.commandState.set(step.context, activity.text);
await this.conversationState.saveChanges(step.context);
}
return await step.beginDialog(LOGIN_PROMPT);
}
}
module.exports.LuisBot = LuisBot;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment