- Fixed bugs in waterfall handling logic.

- Added a slack-todoBotLuis example
This commit is contained in:
Steven Ickman 2016-03-18 02:29:58 -07:00
Родитель c3635d7834
Коммит 40b2b9db20
12 изменённых файлов: 346 добавлений и 79 удалений

Просмотреть файл

@ -1,10 +1,8 @@
/*-----------------------------------------------------------------------------
A simple "Hello World" bot for slack. SlackBot is powered by Botkit and you can
do pretty much anything you can do in Botkit.
More details about setting up Botkit can be found at:
http://howdy.ai/botkit
A bot for managing a teams to-do list. Created tasks are saved to the sessions
channelData object which means that private task lists can be created by sending
the bot a Direct Message and group task lists can be managed by @mentioning
the bot in a channel.
# RUN THE BOT:
@ -24,9 +22,12 @@ More details about setting up Botkit can be found at:
# USE THE BOT:
Find your bot inside Slack
To manage your private tasks find the bot in slack and send it a Direct
Message saying "Hello". The bot will reply with instructions for how to
manage tasks.
Say: "hello"
To manage group tasks invite the bot to a channel by saying "/invite @bot"
then say "@bot hello" for a list of instructions.
-----------------------------------------------------------------------------*/
@ -36,13 +37,13 @@ var index = require('./dialogs/index')
var controller = Botkit.slackbot();
var bot = controller.spawn({
token: process.env.token
token: process.env.token || 'xoxb-27353876818-1JaNnDS8yhLSKuzmX9DncWAI'
});
var slackBot = new builder.SlackBot(controller, bot);
slackBot.add('/', index);
slackBot.listen(['direct_message','direct_mention']);
slackBot.listenForMentions();
bot.startRTM(function(err,bot,payload) {
if (err) {

Просмотреть файл

@ -3,12 +3,10 @@ var prompts = require('../prompts');
// Export Command Dialog
module.exports = new builder.CommandDialog()
.matches('^(hello|hi|howdy)', builder.DialogAction.send(prompts.helpMessage))
.matches('^(hello|hi|howdy|help)', builder.DialogAction.send(prompts.helpMessage))
.matches('^(?:new|save|create|add)(?: (.+))?', saveTask)
.matches('^(?:done|delete|finish|remove)(?: (\\d+))?', finishTask)
.matches('^(list|show|tasks)', listTasks)
.onDefault(builder.DialogAction.send(prompts.notRecognized));
.matches('^(list|show|tasks)', listTasks);
function saveTask(session, args) {
if (args.matches) {

Просмотреть файл

@ -1,11 +1,8 @@
const helpMessage = 'Available commands are:\n\n' +
module.exports = {
helpMessage: 'Available commands are:\n\n' +
'* *list* - show all tasks (also *show*, *tasks*)\n' +
'* *new* [your task] - create a new task (also *save*, *create*, *add*)\n' +
'* *done* [number of task] - finish a task, you can get the number of a task from *list* (also *delete", *finish*, *remove*)';
module.exports = {
helpMessage: helpMessage,
notRecognized: 'I am afraid I don\'t understand. ' + helpMessage,
'* *done* [number of task] - finish a task, you can get the number of a task from *list* (also *delete", *finish*, *remove*)',
saveTaskCreated: 'Created a new task no. %(index)d: %(task)s',
saveTaskMissing: 'You need to tell me what the new task should be. For example: "new Remember the milk"',
listTaskList: 'Tasks:\n%s',

Просмотреть файл

@ -0,0 +1,52 @@
/*-----------------------------------------------------------------------------
A bot for managing a teams to-do list. Created tasks are saved to the sessions
channelData object which means that private task lists can be created by sending
the bot a Direct Message and group task lists can be managed by @mentioning
the bot in a channel.
# RUN THE BOT:
Get a Bot token from Slack:
http://my.slack.com/services/new/bot
Run your bot from the command line:
token=YOUR_TOKEN node app.js
Run your bot from the command line (WINDOWS):
set token=YOUR_TOKEN
node app.js
# USE THE BOT:
To manage your private tasks find the bot in slack and send it a Direct
Message saying "Hello". The bot will reply with instructions for how to
manage tasks.
To manage group tasks invite the bot to a channel by saying "/invite @bot"
then say "@bot hello" for a list of instructions.
-----------------------------------------------------------------------------*/
var Botkit = require('botkit');
var builder = require('../../');
var index = require('./dialogs/index')
var controller = Botkit.slackbot();
var bot = controller.spawn({
token: process.env.token || 'xoxb-27353876818-1JaNnDS8yhLSKuzmX9DncWAI'
});
var slackBot = new builder.SlackBot(controller, bot);
slackBot.add('/', index);
slackBot.listenForMentions();
bot.startRTM(function(err,bot,payload) {
if (err) {
throw new Error('Could not connect to Slack');
}
});

Просмотреть файл

@ -0,0 +1,84 @@
var builder = require('../../../');
var prompts = require('../prompts');
/** Return a LuisDialog that points at our model and then add intent handlers. */
var dialog = new builder.LuisDialog('https://api.projectoxford.ai/luis/v1/application?id=0748d3d3-eb60-4e58-95df-3d5e904c5fdc&subscription-key=fe054e042fd14754a83f0a205f6552a5&q=');
module.exports = dialog;
/** Answer users help requests. We can use a DialogAction to send a static message. */
dialog.on('Help', builder.DialogAction.send(prompts.helpMessage));
/** Prompts a user for the title of the task and saves it. */
dialog.on('SaveTask', [
function (session, args, next) {
// See if got the tasks title from our LUIS model.
var title = builder.EntityRecognizer.findEntity(args.entities, 'TaskTitle');
if (!title) {
// Prompt user to enter title.
builder.Prompts.text(session, prompts.saveTaskMissing);
} else {
// Pass title to next step.
next({ response: title.entity });
}
},
function (session, results) {
// Save the task
if (results.response) {
if (!session.channelData.tasks) {
session.channelData.tasks = [results.response];
} else {
session.channelData.tasks.push(results.response);
}
session.send(prompts.saveTaskCreated, { task: results.response });
} else {
session.send(prompts.canceled);
}
}
]);
/** Prompts the user for the task to delete and then removes it. */
dialog.on('FinishTask', [
function (session, args, next) {
// Do we have any tasks?
if (session.channelData.tasks && session.channelData.tasks.length > 0) {
// See if got the tasks title from our LUIS model.
var topTask;
var title = builder.EntityRecognizer.findEntity(args.entities, 'TaskTitle');
if (title) {
// Find it in our list of tasks
topTask = builder.EntityRecognizer.findBestMatch(session.channelData.tasks, title.entity);
}
// Prompt user if task missing or not found
if (!topTask) {
builder.Prompts.choice(session, prompts.finishTaskMissing, session.channelData.tasks);
} else {
next({ response: topTask });
}
} else {
session.send(prompts.listNoTasks);
}
},
function (session, results) {
if (results && results.response) {
session.channelData.tasks.splice(results.response.index, 1);
session.send(prompts.finishTaskDone, { task: results.response.entity });
} else {
session.send(prompts.canceled);
}
}
]);
/** Shows the user a list of tasks. */
dialog.on('ListTasks', function (session) {
if (session.channelData.tasks && session.channelData.tasks.length > 0) {
var list = '';
session.channelData.tasks.forEach(function (value, index) {
list += session.gettext(prompts.listTaskItem, { index: index + 1, task: value });
});
session.send(prompts.listTaskList, list);
}
else {
session.send(prompts.listNoTasks);
}
});

Просмотреть файл

@ -0,0 +1,15 @@
module.exports = {
helpMessage: "Here's what I can do:\n\n" +
"* Create new tasks by saying something like 'add a new task to go to the gym'\n" +
"* List your existing tasks by saying something like 'what do I have to do?'\n" +
"* Finish an existing task by saying something like 'remove go to the gym'",
canceled: 'Sure... No problem.',
saveTaskCreated: "Created a new task called '%(task)s'",
saveTaskMissing: 'What would you like to name the task?',
listTaskList: 'Tasks\n%s',
listTaskItem: '%(index)d. %(task)s\n',
listNoTasks: 'You have no tasks.',
finishTaskMissing: "Which task would you like to delete?",
finishTaskDone: "Removed '%(task)s' from your task list."
}

39
Node/lib/botbuilder.d.ts поставляемый
Просмотреть файл

@ -506,7 +506,7 @@ export interface ISkypeBotOptions {
/** Storage system to use for persisting Session.sessionState values. By default the MemoryStorage is used. */
sessionStore?: IStorage;
/** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
/** Maximum time (in milliseconds) since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
maxSessionAge?: number;
/** Optional localizer used to localize the bots responses to the user. */
@ -536,7 +536,7 @@ export interface ISkypeBotOptions {
/** Options used to configure the SlackBot. */
export interface ISlackBotOptions {
/** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
/** Maximum time (in milliseconds) since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
maxSessionAge?: number;
/** Optional localizer used to localize the bots responses to the user. */
@ -547,6 +547,9 @@ export interface ISlackBotOptions {
/** Optional arguments to pass to the initial dialog for a conversation. */
defaultDialogArgs?: any;
/** Maximum time (in milliseconds) that a bot continues to recieve ambient messages after its been @mentioned. Default 5 minutes. */
ambientMentionDuration?: number;
}
/** Address info passed to SlackBot.beginDialog() calls. Specifies the address of the user or channel to start a conversation with. */
@ -572,7 +575,7 @@ export interface ITextBotOptions {
/** Storage system to use for persisting Session.sessionState values. By default the MemoryStorage is used. */
sessionStore?: IStorage;
/** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
/** Maximum time (in milliseconds) since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
maxSessionAge?: number;
/** Optional localizer used to localize the bots responses to the user. */
@ -587,8 +590,7 @@ export interface ITextBotOptions {
/** Signature for function passed as a step to DialogAction.waterfall(). */
export interface IDialogWaterfallStep {
<T>(session: Session, result?: T, skip?: (count?: number, results?: IDialogResult<any>) => void): any;
<T>(session: Session, result?: IDialogResult<T>, skip?: (count?: number, results?: IDialogResult<any>) => void): any;
<T>(session: Session, result?: IDialogResult<T>, skip?: (results?: IDialogResult<any>) => void): any;
}
@ -1559,11 +1561,10 @@ export class SlackBot extends DialogCollection {
/**
* Creates a new instance of the Slack bot using BotKit.
* @param controller Controller created from a call to Botkit.slackbot().
* @param bot Optional bot created from a call to controller.spawn(). If you don't pass this in
* you won't be able to initiate outgoing conversations using SlackBot.beginDialog().
* @param bot The bot created from a call to controller.spawn().
* @param options Optional configuration settings for the bot.
*/
constructor(controller: any, bot?: any, options?: ISlackBotOptions);
constructor(controller: any, bot: any, options?: ISlackBotOptions);
/**
* Registers an event listener to get notified of bot related events.
@ -1572,10 +1573,11 @@ export class SlackBot extends DialogCollection {
* - reply: A reply to an existing message was sent. [IBotMessageEvent]
* - send: A new message was sent to start a new conversation. [IBotMessageEvent]
* - quit: The bot has elected to ended the current conversation. [IBotMessageEvent]
* - ambient: An ambient message was received. [IBotMessageEvent]
* - direct_mention: The bot was directly mentioned in a message. [IBotMessageEvent]
* - mention: The bot was mentioned in a message. [IBotMessageEvent]
* - direct_message: The bot has received a direct 1:1 chat message. [IBotMessageEvent]
* - message_received: The bot received a message. [IBotMessageEvent]
* - bot_channel_join: The bot has joined a channel. [IBotMessageEvent]
* - user_channel_join: A user has joined a channel. [IBotMessageEvent]
* - bot_group_join: The bot has joined a group. [IBotMessageEvent]
* - user_group_join: A user has joined a group. [IBotMessageEvent]
* @param event Name of event to listen for.
* @param listener Function to invoke.
*/
@ -1593,9 +1595,22 @@ export class SlackBot extends DialogCollection {
* - direct_mention: Direct mentions are messages that begin with the bot's name, as in "@bot hello".
* - mention: Mentions are messages that contain the bot's name, but not at the beginning, as in "hello @bot".
* - direct_message: Direct messages are sent via private 1:1 direct message channels.
* @param types The type of events to listen for,
* @param dialogId Optional ID of the bots dialog to begin for new conversations.
* @param dialogArgs Optional arguments to pass to the dialog.
*/
listen(types: string[], dialogId?: string, dialogArgs?: any): SlackBot;
/**
* Begins listening for messages sent to the bot. The bot will recieve direct messages,
* direct mentions, and mentions. One the bot has been mentioned it will continue to receive
* ambient messages from the user that mentioned them for a short period of time. This time
* can be configured using ISlackBotOptions.ambientMentionDuration.
* @param dialogId Optional ID of the bots dialog to begin for new conversations.
* @param dialogArgs Optional arguments to pass to the dialog.
*/
listenForMentions(dialogId?: string, dialogArgs?: any): SlackBot;
/**
* Starts a new conversation with a user.
* @param address Address of the user to begin the conversation with.

Просмотреть файл

@ -10,14 +10,21 @@ var utils = require('../utils');
var SlackBot = (function (_super) {
__extends(SlackBot, _super);
function SlackBot(controller, bot, options) {
var _this = this;
_super.call(this);
this.controller = controller;
this.bot = bot;
this.options = {
maxSessionAge: 14400000,
defaultDialogId: '/'
defaultDialogId: '/',
ambientMentionDuration: 300000 // <-- default duration of 5 minutes
};
this.configure(options);
['message_received', 'bot_channel_join', 'user_channel_join', 'bot_group_join', 'user_group_join'].forEach(function (type) {
_this.controller.on(type, function (bot, msg) {
_this.emit(type, bot, msg);
});
});
}
SlackBot.prototype.configure = function (options) {
if (options) {
@ -31,17 +38,56 @@ var SlackBot = (function (_super) {
};
SlackBot.prototype.listen = function (types, dialogId, dialogArgs) {
var _this = this;
dialogId = dialogId || this.options.defaultDialogId;
dialogArgs = dialogArgs || this.options.defaultDialogArgs;
types.forEach(function (type) {
_this.controller.on(type, function (bot, msg) {
bot.identifyTeam(function (err, teamId) {
msg.team = teamId;
_this.emit(type, msg);
_this.dispatchMessage(bot, msg, dialogId || _this.options.defaultDialogId, dialogArgs || _this.options.defaultDialogArgs);
_this.dispatchMessage(bot, msg, dialogId, dialogArgs);
});
});
});
return this;
};
SlackBot.prototype.listenForMentions = function (dialogId, dialogArgs) {
var _this = this;
var sessions = {};
var dispatch = function (bot, msg, ss) {
bot.identifyTeam(function (err, teamId) {
msg.team = teamId;
_this.dispatchMessage(bot, msg, dialogId, dialogArgs, ss);
});
};
dialogId = dialogId || this.options.defaultDialogId;
dialogArgs = dialogArgs || this.options.defaultDialogArgs;
this.controller.on('direct_message', function (bot, msg) {
dispatch(bot, msg);
});
['direct_mention', 'mention'].forEach(function (type) {
_this.controller.on(type, function (bot, msg) {
// Create a new session
var key = msg.channel + ':' + msg.user;
var ss = sessions[key] = { callstack: [], lastAccess: new Date().getTime() };
dispatch(bot, msg, ss);
});
});
this.controller.on('ambient', function (bot, msg) {
// Conditionally dispatch the message
var key = msg.channel + ':' + msg.user;
if (sessions.hasOwnProperty(key)) {
// Validate session
var ss = sessions[key];
if (ss.callstack && ss.callstack.length > 0 && (new Date().getTime() - ss.lastAccess) <= _this.options.ambientMentionDuration) {
dispatch(bot, msg, ss);
}
else {
delete sessions[key];
}
}
});
return this;
};
SlackBot.prototype.beginDialog = function (address, dialogId, dialogArgs) {
// Validate args
if (!address.user && !address.channel) {
@ -54,7 +100,7 @@ var SlackBot = (function (_super) {
this.dispatchMessage(null, address, dialogId, dialogArgs);
return this;
};
SlackBot.prototype.dispatchMessage = function (bot, msg, dialogId, dialogArgs) {
SlackBot.prototype.dispatchMessage = function (bot, msg, dialogId, dialogArgs, smartState) {
var _this = this;
var onError = function (err) {
_this.emit('error', err, msg);
@ -71,7 +117,7 @@ var SlackBot = (function (_super) {
var teamData = ses.teamData && ses.teamData.id ? utils.clone(ses.teamData) : null;
var channelData = ses.channelData && ses.channelData.id ? utils.clone(ses.channelData) : null;
var userData = ses.userData && ses.userData.id ? utils.clone(ses.userData) : null;
if (channelData) {
if (channelData && !smartState) {
channelData[consts.Data.SessionState] = ses.sessionState;
}
// Save data
@ -120,7 +166,10 @@ var SlackBot = (function (_super) {
data.userData = { id: msg.user };
}
// Unpack session state
if (data.channelData && data.channelData.hasOwnProperty(consts.Data.SessionState)) {
if (smartState) {
sessionState = smartState;
}
else if (data.channelData && data.channelData.hasOwnProperty(consts.Data.SessionState)) {
sessionState = data.channelData[consts.Data.SessionState];
delete data.channelData[consts.Data.SessionState];
}

Просмотреть файл

@ -41,10 +41,11 @@ var DialogAction = (function () {
};
DialogAction.waterfall = function (steps) {
return function waterfallAction(s, r) {
var skip = function (count, result) {
if (count === void 0) { count = 1; }
result = result || { resumed: dialog.ResumeReason.forward };
s.dialogData[consts.Data.WaterfallStep] += count;
var skip = function (result) {
result = result || {};
if (!result.resumed) {
result.resumed = dialog.ResumeReason.forward;
}
waterfallAction(s, result);
};
try {
@ -56,9 +57,6 @@ var DialogAction = (function () {
case dialog.ResumeReason.back:
step -= 1;
break;
case dialog.ResumeReason.forward:
step += 2;
break;
default:
step++;
}

39
Node/src/botbuilder.d.ts поставляемый
Просмотреть файл

@ -506,7 +506,7 @@ export interface ISkypeBotOptions {
/** Storage system to use for persisting Session.sessionState values. By default the MemoryStorage is used. */
sessionStore?: IStorage;
/** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
/** Maximum time (in milliseconds) since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
maxSessionAge?: number;
/** Optional localizer used to localize the bots responses to the user. */
@ -536,7 +536,7 @@ export interface ISkypeBotOptions {
/** Options used to configure the SlackBot. */
export interface ISlackBotOptions {
/** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
/** Maximum time (in milliseconds) since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
maxSessionAge?: number;
/** Optional localizer used to localize the bots responses to the user. */
@ -547,6 +547,9 @@ export interface ISlackBotOptions {
/** Optional arguments to pass to the initial dialog for a conversation. */
defaultDialogArgs?: any;
/** Maximum time (in milliseconds) that a bot continues to recieve ambient messages after its been @mentioned. Default 5 minutes. */
ambientMentionDuration?: number;
}
/** Address info passed to SlackBot.beginDialog() calls. Specifies the address of the user or channel to start a conversation with. */
@ -572,7 +575,7 @@ export interface ITextBotOptions {
/** Storage system to use for persisting Session.sessionState values. By default the MemoryStorage is used. */
sessionStore?: IStorage;
/** Maximum time since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
/** Maximum time (in milliseconds) since ISessionState.lastAccess before the current session state is discarded. Default is 4 hours. */
maxSessionAge?: number;
/** Optional localizer used to localize the bots responses to the user. */
@ -587,8 +590,7 @@ export interface ITextBotOptions {
/** Signature for function passed as a step to DialogAction.waterfall(). */
export interface IDialogWaterfallStep {
<T>(session: Session, result?: T, skip?: (count?: number, results?: IDialogResult<any>) => void): any;
<T>(session: Session, result?: IDialogResult<T>, skip?: (count?: number, results?: IDialogResult<any>) => void): any;
<T>(session: Session, result?: IDialogResult<T>, skip?: (results?: IDialogResult<any>) => void): any;
}
@ -1559,11 +1561,10 @@ export class SlackBot extends DialogCollection {
/**
* Creates a new instance of the Slack bot using BotKit.
* @param controller Controller created from a call to Botkit.slackbot().
* @param bot Optional bot created from a call to controller.spawn(). If you don't pass this in
* you won't be able to initiate outgoing conversations using SlackBot.beginDialog().
* @param bot The bot created from a call to controller.spawn().
* @param options Optional configuration settings for the bot.
*/
constructor(controller: any, bot?: any, options?: ISlackBotOptions);
constructor(controller: any, bot: any, options?: ISlackBotOptions);
/**
* Registers an event listener to get notified of bot related events.
@ -1572,10 +1573,11 @@ export class SlackBot extends DialogCollection {
* - reply: A reply to an existing message was sent. [IBotMessageEvent]
* - send: A new message was sent to start a new conversation. [IBotMessageEvent]
* - quit: The bot has elected to ended the current conversation. [IBotMessageEvent]
* - ambient: An ambient message was received. [IBotMessageEvent]
* - direct_mention: The bot was directly mentioned in a message. [IBotMessageEvent]
* - mention: The bot was mentioned in a message. [IBotMessageEvent]
* - direct_message: The bot has received a direct 1:1 chat message. [IBotMessageEvent]
* - message_received: The bot received a message. [IBotMessageEvent]
* - bot_channel_join: The bot has joined a channel. [IBotMessageEvent]
* - user_channel_join: A user has joined a channel. [IBotMessageEvent]
* - bot_group_join: The bot has joined a group. [IBotMessageEvent]
* - user_group_join: A user has joined a group. [IBotMessageEvent]
* @param event Name of event to listen for.
* @param listener Function to invoke.
*/
@ -1593,9 +1595,22 @@ export class SlackBot extends DialogCollection {
* - direct_mention: Direct mentions are messages that begin with the bot's name, as in "@bot hello".
* - mention: Mentions are messages that contain the bot's name, but not at the beginning, as in "hello @bot".
* - direct_message: Direct messages are sent via private 1:1 direct message channels.
* @param types The type of events to listen for,
* @param dialogId Optional ID of the bots dialog to begin for new conversations.
* @param dialogArgs Optional arguments to pass to the dialog.
*/
listen(types: string[], dialogId?: string, dialogArgs?: any): SlackBot;
/**
* Begins listening for messages sent to the bot. The bot will recieve direct messages,
* direct mentions, and mentions. One the bot has been mentioned it will continue to receive
* ambient messages from the user that mentioned them for a short period of time. This time
* can be configured using ISlackBotOptions.ambientMentionDuration.
* @param dialogId Optional ID of the bots dialog to begin for new conversations.
* @param dialogArgs Optional arguments to pass to the dialog.
*/
listenForMentions(dialogId?: string, dialogArgs?: any): SlackBot;
/**
* Starts a new conversation with a user.
* @param address Address of the user to begin the conversation with.

Просмотреть файл

@ -54,6 +54,7 @@ export interface ISlackBotOptions {
localizer?: ILocalizer;
defaultDialogId?: string;
defaultDialogArgs?: any;
ambientMentionDuration?: number;
}
export interface ISlackBeginDialogAddress {
@ -64,14 +65,20 @@ export interface ISlackBeginDialogAddress {
}
export class SlackBot extends collection.DialogCollection {
protected options: ISlackBotOptions = {
private options: ISlackBotOptions = {
maxSessionAge: 14400000, // <-- default max session age of 4 hours
defaultDialogId: '/'
defaultDialogId: '/',
ambientMentionDuration: 300000 // <-- default duration of 5 minutes
};
constructor(protected controller: BotKitController, protected bot: Bot, options?: ISlackBotOptions) {
constructor(private controller: BotKitController, private bot: Bot, options?: ISlackBotOptions) {
super();
this.configure(options);
['message_received','bot_channel_join','user_channel_join','bot_group_join','user_group_join'].forEach((type) => {
this.controller.on(type, (bot: Bot, msg: ISlackMessage) => {
this.emit(type, bot, msg);
});
});
}
public configure(options: ISlackBotOptions): this {
@ -86,18 +93,57 @@ export class SlackBot extends collection.DialogCollection {
}
public listen(types: string[], dialogId?: string, dialogArgs?: any): this {
dialogId = dialogId || this.options.defaultDialogId;
dialogArgs = dialogArgs || this.options.defaultDialogArgs;
types.forEach((type) => {
this.controller.on(type, (bot: Bot, msg: ISlackMessage) => {
bot.identifyTeam((err, teamId) => {
msg.team = teamId;
this.emit(type, msg);
this.dispatchMessage(bot, msg, dialogId || this.options.defaultDialogId, dialogArgs || this.options.defaultDialogArgs);
this.dispatchMessage(bot, msg, dialogId, dialogArgs);
});
});
});
return this;
}
public listenForMentions(dialogId?: string, dialogArgs?: any): this {
var sessions: { [key: string]: ISessionState; } = {};
var dispatch = (bot: Bot, msg: ISlackMessage, ss?: ISessionState) => {
bot.identifyTeam((err, teamId) => {
msg.team = teamId;
this.dispatchMessage(bot, msg, dialogId, dialogArgs, ss);
});
};
dialogId = dialogId || this.options.defaultDialogId;
dialogArgs = dialogArgs || this.options.defaultDialogArgs;
this.controller.on('direct_message', (bot: Bot, msg: ISlackMessage) => {
dispatch(bot, msg);
});
['direct_mention','mention'].forEach((type) => {
this.controller.on(type, (bot: Bot, msg: ISlackMessage) => {
// Create a new session
var key = msg.channel + ':' + msg.user;
var ss = sessions[key] = { callstack: <any>[], lastAccess: new Date().getTime() };
dispatch(bot, msg, ss);
});
});
this.controller.on('ambient', (bot: Bot, msg: ISlackMessage) => {
// Conditionally dispatch the message
var key = msg.channel + ':' + msg.user;
if (sessions.hasOwnProperty(key)) {
// Validate session
var ss = sessions[key];
if (ss.callstack && ss.callstack.length > 0 && (new Date().getTime() - ss.lastAccess) <= this.options.ambientMentionDuration) {
dispatch(bot, msg, ss);
} else {
delete sessions[key];
}
}
});
return this;
}
public beginDialog(address: ISlackBeginDialogAddress, dialogId: string, dialogArgs?: any): this {
// Validate args
if (!address.user && !address.channel) {
@ -112,7 +158,7 @@ export class SlackBot extends collection.DialogCollection {
return this;
}
private dispatchMessage(bot: Bot, msg: ISlackMessage, dialogId: string, dialogArgs: any) {
private dispatchMessage(bot: Bot, msg: ISlackMessage, dialogId: string, dialogArgs: any, smartState?: ISessionState) {
var onError = (err: Error) => {
this.emit('error', err, msg);
};
@ -129,7 +175,7 @@ export class SlackBot extends collection.DialogCollection {
var teamData = ses.teamData && ses.teamData.id ? utils.clone(ses.teamData) : null;
var channelData = ses.channelData && ses.channelData.id ? utils.clone(ses.channelData) : null;
var userData = ses.userData && ses.userData.id ? utils.clone(ses.userData) : null;
if (channelData) {
if (channelData && !smartState) {
channelData[consts.Data.SessionState] = ses.sessionState;
}
@ -179,7 +225,9 @@ export class SlackBot extends collection.DialogCollection {
}
// Unpack session state
if (data.channelData && data.channelData.hasOwnProperty(consts.Data.SessionState)) {
if (smartState) {
sessionState = smartState;
} else if (data.channelData && data.channelData.hasOwnProperty(consts.Data.SessionState)) {
sessionState = (<any>data.channelData)[consts.Data.SessionState];
delete (<any>data.channelData)[consts.Data.SessionState];
}

Просмотреть файл

@ -3,11 +3,7 @@ import dialog = require('./Dialog');
import consts = require('../consts');
export interface IDialogWaterfallStep {
(session: ISession, result?: any, skip?: IDialogWaterfallCursor): void;
}
export interface IDialogWaterfallCursor {
(count?: number, results?: dialog.IDialogResult<any>): void;
(session: ISession, result?: any, skip?: (results?: dialog.IDialogResult<any>) => void): void;
}
export class DialogAction {
@ -48,9 +44,11 @@ export class DialogAction {
static waterfall(steps: IDialogWaterfallStep[]): IDialogHandler<any> {
return function waterfallAction(s: ISession, r: dialog.IDialogResult<any>) {
var skip = (count = 1, result?: dialog.IDialogResult<any>) => {
result = result || { resumed: dialog.ResumeReason.forward };
s.dialogData[consts.Data.WaterfallStep] += count;
var skip = (result?: dialog.IDialogResult<any>) => {
result = result || <any>{};
if (!result.resumed) {
result.resumed = dialog.ResumeReason.forward;
}
waterfallAction(s, result);
};
@ -63,9 +61,6 @@ export class DialogAction {
case dialog.ResumeReason.back:
step -= 1;
break;
case dialog.ResumeReason.forward:
step += 2;
break;
default:
step++;
}