Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
Merge branch 'master' of github.com:howdyai/botkit
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Brown committed Jan 29, 2018
2 parents 61016a8 + dc0e780 commit 9cf9bd0
Show file tree
Hide file tree
Showing 11 changed files with 477 additions and 178 deletions.
63 changes: 63 additions & 0 deletions __test__/lib/Teams.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use strict';
let teamsBot;
let teamsApi;
let config = {
clientId: 'client',
clientSecret: 'secret',
debug: false
};

describe('authentication', () => {
beforeEach(() => {
jest.doMock('../../lib/TeamsAPI', () => {
return jest.fn((configuration) => {
return {
getToken: jest.fn((cb) => {
configuration.token = 'token';
configuration.token_expires_in = '3600';
cb(null);
})
};
});
});

jest.useFakeTimers();
teamsApi = require('../../lib/TeamsAPI');
});

afterEach(() => {
jest.resetModules();
jest.clearAllTimers();
});

test('get token', () => {
let bot = require('../../lib/Teams')(config);
expect(bot.api.getToken).toHaveBeenCalledTimes(1);
});

test('refresh token before expiry', () => {
let bot = require('../../lib/Teams')(config);
expect(bot.api.getToken).toHaveBeenCalledTimes(1);
jest.runOnlyPendingTimers();
expect(bot.api.getToken).toHaveBeenCalledTimes(2);
});

test('token valid for 20 mins should refresh after 10 mins', () => {
teamsApi.mockImplementation(jest.fn((configuration) => {
return {
getToken: jest.fn((cb) => {
configuration.token = 'token';
configuration.token_expires_in = '1200';
cb(null);
})
};
}));
let bot = require('../../lib/Teams')(config);
expect(bot.config.token_expires_in).toBe('1200');
expect(bot.api.getToken).toHaveBeenCalledTimes(1);
jest.runTimersToTime(1000 * 60 * 9);
expect(bot.api.getToken).toHaveBeenCalledTimes(1);
jest.runTimersToTime(1000 * 60 * 1.1);
expect(bot.api.getToken).toHaveBeenCalledTimes(2);
});
});
15 changes: 15 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@

