Added Teams botbuilder changes and sample #57

This commit is contained in:
tracyboehrer 2020-04-06 15:38:18 -05:00
Родитель 6c0427f009
Коммит e5119d48a6
43 изменённых файлов: 3351 добавлений и 218 удалений

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

@ -7,8 +7,11 @@ import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.ActivityTypes;
import com.microsoft.bot.schema.ChannelAccount;
import com.microsoft.bot.schema.MessageReaction;
import com.microsoft.bot.schema.ResourceResponse;
import com.microsoft.bot.schema.SignInConstants;
import org.apache.commons.lang3.StringUtils;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@ -59,6 +62,24 @@ public class ActivityHandler implements Bot {
return onMessageReactionActivity(turnContext);
case ActivityTypes.EVENT:
return onEventActivity(turnContext);
case ActivityTypes.INVOKE:
return onInvokeActivity(turnContext)
.thenCompose(invokeResponse -> {
// If OnInvokeActivityAsync has already sent an InvokeResponse, do not send another one.
if (invokeResponse != null
&& turnContext.getTurnState().get(BotFrameworkAdapter.INVOKE_RESPONSE_KEY) == null) {
Activity activity = new Activity(ActivityTypes.INVOKE);
activity.setValue(invokeResponse);
return turnContext.sendActivity(activity);
}
CompletableFuture<ResourceResponse> noAction = new CompletableFuture<>();
noAction.complete(null);
return noAction;
})
.thenApply(response -> null);
default:
return onUnrecognizedActivityType(turnContext);
@ -267,6 +288,70 @@ public class ActivityHandler implements Bot {
return onEvent(turnContext);
}
/**
* Invoked when an invoke activity is received from the connector when the base behavior of
* onTurn is used.
*
* Invoke activities can be used to communicate many different things.
* By default, this method will call onSignInInvokeAsync if the
* activity's name is 'signin/verifyState' or 'signin/tokenExchange'.
*
* A 'signin/verifyState' or 'signin/tokenExchange' invoke can be triggered by an OAuthCard.
*
* @param turnContext The current TurnContext.
* @return A task that represents the work queued to execute.
*/
protected CompletableFuture<InvokeResponse> onInvokeActivity(TurnContext turnContext) {
if (StringUtils.equals(turnContext.getActivity().getName(), SignInConstants.VERIFY_STATE_OPERATION_NAME)
|| StringUtils
.equals(turnContext.getActivity().getName(), SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME)) {
return onSignInInvoke(turnContext)
.thenApply(aVoid -> createInvokeResponse(null))
.exceptionally(ex -> {
if (ex instanceof InvokeResponseExcetion) {
InvokeResponseExcetion ire = (InvokeResponseExcetion) ex;
return new InvokeResponse(ire.statusCode, ire.body);
}
return new InvokeResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, null);
});
}
CompletableFuture<InvokeResponse> result = new CompletableFuture<>();
result.complete(new InvokeResponse(HttpURLConnection.HTTP_NOT_IMPLEMENTED, null));
return result;
}
/**
* Invoked when a 'signin/verifyState' or 'signin/tokenExchange' event is received when the base
* behavior of onInvokeActivity is used.
*
* If using an OAuthPrompt, override this method to forward this Activity to the current dialog.
* By default, this method does nothing.
*
* When the onInvokeActivity method receives an Invoke with a name of `tokens/response`,
* it calls this method.
*
* If your bot uses the OAuthPrompt, forward the incoming Activity to the current dialog.
*
* @param turnContext The current TurnContext.
* @return A task that represents the work queued to execute.
*/
protected CompletableFuture<Void> onSignInInvoke(TurnContext turnContext) {
CompletableFuture<Void> result = new CompletableFuture<>();
result.completeExceptionally(new InvokeResponseExcetion(HttpURLConnection.HTTP_NOT_IMPLEMENTED));
return result;
}
/**
* Creates a success InvokeResponse with the specified body.
* @param body The body to return in the invoke response.
* @return The InvokeResponse object.
*/
protected static InvokeResponse createInvokeResponse(Object body) {
return new InvokeResponse(HttpURLConnection.HTTP_OK, body);
}
/**
* Invoked when a "tokens/response" event is received when the base behavior of
* {@link #onEventActivity(TurnContext)} is used.
@ -323,4 +408,38 @@ public class ActivityHandler implements Bot {
protected CompletableFuture<Void> onUnrecognizedActivityType(TurnContext turnContext) {
return CompletableFuture.completedFuture(null);
}
/**
* InvokeResponse Exception.
*/
protected class InvokeResponseExcetion extends Exception {
private int statusCode;
private Object body;
/**
* Initializes new instance with HTTP status code value.
* @param withStatusCode The HTTP status code.
*/
public InvokeResponseExcetion(int withStatusCode) {
this(withStatusCode, null);
}
/**
* Initializes new instance with HTTP status code value.
* @param withStatusCode The HTTP status code.
* @param withBody The body. Can be null.
*/
public InvokeResponseExcetion(int withStatusCode, Object withBody) {
statusCode = withStatusCode;
body = withBody;
}
/**
* Returns an InvokeResponse based on this exception.
* @return The InvokeResponse value.
*/
public InvokeResponse createInvokeResponse() {
return new InvokeResponse(statusCode, body);
}
}
}

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

