-
Notifications
You must be signed in to change notification settings - Fork 281
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add search based messaging extension from samples (thanks Steven) (#1267
- Loading branch information
Eric Dahlvang
authored
Oct 7, 2019
1 parent
b12e442
commit 42a2614
Showing
10 changed files
with
467 additions
and
0 deletions.
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
libraries/botbuilder/tests/teams/searchBasedMessagingExtension/.env
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
MicrosoftAppId= | ||
MicrosoftAppPassword= | ||
host= |
84 changes: 84 additions & 0 deletions
84
libraries/botbuilder/tests/teams/searchBasedMessagingExtension/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# Teams Search-based Messaging Extensions Bot | ||
|
||
Search-based Messaging Extensions allow users to have bots search for information on the user's behalf in Teams. | ||
|
||
___ | ||
|
||
To create a Teams Bot that contains Search-based Messaging Extensions, users must first create a [Teams Manifest](https://docs.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) which will outline where the extension is available. | ||
|
||
> _Note_ | ||
> | ||
> _The name of the feature was changed from "compose extension" to "messaging extension" in November, 2017, but the manifest name remains the same so that existing extensions continue to function._ | ||
### Example Teams Manifest for Search-based Messaging Extensions bot | ||
|
||
```json | ||
{ | ||
"$schema": "https://github.com/OfficeDev/microsoft-teams-app-schema/blob/preview/DevPreview/MicrosoftTeams.schema.json", | ||
"manifestVersion": "devPreview", | ||
"version": "1.0", | ||
"id": "<<YOUR_GENERATED_APP_GUID>>", | ||
"packageName": "com.microsoft.teams.samples.v4bot", | ||
"developer": { | ||
"name": "Microsoft Corp", | ||
"websiteUrl": "https://example.azurewebsites.net", | ||
"privacyUrl": "https://example.azurewebsites.net/privacy", | ||
"termsOfUseUrl": "https://example.azurewebsites.net/termsofuse" | ||
}, | ||
"name": { | ||
"short": "search-extension-settings", | ||
"full": "Microsoft Teams V4 Search Messaging Extension Bot and settings" | ||
}, | ||
"description": { | ||
"short": "Microsoft Teams V4 Search Messaging Extension Bot and settings", | ||
"full": "Sample Search Messaging Extension Bot using V4 Bot Builder SDK and V4 Microsoft Teams Extension SDK" | ||
}, | ||
"icons": { | ||
"outline": "icon-outline.png", | ||
"color": "icon-color.png" | ||
}, | ||
"accentColor": "#abcdef", | ||
"bots": [ | ||
{ | ||
"botId": "<<YOUR_BOTS_MSA_APP_ID>>", | ||
"scopes": ["personal", "team"] | ||
} | ||
], | ||
"composeExtensions": [ | ||
{ | ||
"botId": "<<YOUR_BOTS_MSA_APP_ID>>", | ||
"canUpdateConfiguration": true, | ||
"commands": [ | ||
{ | ||
"id": "searchQuery", | ||
"context": ["compose", "commandBox"], | ||
"description": "Test command to run query", | ||
"title": "Search", | ||
"type": "query", | ||
"parameters": [ | ||
{ | ||
"name": "searchQuery", | ||
"title": "Search Query", | ||
"description": "Your search query", | ||
"inputType": "text" | ||
} | ||
] | ||
} | ||
], | ||
"messageHandlers": [ | ||
{ | ||
"type": "link", | ||
"value": { | ||
"domains": [ | ||
"*.ngrok.io" | ||
] | ||
} | ||
} | ||
] | ||
} | ||
], | ||
"validDomains": [ | ||
"*.ngrok.io" | ||
] | ||
} | ||
``` |
30 changes: 30 additions & 0 deletions
30
libraries/botbuilder/tests/teams/searchBasedMessagingExtension/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "search-extension-bot", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "./lib/index.js", | ||
"scripts": { | ||
"start": "tsc --build && node ./lib/index.js", | ||
"build": "tsc --build", | ||
"watch": "nodemon --watch ./src -e ts --exec \"npm run start\"" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"botbuilder": "file:../../../", | ||
"dotenv": "^8.1.0", | ||
"node-fetch": "^2.6.0", | ||
"restify": "^8.4.0", | ||
"uuid": "^3.3.3" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^12.7.1", | ||
"@types/node-fetch": "^2.5.0", | ||
"@types/request": "^2.48.1", | ||
"@types/restify": "^7.2.7", | ||
"nodemon": "^1.19.1", | ||
"ts-node": "^7.0.1", | ||
"typescript": "^3.2.4" | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
libraries/botbuilder/tests/teams/searchBasedMessagingExtension/src/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { config } from 'dotenv'; | ||
import * as path from 'path'; | ||
import * as restify from 'restify'; | ||
|
||
// Import required bot services. | ||
// See https://aka.ms/bot-services to learn more about the different parts of a bot. | ||
import { BotFrameworkAdapter, MemoryStorage, UserState } from 'botbuilder'; | ||
|
||
// This bot's main dialog. | ||
import { TeamsSearchExtensionBot } from './teamsSearchExtensionBot'; | ||
|
||
const ENV_FILE = path.join(__dirname, '..', '.env'); | ||
config({ path: ENV_FILE }); | ||
|
||
// Create HTTP server. | ||
const server = restify.createServer(); | ||
server.listen(process.env.port || process.env.PORT || 3978, () => { | ||
console.log(`\n${server.name} listening to ${server.url}`); | ||
console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`); | ||
console.log(`\nTo test your bot, see: https://aka.ms/debug-with-emulator`); | ||
}); | ||
|
||
// Create adapter. | ||
// See https://aka.ms/about-bot-adapter to learn more about adapters. | ||
const adapter = new BotFrameworkAdapter({ | ||
appId: process.env.MicrosoftAppId, | ||
appPassword: process.env.MicrosoftAppPassword | ||
}); | ||
|
||
// Catch-all for errors. | ||
adapter.onTurnError = async (context, error) => { | ||
// This check writes out errors to console log .vs. app insights. | ||
console.error('[onTurnError]:'); | ||
console.error(error); | ||
// Send a message to the user | ||
await context.sendActivity(`Oops. Something went wrong in the bot!\n ${error.message}`); | ||
}; | ||
|
||
// Define a state store for your bot. See https://aka.ms/about-bot-state to learn more about using MemoryStorage. | ||
// A bot requires a state store to persist the dialog and user state between messages. | ||
|
||
// For local development, in-memory storage is used. | ||
// CAUTION: The Memory Storage used here is for local bot debugging only. When the bot | ||
// is restarted, anything stored in memory will be gone. | ||
const memoryStorage = new MemoryStorage(); | ||
const userState = new UserState(memoryStorage); | ||
|
||
// Create the main dialog. | ||
const myBot = new TeamsSearchExtensionBot(userState); | ||
|
||
server.get('/*', restify.plugins.serveStatic({ | ||
directory: path.join(__dirname, '../static'), | ||
appendRequestPath: false | ||
})); | ||
|
||
// Listen for incoming requests. | ||
server.post('/api/messages', (req, res) => { | ||
adapter.processActivity(req, res, async (context) => { | ||
// Route to main dialog. | ||
await myBot.run(context); | ||
}); | ||
}); |
175 changes: 175 additions & 0 deletions
175
...aries/botbuilder/tests/teams/searchBasedMessagingExtension/src/teamsSearchExtensionBot.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
import { | ||
ActionTypes, | ||
CardFactory, | ||
BotState, | ||
MessagingExtensionResponse, | ||
MessagingExtensionResult, | ||
MessagingExtensionQuery, | ||
TeamsActivityHandler, | ||
TurnContext, | ||
MessagingExtensionSuggestedAction, | ||
MessagingExtensionActionResponse, | ||
AppBasedLinkQuery | ||
} from 'botbuilder'; | ||
|
||
const RICH_CARD_PROPERTY = 'richCardConfig'; | ||
|
||
export class TeamsSearchExtensionBot extends TeamsActivityHandler { | ||
|
||
// For this example, we're using UserState to store the user's preferences for the type of Rich Card they receive. | ||
// Users can specify the type of card they receive by configuring the Messaging Extension. | ||
// To store their configuration, we will use the userState passed in via the constructor. | ||
|
||
/** | ||
* We need to change the key for the user state because the bot might not be in the conversation, which means they get a 403 error. | ||
* @param userState | ||
*/ | ||
constructor(public userState: BotState) { | ||
super(); | ||
|
||
// See https://aka.ms/about-bot-activity-message to learn more about the message and other activity types. | ||
this.onMessage(async (context, next) => { | ||
await context.sendActivity(`You said '${context.activity.text}'`); | ||
// By calling next() you ensure that the next BotHandler is run. | ||
await next(); | ||
}); | ||
|
||
this.onMembersAdded(async (context, next) => { | ||
const membersAdded = context.activity.membersAdded; | ||
for (const member of membersAdded) { | ||
if (member.id !== context.activity.recipient.id) { | ||
await context.sendActivity('Hello and welcome!'); | ||
} | ||
} | ||
|
||
// By calling next() you ensure that the next BotHandler is run. | ||
await next(); | ||
}); | ||
} | ||
|
||
protected async onTeamsMessagingExtensionQuery(context: TurnContext, query: MessagingExtensionQuery): Promise<MessagingExtensionResponse>{ | ||
const accessor = this.userState.createProperty<{ useHeroCard: boolean }>(RICH_CARD_PROPERTY); | ||
const config = await accessor.get(context, { useHeroCard: true }); | ||
|
||
const searchQuery = query.parameters[0].value; | ||
const cardText = `You said "${searchQuery}"`; | ||
let composeExtensionResponse: MessagingExtensionResponse; | ||
|
||
const bfLogo = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU'; | ||
const button = { type: 'openUrl', title: 'Click for more Information', value: "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview" }; | ||
|
||
if (config.useHeroCard) { | ||
const heroCard = CardFactory.heroCard('You searched for:', cardText, [bfLogo], [button]); | ||
const preview = CardFactory.heroCard('You searched for:', cardText, [bfLogo]); | ||
|
||
const secondPreview = CardFactory.heroCard('Learn more about Teams:', "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview", [bfLogo]); | ||
const secondHeroCard = CardFactory.heroCard('Learn more about Teams:', "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview", [bfLogo], [button]); | ||
composeExtensionResponse = { | ||
composeExtension: { | ||
type: 'result', | ||
attachmentLayout: 'list', | ||
attachments: [ | ||
{ ...heroCard, preview }, | ||
{ ...secondHeroCard, preview: secondPreview } | ||
] | ||
} | ||
} | ||
} else { | ||
const thumbnailCard = CardFactory.thumbnailCard('You searched for:', cardText, [bfLogo], [button]); | ||
const preview = CardFactory.thumbnailCard('You searched for:', cardText, [bfLogo]); | ||
|
||
const secondPreview = CardFactory.thumbnailCard('Learn more about Teams:', "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview", [bfLogo]); | ||
const secondThumbnailCard = CardFactory.thumbnailCard('Learn more about Teams:', "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview", [bfLogo], [button]); | ||
composeExtensionResponse = { | ||
composeExtension: { | ||
type: 'result', | ||
attachmentLayout: 'list', | ||
attachments: [ | ||
{ ...thumbnailCard, preview }, | ||
{ ...secondThumbnailCard, preview: secondPreview } | ||
] | ||
} | ||
} | ||
} | ||
|
||
return composeExtensionResponse; | ||
} | ||
|
||
protected async onTeamsAppBasedLinkQuery(context: TurnContext, query: AppBasedLinkQuery): Promise<MessagingExtensionResponse>{ | ||
const accessor = this.userState.createProperty<{ useHeroCard: boolean }>(RICH_CARD_PROPERTY); | ||
const config = await accessor.get(context, { useHeroCard: true }); | ||
|
||
const url = query.url; | ||
const cardText = `You entered "${url}"`; | ||
let composeExtensionResponse: MessagingExtensionResponse; | ||
|
||
const bfLogo = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU'; | ||
const button = { type: 'openUrl', title: 'Click for more Information', value: "https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/bots/bots-overview" }; | ||
|
||
if (config.useHeroCard) { | ||
const heroCard = CardFactory.heroCard('HeroCard for Link Unfurling:', cardText, [bfLogo], [button]); | ||
const preview = CardFactory.heroCard('HeroCard for Link Unfurling:', cardText, [bfLogo]); | ||
|
||
composeExtensionResponse = { | ||
composeExtension: { | ||
type: 'result', | ||
attachmentLayout: 'list', | ||
attachments: [ | ||
{ ...heroCard, preview } | ||
] | ||
} | ||
} | ||
} else { | ||
const thumbnailCard = CardFactory.thumbnailCard('ThumbnailCard for Link Unfurling:', cardText, [bfLogo], [button]); | ||
const preview = CardFactory.thumbnailCard('ThumbnailCard for Link Unfurling:', cardText, [bfLogo]); | ||
|
||
composeExtensionResponse = { | ||
composeExtension: { | ||
type: 'result', | ||
attachmentLayout: 'list', | ||
attachments: [ | ||
{ ...thumbnailCard, preview } | ||
] | ||
} | ||
} | ||
} | ||
|
||
return composeExtensionResponse; | ||
} | ||
|
||
protected async onTeamsMessagingExtensionConfigurationQuerySettingUrl(context: TurnContext, query: MessagingExtensionQuery){ | ||
return <MessagingExtensionActionResponse> | ||
{ | ||
composeExtension: <MessagingExtensionResult> { | ||
type: 'config', | ||
suggestedActions: <MessagingExtensionSuggestedAction> { | ||
actions: [ | ||
{ | ||
type: ActionTypes.OpenUrl, | ||
title: 'Config', | ||
value: process.env.host + '/composeExtensionSettings.html', | ||
}, | ||
] | ||
} | ||
} | ||
} | ||
} | ||
|
||
protected async onTeamsMessagingExtensionConfigurationSetting(context: TurnContext, settings: MessagingExtensionQuery){ | ||
// This event is fired when the settings page is submitted | ||
const accessor = this.userState.createProperty<{ useHeroCard: boolean }>(RICH_CARD_PROPERTY); | ||
const config = await accessor.get(context, { useHeroCard: true }); | ||
|
||
if (settings.state === 'hero') { | ||
config.useHeroCard = true; | ||
} else if (settings.state === 'thumbnail') { | ||
config.useHeroCard = false; | ||
} | ||
|
||
// We should save it after we send the message back to Teams. | ||
await this.userState.saveChanges(context); | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
...botbuilder/tests/teams/searchBasedMessagingExtension/static/composeExtensionSettings.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Bot Info</title> | ||
<meta charset="utf-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<script src='https://statics.teams.microsoft.com/sdk/v1.0/js/MicrosoftTeams.min.js'></script> | ||
<script src='https://code.jquery.com/jquery-1.11.3.min.js'></script> | ||
</head> | ||
|
||
<body> | ||
<p>I prefer:</p> | ||
<button onclick="selectThumbnailCards()">Thumbnail Cards</button> | ||
<button onclick="selectHeroCards()">Hero Cards</button> | ||
<script> | ||
var microsoftTeams; | ||
|
||
$(document).ready(function () { | ||
microsoftTeams.initialize(); | ||
}); | ||
|
||
function selectThumbnailCards() { | ||
microsoftTeams.authentication.notifySuccess('thumbnail'); | ||
} | ||
|
||
function selectHeroCards() { | ||
microsoftTeams.authentication.notifySuccess('hero'); | ||
} | ||
</script> | ||
</body> | ||
</html> |
Binary file added
BIN
+3.12 KB
...der/tests/teams/searchBasedMessagingExtension/teams-app-manifest/icon-color.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+526 Bytes
...r/tests/teams/searchBasedMessagingExtension/teams-app-manifest/icon-outline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.