Merge pull request #424 from microsoft/emimunoz/messaging-ext-action

51 Teams Messaging Action Extension
This commit is contained in:
tracyboehrer 2020-04-23 09:01:25 -05:00 коммит произвёл GitHub
Родитель c5c63b9a49 ee312158c8
Коммит 0036294979
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 159 добавлений и 269 удалений

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

@ -3,8 +3,8 @@
Bot Framework v4 Conversation Bot sample for Teams. Bot Framework v4 Conversation Bot sample for Teams.
This bot has been created using [Bot Framework](https://dev.botframework.com). This sample shows There are two basic types of Messaging Extension in Teams: [Search-based](https://docs.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/search-commands/define-search-command) and [Action-based](https://docs.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/define-action-command). This sample illustrates how to
how to incorporate basic conversational flow into a Teams application. It also illustrates a few of the Teams specific calls you can make from your bot. build an Action-based Messaging Extension.
## Prerequisites ## Prerequisites
@ -48,23 +48,13 @@ the Teams service needs to call into the bot.
## Interacting with the bot ## Interacting with the bot
You can interact with this bot by sending it a message, or selecting a command from the command list. The bot will respond to the following strings. > Note this `manifest.json` specified that the bot will be called from both the `compose` and `message` areas of Teams. Please refer to Teams documentation for more details.
1. **Show Welcome** 1) Selecting the **Create Card** command from the Compose Box command list. The parameters dialog will be displayed and can be submitted to initiate the card creation within the Messaging Extension code.
- **Result:** The bot will send the welcome card for you to interact with
- **Valid Scopes:** personal, group chat, team chat
2. **MentionMe**
- **Result:** The bot will respond to the message and mention the user
- **Valid Scopes:** personal, group chat, team chat
3. **MessageAllMembers**
- **Result:** The bot will send a 1-on-1 message to each member in the current conversation (aka on the conversation's roster).
- **Valid Scopes:** personal, group chat, team chat
You can select an option from the command list by typing ```@TeamsConversationBot``` into the compose message area and ```What can I do?``` text above the compose area. or
### Avoiding Permission-Related Errors 2) Selecting the **Share Message** command from the Message command list.
You may encounter permission-related errors when sending a proactive message. This can often be mitigated by using `MicrosoftAppCredentials.TrustServiceUrl()`. See [the documentation](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=csharp#avoiding-401-unauthorized-errors) for more information.
## Deploy the bot to Azure ## Deploy the bot to Azure

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

@ -14,11 +14,11 @@ import org.springframework.context.annotation.Import;
/** /**
* This is the starting point of the Sprint Boot Bot application. * This is the starting point of the Sprint Boot Bot application.
* <p>
* This class also provides overrides for dependency injections. A class that extends the {@link
* com.microsoft.bot.builder.Bot} interface should be annotated with @Component.
* *
* This class also provides overrides for dependency injections. A class that extends the * @see TeamsMessagingExtensionsActionBot
* {@link com.microsoft.bot.builder.Bot} interface should be annotated with @Component.
*
* @see TeamsConversationBot
*/ */
@SpringBootApplication @SpringBootApplication
@ -29,6 +29,7 @@ import org.springframework.context.annotation.Import;
@Import({BotController.class}) @Import({BotController.class})
public class Application extends BotDependencyConfiguration { public class Application extends BotDependencyConfiguration {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
} }

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

@ -3,210 +3,109 @@
package com.microsoft.bot.sample.teamsaction; package com.microsoft.bot.sample.teamsaction;
import com.codepoetics.protonpack.collectors.CompletableFutures;
import com.microsoft.bot.builder.BotFrameworkAdapter;
import com.microsoft.bot.builder.MessageFactory;
import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.builder.teams.TeamsActivityHandler; import com.microsoft.bot.builder.teams.TeamsActivityHandler;
import com.microsoft.bot.builder.teams.TeamsInfo; import com.microsoft.bot.schema.CardImage;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.integration.Configuration;
import com.microsoft.bot.schema.ActionTypes;
import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.CardAction;
import com.microsoft.bot.schema.ConversationParameters;
import com.microsoft.bot.schema.ConversationReference;
import com.microsoft.bot.schema.HeroCard; import com.microsoft.bot.schema.HeroCard;
import com.microsoft.bot.schema.Mention; import com.microsoft.bot.schema.teams.*;
import com.microsoft.bot.schema.teams.TeamInfo;
import com.microsoft.bot.schema.teams.TeamsChannelAccount;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.net.URLEncoder; import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* This class implements the functionality of the Bot. * This class implements the functionality of the Bot.
* *
* <p>This is where application specific logic for interacting with the users would be * <p>This is where application specific logic for interacting with the users would be
* added. For this sample, the {@link #onMessageActivity(TurnContext)} echos the text * added. There are two basic types of Messaging Extension in Teams: Search-based and Action-based.
* back to the user. The {@link #onMembersAdded(List, TurnContext)} will send a greeting * This sample illustrates how to build an Action-based Messaging Extension.</p>
* to new conversation participants.</p>
*/ */
@Component @Component
public class TeamsMessagingExtensionsActionBot extends TeamsActivityHandler { public class TeamsMessagingExtensionsActionBot extends TeamsActivityHandler {
private String appId;
private String appPassword;
public TeamsMessagingExtensionsActionBot(Configuration configuration) {
appId = configuration.getProperty("MicrosoftAppId");
appPassword = configuration.getProperty("MicrosoftAppPassword");
}
@Override @Override
protected CompletableFuture<Void> onMessageActivity(TurnContext turnContext) { protected CompletableFuture<MessagingExtensionActionResponse> onTeamsMessagingExtensionSubmitAction(
turnContext.getActivity().removeRecipientMention(); TurnContext turnContext,
MessagingExtensionAction action
switch (turnContext.getActivity().getText().trim()) { ) {
case "MentionMe": switch (action.getCommandId()) {
return mentionActivity(turnContext); // These commandIds are defined in the Teams App Manifest.
case "createCard":
case "UpdateCardAction": return createCardCommand(turnContext, action);
return updateCardActivity(turnContext);
case "Delete":
return deleteCardActivity(turnContext);
case "MessageAllMembers":
return messageAllMembers(turnContext);
case "shareMessage":
return shareMessageCommand(turnContext, action);
default: default:
// This will come back deserialized as a Map. return notImplemented(
Object value = new Object() { String.format("Invalid CommandId: %s", action.getCommandId()));
int count = 0;
};
HeroCard card = new HeroCard() {{
setTitle("Welcome Card");
setText("Click the buttons below to update this card");
setButtons(Arrays.asList(
new CardAction() {{
setType(ActionTypes.MESSAGE_BACK);
setTitle("Update Card");
setText("UpdateCardAction");
setValue(value);
}},
new CardAction() {{
setType(ActionTypes.MESSAGE_BACK);
setTitle("Message All Members");
setText("MessageAllMembers");
}}
));
}};
return turnContext.sendActivity(MessageFactory.attachment(card.toAttachment()))
.thenApply(resourceResponse -> null);
} }
} }
@Override private CompletableFuture<MessagingExtensionActionResponse> createCardCommand(
protected CompletableFuture<Void> onTeamsMembersAdded( TurnContext turnContext,
List<TeamsChannelAccount> membersAdded, MessagingExtensionAction action
TeamInfo teamInfo,
TurnContext turnContext
) { ) {
return membersAdded.stream() Map<String, String> actionData = (Map<String, String>) action.getData();
.filter(member -> !StringUtils.equals(member.getId(), turnContext.getActivity().getRecipient().getId()))
.map(channel -> turnContext.sendActivity(
MessageFactory.text("Welcome to the team " + channel.getGivenName() + " " + channel.getSurname() + ".")))
.collect(CompletableFutures.toFutureList())
.thenApply(resourceResponses -> null);
}
private CompletableFuture<Void> deleteCardActivity(TurnContext turnContext) {
return turnContext.deleteActivity(turnContext.getActivity().getReplyToId());
}
// If you encounter permission-related errors when sending this message, see
// https://aka.ms/BotTrustServiceUrl
private CompletableFuture<Void> messageAllMembers(TurnContext turnContext) {
String teamsChannelId = turnContext.getActivity().teamsGetChannelId();
String serviceUrl = turnContext.getActivity().getServiceUrl();
MicrosoftAppCredentials credentials = new MicrosoftAppCredentials(appId, appPassword);
return TeamsInfo.getMembers(turnContext)
.thenCompose(members -> {
List<CompletableFuture<Void>> conversations = new ArrayList<>();
// Send a message to each member. These will all go out
// at the same time.
for (TeamsChannelAccount member : members) {
Activity proactiveMessage = MessageFactory.text(
"Hello " + member.getGivenName() + " " + member.getSurname()
+ ". I'm a Teams conversation bot.");
ConversationParameters conversationParameters = new ConversationParameters() {{
setIsGroup(false);
setBot(turnContext.getActivity().getRecipient());
setMembers(Collections.singletonList(member));
setTenantId(turnContext.getActivity().getConversation().getTenantId());
}};
conversations.add(
((BotFrameworkAdapter) turnContext.getAdapter()).createConversation(
teamsChannelId,
serviceUrl,
credentials,
conversationParameters,
(context) -> {
ConversationReference reference = context.getActivity().getConversationReference();
return context.getAdapter().continueConversation(
appId,
reference,
(inner_context) -> inner_context.sendActivity(proactiveMessage)
.thenApply(resourceResponse -> null)
);
}
)
);
}
return CompletableFuture.allOf(conversations.toArray(new CompletableFuture[0]));
})
// After all member messages are sent, send confirmation to the user.
.thenApply(conversations -> turnContext.sendActivity(MessageFactory.text("All messages have been sent.")))
.thenApply(allSent -> null);
}
private CompletableFuture<Void> updateCardActivity(TurnContext turnContext) {
Map data = (Map) turnContext.getActivity().getValue();
data.put("count", (int) data.get("count") + 1);
HeroCard card = new HeroCard() {{ HeroCard card = new HeroCard() {{
setTitle("Welcome Card"); setTitle(actionData.get("title"));
setText("Update count - " + data.get("count")); setSubtitle(actionData.get("subTitle"));
setButtons(Arrays.asList( setText(actionData.get("text"));
new CardAction() {{
setType(ActionTypes.MESSAGE_BACK);
setTitle("Update Card");
setText("UpdateCardAction");
setValue(data);
}},
new CardAction() {{
setType(ActionTypes.MESSAGE_BACK);
setTitle("Message All Members");
setText("MessageAllMembers");
}},
new CardAction() {{
setType(ActionTypes.MESSAGE_BACK);
setTitle("Delete card");
setText("Delete");
}}
));
}}; }};
Activity updatedActivity = MessageFactory.attachment(card.toAttachment()); List<MessagingExtensionAttachment> attachments = Arrays
updatedActivity.setId(turnContext.getActivity().getReplyToId()); .asList(new MessagingExtensionAttachment() {{
setContent(card);
setContentType(HeroCard.CONTENTTYPE);
setPreview(card.toAttachment());
}});
return turnContext.updateActivity(updatedActivity) return CompletableFuture.completedFuture(new MessagingExtensionActionResponse() {{
.thenApply(resourceResponse -> null); setComposeExtension(new MessagingExtensionResult() {{
setAttachments(attachments);
setAttachmentLayout("list");
setType("result");
}});
}});
} }
private CompletableFuture<Void> mentionActivity(TurnContext turnContext) { private CompletableFuture<MessagingExtensionActionResponse> shareMessageCommand(
Mention mention = new Mention(); TurnContext turnContext,
mention.setMentioned(turnContext.getActivity().getFrom()); MessagingExtensionAction action
mention.setText("<at>" + URLEncoder.encode(turnContext.getActivity().getFrom().getName()) + "</at>"); ) {
Map<String, String> actionData = (Map<String, String>) action.getData();
Activity replyActivity = MessageFactory.text("Hello " + mention.getText() + ".'"); HeroCard card = new HeroCard() {{
replyActivity.setMentions(Collections.singletonList(mention)); setTitle(
action.getMessagePayload().getFrom().getUser() != null ? action.getMessagePayload()
.getFrom().getUser().getDisplayName() : "");
setText(action.getMessagePayload().getBody().getContent());
}};
return turnContext.sendActivity(replyActivity) if (action.getMessagePayload().getAttachments() != null && !action.getMessagePayload()
.thenApply(resourceResponse -> null); .getAttachments().isEmpty()) {
card.setSubtitle("Attachments not included)");
}
boolean includeImage = actionData.get("includeImage") != null ? (
Boolean.valueOf(actionData.get("includeImage"))
) : false;
if (includeImage) {
card.setImages(Arrays.asList(new CardImage() {{
setUrl(
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU");
}}));
}
return CompletableFuture.completedFuture(new MessagingExtensionActionResponse() {{
setComposeExtension(new MessagingExtensionResult() {{
setAttachmentLayout("list");
setType("result");
setAttachments(Arrays.asList(new MessagingExtensionAttachment() {{
setContent(card);
setContentType(HeroCard.CONTENTTYPE);
setPreview(card.toAttachment());
}}));
}});
}});
} }
} }

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

@ -1,78 +1,78 @@
{ {
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5", "manifestVersion": "1.5",
"version": "1.0", "version": "1.0",
"id": "<<YOUR-MICROSOFT-APP-ID>>", "id": "<<YOUR-MICROSOFT-APP-ID>>",
"packageName": "com.microsoft.teams.samples", "packageName": "com.microsoft.teams.samples",
"developer": { "developer": {
"name": "Microsoft", "name": "Microsoft",
"websiteUrl": "https://dev.botframework.com", "websiteUrl": "https://dev.botframework.com",
"privacyUrl": "https://privacy.microsoft.com", "privacyUrl": "https://privacy.microsoft.com",
"termsOfUseUrl": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx" "termsOfUseUrl": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx"
}, },
"name": { "name": {
"short": "Action Messaging Extension", "short": "Action Messaging Extension",
"full": "Microsoft Teams Action Based Messaging Extension" "full": "Microsoft Teams Action Based Messaging Extension"
}, },
"description": { "description": {
"short": "Sample demonstrating an Action Based Messaging Extension", "short": "Sample demonstrating an Action Based Messaging Extension",
"full": "Sample Action Messaging Extension built with the Bot Builder SDK" "full": "Sample Action Messaging Extension built with the Bot Builder SDK"
}, },
"icons": { "icons": {
"outline": "icon-outline.png", "outline": "icon-outline.png",
"color": "icon-color.png" "color": "icon-color.png"
}, },
"accentColor": "#FFFFFF", "accentColor": "#FFFFFF",
"composeExtensions": [ "composeExtensions": [
{
"botId": "<<YOUR-MICROSOFT-APP-ID>>",
"commands": [
{ {
"id": "createCard", "botId": "<<YOUR-MICROSOFT-APP-ID>>",
"type": "action", "commands": [
"context": [ "compose" ], {
"description": "Command to run action to create a Card from Compose Box", "id": "createCard",
"title": "Create Card", "type": "action",
"parameters": [ "context": [ "compose" ],
{ "description": "Command to run action to create a Card from Compose Box",
"name": "title", "title": "Create Card",
"title": "Card title", "parameters": [
"description": "Title for the card", {
"inputType": "text" "name": "title",
}, "title": "Card title",
{ "description": "Title for the card",
"name": "subTitle", "inputType": "text"
"title": "Subtitle", },
"description": "Subtitle for the card", {
"inputType": "text" "name": "subTitle",
}, "title": "Subtitle",
{ "description": "Subtitle for the card",
"name": "text", "inputType": "text"
"title": "Text", },
"description": "Text for the card", {
"inputType": "textarea" "name": "text",
} "title": "Text",
] "description": "Text for the card",
}, "inputType": "textarea"
{ }
"id": "shareMessage", ]
"type": "action", },
"context": [ "message" ], {
"description": "Test command to run action on message context (message sharing)", "id": "shareMessage",
"title": "Share Message", "type": "action",
"parameters": [ "context": [ "message" ],
{ "description": "Test command to run action on message context (message sharing)",
"name": "includeImage", "title": "Share Message",
"title": "Include Image", "parameters": [
"description": "Include image in Hero Card", {
"inputType": "toggle" "name": "includeImage",
} "title": "Include Image",
] "description": "Include image in Hero Card",
"inputType": "toggle"
}
]
}
]
} }
] ],
} "permissions": [
], "identity"
"permissions": [ ]
"identity"
]
} }