@ -43,6 +43,7 @@ import com.microsoft.bot.schema.TokenStatus;
import com.microsoft.bot.rest.retry.RetryStrategy;
import org.apache.commons.lang3.StringUtils;
import java.net.HttpURLConnection;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
@ -74,19 +75,19 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class BotFrameworkAdapter extends BotAdapter implements AdapterIntegration, UserTokenProvider {
/**
* Key to store Activity to match .Net.
* Key to store InvokeResponse.
*/
private static final String INVOKE_RESPONSE_KEY = "BotFrameworkAdapter.InvokeResponse";
public static final String INVOKE_RESPONSE_KEY = "BotFrameworkAdapter.InvokeResponse";
/**
* Key to store bot claims identity to match .Net.
* Key to store bot claims identity.
*/
private static final String BOT_IDENTITY_KEY = "BotIdentity";
/**
* Key to store ConnectorClient to match .Net.
* Key to store ConnectorClient.
*/
private static final String CONNECTOR_CLIENT_KEY = "ConnectorClient";
public static final String CONNECTOR_CLIENT_KEY = "ConnectorClient";
private AppCredentials appCredentials;
@ -365,7 +366,9 @@ public class BotFrameworkAdapter extends BotAdapter implements AdapterIntegratio
if (activity.isType(ActivityTypes.INVOKE)) {
Activity invokeResponse = context.getTurnState().get(INVOKE_RESPONSE_KEY);
if (invokeResponse == null) {
throw new IllegalStateException("Bot failed to return a valid 'invokeResponse' activity.");
return CompletableFuture.completedFuture(
new InvokeResponse(HttpURLConnection.HTTP_NOT_IMPLEMENTED, null)
);
} else {
return CompletableFuture.completedFuture((InvokeResponse) invokeResponse.getValue());
}

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

@ -21,6 +21,16 @@ public class InvokeResponse {
*/
private Object body;
/**
* Initializes new instance of InvokeResponse.
* @param withStatus The invoke response status.
* @param withBody The invoke response body.
*/
public InvokeResponse(int withStatus, Object withBody) {
status = withStatus;
body = withBody;
}
/**
* Gets the HTTP status code for the response.
* @return The HTTP status code.

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

@ -0,0 +1,518 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.builder.teams;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.bot.builder.ActivityHandler;
import com.microsoft.bot.builder.InvokeResponse;
import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.schema.ChannelAccount;
import com.microsoft.bot.schema.ResultPair;
import com.microsoft.bot.schema.Serialization;
import com.microsoft.bot.schema.teams.AppBasedLinkQuery;
import com.microsoft.bot.schema.teams.ChannelInfo;
import com.microsoft.bot.schema.teams.FileConsentCardResponse;
import com.microsoft.bot.schema.teams.MessagingExtensionAction;
import com.microsoft.bot.schema.teams.MessagingExtensionActionResponse;
import com.microsoft.bot.schema.teams.MessagingExtensionQuery;
import com.microsoft.bot.schema.teams.MessagingExtensionResponse;
import com.microsoft.bot.schema.teams.O365ConnectorCardActionQuery;
import com.microsoft.bot.schema.teams.TaskModuleRequest;
import com.microsoft.bot.schema.teams.TaskModuleResponse;
import com.microsoft.bot.schema.teams.TeamInfo;
import com.microsoft.bot.schema.teams.TeamsChannelAccount;
import com.microsoft.bot.schema.teams.TeamsChannelData;
import org.apache.commons.lang3.StringUtils;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.stream.Collectors;
/**
* A Teams implementation of the Bot interface intended for further subclassing.
* Derive from this class to plug in code to handle particular Activity types.
* Pre and post processing of Activities can be plugged in by deriving and calling
* the base class implementation.
*/
@SuppressWarnings({"checkstyle:JavadocMethod", "checkstyle:DesignForExtension"})
public class TeamsActivityHandler extends ActivityHandler {
/**
* Invoked when an invoke activity is received from the connector when the base behavior of
* onTurn is used.
*
* @param turnContext The current TurnContext.
* @return A task that represents the work queued to execute.
*/
@Override
protected CompletableFuture<InvokeResponse> onInvokeActivity(TurnContext turnContext) {
CompletableFuture<InvokeResponse> result;
try {
if (turnContext.getActivity().getName() == null && turnContext.getActivity().isTeamsActivity()) {
result = onTeamsCardActionInvoke(turnContext);
} else {
switch (turnContext.getActivity().getName()) {
case "fileConsent/invoke":
result = onTeamsFileConsent(turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), FileConsentCardResponse.class)
);
break;
case "actionableMessage/executeAction":
result = onTeamsO365ConnectorCardAction(turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), O365ConnectorCardActionQuery.class)
)
.thenApply(aVoid -> createInvokeResponse(null));
break;
case "composeExtension/queryLink":
result = onTeamsAppBasedLinkQuery(turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), AppBasedLinkQuery.class)
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "composeExtension/query":
result = onTeamsMessagingExtensionQuery(turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), MessagingExtensionQuery.class)
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "composeExtension/selectItem":
result = onTeamsMessagingExtensionSelectItem(turnContext, turnContext.getActivity().getValue())
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "composeExtension/submitAction":
result = onTeamsMessagingExtensionSubmitActionDispatch(turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), MessagingExtensionAction.class)
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "composeExtension/fetchTask":
result = onTeamsMessagingExtensionFetchTask(turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), MessagingExtensionAction.class)
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "composeExtension/querySettingUrl":
result = onTeamsMessagingExtensionConfigurationQuerySettingUrl(
turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), MessagingExtensionQuery.class
)
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "composeExtension/setting":
result = onTeamsMessagingExtensionConfigurationSetting(
turnContext, turnContext.getActivity().getValue()
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "composeExtension/onCardButtonClicked":
result = onTeamsMessagingExtensionCardButtonClicked(
turnContext, turnContext.getActivity().getValue()
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "task/fetch":
result = onTeamsTaskModuleFetch(turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), TaskModuleRequest.class)
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
case "task/submit":
result = onTeamsTaskModuleSubmit(turnContext, Serialization.safeGetAs(
turnContext.getActivity().getValue(), TaskModuleRequest.class)
)
.thenApply(ActivityHandler::createInvokeResponse);
break;
default:
result = super.onInvokeActivity(turnContext);
break;
}
}
} catch (Throwable t) {
result = new CompletableFuture<>();
result.completeExceptionally(t);
}
return result.exceptionally(e -> {
if (e instanceof InvokeResponseExcetion) {
return ((InvokeResponseExcetion) e).createInvokeResponse();
}
return new InvokeResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, e.getLocalizedMessage());
});
}
protected CompletableFuture<InvokeResponse> onTeamsCardActionInvoke(TurnContext turnContext) {
return notImplemented();
}
protected CompletableFuture<Void> onSignInInvoke(TurnContext turnContext) {
return onTeamsSigninVerifyState(turnContext);
}
protected CompletableFuture<Void> onTeamsSigninVerifyState(TurnContext turnContext) {
return notImplemented();
}
protected CompletableFuture<InvokeResponse> onTeamsFileConsent(
TurnContext turnContext,
FileConsentCardResponse fileConsentCardResponse) {
switch (fileConsentCardResponse.getAction()) {
case "accept":
return onTeamsFileConsentAccept(turnContext, fileConsentCardResponse)
.thenApply(aVoid -> createInvokeResponse(null));
case "decline":
return onTeamsFileConsentDecline(turnContext, fileConsentCardResponse)
.thenApply(aVoid -> createInvokeResponse(null));
default:
CompletableFuture<InvokeResponse> result = new CompletableFuture<>();
result.completeExceptionally(new InvokeResponseExcetion(
HttpURLConnection.HTTP_BAD_REQUEST,
fileConsentCardResponse.getAction() + " is not a supported Action."
));
return result;
}
}
protected CompletableFuture<Void> onTeamsFileConsentAccept(
TurnContext turnContext,
FileConsentCardResponse fileConsentCardResponse) {
return notImplemented();
}
protected CompletableFuture<Void> onTeamsFileConsentDecline(
TurnContext turnContext,
FileConsentCardResponse fileConsentCardResponse) {
return notImplemented();
}
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionQuery(
TurnContext turnContext,
MessagingExtensionQuery query) {
return notImplemented();
}
protected CompletableFuture<Void> onTeamsO365ConnectorCardAction(
TurnContext turnContext,
O365ConnectorCardActionQuery query) {
return notImplemented();
}
protected CompletableFuture<MessagingExtensionResponse> onTeamsAppBasedLinkQuery(
TurnContext turnContext,
AppBasedLinkQuery query) {
return notImplemented();
}
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionSelectItem(
TurnContext turnContext,
Object query) {
return notImplemented();
}
protected CompletableFuture<MessagingExtensionActionResponse> onTeamsMessagingExtensionFetchTask(
TurnContext turnContext,
MessagingExtensionAction action) {
return notImplemented();
}
protected CompletableFuture<MessagingExtensionActionResponse> onTeamsMessagingExtensionSubmitActionDispatch(
TurnContext turnContext,
MessagingExtensionAction action) {
if (!StringUtils.isEmpty(action.getBotMessagePreviewAction())) {
switch (action.getBotMessagePreviewAction()) {
case "edit":
return onTeamsMessagingExtensionBotMessagePreviewEdit(turnContext, action);
case "send":
return onTeamsMessagingExtensionBotMessagePreviewSend(turnContext, action);
default:
CompletableFuture<MessagingExtensionActionResponse> result = new CompletableFuture<>();
result.completeExceptionally(new InvokeResponseExcetion(
HttpURLConnection.HTTP_BAD_REQUEST,
action.getBotMessagePreviewAction() + " is not a support BotMessagePreviewAction"
));
return result;
}
} else {
return onTeamsMessagingExtensionSubmitAction(turnContext, action);
}
}
protected CompletableFuture<MessagingExtensionActionResponse> onTeamsMessagingExtensionSubmitAction(
TurnContext turnContext,
MessagingExtensionAction action) {
return notImplemented();
}
protected CompletableFuture<MessagingExtensionActionResponse> onTeamsMessagingExtensionBotMessagePreviewEdit(
TurnContext turnContext,
MessagingExtensionAction action) {
return notImplemented();
}
protected CompletableFuture<MessagingExtensionActionResponse> onTeamsMessagingExtensionBotMessagePreviewSend(
TurnContext turnContext,
MessagingExtensionAction action) {
return notImplemented();
}
protected CompletableFuture<MessagingExtensionResponse> onTeamsMessagingExtensionConfigurationQuerySettingUrl(
TurnContext turnContext,
MessagingExtensionQuery query) {
return notImplemented();
}
protected CompletableFuture<Void> onTeamsMessagingExtensionConfigurationSetting(
TurnContext turnContext,
Object settings) {
return notImplemented();
}
protected CompletableFuture<TaskModuleResponse> onTeamsTaskModuleFetch(
TurnContext turnContext,
TaskModuleRequest taskModuleRequest) {
return notImplemented();
}
protected CompletableFuture<Void> onTeamsMessagingExtensionCardButtonClicked(
TurnContext turnContext,
Object cardData) {
return notImplemented();
}
protected CompletableFuture<Void> onTeamsTaskModuleSubmit(
TurnContext turnContext,
TaskModuleRequest taskModuleRequest) {
return notImplemented();
}
protected CompletableFuture<Void> onConversationUpdateActivity(TurnContext turnContext) {
if (turnContext.getActivity().isTeamsActivity()) {
ResultPair<TeamsChannelData> channelData =
turnContext.getActivity().tryGetChannelData(TeamsChannelData.class);
if (turnContext.getActivity().getMembersAdded() != null) {
return onTeamsMembersAddedDispatch(
turnContext.getActivity().getMembersAdded(),
channelData.result() ? channelData.value().getTeam() : null,
turnContext
);
}
if (turnContext.getActivity().getMembersRemoved() != null) {
return onTeamsMembersRemovedDispatch(
turnContext.getActivity().getMembersAdded(),
channelData.result() ? channelData.value().getTeam() : null,
turnContext
);
}
if (channelData.result()) {
switch (channelData.value().getEventType()) {
case "channelCreated":
return onTeamsChannelCreated(
channelData.value().getChannel(),
channelData.value().getTeam(),
turnContext
);
case "channelDeleted":
return onTeamsChannelDeleted(
channelData.value().getChannel(),
channelData.value().getTeam(),
turnContext
);
case "channelRenamed":
return onTeamsChannelRenamed(
channelData.value().getChannel(),
channelData.value().getTeam(),
turnContext
);
case "teamRenamed":
return onTeamsTeamRenamed(
channelData.value().getChannel(),
channelData.value().getTeam(),
turnContext
);
default:
return super.onConversationUpdateActivity(turnContext);
}
}
}
return super.onConversationUpdateActivity(turnContext);
}
protected CompletableFuture<Void> onTeamsMembersAddedDispatch(
List<ChannelAccount> membersAdded,
TeamInfo teamInfo,
TurnContext turnContext
) {
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
Map<String, TeamsChannelAccount> teamMembers = null;
List<TeamsChannelAccount> teamsMembersAdded = new ArrayList<>();
for (ChannelAccount memberAdded : membersAdded) {
if (!memberAdded.getProperties().isEmpty()) {
try {
JsonNode node = mapper.valueToTree(memberAdded);
teamsMembersAdded.add(mapper.treeToValue(node, TeamsChannelAccount.class));
} catch (JsonProcessingException jpe) {
return withException(jpe);
}
} else {
// this code path is intended to be temporary and should be removed in 4.7/4.8
// or whenever Teams is updated
// we have a simple ChannelAccount so will try to flesh out the details using
// the getMembers call
if (teamMembers == null) {
List<TeamsChannelAccount> result = TeamsInfo.getMembers(turnContext).join();
teamMembers = result.stream().collect(Collectors.toMap(ChannelAccount::getId, item -> item));
}
if (teamMembers.containsKey(memberAdded.getId())) {
teamsMembersAdded.add(teamMembers.get(memberAdded.getId()));
} else {
// unable to find the member added in ConversationUpdate Activity in the response from
// the getMembers call
TeamsChannelAccount newTeamsChannelAccount = new TeamsChannelAccount();
newTeamsChannelAccount.setId(memberAdded.getId());
newTeamsChannelAccount.setName(memberAdded.getName());
newTeamsChannelAccount.setAadObjectId(memberAdded.getAadObjectId());
newTeamsChannelAccount.setRole(memberAdded.getRole());
teamsMembersAdded.add(newTeamsChannelAccount);
}
}
}
return onTeamsMembersAdded(teamsMembersAdded, teamInfo, turnContext);
}
protected CompletableFuture<Void> onTeamsMembersRemovedDispatch(
List<ChannelAccount> membersRemoved,
TeamInfo teamInfo,
TurnContext turnContext
) {
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
List<TeamsChannelAccount> teamsMembersRemoved = new ArrayList<>();
for (ChannelAccount memberRemoved : membersRemoved) {
try {
JsonNode node = mapper.valueToTree(memberRemoved);
teamsMembersRemoved.add(mapper.treeToValue(node, TeamsChannelAccount.class));
} catch (JsonProcessingException jpe) {
return withException(jpe);
}
}
return onTeamsMembersRemoved(teamsMembersRemoved, teamInfo, turnContext);
}
protected CompletableFuture<Void> onTeamsMembersAdded(
List<TeamsChannelAccount> membersAdded,
TeamInfo teamInfo,
TurnContext turnContext
) {
return onMembersAdded(new ArrayList<>(membersAdded), turnContext);
}
protected CompletableFuture<Void> onTeamsMembersRemoved(
List<TeamsChannelAccount> membersRemoved,
TeamInfo teamInfo,
TurnContext turnContext
) {
return onMembersRemoved(new ArrayList<>(membersRemoved), turnContext);
}
protected CompletableFuture<Void> onTeamsChannelCreated(
ChannelInfo channelInfo,
TeamInfo teamInfo,
TurnContext turnContext
) {
return CompletableFuture.completedFuture(null);
}
protected CompletableFuture<Void> onTeamsChannelDeleted(
ChannelInfo channelInfo,
TeamInfo teamInfo,
TurnContext turnContext
) {
return CompletableFuture.completedFuture(null);
}
protected CompletableFuture<Void> onTeamsChannelRenamed(
ChannelInfo channelInfo,
TeamInfo teamInfo,
TurnContext turnContext
) {
return CompletableFuture.completedFuture(null);
}
protected CompletableFuture<Void> onTeamsTeamRenamed(
ChannelInfo channelInfo,
TeamInfo teamInfo,
TurnContext turnContext
) {
return CompletableFuture.completedFuture(null);
}
private <T> CompletableFuture<T> notImplemented() {
CompletableFuture<T> result = new CompletableFuture<>();
result.completeExceptionally(new InvokeResponseExcetion(HttpURLConnection.HTTP_NOT_IMPLEMENTED));
return result;
}
private <T> CompletableFuture<T> withException(Throwable t) {
CompletableFuture<T> result = new CompletableFuture<>();
result.completeExceptionally(new CompletionException(t));
return result;
}
}

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

