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

Commit 18e2c79

Browse files
committed
Fix for #1961: add teams helper features
1 parent 9090887 commit 18e2c79

File tree

6 files changed

+239
-16
lines changed

6 files changed

+239
-16
lines changed

packages/botkit/src/adapter.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
import { BotFrameworkAdapter, TurnContext } from 'botbuilder';
99
import { ConnectorClient, TokenApiClient } from 'botframework-connector';
10+
import { TeamsBotWorker } from './teamsHelpers';
1011
import * as request from 'request';
1112
import * as os from 'os';
1213

@@ -27,19 +28,8 @@ const USER_AGENT: string = `Microsoft-BotFramework/3.1 Botkit/${ pjson.version }
2728
* * Adds middleware for adjusting location of tenant id field (MS Teams)
2829
*/
2930
export class BotkitBotFrameworkAdapter extends BotFrameworkAdapter {
30-
public constructor(options) {
31-
super(options);
3231

33-
// Fix a (temporary) issue with transitional location of MS Teams tenantId
34-
// this fix should already be present in botbuilder 4.4
35-
// when/if that happens, this can be removed.
36-
this.use(async (context, next) => {
37-
if (!context.activity.conversation.tenantId && context.activity.channelData && context.activity.channelData.tenant) {
38-
context.activity.conversation.tenantId = context.activity.channelData.tenant.id;
39-
}
40-
await next();
41-
});
42-
}
32+
public botkit_worker = TeamsBotWorker;
4333

4434
/**
4535
* Allows for mocking of the connector client in unit tests.

packages/botkit/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export * from './core';
1010
export * from './conversation';
1111
export * from './botworker';
1212
export * from './dialogWrapper';
13+
export * from './teamsHelpers';
1314
export * from './testClient';

packages/botkit/src/teamsHelpers.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* @module botkit
3+
*/
4+
/**
5+
* Copyright (c) Microsoft Corporation. All rights reserved.
6+
* Licensed under the MIT License.
7+
*/
8+
import { Botkit, BotkitMessage } from './core';
9+
import { BotWorker } from './botworker';
10+
import { TeamsInfo, MiddlewareSet, TurnContext, TaskModuleTaskInfo } from 'botbuilder';
11+
12+
/**
13+
* An extension of the core BotWorker class that exposes the TeamsInfo helper class for MS Teams.
14+
* This BotWorker is used with the built-in Bot Framework adapter.
15+
*/
16+
export class TeamsBotWorker extends BotWorker {
17+
18+
public constructor(controller: Botkit, config) {
19+
super(controller, config);
20+
}
21+
22+
/**
23+
* Grants access to the TeamsInfo helper class
24+
* See: https://docs.microsoft.com/en-us/javascript/api/botbuilder/teamsinfo?view=botbuilder-ts-latest
25+
*/
26+
public teams: TeamsInfo = TeamsInfo;
27+
28+
/**
29+
* Reply to a Teams task module task/fetch or task/submit with a task module response.
30+
* See https://docs.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/task-modules/task-modules-bots
31+
* @param message
32+
* @param taskInfo { type: 'continue|message', value: {} }
33+
*/
34+
public async replyWithTaskInfo(message: BotkitMessage, taskInfo: any) {
35+
if (!taskInfo || taskInfo == {}) {
36+
// send a null response back
37+
taskInfo = {
38+
type: 'message',
39+
value: '',
40+
}
41+
}
42+
return new Promise((resolve, reject) => {
43+
this.controller.middleware.send.run(this, taskInfo, async (err, bot, taskInfo) => {
44+
if (err) {
45+
return reject(err);
46+
}
47+
resolve(await this.getConfig('context').sendActivity({
48+
type: 'invokeResponse',
49+
value: {
50+
status: 200,
51+
body: {
52+
task: taskInfo
53+
}
54+
}
55+
}));
56+
});
57+
});
58+
}
59+
}
60+
61+
/**
62+
* When used, causes Botkit to emit special events for teams "invokes"
63+
* Based on https://github.com/microsoft/botbuilder-js/blob/master/libraries/botbuilder/src/teamsActivityHandler.ts
64+
* This allows Botkit bots to respond directly to task/fetch or task/submit events, as an example.
65+
*/
66+
export class TeamsInvokeMiddleware extends MiddlewareSet {
67+
/**
68+
* Not for direct use - implements the MiddlewareSet's required onTurn function used to process the event
69+
* @param context
70+
* @param next
71+
*/
72+
public async onTurn(context: TurnContext, next: () => Promise<any>): Promise<void> {
73+
if (context.activity.type === 'invoke') {
74+
if (!context.activity.name && context.activity.channelId === 'msteams') {
75+
context.activity.channelData.botkitEventType = 'cardAction';
76+
} else {
77+
switch (context.activity.name) {
78+
case 'fileConsent/invoke':
79+
case 'actionableMessage/executeAction':
80+
case 'composeExtension/queryLink':
81+
case 'composeExtension/query':
82+
case 'composeExtension/selectItem':
83+
case 'composeExtension/submitAction':
84+
case 'composeExtension/fetchTask':
85+
case 'composeExtension/querySettingUrl':
86+
case 'composeExtension/setting':
87+
case 'composeExtension/onCardButtonClicked':
88+
case 'task/fetch':
89+
case 'task/submit':
90+
context.activity.channelData.botkitEventType = context.activity.name;
91+
break;
92+
}
93+
}
94+
}
95+
await next();
96+
}
97+
}

packages/botkit/tests/Core.tests.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const assert = require('assert');
2-
const { Botkit, BotWorker } = require('../');
2+
const { Botkit, TeamsBotWorker } = require('../');
33
const { TwilioAdapter, TwilioBotWorker } = require('../../botbuilder-adapter-twilio-sms');
44

55
describe('Botkit', function() {
@@ -26,7 +26,7 @@ describe('Botkit', function() {
2626
const anotherAdapter = new TwilioAdapter({enable_incomplete: true});
2727

2828
const bot = await controller.spawn({});
29-
assert((bot instanceof BotWorker), 'Default Bot worker is wrong type');
29+
assert((bot instanceof TeamsBotWorker), 'Default Bot worker is wrong type');
3030

3131
const tbot = await controller.spawn({}, anotherAdapter);
3232
assert((tbot instanceof TwilioBotWorker), 'Secondary Bot worker is wrong type');

packages/testbot/features/middlewares.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
module.exports = function(controller) {
22

33
controller.middleware.receive.use((bot, message, next) => {
4-
console.log('IN > ', message.text);
4+
console.log('IN > ', message.text, message.type);
55
next();
66
});
77

88
controller.middleware.send.use((bot, message, next) => {
9-
console.log('OUT > ', message.text, message.channelData.quick_replies ? message.channelData.quick_replies : null, message.channelData.attachments ? message.channelData.attachments : null);
9+
console.log('OUT > ', message.text, message.channelData && message.channelData.quick_replies ? message.channelData.quick_replies : null, message.channelData && message.channelData.attachments ? message.channelData.attachments : null);
1010
next();
1111
});
1212

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
const request = require('request');
2+
3+
module.exports = function(controller) {
4+
5+
if (!controller.adapter.name) {
6+
7+
const adaptiveCard = {
8+
"type": "AdaptiveCard",
9+
"body": [
10+
{
11+
"type": "TextBlock",
12+
"text": "Here is a ninja cat:"
13+
},
14+
{
15+
"type": "Image",
16+
"url": "http://adaptivecards.io/content/cats/1.png",
17+
"size": "Medium"
18+
},
19+
{
20+
"type": "ActionSet",
21+
"actions": [
22+
{
23+
"type": "Action.Submit",
24+
"title": "Message Submit",
25+
"id": "begin",
26+
"data": {
27+
"command": "message"
28+
}
29+
},
30+
{
31+
"type": "Action.Submit",
32+
"title": "Card Submit",
33+
"id": "begin",
34+
"data": {
35+
"command": "card"
36+
}
37+
},
38+
{
39+
"type": "Action.Submit",
40+
"title": "Close",
41+
"id": "begin",
42+
"data": {
43+
"command": "close"
44+
}
45+
},
46+
],
47+
"horizontalAlignment": "Center"
48+
}
49+
],
50+
"version": "1.0"
51+
}
52+
53+
controller.hears('getTeamDetails', 'message', async(bot, message) => {
54+
try {
55+
await bot.reply(message, JSON.stringify(await bot.teams.getTeamDetails(bot.getConfig('context'))));
56+
} catch(err) {
57+
await bot.reply(message, err.message);
58+
}
59+
});
60+
61+
62+
controller.hears('getMember', 'message', async(bot, message) => {
63+
try {
64+
await bot.reply(message, JSON.stringify(await bot.teams.getMember(bot.getConfig('context'), message.user)));
65+
} catch(err) {
66+
await bot.reply(message, err.message);
67+
}
68+
});
69+
70+
controller.hears('taskModule', 'message', async(bot, message) => {
71+
await bot.reply(message,{
72+
attachments: [{
73+
"contentType": "application/vnd.microsoft.card.hero",
74+
"content": {
75+
"buttons": [
76+
{
77+
"type": "invoke",
78+
"title": "Task Module",
79+
"value": {type: 'task/fetch'}
80+
}
81+
],
82+
"text": "Launch a task module by clicking the button.",
83+
"title": "INVOKE A TASK MODULE!"
84+
}
85+
}]
86+
});
87+
});
88+
89+
controller.on('task/fetch', async(bot, message) => {
90+
await bot.replyWithTaskInfo(message,{
91+
"type": "continue",
92+
"value": {
93+
"title": "Task module title",
94+
"height": 500,
95+
"width": "medium",
96+
card: {
97+
contentType: 'application/vnd.microsoft.card.adaptive',
98+
content: adaptiveCard,
99+
}
100+
}
101+
})
102+
});
103+
104+
105+
controller.on('task/submit', async(bot, message) => {
106+
107+
if (message.value.data.command == 'message') {
108+
// reply with a message
109+
await bot.replyWithTaskInfo(message, {
110+
type: 'message',
111+
value: 'Submitted!',
112+
});
113+
} else if (message.value.data.command == 'card') {
114+
// reply with another card
115+
await bot.replyWithTaskInfo(message, {
116+
"type": "continue",
117+
"value": {
118+
"title": "Task module title",
119+
"height": 500,
120+
"width": "medium",
121+
card: {
122+
contentType: 'application/vnd.microsoft.card.adaptive',
123+
content: adaptiveCard,
124+
}
125+
}
126+
});
127+
} else {
128+
// just close the task module
129+
await bot.replyWithTaskInfo(message, null);
130+
}
131+
132+
});
133+
134+
}
135+
}

0 commit comments

Comments
 (0)