[Want to contribute? Read our guide!](https://github.com/howdyai/botkit/blob/master/CONTRIBUTING.md)

# 0.6.9

* Add 2 new middleware endpoints that occur during conversations - `conversationStart(bot, convo, next)` and `conversationEnd(bot, convo, next)`. [Some new documentation](docs/readme.md#conversation-events-and-middleware-endpoints)
* Conversations powered by Botkit Studio will now include `convo.context.script_name` and `convo.context.script_id` which point back to the script loaded from the Botkit Studio API
* When using Botkit Studio's execute script action, the resulting conversation object will have 2 additional context fields: `convo.context.transition_from` and `convo.context.transition_from_id` which will point to the script from which the user transitioned
* When using Botkit Studio's execute script action, the original conversation from which the user is transitioning will have 2 additional context fields: `convo.context.transition_to` and `convo.context.transition_to_id` which will point to the script to which the user transitioned
* Fix for Botkit Studio scripts which used "end and mark successful" action from a condition. Previously this would end, but not mark successful.

Merged Pull Requests:
* Make sure Facebook API errors are passed to callback if specified [PR #1225](https://github.com/howdyai/botkit/pull/1225)
* Refresh Microsoft Teams token when it has expired. [PR #1230](https://github.com/howdyai/botkit/pull/1230)
* Update TypeScript definition for Web bots [PR #1231](https://github.com/howdyai/botkit/pull/1231)
* Update TypeScript definition for bot.replyAndUpdate [PR #1232](https://github.com/howdyai/botkit/pull/1232)
* Fix for Microsoft teams button builder function [PR #1233](https://github.com/howdyai/botkit/pull/1233)

# 0.6.8

BIG UPDATE:
Expand Down
2 changes: 1 addition & 1 deletion docs/provisioning/slack-events-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Your bot is now ready to receive messages!

## 7. Add your bot to your Slack team

Now that your bot is configured, and your appliacation is up and running, you can login and add your bot. Visit `https://MYURL/`, and you will be automatically directed to Slack's login page. Login and choose a team. You'll get one more confirmation before being redirected back to your app.
Now that your bot is configured, and your application is up and running, you can login and add your bot. Visit `https://MYURL/`, and you will be automatically directed to Slack's login page. Login and choose a team. You'll get one more confirmation before being redirected back to your app.

Meanwhile, your bot should appear inside your Slack team. You should receive a friendly welcome message to indicates your bot is now online and working!

Expand Down
2 changes: 1 addition & 1 deletion docs/readme-facebook.md
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ Allows the Primary Receiver app to retrieve the list of apps that are Secondary

- To retrieve the list of Secondary Receivers:
```javascript
controller.api.handover.get_secondary_receivers_list('id,name', function (result) {
controller.api.handover.get_secondary_receivers_list('id,name', function (err, result) {
// result.data = list of Secondary Receivers
});
```
Expand Down
57 changes: 52 additions & 5 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ Only the user who sent the original incoming message will be able to respond to
| message | message object containing {user: userId} of the user you would like to start a conversation with
| callback | a callback function in the form of function(err,conversation) { ... }

`startPrivateConversation()` is a function that initiates a conversation with a specific user. Note function is currently *Slack-only!*
`startPrivateConversation()` is a function that initiates a conversation with a specific user. Note that this function only works on platforms with multiple channels where there are public and private channels, like Slack, Microsoft Teams and Cisco Spark.

#### bot.createConversation()
| Argument | Description
Expand Down Expand Up @@ -712,7 +712,7 @@ Set the action field of a message to `stop` end immediately, but mark as failed.

Set the action field of a message to `timeout` to end immediately and indicate that the conversation has timed out.

After the conversation ends, these values will be available in the `convo.status` field. This field can then be used to check the final outcome of a conversation. See [handling the end of conversations](#handling-end-of-conversation).
After the conversation ends, these values will be available in the `convo.status` field. This field can then be used to check the final outcome of a conversation. See [handling the end of conversations](#conversation-events-and-middleware-endpoints).

### Using Variable Tokens and Templates in Conversation Threads

Expand Down Expand Up @@ -796,10 +796,57 @@ so that it is sent immediately, before any other queued messages.

`convo.setTimeout(timeout)` times out conversation if no response from user after specified time period (in milliseconds).

### Handling End of Conversation
### Conversation Events and Middleware Endpoints

As conversations are conducted, Botkit will emit several types of events, and fire any developer-specified middleware functions that allow the conversation object to be observed and modified.

**Conversation Events**

| Event Name | Description
|--- |---
| conversationStarted | A conversation has begun
| conversationEnded | A conversation has ended

These events are emitted by the main Botkit controller, not the conversation object. They will fire for all conversations. This should not be confused with the `end` event emitted by an individual conversation object, [detailed here](#handling-end-of-conversation).

Note that the signature for handler functions for these events is slightly different than other Botkit events.
The second parameter for these events is the conversation object, not an individual message event.

```js
controller.on('conversationStarted', function(bot, convo) {
// do something with this convo object
});

controller.on('conversationEnded', function(bot, convo) {
// do something with this convo object
});
```

Conversations trigger events during the course of their life. Currently,
only two events are fired, and only one is very useful: end.
**Conversation Middleware Endpoints**

| Endpoint | Description
|--- |---
| conversationStart | Fires when a conversation is activated, but before the first message is sent
| conversationEnd | Fires after a conversation is over, but the conversation object is marked completed

If you need to not only inspect but also modify the content of the conversation as it begins or ends, use middleware instead of event handlers. Middleware functions registered to these endpoints fire as the conversation transitions from one state to another, and fire synchronously, in the order they are added. They can be used to do things like initialize conversations with variable values, or capture responses to a database on a global basis.

```js
controller.middleware.conversationStart.use(function(bot, convo, next) {
console.log('CONVERSATION START MIDDLEWARE!!!!');

// this variable will be available in EVERY SINGLE conversation!
convo.setVar('foo','bar');
next();
});

controller.middleware.conversationEnd.use(function(bot, convo, next) {
console.log('CONVERSATION END MIDDLEWARE!!!!');
next();
});
```

### Handling End of Conversation

Conversations end naturally when the last message has been sent and no messages remain in the queue.
In this case, the value of `convo.status` will be `completed`. Other values for this field include `active`, `stopped`, and `timeout`.
Expand Down
24 changes: 15 additions & 9 deletions lib/Botkit.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ declare namespace botkit {
function sparkbot(configuration: CiscoSparkConfiguration): CiscoSparkController;
function twilioipmbot(configuration: TwilioIPMConfiguration): TwilioIPMController;
function twiliosmsbot(configuration: TwilioSMSConfiguration): TwilioSMSController;
function socketbot(configuration: SocketConfiguration): SocketController;
function socketbot(configuration: WebConfiguration): WebController;
function anywhere(configuration: WebConfiguration): WebController;

interface Bot<S, M extends Message> {
readonly botkit: Controller<S, M, this>;
readonly identity: Identity;
Expand Down Expand Up @@ -244,6 +246,9 @@ declare namespace botkit {
title_link?: string;
ts?: string;
}
interface SlackUpdateMessageCallback {
(newResponse: string | SlackMessage, cb?: (err: Error) => void): void
}
interface SlackBot extends Bot<SlackSpawnConfiguration, SlackMessage> {
readonly api: SlackWebAPI;
configureIncomingWebhook(config: { url: string; }): this;
Expand All @@ -254,7 +259,7 @@ declare namespace botkit {
identifyTeam(): string;
identifyBot(): { id: string; name: string; team_id: string; };
replyAcknowledge(cb?: (err: Error) => void): void;
replyAndUpdate(src: SlackMessage, resp: string | SlackMessage, cb: (err: Error, res: string) => void): void;
replyAndUpdate(src: SlackMessage, resp: string | SlackMessage, cb: (err: Error, res: string, updateResponse: SlackUpdateMessageCallback) => void): void;
replyInThread(src: SlackMessage, resp: string | SlackMessage, cb: (err: Error, res: string) => void): void;
replyPrivate(src: SlackMessage, resp: string | SlackMessage, cb?: (err: Error) => void): void;
replyPrivateDelayed(src: SlackMessage, resp: string | SlackMessage, cb?: (err: Error) => void): void;
Expand Down Expand Up @@ -492,21 +497,22 @@ declare namespace botkit {
}
interface TwilioSMSSpawnConfiguration {
}
interface SocketBot extends Bot<SocketSpawnConfiguration, SocketMessage> {
send(src: SocketMessage, cb?: (err: Error, res: any) => void): void;
findConversation(message: SocketMessage, cb: (convo?: Conversation<SocketMessage>) => void): void;
interface WebBot extends Bot<WebSpawnConfiguration, WebMessage> {
connected: boolean;
send(src: WebMessage, cb?: (err: Error, res: any) => void): void;
findConversation(message: WebMessage, cb: (convo?: Conversation<WebMessage>) => void): void;
}
interface SocketConfiguration extends Configuration {
interface WebConfiguration extends Configuration {
replyWithTyping?: boolean;
}
interface SocketController extends Controller<SocketSpawnConfiguration, SocketMessage, SocketBot> {
interface WebController extends Controller<WebSpawnConfiguration, WebMessage, WebBot> {
httpserver: http.Server;
webserver: express.Express;
openSocketServer(server: http.Server): void;
}
export interface SocketMessage extends Message {
export interface WebMessage extends Message {
}
interface SocketSpawnConfiguration {
interface WebSpawnConfiguration {
}
interface User {
id: string;
Expand Down
61 changes: 42 additions & 19 deletions lib/CoreBot.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ function Botkit(configuration) {
capture: ware(),
format: ware(),
send: ware(),
conversationStart: ware(),
conversationEnd: ware(),
};

// list of events to exclude from conversations
Expand Down Expand Up @@ -152,6 +154,8 @@ function Botkit(configuration) {
user: message.user,
channel: message.channel,
bot: task.bot,
script_name: message.script_name || null,
script_id: message.script_id || null,
};

this.events = {};
Expand Down Expand Up @@ -270,9 +274,14 @@ function Botkit(configuration) {
};

this.activate = function() {
this.task.trigger('conversationStarted', [this]);
this.task.botkit.trigger('conversationStarted', [this.task.bot, this]);
this.status = 'active';

botkit.middleware.conversationStart.run(this.task.bot, this, function(err, bot, convo) {

convo.status = 'active';
convo.task.trigger('conversationStarted', [convo]);
convo.task.botkit.trigger('conversationStarted', [bot, convo]);

});
};

/**
Expand Down Expand Up @@ -344,10 +353,17 @@ function Botkit(configuration) {
if (condition.execute) {
var script = condition.execute.script;
var thread = condition.execute.thread;
that.stop('transitioning to ' + script);
botkit.studio.get(that.context.bot, script, that.source_message.user, that.source_message.channel, that.source_message).then(function(new_convo) {

that.context.transition_to = new_convo.context.script_name || null;
that.context.transition_to_id = new_convo.context.script_id || null;
that.stop('transitioning to ' + script);

new_convo.responses = that.responses;
new_convo.vars = that.vars;
new_convo.context.transition_from = that.context.script_name || null;
new_convo.context.transition_from_id = that.context.script_id || null;

new_convo.gotoThread(thread);
new_convo.activate();
});
Expand Down Expand Up @@ -909,6 +925,8 @@ function Botkit(configuration) {
this.silentRepeat();
} else if (message.action == 'stop') {
this.stop();
} else if (message.action == 'complete') {
this.stop('completed');
} else if (message.action == 'timeout') {
this.stop('timeout');
} else if (this.threads[message.action]) {
Expand Down Expand Up @@ -958,34 +976,39 @@ function Botkit(configuration) {
var convo = new Conversation(this, message);
convo.id = botkit.convoCount++;
this.convos.push(convo);

return convo;
};

this.startConversation = function(message) {

var convo = this.createConversation(message);
botkit.log('> [Start] ', convo.id, ' Conversation with ', message.user, 'in', message.channel);
botkit.debug('> [Start] ', convo.id, ' Conversation with ', message.user, 'in', message.channel);

convo.activate();
return convo;
};

this.conversationEnded = function(convo) {
botkit.log('> [End] ', convo.id, ' Conversation with ',
convo.source_message.user, 'in', convo.source_message.channel);
this.trigger('conversationEnded', [convo]);
this.botkit.trigger('conversationEnded', [bot, convo]);
convo.trigger('end', [convo]);
var actives = 0;
for (var c = 0; c < this.convos.length; c++) {
if (this.convos[c].isActive()) {
actives++;
}
}
if (actives == 0) {
this.taskEnded();
}

var that = this;
botkit.middleware.conversationEnd.run(this.bot, convo, function(err, bot, convo) {

botkit.debug('> [End] ', convo.id, ' Conversation with ',
convo.source_message.user, 'in', convo.source_message.channel);
that.trigger('conversationEnded', [convo]);
that.botkit.trigger('conversationEnded', [bot, convo]);
convo.trigger('end', [convo]);
var actives = 0;
for (var c = 0; c < that.convos.length; c++) {
if (that.convos[c].isActive()) {
actives++;
}
}
if (actives == 0) {
that.taskEnded();
}
});
};

this.endImmediately = function(reason) {
Expand Down
Loading

0 comments on commit 9cf9bd0

Please sign in to comment.