diff --git a/samples/51.teams-messaging-extensions-action/README.md b/samples/51.teams-messaging-extensions-action/README.md index eed334ee..29cf1f85 100644 --- a/samples/51.teams-messaging-extensions-action/README.md +++ b/samples/51.teams-messaging-extensions-action/README.md @@ -3,8 +3,8 @@ Bot Framework v4 Conversation Bot sample for Teams. -This bot has been created using [Bot Framework](https://dev.botframework.com). This sample shows -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. +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 +build an Action-based Messaging Extension. ## Prerequisites @@ -48,23 +48,13 @@ the Teams service needs to call into 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** - - **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 +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. -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 - -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. +2) Selecting the **Share Message** command from the Message command list. ## Deploy the bot to Azure diff --git a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java index d7e43318..c4931047 100644 --- a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java +++ b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java @@ -14,11 +14,11 @@ import org.springframework.context.annotation.Import; /** * This is the starting point of the Sprint Boot Bot application. + *

+ * 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 - * {@link com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see TeamsConversationBot + * @see TeamsMessagingExtensionsActionBot */ @SpringBootApplication @@ -29,6 +29,7 @@ import org.springframework.context.annotation.Import; @Import({BotController.class}) public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { SpringApplication.run(Application.class, args); } diff --git a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java index ed13ac0e..867fb466 100644 --- a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java +++ b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java @@ -3,210 +3,109 @@ 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.teams.TeamsActivityHandler; -import com.microsoft.bot.builder.teams.TeamsInfo; -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.CardImage; import com.microsoft.bot.schema.HeroCard; -import com.microsoft.bot.schema.Mention; -import com.microsoft.bot.schema.teams.TeamInfo; -import com.microsoft.bot.schema.teams.TeamsChannelAccount; -import org.apache.commons.lang3.StringUtils; +import com.microsoft.bot.schema.teams.*; import org.springframework.stereotype.Component; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.CompletableFuture; /** * This class implements the functionality of the Bot. * *

This is where application specific logic for interacting with the users would be - * added. For this sample, the {@link #onMessageActivity(TurnContext)} echos the text - * back to the user. The {@link #onMembersAdded(List, TurnContext)} will send a greeting - * to new conversation participants.

+ * added. There are two basic types of Messaging Extension in Teams: Search-based and Action-based. + * This sample illustrates how to build an Action-based Messaging Extension.

*/ @Component public class TeamsMessagingExtensionsActionBot extends TeamsActivityHandler { - private String appId; - private String appPassword; - - public TeamsMessagingExtensionsActionBot(Configuration configuration) { - appId = configuration.getProperty("MicrosoftAppId"); - appPassword = configuration.getProperty("MicrosoftAppPassword"); - } - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - turnContext.getActivity().removeRecipientMention(); - - switch (turnContext.getActivity().getText().trim()) { - case "MentionMe": - return mentionActivity(turnContext); - - case "UpdateCardAction": - return updateCardActivity(turnContext); - - case "Delete": - return deleteCardActivity(turnContext); - - case "MessageAllMembers": - return messageAllMembers(turnContext); + protected CompletableFuture onTeamsMessagingExtensionSubmitAction( + TurnContext turnContext, + MessagingExtensionAction action + ) { + switch (action.getCommandId()) { + // These commandIds are defined in the Teams App Manifest. + case "createCard": + return createCardCommand(turnContext, action); + case "shareMessage": + return shareMessageCommand(turnContext, action); default: - // This will come back deserialized as a Map. - Object value = new Object() { - 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); + return notImplemented( + String.format("Invalid CommandId: %s", action.getCommandId())); } } - @Override - protected CompletableFuture onTeamsMembersAdded( - List membersAdded, - TeamInfo teamInfo, - TurnContext turnContext + private CompletableFuture createCardCommand( + TurnContext turnContext, + MessagingExtensionAction action ) { - return membersAdded.stream() - .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 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 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> 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 updateCardActivity(TurnContext turnContext) { - Map data = (Map) turnContext.getActivity().getValue(); - data.put("count", (int) data.get("count") + 1); + Map actionData = (Map) action.getData(); HeroCard card = new HeroCard() {{ - setTitle("Welcome Card"); - setText("Update count - " + data.get("count")); - setButtons(Arrays.asList( - 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"); - }} - )); + setTitle(actionData.get("title")); + setSubtitle(actionData.get("subTitle")); + setText(actionData.get("text")); }}; - Activity updatedActivity = MessageFactory.attachment(card.toAttachment()); - updatedActivity.setId(turnContext.getActivity().getReplyToId()); + List attachments = Arrays + .asList(new MessagingExtensionAttachment() {{ + setContent(card); + setContentType(HeroCard.CONTENTTYPE); + setPreview(card.toAttachment()); + }}); - return turnContext.updateActivity(updatedActivity) - .thenApply(resourceResponse -> null); + return CompletableFuture.completedFuture(new MessagingExtensionActionResponse() {{ + setComposeExtension(new MessagingExtensionResult() {{ + setAttachments(attachments); + setAttachmentLayout("list"); + setType("result"); + }}); + }}); } - private CompletableFuture mentionActivity(TurnContext turnContext) { - Mention mention = new Mention(); - mention.setMentioned(turnContext.getActivity().getFrom()); - mention.setText("" + URLEncoder.encode(turnContext.getActivity().getFrom().getName()) + ""); + private CompletableFuture shareMessageCommand( + TurnContext turnContext, + MessagingExtensionAction action + ) { + Map actionData = (Map) action.getData(); - Activity replyActivity = MessageFactory.text("Hello " + mention.getText() + ".'"); - replyActivity.setMentions(Collections.singletonList(mention)); + HeroCard card = new HeroCard() {{ + setTitle( + action.getMessagePayload().getFrom().getUser() != null ? action.getMessagePayload() + .getFrom().getUser().getDisplayName() : ""); + setText(action.getMessagePayload().getBody().getContent()); + }}; - return turnContext.sendActivity(replyActivity) - .thenApply(resourceResponse -> null); + if (action.getMessagePayload().getAttachments() != null && !action.getMessagePayload() + .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()); + }})); + }}); + }}); } } diff --git a/samples/51.teams-messaging-extensions-action/teamsAppManifest/manifest.json b/samples/51.teams-messaging-extensions-action/teamsAppManifest/manifest.json index 0dcba156..6faf2f43 100644 --- a/samples/51.teams-messaging-extensions-action/teamsAppManifest/manifest.json +++ b/samples/51.teams-messaging-extensions-action/teamsAppManifest/manifest.json @@ -1,78 +1,78 @@ { - "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", - "manifestVersion": "1.5", - "version": "1.0", - "id": "<>", - "packageName": "com.microsoft.teams.samples", - "developer": { - "name": "Microsoft", - "websiteUrl": "https://dev.botframework.com", - "privacyUrl": "https://privacy.microsoft.com", - "termsOfUseUrl": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx" - }, - "name": { - "short": "Action Messaging Extension", - "full": "Microsoft Teams Action Based Messaging Extension" - }, - "description": { - "short": "Sample demonstrating an Action Based Messaging Extension", - "full": "Sample Action Messaging Extension built with the Bot Builder SDK" - }, - "icons": { - "outline": "icon-outline.png", - "color": "icon-color.png" - }, - "accentColor": "#FFFFFF", - "composeExtensions": [ - { - "botId": "<>", - "commands": [ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json", + "manifestVersion": "1.5", + "version": "1.0", + "id": "<>", + "packageName": "com.microsoft.teams.samples", + "developer": { + "name": "Microsoft", + "websiteUrl": "https://dev.botframework.com", + "privacyUrl": "https://privacy.microsoft.com", + "termsOfUseUrl": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx" + }, + "name": { + "short": "Action Messaging Extension", + "full": "Microsoft Teams Action Based Messaging Extension" + }, + "description": { + "short": "Sample demonstrating an Action Based Messaging Extension", + "full": "Sample Action Messaging Extension built with the Bot Builder SDK" + }, + "icons": { + "outline": "icon-outline.png", + "color": "icon-color.png" + }, + "accentColor": "#FFFFFF", + "composeExtensions": [ { - "id": "createCard", - "type": "action", - "context": [ "compose" ], - "description": "Command to run action to create a Card from Compose Box", - "title": "Create Card", - "parameters": [ - { - "name": "title", - "title": "Card title", - "description": "Title for the card", - "inputType": "text" - }, - { - "name": "subTitle", - "title": "Subtitle", - "description": "Subtitle for the card", - "inputType": "text" - }, - { - "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)", - "title": "Share Message", - "parameters": [ - { - "name": "includeImage", - "title": "Include Image", - "description": "Include image in Hero Card", - "inputType": "toggle" - } - ] + "botId": "<>", + "commands": [ + { + "id": "createCard", + "type": "action", + "context": [ "compose" ], + "description": "Command to run action to create a Card from Compose Box", + "title": "Create Card", + "parameters": [ + { + "name": "title", + "title": "Card title", + "description": "Title for the card", + "inputType": "text" + }, + { + "name": "subTitle", + "title": "Subtitle", + "description": "Subtitle for the card", + "inputType": "text" + }, + { + "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)", + "title": "Share Message", + "parameters": [ + { + "name": "includeImage", + "title": "Include Image", + "description": "Include image in Hero Card", + "inputType": "toggle" + } + ] + } + ] } - ] - } - ], - "permissions": [ - "identity" - ] + ], + "permissions": [ + "identity" + ] }