@ -0,0 +1,326 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.builder.teams;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.bot.builder.BotFrameworkAdapter;
import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.connector.ConnectorClient;
import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials;
import com.microsoft.bot.connector.rest.RestTeamsConnectorClient;
import com.microsoft.bot.connector.teams.TeamsConnectorClient;
import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.ConversationParameters;
import com.microsoft.bot.schema.ConversationReference;
import com.microsoft.bot.schema.PagedMembersResult;
import com.microsoft.bot.schema.Pair;
import com.microsoft.bot.schema.teams.ChannelInfo;
import com.microsoft.bot.schema.teams.ConversationList;
import com.microsoft.bot.schema.teams.TeamDetails;
import com.microsoft.bot.schema.teams.TeamsChannelAccount;
import com.microsoft.bot.schema.teams.TeamsChannelData;
import com.microsoft.bot.schema.teams.TeamsPagedMembersResult;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
/**
* Teams helper methods.
*/
@SuppressWarnings({"checkstyle:JavadocMethod"})
public final class TeamsInfo {
private TeamsInfo() {
}
/**
* Returns TeamDetails for a Team.
*
* @param turnContext The current TurnContext.
* @param teamId The team id.
* @return A TeamDetails object.
*/
public static CompletableFuture<TeamDetails> getTeamDetails(TurnContext turnContext, String teamId) {
String effectiveTeamId = teamId != null ? teamId : turnContext.getActivity().teamsGetTeamId();
if (effectiveTeamId == null) {
return illegalArgument("This method is only valid within the scope of MS Teams Team.");
}
return getTeamsConnectorClient(turnContext).getTeams().fetchTeamDetails(effectiveTeamId);
}
/**
* Returns a list of Teams channels.
*
* @param turnContext The current TurnContext.
* @param teamId The team id.
* @return A list of ChannelInfo objects.
*/
public static CompletableFuture<List<ChannelInfo>> getTeamChannels(TurnContext turnContext, String teamId) {
String effectiveTeamId = teamId != null ? teamId : turnContext.getActivity().teamsGetTeamId();
if (effectiveTeamId == null) {
return illegalArgument("This method is only valid within the scope of MS Teams Team.");
}
return getTeamsConnectorClient(turnContext).getTeams().fetchChannelList(effectiveTeamId)
.thenApply(ConversationList::getConversations);
}
/**
* Returns a list of team members for the specified team.
*
* @param turnContext The current TurnContext.
* @param teamId The team id.
* @return A list of TeamChannelAccount objects.
*/
public static CompletableFuture<List<TeamsChannelAccount>> getTeamMembers(TurnContext turnContext, String teamId) {
String effectiveTeamId = teamId != null ? teamId : turnContext.getActivity().teamsGetTeamId();
if (effectiveTeamId == null) {
return illegalArgument("This method is only valid within the scope of MS Teams Team.");
}
return getMembers(getConnectorClient(turnContext), effectiveTeamId);
}
/**
* Returns info for the specified user.
*
* @param turnContext The current TurnContext.
* @param userId The user id.
* @param teamId The team id for the user.
* @return A TeamsChannelAccount for the user, or null if not found.
*/
public static CompletableFuture<TeamsChannelAccount> getTeamMember(TurnContext turnContext, String userId,
String teamId) {
String effectiveTeamId = teamId != null ? teamId : turnContext.getActivity().teamsGetTeamId();
if (effectiveTeamId == null) {
return illegalArgument("This method is only valid within the scope of MS Teams Team.");
}
return getMember(getConnectorClient(turnContext), userId, effectiveTeamId);
}
/**
* Returns a list of members for the current conversation.
*
* @param turnContext The current TurnContext.
* @return A list of TeamsChannelAccount for each member. If this isn't a Teams conversation, a list
* of ChannelAccounts is converted to TeamsChannelAccount.
*/
public static CompletableFuture<List<TeamsChannelAccount>> getMembers(TurnContext turnContext) {
String teamId = turnContext.getActivity().teamsGetTeamId();
if (!StringUtils.isEmpty(teamId)) {
return getTeamMembers(turnContext, teamId);
}
String conversationId = turnContext.getActivity().getConversation() != null
? turnContext.getActivity().getConversation().getId()
: null;
return getMembers(getConnectorClient(turnContext), conversationId);
}
public static CompletableFuture<TeamsChannelAccount> getMember(TurnContext turnContext, String userId) {
String teamId = turnContext.getActivity().teamsGetTeamId();
if (!StringUtils.isEmpty(teamId)) {
return getTeamMember(turnContext, userId, teamId);
}
String conversationId = turnContext.getActivity().getConversation() != null
? turnContext.getActivity().getConversation().getId()
: null;
return getMember(getConnectorClient(turnContext), userId, conversationId);
}
/**
* Returns paged Team member list.
*
* @param turnContext The current TurnContext.
* @param teamId The team id.
* @param continuationToken The continuationToken from a previous call, or null the first call.
* @return A TeamsPageMembersResult.
*/
public static CompletableFuture<TeamsPagedMembersResult> getPagedTeamMembers(TurnContext turnContext,
String teamId,
String continuationToken) {
String effectiveTeamId = teamId != null ? teamId : turnContext.getActivity().teamsGetTeamId();
if (effectiveTeamId == null) {
return illegalArgument("This method is only valid within the scope of MS Teams Team.");
}
return getPagedMembers(getConnectorClient(turnContext), effectiveTeamId, continuationToken);
}
/**
* Returns paged Team member list. If the Activity is not from a Teams channel, a PagedMembersResult
* is converted to TeamsPagedMembersResult.
*
* @param turnContext The current TurnContext.
* @param continuationToken The continuationToken from a previous call, or null the first call.
* @return A TeamsPageMembersResult.
*/
public static CompletableFuture<TeamsPagedMembersResult> getPagedMembers(TurnContext turnContext,
String continuationToken) {
String teamId = turnContext.getActivity().teamsGetTeamId();
if (!StringUtils.isEmpty(teamId)) {
return getPagedTeamMembers(turnContext, teamId, continuationToken);
}
String conversationId = turnContext.getActivity().getConversation() != null
? turnContext.getActivity().getConversation().getId()
: null;
return getPagedMembers(getConnectorClient(turnContext), conversationId, continuationToken);
}
private static CompletableFuture<List<TeamsChannelAccount>> getMembers(ConnectorClient connectorClient,
String conversationId) {
if (StringUtils.isEmpty(conversationId)) {
return illegalArgument("The getMembers operation needs a valid conversation Id.");
}
return connectorClient.getConversations().getConversationMembers(conversationId)
.thenApply(teamMembers -> {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
List<TeamsChannelAccount> members = teamMembers.stream()
.map(channelAccount -> {
try {
// convert fro ChannelAccount to TeamsChannelAccount by going to JSON then back
// to TeamsChannelAccount.
JsonNode node = objectMapper.valueToTree(channelAccount);
return objectMapper.treeToValue(node, TeamsChannelAccount.class);
} catch (JsonProcessingException jpe) {
// this would be a conversion error. for now, return null and filter the results
// below. there is probably a more elegant way to handle this.
return null;
}
})
.collect(Collectors.toCollection(ArrayList::new));
members.removeIf(Objects::isNull);
return members;
});
}
private static CompletableFuture<TeamsChannelAccount> getMember(ConnectorClient connectorClient, String userId,
String conversationId) {
if (StringUtils.isEmpty(conversationId) || StringUtils.isEmpty(userId)) {
return illegalArgument("The getMember operation needs a valid userId and conversationId.");
}
return connectorClient.getConversations().getConversationMember(userId, conversationId)
.thenApply(teamMember -> {
if (teamMember == null) {
return null;
}
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
try {
// convert fro ChannelAccount to TeamsChannelAccount by going to JSON then back
// to TeamsChannelAccount.
JsonNode node = objectMapper.valueToTree(teamMember);
return objectMapper.treeToValue(node, TeamsChannelAccount.class);
} catch (JsonProcessingException jpe) {
// this would be a conversion error.
return null;
}
});
}
private static CompletableFuture<TeamsPagedMembersResult> getPagedMembers(ConnectorClient connectorClient,
String conversationId,
String continuationToken) {
if (StringUtils.isEmpty(conversationId)) {
return illegalArgument("The getPagedMembers operation needs a valid conversation Id.");
}
CompletableFuture<PagedMembersResult> pagedResult;
if (StringUtils.isEmpty(continuationToken)) {
pagedResult = connectorClient.getConversations().getConversationPagedMembers(conversationId);
} else {
pagedResult =
connectorClient.getConversations().getConversationPagedMembers(conversationId, continuationToken);
}
// return a converted TeamsPagedMembersResult
return pagedResult.thenApply(TeamsPagedMembersResult::new);
}
private static ConnectorClient getConnectorClient(TurnContext turnContext) {
ConnectorClient client = turnContext.getTurnState().get(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY);
if (client == null) {
throw new IllegalStateException("This method requires a connector client.");
}
return client;
}
private static TeamsConnectorClient getTeamsConnectorClient(TurnContext turnContext) {
ConnectorClient client = getConnectorClient(turnContext);
return new RestTeamsConnectorClient(client.baseUrl(), client.credentials());
}
public static CompletableFuture<Pair<ConversationReference, String>> sendMessageToTeamsChannel(
TurnContext turnContext,
Activity activity,
String teamsChannelId,
MicrosoftAppCredentials credentials
) {
if (turnContext == null) {
return illegalArgument("turnContext is required");
}
if (turnContext.getActivity() == null) {
return illegalArgument("turnContext.Activity is required");
}
if (StringUtils.isEmpty(teamsChannelId)) {
return illegalArgument("teamsChannelId is required");
}
if (credentials == null) {
return illegalArgument("credentials is required");
}
AtomicReference<ConversationReference> conversationReference = new AtomicReference<>();
AtomicReference<String> newActivityId = new AtomicReference<>();
String serviceUrl = turnContext.getActivity().getServiceUrl();
TeamsChannelData teamsChannelData = new TeamsChannelData();
teamsChannelData.setChannel(new ChannelInfo(teamsChannelId));
ConversationParameters conversationParameters = new ConversationParameters();
conversationParameters.setIsGroup(true);
// conversationParameters.setChannelData(new Object() {
// Object channel = new Object() {
// String id = teamsChannelId;
// };
// });
conversationParameters.setChannelData(teamsChannelData);
conversationParameters.setActivity(activity);
return ((BotFrameworkAdapter) turnContext.getAdapter()).createConversation(
teamsChannelId,
serviceUrl,
credentials,
conversationParameters,
(TurnContext context) -> {
conversationReference.set(context.getActivity().getConversationReference());
newActivityId.set(context.getActivity().getId());
return CompletableFuture.completedFuture(null);
}
)
.thenApply(aVoid -> new Pair<>(conversationReference.get(), newActivityId.get()));
}
private static <T> CompletableFuture<T> illegalArgument(String message) {
CompletableFuture<T> detailResult = new CompletableFuture<>();
detailResult.completeExceptionally(new IllegalArgumentException(message));
return detailResult;
}
}

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

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
/**
* This package contains the classes for Bot-Builder-Inspection.
*/
package com.microsoft.bot.builder.teams;

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

