Skip to content
This repository was archived by the owner on Dec 4, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions samples/51.teams-messaging-extensions-action/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

/**
* 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
* {@link com.microsoft.bot.builder.Bot} interface should be annotated with @Component.
*
* @see TeamsConversationBot
* @see TeamsMessagingExtensionsActionBot
*/
@SpringBootApplication

Expand All @@ -29,6 +29,7 @@
@Import({BotController.class})

public class Application extends BotDependencyConfiguration {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
* <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
* back to the user. The {@link #onMembersAdded(List, TurnContext)} will send a greeting
* to new conversation participants.</p>
* 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.</p>
*/
@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<Void> 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<MessagingExtensionActionResponse> 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<Void> onTeamsMembersAdded(
List<TeamsChannelAccount> membersAdded,
TeamInfo teamInfo,
TurnContext turnContext
private CompletableFuture<MessagingExtensionActionResponse> 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<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.");
Map<String, String> actionData = (Map<String, String>) action.getData();

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)
);
}
)
);
}
HeroCard card = new HeroCard() {{
setTitle(actionData.get("title"));
setSubtitle(actionData.get("subTitle"));
setText(actionData.get("text"));
}};

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);
List<MessagingExtensionAttachment> attachments = Arrays
.asList(new MessagingExtensionAttachment() {{
setContent(card);
setContentType(HeroCard.CONTENTTYPE);
setPreview(card.toAttachment());
}});

return CompletableFuture.completedFuture(new MessagingExtensionActionResponse() {{
setComposeExtension(new MessagingExtensionResult() {{
setAttachments(attachments);
setAttachmentLayout("list");
setType("result");
}});
}});
}

private CompletableFuture<Void> updateCardActivity(TurnContext turnContext) {
Map data = (Map) turnContext.getActivity().getValue();
data.put("count", (int) data.get("count") + 1);
private CompletableFuture<MessagingExtensionActionResponse> shareMessageCommand(
TurnContext turnContext,
MessagingExtensionAction action
) {
Map<String, String> actionData = (Map<String, String>) 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(
action.getMessagePayload().getFrom().getUser() != null ? action.getMessagePayload()
.getFrom().getUser().getDisplayName() : "");
setText(action.getMessagePayload().getBody().getContent());
}};

Activity updatedActivity = MessageFactory.attachment(card.toAttachment());
updatedActivity.setId(turnContext.getActivity().getReplyToId());

return turnContext.updateActivity(updatedActivity)
.thenApply(resourceResponse -> null);
}

private CompletableFuture<Void> mentionActivity(TurnContext turnContext) {
Mention mention = new Mention();
mention.setMentioned(turnContext.getActivity().getFrom());
mention.setText("<at>" + URLEncoder.encode(turnContext.getActivity().getFrom().getName()) + "</at>");
if (action.getMessagePayload().getAttachments() != null && !action.getMessagePayload()
.getAttachments().isEmpty()) {
card.setSubtitle("Attachments not included)");
}

Activity replyActivity = MessageFactory.text("Hello " + mention.getText() + ".'");
replyActivity.setMentions(Collections.singletonList(mention));
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 turnContext.sendActivity(replyActivity)
.thenApply(resourceResponse -> null);
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());
}}));
}});
}});
}
}
Loading