@ -3,7 +3,6 @@ package com.microsoft.bot.builder;
import com.microsoft.bot.schema.*;
import org.junit.Assert;
import org.junit.Test;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.util.ArrayList;
import java.util.List;
@ -280,17 +279,17 @@ public class ActivityHandlerTests {
private static class NotImplementedAdapter extends BotAdapter {
@Override
public CompletableFuture<ResourceResponse[]> sendActivities(TurnContext context, List<Activity> activities) {
throw new NotImplementedException();
throw new RuntimeException();
}
@Override
public CompletableFuture<ResourceResponse> updateActivity(TurnContext context, Activity activity) {
throw new NotImplementedException();
throw new RuntimeException();
}
@Override
public CompletableFuture<Void> deleteActivity(TurnContext context, ConversationReference reference) {
throw new NotImplementedException();
throw new RuntimeException();
}
}

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

@ -8,6 +8,7 @@ import com.microsoft.bot.builder.adapters.TestFlow;
import com.microsoft.bot.connector.Attachments;
import com.microsoft.bot.connector.ConnectorClient;
import com.microsoft.bot.connector.Conversations;
import com.microsoft.bot.rest.credentials.ServiceClientCredentials;
import com.microsoft.bot.schema.Activity;
import com.microsoft.bot.schema.ActivityTypes;
import com.microsoft.bot.schema.ConversationAccount;
@ -578,7 +579,7 @@ public class TurnContextTests {
}
@Override
public void setLongRunningOperationRetryTimeout(int longRunningOperationRetryTimeout) {
public void setLongRunningOperationRetryTimeout(int timeout) {
}
@ -592,6 +593,12 @@ public class TurnContextTests {
}
@Override
public String baseUrl() { return null; }
@Override
public ServiceClientCredentials credentials() { return null; }
@Override
public Attachments getAttachments() {
return null;

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

@ -11,6 +11,7 @@
package com.microsoft.bot.connector;
import com.microsoft.bot.rest.RestClient;
import com.microsoft.bot.rest.credentials.ServiceClientCredentials;
/**
* The interface for ConnectorClient class.
@ -23,6 +24,18 @@ public interface ConnectorClient extends AutoCloseable {
*/
RestClient getRestClient();
/**
* Returns the base url for this ConnectorClient.
* @return The base url.
*/
String baseUrl();
/**
* Returns the credentials in use.
* @return The ServiceClientCredentials in use.
*/
ServiceClientCredentials credentials();
/**
* Gets the User-Agent header for the client.
*

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

@ -216,6 +216,14 @@ public interface Conversations {
*/
CompletableFuture<List<ChannelAccount>> getConversationMembers(String conversationId);
/**
* Retrieves a single member of a conversation by ID.
* @param userId The user id.
* @param conversationId The conversation id.
* @return The ChannelAccount for the user.
*/
CompletableFuture<ChannelAccount> getConversationMember(String userId, String conversationId);
/**
* DeleteConversationMember.
* Deletes a member from a conversation.
@ -279,9 +287,9 @@ public interface Conversations {
* of the conversation and a continuation token that can be used to get more values.
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. The pageSize parameter can be used as a suggestion. If there are no
* additional results the response will not contain a continuation token. If there are no members in the
* conversation the Members will be empty or not present in the response.
* vary between channels and calls. If there are no additional results the response will not contain a
* continuation token. If there are no members in the conversation the Members will be empty or not
* present in the response.
*
* A response to a request that has a continuation token from a prior request may rarely return members
* from a previous request.
@ -292,4 +300,27 @@ public interface Conversations {
* @return the PagedMembersResult object if successful.
*/
CompletableFuture<PagedMembersResult> getConversationPagedMembers(String conversationId);
/**
* Enumerate the members of a conversation one page at a time.
*
* This REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided.
* It returns a PagedMembersResult, which contains an array of ChannelAccounts representing the members
* of the conversation and a continuation token that can be used to get more values.
*
* One page of ChannelAccounts records are returned with each call. The number of records in a page may
* vary between channels and calls. If there are no additional results the response will not contain a
* continuation token. If there are no members in the conversation the Members will be empty or not
* present in the response.
*
* A response to a request that has a continuation token from a prior request may rarely return members
* from a previous request.
*
* @param conversationId Conversation ID
* @param continuationToken The continuationToken from a previous call.
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the PagedMembersResult object if successful.
*/
CompletableFuture<PagedMembersResult> getConversationPagedMembers(String conversationId, String continuationToken);
}

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

@ -94,6 +94,23 @@ public class RestConnectorClient extends AzureServiceClient implements Connector
return super.restClient();
}
/**
* Returns the base url for this ConnectorClient.
* @return The base url.
*/
@Override
public String baseUrl() {
return getRestClient().retrofit().baseUrl().toString();
}
/**
* Returns the credentials in use.
* @return The ServiceClientCredentials in use.
*/
public ServiceClientCredentials credentials() {
return getRestClient().credentials();
}
/** Gets or sets the preferred language for the response. */
private String acceptLanguage;
private String userAgentString;

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

@ -20,6 +20,7 @@ import com.microsoft.bot.connector.Conversations;
import com.google.common.reflect.TypeToken;
import com.microsoft.bot.rest.ServiceResponse;
import com.microsoft.bot.rest.Validator;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.List;
@ -42,16 +43,20 @@ import retrofit2.Response;
* in Conversations.
*/
public class RestConversations implements Conversations {
/** The Retrofit service to perform REST calls. */
/**
* The Retrofit service to perform REST calls.
*/
private ConversationsService service;
/** The service client containing this operation class. */
/**
* The service client containing this operation class.
*/
private RestConnectorClient client;
/**
* Initializes an instance of ConversationsImpl.
*
* @param withRetrofit the Retrofit instance built from a Retrofit Builder.
* @param withClient the instance of the service client containing this operation class.
* @param withClient the instance of the service client containing this operation class.
*/
RestConversations(Retrofit withRetrofit, RestConnectorClient withClient) {
this.service = withRetrofit.create(ConversationsService.class);
@ -64,91 +69,123 @@ public class RestConversations implements Conversations {
*/
@SuppressWarnings({"checkstyle:linelength", "checkstyle:JavadocMethod"})
interface ConversationsService {
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations getConversations" })
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations getConversations"})
@GET("v3/conversations")
CompletableFuture<Response<ResponseBody>> getConversations(@Query("continuationToken") String continuationToken,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations createConversation" })
@POST("v3/conversations")
CompletableFuture<Response<ResponseBody>> createConversation(@Body ConversationParameters parameters,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations sendToConversation" })
@POST("v3/conversations/{conversationId}/activities")
CompletableFuture<Response<ResponseBody>> sendToConversation(@Path("conversationId") String conversationId,
@Body Activity activity,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations updateActivity" })
@PUT("v3/conversations/{conversationId}/activities/{activityId}")
CompletableFuture<Response<ResponseBody>> updateActivity(@Path("conversationId") String conversationId,
@Path("activityId") String activityId,
@Body Activity activity,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations replyToActivity" })
@POST("v3/conversations/{conversationId}/activities/{activityId}")
CompletableFuture<Response<ResponseBody>> replyToActivity(@Path("conversationId") String conversationId,
@Path("activityId") String activityId,
@Body Activity activity,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations deleteActivity" })
@HTTP(path = "v3/conversations/{conversationId}/activities/{activityId}", method = "DELETE", hasBody = true)
CompletableFuture<Response<ResponseBody>> deleteActivity(@Path("conversationId") String conversationId,
@Path("activityId") String activityId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations getConversationMembers" })
@GET("v3/conversations/{conversationId}/members")
CompletableFuture<Response<ResponseBody>> getConversationMembers(@Path("conversationId") String conversationId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations deleteConversationMember" })
@HTTP(path = "v3/conversations/{conversationId}/members/{memberId}", method = "DELETE", hasBody = true)
CompletableFuture<Response<ResponseBody>> deleteConversationMember(@Path("conversationId") String conversationId,
@Path("memberId") String memberId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations getActivityMembers" })
@GET("v3/conversations/{conversationId}/activities/{activityId}/members")
CompletableFuture<Response<ResponseBody>> getActivityMembers(@Path("conversationId") String conversationId,
@Path("activityId") String activityId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations uploadAttachment" })
@POST("v3/conversations/{conversationId}/attachments")
CompletableFuture<Response<ResponseBody>> uploadAttachment(@Path("conversationId") String conversationId,
@Body AttachmentData attachmentUpload,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations sendConversationHistory" })
@POST("v3/conversations/{conversationId}/activities/history")
CompletableFuture<Response<ResponseBody>> sendConversationHistory(@Path("conversationId") String conversationId,
@Body Transcript history,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Conversations getConversationPagedMembers" })
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations createConversation"})
@POST("v3/conversations")
CompletableFuture<Response<ResponseBody>> createConversation(@Body ConversationParameters parameters,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations sendToConversation"})
@POST("v3/conversations/{conversationId}/activities")
CompletableFuture<Response<ResponseBody>> sendToConversation(@Path("conversationId") String conversationId,
@Body Activity activity,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations updateActivity"})
@PUT("v3/conversations/{conversationId}/activities/{activityId}")
CompletableFuture<Response<ResponseBody>> updateActivity(@Path("conversationId") String conversationId,
@Path("activityId") String activityId,
@Body Activity activity,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations replyToActivity"})
@POST("v3/conversations/{conversationId}/activities/{activityId}")
CompletableFuture<Response<ResponseBody>> replyToActivity(@Path("conversationId") String conversationId,
@Path("activityId") String activityId,
@Body Activity activity,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations deleteActivity"})
@HTTP(path = "v3/conversations/{conversationId}/activities/{activityId}", method = "DELETE", hasBody = true)
CompletableFuture<Response<ResponseBody>> deleteActivity(@Path("conversationId") String conversationId,
@Path("activityId") String activityId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations getConversationMembers"})
@GET("v3/conversations/{conversationId}/members")
CompletableFuture<Response<ResponseBody>> getConversationMembers(@Path("conversationId") String conversationId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations getConversationMembers"})
@GET("v3/conversations/{conversationId}/members/{userId}")
CompletableFuture<Response<ResponseBody>> getConversationMember(
@Path("userId") String userId,
@Path("conversationId") String conversationId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations deleteConversationMember"})
@HTTP(path = "v3/conversations/{conversationId}/members/{memberId}", method = "DELETE", hasBody = true)
CompletableFuture<Response<ResponseBody>> deleteConversationMember(
@Path("conversationId") String conversationId,
@Path("memberId") String memberId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations getActivityMembers"})
@GET("v3/conversations/{conversationId}/activities/{activityId}/members")
CompletableFuture<Response<ResponseBody>> getActivityMembers(@Path("conversationId") String conversationId,
@Path("activityId") String activityId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations uploadAttachment"})
@POST("v3/conversations/{conversationId}/attachments")
CompletableFuture<Response<ResponseBody>> uploadAttachment(@Path("conversationId") String conversationId,
@Body AttachmentData attachmentUpload,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations sendConversationHistory"})
@POST("v3/conversations/{conversationId}/activities/history")
CompletableFuture<Response<ResponseBody>> sendConversationHistory(@Path("conversationId") String conversationId,
@Body Transcript history,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations getConversationPagedMembers"})
@GET("v3/conversations/{conversationId}/pagedmembers")
CompletableFuture<Response<ResponseBody>> getConversationPagedMembers(@Path("conversationId") String conversationId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
CompletableFuture<Response<ResponseBody>> getConversationPagedMembers(
@Path("conversationId") String conversationId,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
@Headers({"Content-Type: application/json; charset=utf-8",
"x-ms-logging-context: com.microsoft.bot.schema.Conversations getConversationPagedMembers"})
@GET("v3/conversations/{conversationId}/pagedmembers?continuationToken={continuationToken}")
CompletableFuture<Response<ResponseBody>> getConversationPagedMembers(
@Path("conversationId") String conversationId,
@Path("continuationToken") String continuationToken,
@Header("accept-language") String acceptLanguage,
@Header("User-Agent") String userAgent);
}
/**
* Implementation of getConversationsAsync.
* Implementation of getConversations.
*
* @see Conversations#getConversations
*/
@ -158,7 +195,7 @@ public class RestConversations implements Conversations {
}
/**
* Implementation of getConversationsAsync.
* Implementation of getConversations.
*
* @see Conversations#getConversations
*/
@ -181,13 +218,14 @@ public class RestConversations implements Conversations {
return client.restClient().responseBuilderFactory()
.<ConversationsResult, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<ConversationsResult>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<ConversationsResult>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of createConversationWithServiceResponseAsync.
* Implementation of createConversation.
*
* @see Conversations#createConversation
*/
@ -215,15 +253,18 @@ public class RestConversations implements Conversations {
return client.restClient().responseBuilderFactory()
.<ConversationResourceResponse, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<ConversationResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ConversationResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ConversationResourceResponse>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<ConversationResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ConversationResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ConversationResourceResponse>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of sendToConversationAsync.
* Implementation of sendToConversation.
*
* @see Conversations#sendToConversation
*/
@ -254,15 +295,18 @@ public class RestConversations implements Conversations {
return client.restClient()
.responseBuilderFactory().<ResourceResponse, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of updateActivityAsync.
* Implementation of updateActivity.
*
* @see Conversations#updateActivity
*/
@ -282,7 +326,7 @@ public class RestConversations implements Conversations {
Validator.validate(activity);
return service.updateActivity(conversationId, activityId, activity,
client.getAcceptLanguage(), client.getUserAgent())
client.getAcceptLanguage(), client.getUserAgent())
.thenApply(responseBodyResponse -> {
try {
@ -300,15 +344,18 @@ public class RestConversations implements Conversations {
return client.restClient()
.responseBuilderFactory().<ResourceResponse, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of replyToActivityAsync.
* Implementation of replyToActivity.
*
* @see Conversations#replyToActivity
*/
@ -328,7 +375,7 @@ public class RestConversations implements Conversations {
Validator.validate(activity);
return service.replyToActivity(conversationId, activityId, activity,
client.getAcceptLanguage(), client.getUserAgent())
client.getAcceptLanguage(), client.getUserAgent())
.thenApply(responseBodyResponse -> {
try {
@ -346,15 +393,18 @@ public class RestConversations implements Conversations {
return client.restClient()
.responseBuilderFactory().<ResourceResponse, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of deleteActivityWithServiceResponseAsync.
* Implementation of deleteActivity.
*
* @see Conversations#deleteActivity
*/
@ -384,14 +434,16 @@ public class RestConversations implements Conversations {
return client.restClient()
.responseBuilderFactory().<Void, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<Void>() { }.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<Void>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<Void>() {
}.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<Void>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of getConversationMembersAsync.
* Implementation of getConversationMembers.
*
* @see Conversations#getConversationMembers
*/
@ -418,13 +470,51 @@ public class RestConversations implements Conversations {
return client.restClient().responseBuilderFactory()
.<List<ChannelAccount>, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<List<ChannelAccount>>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<List<ChannelAccount>>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of deleteConversationMemberWithServiceResponseAsync.
* Implementation of getConversationMember.
*
* @see Conversations#getConversationMember
*/
@Override
public CompletableFuture<ChannelAccount> getConversationMember(String userId, String conversationId) {
if (userId == null) {
throw new IllegalArgumentException("Parameter userId is required and cannot be null.");
}
if (conversationId == null) {
throw new IllegalArgumentException("Parameter conversationId is required and cannot be null.");
}
return service.getConversationMember(userId, conversationId, client.getAcceptLanguage(), client.getUserAgent())
.thenApply(responseBodyResponse -> {
try {
return getConversationMemberDelegate(responseBodyResponse).body();
} catch (ErrorResponseException e) {
throw e;
} catch (Throwable t) {
throw new ErrorResponseException("getConversationMembersAsync", responseBodyResponse);
}
});
}
private ServiceResponse<ChannelAccount> getConversationMemberDelegate(
Response<ResponseBody> response) throws ErrorResponseException, IOException, IllegalArgumentException {
return client.restClient().responseBuilderFactory()
.<ChannelAccount, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<ChannelAccount>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of deleteConversationMember.
*
* @see Conversations#deleteConversationMember
*/
@ -438,7 +528,7 @@ public class RestConversations implements Conversations {
}
return service.deleteConversationMember(conversationId, memberId,
client.getAcceptLanguage(), client.getUserAgent())
client.getAcceptLanguage(), client.getUserAgent())
.thenApply(responseBodyResponse -> {
try {
@ -457,14 +547,16 @@ public class RestConversations implements Conversations {
return client.restClient()
.responseBuilderFactory().<Void, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<Void>() { }.getType())
.register(HttpURLConnection.HTTP_NO_CONTENT, new TypeToken<Void>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<Void>() {
}.getType())
.register(HttpURLConnection.HTTP_NO_CONTENT, new TypeToken<Void>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of getActivityMembersAsync.
* Implementation of getActivityMembers.
*
* @see Conversations#getActivityMembers
*/
@ -494,13 +586,14 @@ public class RestConversations implements Conversations {
return client.restClient().responseBuilderFactory()
.<List<ChannelAccount>, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<List<ChannelAccount>>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<List<ChannelAccount>>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of uploadAttachmentAsync.
* Implementation of uploadAttachment.
*
* @see Conversations#uploadAttachment
*/
@ -516,7 +609,7 @@ public class RestConversations implements Conversations {
Validator.validate(attachmentUpload);
return service.uploadAttachment(conversationId, attachmentUpload,
client.getAcceptLanguage(), client.getUserAgent())
client.getAcceptLanguage(), client.getUserAgent())
.thenApply(responseBodyResponse -> {
try {
@ -534,16 +627,19 @@ public class RestConversations implements Conversations {
return client.restClient()
.responseBuilderFactory().<ResourceResponse, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of sendConversationHistoryAsync.
* Implementation of sendConversationHistory.
*
* @see Conversations#sendConversationHistory
*/
@ -558,7 +654,7 @@ public class RestConversations implements Conversations {
Validator.validate(history);
return service.sendConversationHistory(conversationId, history,
client.getAcceptLanguage(), client.getUserAgent())
client.getAcceptLanguage(), client.getUserAgent())
.thenApply(responseBodyResponse -> {
try {
@ -576,18 +672,21 @@ public class RestConversations implements Conversations {
return client.restClient()
.responseBuilderFactory().<ResourceResponse, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() { }.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_CREATED, new TypeToken<ResourceResponse>() {
}.getType())
.register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken<ResourceResponse>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of getConversationPagedMembersAsync.
* Implementation of getConversationPagedMembers.
*
* @see Conversations#getConversationPagedMembers
* @see Conversations#getConversationPagedMembers(String conversationId)
*/
@Override
public CompletableFuture<PagedMembersResult> getConversationPagedMembers(String conversationId) {
@ -602,7 +701,7 @@ public class RestConversations implements Conversations {
} catch (ErrorResponseException e) {
throw e;
} catch (Throwable t) {
throw new ErrorResponseException("getConversationPagedMembersAsync", responseBodyResponse);
throw new ErrorResponseException("getConversationPagedMembers", responseBodyResponse);
}
});
}
@ -612,8 +711,53 @@ public class RestConversations implements Conversations {
return client.restClient().responseBuilderFactory()
.<PagedMembersResult, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<PagedMembersResult>() { }.getType())
.registerError(ErrorResponseException.class)
.build(response);
.register(HttpURLConnection.HTTP_OK, new TypeToken<PagedMembersResult>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
/**
* Implementation of getConversationPagedMembers.
*
* @see Conversations#getConversationPagedMembers(String conversationId, String continuationToken)
*
* @param conversationId Conversation ID
* @param continuationToken The continuationToken from a previous call.
* @throws IllegalArgumentException thrown if parameters fail the validation
* @throws RuntimeException all other wrapped checked exceptions if the request fails to be sent
* @return the PagedMembersResult object if successful.
*/
public CompletableFuture<PagedMembersResult> getConversationPagedMembers(String conversationId,
String continuationToken) {
if (conversationId == null) {
throw new IllegalArgumentException("Parameter conversationId is required and cannot be null.");
}
if (continuationToken == null) {
throw new IllegalArgumentException("Parameter continuationToken is required and cannot be null.");
}
return service.getConversationPagedMembers(
conversationId, continuationToken, client.getAcceptLanguage(), client.getUserAgent())
.thenApply(responseBodyResponse -> {
try {
return getConversationPagedMembers2Delegate(responseBodyResponse).body();
} catch (ErrorResponseException e) {
throw e;
} catch (Throwable t) {
throw new ErrorResponseException("getConversationPagedMembers", responseBodyResponse);
}
});
}
private ServiceResponse<PagedMembersResult> getConversationPagedMembers2Delegate(
Response<ResponseBody> response) throws ErrorResponseException, IOException, IllegalArgumentException {
return client.restClient().responseBuilderFactory()
.<PagedMembersResult, ErrorResponseException>newInstance(client.serializerAdapter())
.register(HttpURLConnection.HTTP_OK, new TypeToken<PagedMembersResult>() {
}.getType())
.registerError(ErrorResponseException.class)
.build(response);
}
}

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

@ -63,7 +63,7 @@ public class RestTeamsConnectorClient extends AzureServiceClient implements Team
* @param baseUrl the base URL of the host
* @param credentials the management credentials for Azure
*/
protected RestTeamsConnectorClient(String baseUrl, ServiceClientCredentials credentials) {
public RestTeamsConnectorClient(String baseUrl, ServiceClientCredentials credentials) {
super(baseUrl, credentials);
initialize();
}
@ -100,6 +100,23 @@ public class RestTeamsConnectorClient extends AzureServiceClient implements Team
return super.restClient();
}
/**
* Returns the base url for this ConnectorClient.
* @return The base url.
*/
@Override
public String baseUrl() {
return getRestClient().retrofit().baseUrl().toString();
}
/**
* Returns the credentials in use.
* @return The ServiceClientCredentials in use.
*/
public ServiceClientCredentials credentials() {
return getRestClient().credentials();
}
/**
* Gets the preferred language for the response..
* @return the acceptLanguage value.

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

@ -1,6 +1,7 @@
package com.microsoft.bot.connector.teams;
import com.microsoft.bot.rest.RestClient;
import com.microsoft.bot.rest.credentials.ServiceClientCredentials;
/**
* Teams operations.
@ -13,6 +14,18 @@ public interface TeamsConnectorClient extends AutoCloseable {
*/
RestClient getRestClient();
/**
* Returns the base url for this ConnectorClient.
* @return The base url.
*/
String baseUrl();
/**
* Returns the credentials in use.
* @return The ServiceClientCredentials in use.
*/
ServiceClientCredentials credentials();
/**
* Gets the User-Agent header for the client.
* @return the user agent string.

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

@ -3,6 +3,8 @@
package com.microsoft.bot.schema;
import com.microsoft.bot.schema.teams.NotificationInfo;
import com.microsoft.bot.schema.teams.TeamInfo;
import com.microsoft.bot.schema.teams.TeamsChannelData;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
@ -803,6 +805,19 @@ public class Activity {
this.entities = withEntities;
}
/**
* Sets payload version of the Mentions in an Activity.
* @param withMentions The payload entities.
* @see Entity
*/
public void setMentions(List<Mention> withMentions) {
setEntities(withMentions.stream()
.filter(entity -> entity.getType().equalsIgnoreCase("mention"))
.map(entity -> Entity.getAs(entity, Entity.class))
.collect(Collectors.toCollection(ArrayList::new))
);
}
/**
* Gets channel-specific content.
* @return Channel specific data.
@ -1307,7 +1322,7 @@ public class Activity {
* @param <TypeT> The type of the returned object.
* @return ChannelData as TypeT
*/
public <TypeT> ResultPair<Boolean, TypeT> tryGetChannelData(Class<TypeT> clsType) {
public <TypeT> ResultPair<TypeT> tryGetChannelData(Class<TypeT> clsType) {
TypeT instance = null;
if (this.getChannelData() == null) {
return new ResultPair<>(false, instance);
@ -1316,9 +1331,9 @@ public class Activity {
try {
instance = this.getChannelData(clsType);
} catch (JsonProcessingException e) {
return new ResultPair<Boolean, TypeT>(false, instance);
return new ResultPair<TypeT>(false, instance);
}
return new ResultPair<Boolean, TypeT>(true, instance);
return new ResultPair<TypeT>(true, instance);
}
/**
@ -1517,15 +1532,19 @@ public class Activity {
/**
* Get unique identifier representing a channel.
*
* @throws JsonProcessingException when channel data can't be parsed to TeamChannelData
* @return Unique identifier representing a channel
* @return If this is a Teams Activity with valid data, the unique identifier representing a channel.
*/
public String teamsGetChannelId() throws JsonProcessingException {
TeamsChannelData teamsChannelData = getChannelData(TeamsChannelData.class);
String teamsChannelId = teamsChannelData.getTeamsChannelId();
if (teamsChannelId == null && teamsChannelData.getChannel() != null) {
teamsChannelId = teamsChannelData.getChannel().getId();
public String teamsGetChannelId() {
String teamsChannelId;
try {
TeamsChannelData teamsChannelData = getChannelData(TeamsChannelData.class);
teamsChannelId = teamsChannelData.getTeamsChannelId();
if (teamsChannelId == null && teamsChannelData.getChannel() != null) {
teamsChannelId = teamsChannelData.getChannel().getId();
}
} catch (JsonProcessingException jpe) {
teamsChannelId = null;
}
return teamsChannelId;
@ -1533,17 +1552,57 @@ public class Activity {
/**
* Get unique identifier representing a team.
*
* @throws JsonProcessingException when channel data can't be parsed to TeamChannelData
* @return Unique identifier representing a team.
* @return If this is a Teams Activity with valid data, the unique identifier representing a team.
*/
public String teamsGetTeamId() throws JsonProcessingException {
TeamsChannelData teamsChannelData = getChannelData(TeamsChannelData.class);
String teamId = teamsChannelData.getTeamsTeamId();
if (teamId == null && teamsChannelData.getTeam() != null) {
teamId = teamsChannelData.getTeam().getId();
public String teamsGetTeamId() {
String teamId;
try {
TeamsChannelData teamsChannelData = getChannelData(TeamsChannelData.class);
teamId = teamsChannelData.getTeamsTeamId();
if (teamId == null && teamsChannelData.getTeam() != null) {
teamId = teamsChannelData.getTeam().getId();
}
} catch (JsonProcessingException jpe) {
teamId = null;
}
return teamId;
}
/**
* Get Teams TeamInfo data.
* @return If this is a Teams Activity with valid data, the TeamInfo object.
*/
public TeamInfo teamsGetTeamInfo() {
TeamsChannelData teamsChannelData;
try {
teamsChannelData = getChannelData(TeamsChannelData.class);
} catch (JsonProcessingException jpe) {
teamsChannelData = null;
}
return teamsChannelData != null ? teamsChannelData.getTeam() : null;
}
/**
* Sets the notification value in the TeamsChannelData to true.
*/
public void teamsNotifyUser() {
TeamsChannelData teamsChannelData;
try {
teamsChannelData = getChannelData(TeamsChannelData.class);
} catch (JsonProcessingException jpe) {
teamsChannelData = null;
}
if (teamsChannelData == null) {
teamsChannelData = new TeamsChannelData();
setChannelData(teamsChannelData);
}
teamsChannelData.setNotification(new NotificationInfo(true));
}
}

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

@ -22,7 +22,7 @@ import java.util.stream.Collectors;
/**
* Metadata object pertaining to an activity.
*/
public class Entity {
public class Entity implements EntitySerialization {
private static ObjectMapper objectMapper;
static {
@ -60,7 +60,7 @@ public class Entity {
* @param entities The List of Entities to clone.
* @return A cloned List.
*/
public static List<Entity> cloneList(List<Entity> entities) {
public static List<Entity> cloneList(List<? extends Entity> entities) {
if (entities == null) {
return null;
}
@ -114,11 +114,21 @@ public class Entity {
*/
@JsonIgnore
public <T extends EntitySerialization> T getAs(Class<T> classType) {
return getAs(this, classType);
}
/**
* Converts Entity to other Entity types.
* @param entity The entity type object.
* @param classType Class extended EntitySerialization
* @param <T> The type of the return value.
* @return Entity converted to type T
*/
public static <T extends EntitySerialization> T getAs(EntitySerialization entity, Class<T> classType) {
// Serialize
String tempJson;
try {
tempJson = objectMapper.writeValueAsString(this);
tempJson = objectMapper.writeValueAsString(entity);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;

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

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.schema;
/**
* A simple 2 Tuple-like class since Java doesn't natively support them.
* This is an immutable object.
* @param <L> The type of the left tuple value.
* @param <R> The type of the right tuple value.
*/
public class Pair<L, R> {
private L left;
private R right;
/**
* Creates a new Pair.
* @param withLeft The left value.
* @param withRight The right value.
*/
public Pair(L withLeft, R withRight) {
left = withLeft;
right = withRight;
}
/**
* Gets the left value.
* @return The left vale of type L.
*/
public L getLeft() {
return left;
}
/**
* Gets the right value.
* @return The right value of type R.
*/
public R getRight() {
return right;
}
}

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

@ -5,22 +5,31 @@ package com.microsoft.bot.schema;
/**
* Result pair.
* @param <X> Type of x.
* @param <Y> Type of y.
* @param <OUT_VALUE> Type of 'Right' value.
*/
public class ResultPair<X, Y> {
@SuppressWarnings("checkstyle:VisibilityModifier")
public final X x;
@SuppressWarnings("checkstyle:VisibilityModifier")
public final Y y;
public class ResultPair<OUT_VALUE> extends Pair<Boolean, OUT_VALUE> {
/**
* Creates a new immutable instance of a ResultPair.
* @param withResult The result of the ResultPair value.
* @param withValue The value.
*/
public ResultPair(Boolean withResult, OUT_VALUE withValue) {
super(withResult, withValue);
}
/**
* ResultPair with values.
* @param withX The X.
* @param withY The Y.
* Gets the result.
* @return True if successful.
*/
public ResultPair(X withX, Y withY) {
this.x = withX;
this.y = withY;
public Boolean result() {
return getLeft();
}
/**
* Gets the value.
* @return The value of type OUT_VALUE.
*/
public OUT_VALUE value() {
return getRight();
}
}

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

@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.schema;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
/**
* Serialization helpers.
*/
public final class Serialization {
private Serialization() {
}
private static ObjectMapper objectMapper;
static {
objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
}
/**
* Deserialize a value.
* @param obj The object to deserialize.
* @param classType The class type to convert to.
* @param <T> The type of the return value.
* @return A deserialized POJO, or null for error.
*/
public static <T> T getAs(Object obj, Class<T> classType) {
try {
return safeGetAs(obj, classType);
} catch (JsonProcessingException jpe) {
return null;
}
}
/**
* Deserialize a value.
* @param obj The object to deserialize.
* @param classType The class type to convert to.
* @param <T> The type of the return value.
* @return A deserialized POJO, or null.
* @throws JsonProcessingException The JSON processing exception.
*/
public static <T> T safeGetAs(Object obj, Class<T> classType) throws JsonProcessingException {
if (obj == null) {
return null;
}
JsonNode node = objectMapper.valueToTree(obj);
return objectMapper.treeToValue(node, classType);
}
/**
* Deserializes an object to a type as a future to ease CompletableFuture chaining.
* @param obj The object to deserialize.
* @param classType Class information to convert to.
* @param <R> The return Type.
* @return A CompletableFuture containing the value or exception for an error.
*/
public static <R> CompletableFuture<R> futureGetAs(Object obj, Class<R> classType) {
CompletableFuture<R> futureResult = new CompletableFuture<>();
try {
futureResult.complete(Serialization.safeGetAs(obj, classType));
} catch (JsonProcessingException jpe) {
futureResult.completeExceptionally(new CompletionException("Unable to deserialize", jpe));
}
return futureResult;
}
/**
* Converts an input object to another type.
* @param source The object to convert.
* @param toClass The class to convert to.
* @param <T> Type of return value.
* @return The converted object, or null.
*/
public static <T> T convert(Object source, Class<T> toClass) {
return getAs(source, toClass);
}
}

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

@ -0,0 +1,27 @@
package com.microsoft.bot.schema;
/**
* Names for signin invoke operations in the token protocol.
*/
public final class SignInConstants {
private SignInConstants() {
}
/**
* Name for the signin invoke to verify the 6-digit authentication code as part of sign-in.
* This invoke operation includes a value containing a state property for the magic code.
*/
public static final String VERIFY_STATE_OPERATION_NAME = "signin/verifyState";
/**
* Name for signin invoke to perform a token exchange.
* This invoke operation includes a value of the token exchange class.
*/
public static final String TOKEN_EXCHANGE_OPERATION_NAME = "signin/tokenExchange";
/**
* The EventActivity name when a token is sent to the bot.
*/
public static final String TOKEN_RESPONSE_EVENT_NAME = "tokens/response";
}

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

@ -71,4 +71,12 @@ public class ChannelInfo {
*/
public ChannelInfo() {
}
/**
* Initialzies a new instance of the ChannelInfo class with an id.
* @param withId The id.
*/
public ChannelInfo(String withId) {
id = withId;
}
}

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

@ -15,6 +15,14 @@ public class NotificationInfo {
@JsonProperty(value = "alert")
private Boolean alert;
/**
* Initialize new NotificationInfo.
* @param withAlert initial alert value.
*/
public NotificationInfo(boolean withAlert) {
setAlert(withAlert);
}
/**
* Getter for alert.
*

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

@ -4,11 +4,12 @@
package com.microsoft.bot.schema.teams;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.microsoft.bot.schema.ChannelAccount;
/**
* Teams channel account detailing user Azure Active Directory details.
*/
public class TeamsChannelAccount {
public class TeamsChannelAccount extends ChannelAccount {
@JsonProperty(value = "givenName")
private String givenName;
@ -21,9 +22,6 @@ public class TeamsChannelAccount {
@JsonProperty(value = "userPrincipalName")
private String userPrincipalName;
@JsonProperty(value = "objectId")
private String aadObjectId;
/**
* Gets given name part of the user name.
* @return The users given name.
@ -87,20 +85,4 @@ public class TeamsChannelAccount {
public void setUserPrincipalName(String withUserPrincipalName) {
userPrincipalName = withUserPrincipalName;
}
/**
* Gets the AAD Object Id.
* @return The AAD object id.
*/
public String getAadObjectId() {
return aadObjectId;
}
/**
* Sets the AAD Object Id.
* @param withAadObjectId The AAD object id.
*/
public void setAadObjectId(String withAadObjectId) {
aadObjectId = withAadObjectId;
}
}

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

@ -5,8 +5,15 @@ package com.microsoft.bot.schema.teams;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microsoft.bot.schema.PagedMembersResult;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Teams page of members.
@ -20,6 +27,35 @@ public class TeamsPagedMembersResult {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<TeamsChannelAccount> members;
/**
* Converts a PagedMembersResult to a TeamsPagedMembersResult.
* @param pagedMembersResult The PagedMembersResult value.
*/
public TeamsPagedMembersResult(PagedMembersResult pagedMembersResult) {
continuationToken = pagedMembersResult.getContinuationToken();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
members = pagedMembersResult.getMembers().stream()
.map(channelAccount -> {
try {
// convert fro ChannelAccount to TeamsChannelAccount by going to JSON then back
// to TeamsChannelAccount.
// Is this really the most efficient way to handle this?
JsonNode node = objectMapper.valueToTree(channelAccount);
return objectMapper.treeToValue(node, TeamsChannelAccount.class);
} catch (JsonProcessingException jpe) {
// this would be a conversion error. for now, return null and filter the results
// below. there is probably a more elegant way to handle this.
return null;
}
})
.collect(Collectors.toCollection(ArrayList::new));
members.removeIf(Objects::isNull);
}
/**
* Gets paging token.
* @return The continuation token to be used in the next call.

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

@ -375,6 +375,7 @@
<module>samples/16.proactive-messages</module>
<module>samples/45.state-management</module>
<module>samples/47.inspection</module>
<module>samples/57.teams-conversation-bot</module>
</modules>
<build>

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

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

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

@ -0,0 +1,76 @@

# Teams Conversation Bot
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.
## Prerequisites
- Microsoft Teams is installed and you have an account
- [ngrok](https://ngrok.com/) or equivalent tunnelling solution
## To try this sample
> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because
the Teams service needs to call into the bot.
1) Clone the repository
```bash
git clone https://github.com/Microsoft/botbuilder-java.git
```
1) Run ngrok - point to port 8080
```bash
ngrok http -host-header=rewrite 8080
```
1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure
- Use the current `https` URL you were given by running ngrok. Append with the path `/api/messages` used by this sample
- Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0)
- __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework)
1) Update the `resources/application.properties` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.)
1) __*This step is specific to Teams.*__
- **Edit** the `manifest.json` contained in the `teamsAppManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<<YOUR-MICROSOFT-APP-ID>>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`)
- **Zip** up the contents of the `teamsAppManifest` folder to create a `manifest.zip`
- **Upload** the `manifest.zip` to Teams (in the Apps view click "Upload a custom app")
1) From the root of this project folder:
- Build the sample using `mvn package`
- Unless done previously, install the packages in the local cache by using `mvn install`
- Run it by using `java -jar .\target\bot-teams-conversation-sample.jar`
## 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.
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
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.
### 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.
## Deploy the bot to Azure
To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions.
## Further reading
- [How Microsoft Teams bots work](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics-teams?view=azure-bot-service-4.0&tabs=javascript)

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

@ -0,0 +1,42 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"groupLocation": {
"value": ""
},
"groupName": {
"value": ""
},
"appId": {
"value": ""
},
"appSecret": {
"value": ""
},
"botId": {
"value": ""
},
"botSku": {
"value": ""
},
"newAppServicePlanName": {
"value": ""
},
"newAppServicePlanSku": {
"value": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
}
},
"newAppServicePlanLocation": {
"value": ""
},
"newWebAppName": {
"value": ""
}
}
}

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

@ -0,0 +1,39 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appId": {
"value": ""
},
"appSecret": {
"value": ""
},
"botId": {
"value": ""
},
"botSku": {
"value": ""
},
"newAppServicePlanName": {
"value": ""
},
"newAppServicePlanSku": {
"value": {
"name": "S1",
"tier": "Standard",
"size": "S1",
"family": "S",
"capacity": 1
}
},
"appServicePlanLocation": {
"value": ""
},
"existingAppServicePlan": {
"value": ""
},
"newWebAppName": {
"value": ""
}
}
}

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

@ -0,0 +1,191 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"groupLocation": {
"defaultValue": "",
"type": "string",
"metadata": {
"description": "Specifies the location of the Resource Group."
}
},
"groupName": {
"type": "string",
"metadata": {
"description": "Specifies the name of the Resource Group."
}
},
"appId": {
"type": "string",
"metadata": {
"description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings."
}
},
"appSecret": {
"type": "string",
"metadata": {
"description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings."
}
},
"botId": {
"type": "string",
"metadata": {
"description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable."
}
},
"botSku": {
"defaultValue": "F0",
"type": "string",
"metadata": {
"description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
}
},
"newAppServicePlanName": {
"defaultValue": "",
"type": "string",
"metadata": {
"description": "The name of the App Service Plan."
}
},
"newAppServicePlanSku": {
"type": "object",
"defaultValue": {
"name": "P1v2",
"tier": "PremiumV2",
"size": "P1v2",
"family": "Pv2",
"capacity": 1
},
"metadata": {
"description": "The SKU of the App Service Plan. Defaults to Standard values."
}
},
"newAppServicePlanLocation": {
"defaultValue": "",
"type": "string",
"metadata": {
"description": "The location of the App Service Plan. Defaults to \"westus\"."
}
},
"newWebAppName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"."
}
}
},
"variables": {
"resourcesLocation": "[deployment().location]",
"effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]",
"effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]",
"appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]",
"webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]",
"siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]",
"botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]"
},
"resources": [
{
"name": "[parameters('groupName')]",
"type": "Microsoft.Resources/resourceGroups",
"apiVersion": "2018-05-01",
"location": "[variables('effectiveGroupLocation')]",
"properties": {
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2018-05-01",
"name": "storageDeployment",
"resourceGroup": "[parameters('groupName')]",
"dependsOn": [
"[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]"
],
"properties": {
"mode": "Incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [
{
"comments": "Create a new App Service Plan",
"type": "Microsoft.Web/serverfarms",
"name": "[variables('appServicePlanName')]",
"apiVersion": "2018-02-01",
"location": "[variables('effectivePlanLocation')]",
"sku": "[parameters('newAppServicePlanSku')]",
"kind": "linux",
"properties": {
"name": "[variables('appServicePlanName')]",
"reserved":true
}
},
{
"comments": "Create a Web App using the new App Service Plan",
"type": "Microsoft.Web/sites",
"apiVersion": "2015-08-01",
"location": "[variables('resourcesLocation')]",
"kind": "app",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]"
],
"name": "[variables('webAppName')]",
"properties": {
"name": "[variables('webAppName')]",
"serverFarmId": "[variables('appServicePlanName')]",
"siteConfig": {
"appSettings": [
{
"name": "JAVA_OPTS",
"value": "-Dserver.port=80"
},
{
"name": "MicrosoftAppId",
"value": "[parameters('appId')]"
},
{
"name": "MicrosoftAppPassword",
"value": "[parameters('appSecret')]"
}
],
"cors": {
"allowedOrigins": [
"https://botservice.hosting.portal.azure.net",
"https://hosting.onecloud.azure-test.net/"
]
}
}
}
},
{
"apiVersion": "2017-12-01",
"type": "Microsoft.BotService/botServices",
"name": "[parameters('botId')]",
"location": "global",
"kind": "bot",
"sku": {
"name": "[parameters('botSku')]"
},
"properties": {
"name": "[parameters('botId')]",
"displayName": "[parameters('botId')]",
"endpoint": "[variables('botEndpoint')]",
"msaAppId": "[parameters('appId')]",
"developerAppInsightsApplicationId": null,
"developerAppInsightKey": null,
"publishingCredentials": null,
"storageResourceId": null
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', variables('webAppName'))]"
]
}
],
"outputs": {}
}
}
}
]
}

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

@ -0,0 +1,158 @@
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"appId": {
"type": "string",
"metadata": {
"description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings."
}
},
"appSecret": {
"type": "string",
"metadata": {
"description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"."
}
},
"botId": {
"type": "string",
"metadata": {
"description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable."
}
},
"botSku": {
"defaultValue": "S1",
"type": "string",
"metadata": {
"description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1."
}
},
"newAppServicePlanName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The name of the new App Service Plan."
}
},
"newAppServicePlanSku": {
"type": "object",
"defaultValue": {
"name": "P1v2",
"tier": "PremiumV2",
"size": "P1v2",
"family": "Pv2",
"capacity": 1
},
"metadata": {
"description": "The SKU of the App Service Plan. Defaults to Standard values."
}
},
"appServicePlanLocation": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The location of the App Service Plan."
}
},
"existingAppServicePlan": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "Name of the existing App Service Plan used to create the Web App for the bot."
}
},
"newWebAppName": {
"type": "string",
"defaultValue": "",
"metadata": {
"description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"."
}
}
},
"variables": {
"defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]",
"useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]",
"servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]",
"resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]",
"webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]",
"siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]",
"botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]"
},
"resources": [
{
"comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.",
"type": "Microsoft.Web/serverfarms",
"condition": "[not(variables('useExistingAppServicePlan'))]",
"name": "[variables('servicePlanName')]",
"apiVersion": "2018-02-01",
"location": "[variables('resourcesLocation')]",
"sku": "[parameters('newAppServicePlanSku')]",
"kind": "linux",
"properties": {
"name": "[variables('servicePlanName')]",
"reserved":true
}
},
{
"comments": "Create a Web App using an App Service Plan",
"type": "Microsoft.Web/sites",
"apiVersion": "2016-08-01",
"location": "[variables('resourcesLocation')]",
"kind": "app",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]"
],
"name": "[variables('webAppName')]",
"properties": {
"name": "[variables('webAppName')]",
"serverFarmId": "[variables('servicePlanName')]",
"siteConfig": {
"linuxFxVersion": "JAVA|8-jre8",
"appSettings": [
{
"name": "JAVA_OPTS",
"value": "-Dserver.port=80"
},
{
"name": "MicrosoftAppId",
"value": "[parameters('appId')]"
},
{
"name": "MicrosoftAppPassword",
"value": "[parameters('appSecret')]"
}
],
"cors": {
"allowedOrigins": [
"https://botservice.hosting.portal.azure.net",
"https://hosting.onecloud.azure-test.net/"
]
}
}
}
},
{
"apiVersion": "2017-12-01",
"type": "Microsoft.BotService/botServices",
"name": "[parameters('botId')]",
"location": "global",
"kind": "bot",
"sku": {
"name": "[parameters('botSku')]"
},
"properties": {
"name": "[parameters('botId')]",
"displayName": "[parameters('botId')]",
"endpoint": "[variables('botEndpoint')]",
"msaAppId": "[parameters('appId')]",
"developerAppInsightsApplicationId": null,
"developerAppInsightKey": null,
"publishingCredentials": null,
"storageResourceId": null
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites/', variables('webAppName'))]"
]
}
]
}

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

@ -0,0 +1,315 @@
<?xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.bot.sample</groupId>
<artifactId>bot-teams-conversation</artifactId>
<version>sample</version>
<packaging>jar</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description>This package contains a Java Teams Conversation sample using Spring Boot.</description>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
<relativePath/>
</parent>
<licenses>
<license>
<name>MIT License</name>
<url>http://www.opensource.org/licenses/mit-license.php</url>
</license>
</licenses>
<developers>
<developer>
<name>Bot Framework Development</name>
<email></email>
<organization>Microsoft</organization>
<organizationUrl>https://dev.botframework.com/</organizationUrl>
</developer>
</developers>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<start-class>com.microsoft.bot.sample.teamsconversation.Application</start-class>
<repo.url>https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/</repo.url>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.1.8.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.bot</groupId>
<artifactId>bot-integration-spring</artifactId>
<version>4.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>MyGet</id>
<url>${repo.url}</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>ossrh</id>
<!-- <url>${repo.url}</url>-->
<url>https://oss.sonatype.org/</url>
<!-- <url>https://repo.maven.apache.org/maven2</url>-->
</repository>
</distributionManagement>
<profiles>
<profile>
<id>build</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.3</version>
<configuration>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<mainClass>com.microsoft.bot.sample.teamsconversation.Application</mainClass>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-webapp-maven-plugin</artifactId>
<version>1.7.0</version>
<configuration>
<schemaVersion>V2</schemaVersion>
<resourceGroup>{groupname}</resourceGroup>
<appName>{botname}</appName>
<appSettings>
<property>
<name>JAVA_OPTS</name>
<value>-Dserver.port=80</value>
</property>
</appSettings>
<runtime>
<os>linux</os>
<javaVersion>jre8</javaVersion>
<webContainer>jre8</webContainer>
</runtime>
<deployment>
<resources>
<resource>
<directory>${project.basedir}/target</directory>
<includes>
<include>*.jar</include>
</includes>
</resource>
</resources>
</deployment>
</configuration>
</plugin>
<plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>4.3.0</version>
<configuration>
<repoToken>yourcoverallsprojectrepositorytoken</repoToken>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
<configuration>
<outputDirectory>../../cobertura-report/spring-teamsconversation-sample</outputDirectory>
<format>xml</format>
<maxmem>256m</maxmem>
<!-- aggregated reports for multi-module projects -->
<aggregate>true</aggregate>
</configuration>
</plugin>
<!--<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.12.0</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>publish</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
<extensions>true</extensions>
<configuration>
<skipRemoteStaging>true</skipRemoteStaging>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<source>8</source>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.12.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.0</version>
<reportSets>
<reportSet>
<reports>
<report>checkstyle</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
</project>

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

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.sample.teamsconversation;
import com.microsoft.bot.integration.AdapterWithErrorHandler;
import com.microsoft.bot.integration.BotFrameworkHttpAdapter;
import com.microsoft.bot.integration.Configuration;
import com.microsoft.bot.integration.spring.BotController;
import com.microsoft.bot.integration.spring.BotDependencyConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
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.
*
* @see TeamsConversationBot
*/
@SpringBootApplication
// Use the default BotController to receive incoming Channel messages. A custom controller
// could be used by eliminating this import and creating a new RestController. The default
// controller is created by the Spring Boot container using dependency injection. The
// default route is /api/messages.
@Import({BotController.class})
public class Application extends BotDependencyConfiguration {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* Returns a custom Adapter that provides error handling.
*
* @param configuration The Configuration object to use.
* @return An error handling BotFrameworkHttpAdapter.
*/
@Override
public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) {
return new AdapterWithErrorHandler(configuration);
}
}

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

@ -0,0 +1,212 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.sample.teamsconversation;
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.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 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.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>
*/
@Component
public class TeamsConversationBot extends TeamsActivityHandler {
private String appId;
private String appPassword;
public TeamsConversationBot(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);
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);
}
}
@Override
protected CompletableFuture<Void> onTeamsMembersAdded(
List<TeamsChannelAccount> membersAdded,
TeamInfo teamInfo,
TurnContext turnContext
) {
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.");
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() {{
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");
}}
));
}};
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>");
Activity replyActivity = MessageFactory.text("Hello " + mention.getText() + ".'");
replyActivity.setMentions(Collections.singletonList(mention));
return turnContext.sendActivity(replyActivity)
.thenApply(resourceResponse -> null);
}
}

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

@ -0,0 +1,2 @@
MicrosoftAppId=
MicrosoftAppPassword=

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

@ -0,0 +1,18 @@
{
"configuration": {
"name": "Default",
"appenders": {
"Console": {
"name": "Console-Appender",
"target": "SYSTEM_OUT",
"PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"}
}
},
"loggers": {
"root": {
"level": "debug",
"appender-ref": {"ref": "Console-Appender","level": "debug"}
}
}
}
}

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

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Class-Path:

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

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>

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

@ -0,0 +1,418 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>EchoBot</title>
<style>
body {
margin: 0px;
padding: 0px;
font-family: Segoe UI;
}
html,
body {
height: 100%;
}
header {
background-image: url("data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 4638.9 651.6' style='enable-background:new 0 0 4638.9 651.6;' xml:space='preserve'%3E%3Cstyle type='text/css'%3E .st0%7Bfill:%2355A0E0;%7D .st1%7Bfill:none;%7D .st2%7Bfill:%230058A8;%7D .st3%7Bfill:%23328BD8;%7D .st4%7Bfill:%23B6DCF1;%7D .st5%7Bopacity:0.2;fill:url(%23SVGID_1_);enable-background:new ;%7D%0A%3C/style%3E%3Crect y='1.1' class='st0' width='4640' height='646.3'/%3E%3Cpath class='st1' d='M3987.8,323.6L4310.3,1.1h-65.6l-460.1,460.1c-17.5,17.5-46.1,17.5-63.6,0L3260.9,1.1H0v646.3h3660.3 L3889,418.7c17.5-17.5,46.1-17.5,63.6,0l228.7,228.7h66.6l-260.2-260.2C3970.3,369.8,3970.3,341.1,3987.8,323.6z'/%3E%3Cpath class='st2' d='M3784.6,461.2L4244.7,1.1h-983.9l460.1,460.1C3738.4,478.7,3767.1,478.7,3784.6,461.2z'/%3E%3Cpath class='st3' d='M4640,1.1h-329.8l-322.5,322.5c-17.5,17.5-17.5,46.1,0,63.6l260.2,260.2H4640L4640,1.1L4640,1.1z'/%3E%3Cpath class='st4' d='M3889,418.8l-228.7,228.7h521.1l-228.7-228.7C3935.2,401.3,3906.5,401.3,3889,418.8z'/%3E%3ClinearGradient id='SVGID_1_' gradientUnits='userSpaceOnUse' x1='3713.7576' y1='438.1175' x2='3911.4084' y2='14.2535' gradientTransform='matrix(1 0 0 -1 0 641.3969)'%3E%3Cstop offset='0' style='stop-color:%23FFFFFF;stop-opacity:0.5'/%3E%3Cstop offset='1' style='stop-color:%23FFFFFF'/%3E%3C/linearGradient%3E%3Cpath class='st5' d='M3952.7,124.5c-17.5-17.5-46.1-17.5-63.6,0l-523,523h1109.6L3952.7,124.5z'/%3E%3C/svg%3E%0A");
background-repeat: no-repeat;
background-size: 100%;
background-position: right;
background-color: #55A0E0;
width: 100%;
font-size: 44px;
height: 120px;
color: white;
padding: 30px 0 40px 0px;
display: inline-block;
}
.header-icon {
background-image: url("data:image/svg+xml;utf8,%3Csvg%20version%3D%221.1%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%20x%3D%220px%22%20y%3D%220px%22%0A%09%20viewBox%3D%220%200%20150.2%20125%22%20style%3D%22enable-background%3Anew%200%200%20150.2%20125%3B%22%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text/css%22%3E%0A%09.st0%7Bfill%3Anone%3B%7D%0A%09.st1%7Bfill%3A%23FFFFFF%3B%7D%0A%3C/style%3E%0A%3Crect%20x%3D%220.5%22%20class%3D%22st0%22%20width%3D%22149.7%22%20height%3D%22125%22/%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M59%2C102.9L21.8%2C66c-3.5-3.5-3.5-9.1%2C0-12.5l37-36.5l2.9%2C3l-37%2C36.4c-1.8%2C1.8-1.8%2C4.7%2C0%2C6.6l37.2%2C37L59%2C102.9z%22%0A%09%09/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M92.5%2C102.9l-3-3l37.2-37c0.9-0.9%2C1.4-2%2C1.4-3.3c0-1.2-0.5-2.4-1.4-3.3L89.5%2C20l2.9-3l37.2%2C36.4%0A%09%09c1.7%2C1.7%2C2.6%2C3.9%2C2.6%2C6.3s-0.9%2C4.6-2.6%2C6.3L92.5%2C102.9z%22/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M90.1%2C68.4c-4.5%2C0-8-3.5-8-8.1c0-4.5%2C3.5-8.1%2C8-8.1c4.4%2C0%2C8%2C3.7%2C8%2C8.1C98.1%2C64.7%2C94.4%2C68.4%2C90.1%2C68.4z%0A%09%09%20M90.1%2C56.5c-2.2%2C0-3.8%2C1.7-3.8%2C3.9c0%2C2.2%2C1.7%2C3.9%2C3.8%2C3.9c1.9%2C0%2C3.8-1.6%2C3.8-3.9S91.9%2C56.5%2C90.1%2C56.5z%22/%3E%0A%3C/g%3E%0A%3Cg%3E%0A%09%3Cpath%20class%3D%22st1%22%20d%3D%22M61.4%2C68.4c-4.5%2C0-8-3.5-8-8.1c0-4.5%2C3.5-8.1%2C8-8.1c4.4%2C0%2C8%2C3.7%2C8%2C8.1C69.5%2C64.7%2C65.8%2C68.4%2C61.4%2C68.4z%0A%09%09%20M61.4%2C56.5c-2.2%2C0-3.8%2C1.7-3.8%2C3.9c0%2C2.2%2C1.7%2C3.9%2C3.8%2C3.9c1.9%2C0%2C3.8-1.6%2C3.8-3.9S63.3%2C56.5%2C61.4%2C56.5z%22/%3E%0A%3C/g%3E%0A%3C/svg%3E%0A");
background-repeat: no-repeat;
float: left;
height: 140px;
width: 140px;
display: inline-block;
vertical-align: middle;
}
.header-text {
padding-left: 1%;
color: #FFFFFF;
font-family: "Segoe UI";
font-size: 72px;
font-weight: 300;
letter-spacing: 0.35px;
line-height: 96px;
display: inline-block;
vertical-align: middle;
}
.header-inner-container {
min-width: 480px;
max-width: 1366px;
margin-left: auto;
margin-right: auto;
vertical-align: middle;
}
.header-inner-container::after {
content: "";
clear: both;
display: table;
}
.main-content-area {
padding-left: 30px;
}
.content-title {
color: #000000;
font-family: "Segoe UI";
font-size: 46px;
font-weight: 300;
line-height: 62px;
}
.main-text {
color: #808080;
font-size: 24px;
font-family: "Segoe UI";
font-size: 24px;
font-weight: 200;
line-height: 32px;
}
.main-text-p1{
padding-top: 48px;
padding-bottom: 28px;
}
.endpoint {
height: 32px;
width: 571px;
color: #808080;
font-family: "Segoe UI";
font-size: 24px;
font-weight: 200;
line-height: 32px;
padding-top: 28px;
}
.how-to-build-section {
padding-top: 20px;
padding-left: 30px;
}
.how-to-build-section>h3 {
font-size: 16px;
font-weight: 600;
letter-spacing: 0.35px;
line-height: 22px;
margin: 0 0 24px 0;
text-transform: uppercase;
}
.step-container {
display: flex;
align-items: stretch;
position: relative;
}
.step-container dl {
border-left: 1px solid #A0A0A0;
display: block;
padding: 0 24px;
margin: 0;
}
.step-container dl>dt::before {
background-color: white;
border: 1px solid #A0A0A0;
border-radius: 100%;
content: '';
left: 47px;
height: 11px;
position: absolute;
width: 11px;
}
.step-container dl>.test-bullet::before {
background-color: blue;
}
.step-container dl>dt {
display: block;
font-size: inherit;
font-weight: bold;
line-height: 20px;
}
.step-container dl>dd {
font-size: inherit;
line-height: 20px;
margin-left: 0;
padding-bottom: 32px;
}
.step-container:last-child dl {
border-left: 1px solid transparent;
}
.ctaLink {
background-color: transparent;
border: 1px solid transparent;
color: #006AB1;
cursor: pointer;
font-weight: 600;
padding: 0;
white-space: normal;
}
.ctaLink:focus {
outline: 1px solid #00bcf2;
}
.ctaLink:hover {
text-decoration: underline;
}
.step-icon {
display: flex;
height: 38px;
margin-right: 15px;
width: 38px;
}
.step-icon>div {
height: 30px;
width: 30px;
background-repeat: no-repeat;
}
.ms-logo-container {
min-width: 580px;
max-width: 980px;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
transition: bottom 400ms;
}
.ms-logo {
float: right;
background-image: url("data:image/svg+xml;utf8,%0A%3Csvg%20version%3D%221.1%22%20id%3D%22MS-symbol%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20xmlns%3Axlink%3D%22http%3A//www.w3.org/1999/xlink%22%20x%3D%220px%22%20y%3D%220px%22%0A%09%20viewBox%3D%220%200%20400%20120%22%20style%3D%22enable-background%3Anew%200%200%20400%20120%3B%22%20xml%3Aspace%3D%22preserve%22%3E%0A%3Cstyle%20type%3D%22text/css%22%3E%0A%09.st0%7Bfill%3Anone%3B%7D%0A%09.st1%7Bfill%3A%23737474%3B%7D%0A%09.st2%7Bfill%3A%23D63F26%3B%7D%0A%09.st3%7Bfill%3A%23167D3E%3B%7D%0A%09.st4%7Bfill%3A%232E76BC%3B%7D%0A%09.st5%7Bfill%3A%23FDB813%3B%7D%0A%3C/style%3E%0A%3Crect%20x%3D%220.6%22%20class%3D%22st0%22%20width%3D%22398.7%22%20height%3D%22119%22/%3E%0A%3Cpath%20class%3D%22st1%22%20d%3D%22M171.3%2C38.4v43.2h-7.5V47.7h-0.1l-13.4%2C33.9h-5l-13.7-33.9h-0.1v33.9h-6.9V38.4h10.8l12.4%2C32h0.2l13.1-32H171.3%0A%09z%20M177.6%2C41.7c0-1.2%2C0.4-2.2%2C1.3-3c0.9-0.8%2C1.9-1.2%2C3.1-1.2c1.3%2C0%2C2.4%2C0.4%2C3.2%2C1.3c0.8%2C0.8%2C1.3%2C1.8%2C1.3%2C3c0%2C1.2-0.4%2C2.2-1.3%2C3%0A%09c-0.9%2C0.8-1.9%2C1.2-3.2%2C1.2s-2.3-0.4-3.1-1.2C178%2C43.8%2C177.6%2C42.8%2C177.6%2C41.7z%20M185.7%2C50.6v31h-7.3v-31H185.7z%20M207.8%2C76.3%0A%09c1.1%2C0%2C2.3-0.3%2C3.6-0.8c1.3-0.5%2C2.5-1.2%2C3.6-2v6.8c-1.2%2C0.7-2.5%2C1.2-4%2C1.5c-1.5%2C0.3-3.1%2C0.5-4.9%2C0.5c-4.6%2C0-8.3-1.4-11.1-4.3%0A%09c-2.9-2.9-4.3-6.6-4.3-11c0-5%2C1.5-9.1%2C4.4-12.3c2.9-3.2%2C7-4.8%2C12.4-4.8c1.4%2C0%2C2.7%2C0.2%2C4.1%2C0.5c1.4%2C0.4%2C2.5%2C0.8%2C3.3%2C1.2v7%0A%09c-1.1-0.8-2.3-1.5-3.4-1.9c-1.2-0.5-2.4-0.7-3.6-0.7c-2.9%2C0-5.2%2C0.9-7%2C2.8c-1.8%2C1.9-2.7%2C4.4-2.7%2C7.6c0%2C3.1%2C0.8%2C5.6%2C2.5%2C7.3%0A%09C202.6%2C75.4%2C204.9%2C76.3%2C207.8%2C76.3z%20M235.7%2C50.1c0.6%2C0%2C1.1%2C0%2C1.6%2C0.1s0.9%2C0.2%2C1.2%2C0.3v7.4c-0.4-0.3-0.9-0.5-1.7-0.8%0A%09c-0.7-0.3-1.6-0.4-2.7-0.4c-1.8%2C0-3.3%2C0.8-4.5%2C2.3c-1.2%2C1.5-1.9%2C3.8-1.9%2C7v15.6h-7.3v-31h7.3v4.9h0.1c0.7-1.7%2C1.7-3%2C3-4%0A%09C232.2%2C50.6%2C233.8%2C50.1%2C235.7%2C50.1z%20M238.9%2C66.6c0-5.1%2C1.4-9.2%2C4.3-12.2c2.9-3%2C6.9-4.5%2C12.1-4.5c4.8%2C0%2C8.6%2C1.4%2C11.3%2C4.3%0A%09c2.7%2C2.9%2C4.1%2C6.8%2C4.1%2C11.7c0%2C5-1.4%2C9-4.3%2C12c-2.9%2C3-6.8%2C4.5-11.8%2C4.5c-4.8%2C0-8.6-1.4-11.4-4.2C240.3%2C75.3%2C238.9%2C71.4%2C238.9%2C66.6z%0A%09%20M246.5%2C66.3c0%2C3.2%2C0.7%2C5.7%2C2.2%2C7.4c1.5%2C1.7%2C3.6%2C2.6%2C6.3%2C2.6c2.7%2C0%2C4.7-0.9%2C6.1-2.6c1.4-1.7%2C2.1-4.2%2C2.1-7.6c0-3.3-0.7-5.8-2.2-7.5%0A%09c-1.4-1.7-3.4-2.5-6-2.5c-2.7%2C0-4.7%2C0.9-6.2%2C2.7C247.2%2C60.5%2C246.5%2C63%2C246.5%2C66.3z%20M281.5%2C58.8c0%2C1%2C0.3%2C1.9%2C1%2C2.5%0A%09c0.7%2C0.6%2C2.1%2C1.3%2C4.4%2C2.2c2.9%2C1.2%2C5%2C2.5%2C6.1%2C3.9c1.2%2C1.5%2C1.8%2C3.2%2C1.8%2C5.3c0%2C2.9-1.1%2C5.3-3.4%2C7c-2.2%2C1.8-5.3%2C2.7-9.1%2C2.7%0A%09c-1.3%2C0-2.7-0.2-4.3-0.5c-1.6-0.3-2.9-0.7-4-1.2v-7.2c1.3%2C0.9%2C2.8%2C1.7%2C4.3%2C2.2c1.5%2C0.5%2C2.9%2C0.8%2C4.2%2C0.8c1.6%2C0%2C2.9-0.2%2C3.6-0.7%0A%09c0.8-0.5%2C1.2-1.2%2C1.2-2.3c0-1-0.4-1.9-1.2-2.5c-0.8-0.7-2.4-1.5-4.6-2.4c-2.7-1.1-4.6-2.4-5.7-3.8c-1.1-1.4-1.7-3.2-1.7-5.4%0A%09c0-2.8%2C1.1-5.1%2C3.3-6.9c2.2-1.8%2C5.1-2.7%2C8.6-2.7c1.1%2C0%2C2.3%2C0.1%2C3.6%2C0.4c1.3%2C0.2%2C2.5%2C0.6%2C3.4%2C0.9v6.9c-1-0.6-2.1-1.2-3.4-1.7%0A%09c-1.3-0.5-2.6-0.7-3.8-0.7c-1.4%2C0-2.5%2C0.3-3.2%2C0.8C281.9%2C57.1%2C281.5%2C57.8%2C281.5%2C58.8z%20M297.9%2C66.6c0-5.1%2C1.4-9.2%2C4.3-12.2%0A%09c2.9-3%2C6.9-4.5%2C12.1-4.5c4.8%2C0%2C8.6%2C1.4%2C11.3%2C4.3c2.7%2C2.9%2C4.1%2C6.8%2C4.1%2C11.7c0%2C5-1.4%2C9-4.3%2C12c-2.9%2C3-6.8%2C4.5-11.8%2C4.5%0A%09c-4.8%2C0-8.6-1.4-11.4-4.2C299.4%2C75.3%2C297.9%2C71.4%2C297.9%2C66.6z%20M305.5%2C66.3c0%2C3.2%2C0.7%2C5.7%2C2.2%2C7.4c1.5%2C1.7%2C3.6%2C2.6%2C6.3%2C2.6%0A%09c2.7%2C0%2C4.7-0.9%2C6.1-2.6c1.4-1.7%2C2.1-4.2%2C2.1-7.6c0-3.3-0.7-5.8-2.2-7.5c-1.4-1.7-3.4-2.5-6-2.5c-2.7%2C0-4.7%2C0.9-6.2%2C2.7%0A%09C306.3%2C60.5%2C305.5%2C63%2C305.5%2C66.3z%20M353.9%2C56.6h-10.9v25h-7.4v-25h-5.2v-6h5.2v-4.3c0-3.3%2C1.1-5.9%2C3.2-8c2.1-2.1%2C4.8-3.1%2C8.1-3.1%0A%09c0.9%2C0%2C1.7%2C0%2C2.4%2C0.1c0.7%2C0.1%2C1.3%2C0.2%2C1.8%2C0.4V42c-0.2-0.1-0.7-0.3-1.3-0.5c-0.6-0.2-1.3-0.3-2.1-0.3c-1.5%2C0-2.7%2C0.5-3.5%2C1.4%0A%09s-1.2%2C2.4-1.2%2C4.2v3.7h10.9v-7l7.3-2.2v9.2h7.4v6h-7.4v14.5c0%2C1.9%2C0.3%2C3.3%2C1%2C4c0.7%2C0.8%2C1.8%2C1.2%2C3.3%2C1.2c0.4%2C0%2C0.9-0.1%2C1.5-0.3%0A%09c0.6-0.2%2C1.1-0.4%2C1.6-0.7v6c-0.5%2C0.3-1.2%2C0.5-2.3%2C0.7c-1.1%2C0.2-2.1%2C0.3-3.2%2C0.3c-3.1%2C0-5.4-0.8-6.9-2.5c-1.5-1.6-2.3-4.1-2.3-7.4%0A%09V56.6z%22/%3E%0A%3Cg%3E%0A%09%3Crect%20x%3D%2231%22%20y%3D%2224%22%20class%3D%22st2%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2268.8%22%20y%3D%2224%22%20class%3D%22st3%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2231%22%20y%3D%2261.8%22%20class%3D%22st4%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%09%3Crect%20x%3D%2268.8%22%20y%3D%2261.8%22%20class%3D%22st5%22%20width%3D%2234.2%22%20height%3D%2234.2%22/%3E%0A%3C/g%3E%0A%3C/svg%3E%0A");
}
.ms-logo-container>div {
min-height: 60px;
width: 150px;
background-repeat: no-repeat;
}
.row {
padding: 90px 0px 0 20px;
min-width: 480px;
max-width: 1366px;
margin-left: auto;
margin-right: auto;
}
.column {
float: left;
width: 45%;
padding-right: 20px;
}
.row:after {
content: "";
display: table;
clear: both;
}
a {
text-decoration: none;
}
.download-the-emulator {
height: 20px;
color: #0063B1;
font-size: 15px;
line-height: 20px;
padding-bottom: 70px;
}
.how-to-iframe {
max-width: 700px !important;
min-width: 650px !important;
height: 700px !important;
}
.remove-frame-height {
height: 10px;
}
@media only screen and (max-width: 1300px) {
.ms-logo {
padding-top: 30px;
}
.header-text {
font-size: 40x;
}
.column {
float: none;
padding-top: 30px;
width: 100%;
}
.ms-logo-container {
padding-top: 30px;
min-width: 480px;
max-width: 650px;
margin-left: auto;
margin-right: auto;
}
.row {
padding: 20px 0px 0 20px;
min-width: 480px;
max-width: 650px;
margin-left: auto;
margin-right: auto;
}
}
@media only screen and (max-width: 1370px) {
header {
background-color: #55A0E0;
background-size: auto 200px;
}
}
@media only screen and (max-width: 1230px) {
header {
background-color: #55A0E0;
background-size: auto 200px;
}
.header-text {
font-size: 44px;
}
.header-icon {
height: 120px;
width: 120px;
}
}
@media only screen and (max-width: 1000px) {
header {
background-color: #55A0E0;
background-image: none;
}
}
@media only screen and (max-width: 632px) {
.header-text {
font-size: 32px;
}
.row {
padding: 10px 0px 0 10px;
max-width: 490px !important;
min-width: 410px !important;
}
.endpoint {
font-size: 25px;
}
.main-text {
font-size: 20px;
}
.step-container dl>dd {
font-size: 14px;
}
.column {
padding-right: 5px;
}
.header-icon {
height: 110px;
width: 110px;
}
.how-to-iframe {
max-width: 480px !important;
min-width: 400px !important;
height: 650px !important;
overflow: hidden;
}
}
.remove-frame-height {
max-height: 10px;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function () {
loadFrame();
});
var loadFrame = function () {
var iframe = document.createElement('iframe');
iframe.setAttribute("id", "iframe");
var offLineHTMLContent = "";
var frameElement = document.getElementById("how-to-iframe");
if (window.navigator.onLine) {
iframe.src = 'https://docs.botframework.com/static/abs/pages/f5.htm';
iframe.setAttribute("scrolling", "no");
iframe.setAttribute("frameborder", "0");
iframe.setAttribute("width", "100%");
iframe.setAttribute("height", "100%");
var frameDiv = document.getElementById("how-to-iframe");
frameDiv.appendChild(iframe);
} else {
frameElement.classList.add("remove-frame-height");
}
};
</script>
</head>
<body>
<header class="header">
<div class="header-inner-container">
<div class="header-icon" style="display: inline-block"></div>
<div class="header-text" style="display: inline-block">Spring Boot Bot</div>
</div>
</header>
<div class="row">
<div class="column" class="main-content-area">
<div class="content-title">Your bot is ready!</div>
<div class="main-text main-text-p1">You can test your bot in the Bot Framework Emulator<br />
by connecting to http://localhost:8080/api/messages.</div>
<div class="main-text download-the-emulator"><a class="ctaLink" href="https://aka.ms/bot-framework-F5-download-emulator"
target="_blank">Download the Emulator</a></div>
<div class="main-text">Visit <a class="ctaLink" href="https://aka.ms/bot-framework-F5-abs-home" target="_blank">Azure
Bot Service</a> to register your bot and add it to<br />
various channels. The bot's endpoint URL typically looks
like this:</div>
<div class="endpoint">https://<i>your_bots_hostname</i>/api/messages</div>
</div>
<div class="column how-to-iframe" id="how-to-iframe"></div>
</div>
<div class="ms-logo-container">
<div class="ms-logo"></div>
</div>
</body>
</html>

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

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.sample.teamsconversation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Test
public void contextLoads() {
}
}

Двоичные данные
samples/57.teams-conversation-bot/teamsAppManifest/icon-color.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.2 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 383 B

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

@ -0,0 +1,66 @@
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.5/MicrosoftTeams.schema.json",
"manifestVersion": "1.5",
"version": "1.0.0",
"id": "<<YOUR-MICROSOFT-APP-ID>>",
"packageName": "com.teams.sample.teamsconversationbot",
"developer": {
"name": "teamsConversationBot",
"websiteUrl": "https://www.microsoft.com",
"privacyUrl": "https://www.teams.com/privacy",
"termsOfUseUrl": "https://www.teams.com/termsofuser"
},
"icons": {
"outline": "icon-outline.png",
"color": "icon-color.png"
},
"name": {
"short": "TeamsConversationBot",
"full": "TeamsConversationBot"
},
"description": {
"short": "TeamsConversationBot",
"full": "TeamsConversationBot"
},
"accentColor": "#FFFFFF",
"bots": [
{
"botId": "<<YOUR-MICROSOFT-APP-ID>>",
"scopes": [
"personal",
"groupchat",
"team"
],
"supportsFiles": false,
"isNotificationOnly": false,
"commandLists": [
{
"scopes": [
"personal",
"groupchat",
"team"
],
"commands": [
{
"title": "MentionMe",
"description": "Sends message with @mention of the sender"
},
{
"title": "Show Welcome",
"description": "Shows the welcome card"
},
{
"title": "MessageAllMembers",
"description": "Send 1 to 1 message to all members of the current conversation"
}
]
}
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": []
}