diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsActivityHandler.java index 3d51319f4..35c0d7464 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsActivityHandler.java @@ -9,6 +9,8 @@ import com.microsoft.bot.builder.ActivityHandler; import com.microsoft.bot.builder.InvokeResponse; import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.Channels; import com.microsoft.bot.connector.rest.ErrorResponseException; import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.Error; @@ -18,6 +20,8 @@ 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.MeetingEndEventDetails; +import com.microsoft.bot.schema.teams.MeetingStartEventDetails; import com.microsoft.bot.schema.teams.MessagingExtensionAction; import com.microsoft.bot.schema.teams.MessagingExtensionActionResponse; import com.microsoft.bot.schema.teams.MessagingExtensionQuery; @@ -45,7 +49,7 @@ * Pre and post processing of Activities can be plugged in by deriving and * calling the base class implementation. */ -@SuppressWarnings({ "checkstyle:JavadocMethod", "checkstyle:DesignForExtension", "checkstyle:MethodLength" }) +@SuppressWarnings({"checkstyle:JavadocMethod", "checkstyle:DesignForExtension", "checkstyle:MethodLength"}) public class TeamsActivityHandler extends ActivityHandler { /** * Invoked when an invoke activity is received from the connector when the base @@ -59,87 +63,66 @@ protected CompletableFuture onInvokeActivity(TurnContext turnCon CompletableFuture result; try { - if ( - turnContext.getActivity().getName() == null - && turnContext.getActivity().isTeamsActivity() - ) { + 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 - ) + Serialization.safeGetAs(turnContext.getActivity().getValue(), FileConsentCardResponse.class) ); break; case "actionableMessage/executeAction": result = onTeamsO365ConnectorCardAction( turnContext, - Serialization.safeGetAs( - turnContext.getActivity().getValue(), - O365ConnectorCardActionQuery.class - ) + 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 - ) + Serialization.safeGetAs(turnContext.getActivity().getValue(), AppBasedLinkQuery.class) ).thenApply(ActivityHandler::createInvokeResponse); break; case "composeExtension/query": result = onTeamsMessagingExtensionQuery( turnContext, - Serialization.safeGetAs( - turnContext.getActivity().getValue(), - MessagingExtensionQuery.class - ) + Serialization.safeGetAs(turnContext.getActivity().getValue(), MessagingExtensionQuery.class) ).thenApply(ActivityHandler::createInvokeResponse); break; case "composeExtension/selectItem": - result = onTeamsMessagingExtensionSelectItem( - turnContext, - turnContext.getActivity().getValue() - ).thenApply(ActivityHandler::createInvokeResponse); + 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); + 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); + 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 - ) + Serialization.safeGetAs(turnContext.getActivity().getValue(), MessagingExtensionQuery.class) ).thenApply(ActivityHandler::createInvokeResponse); break; @@ -160,40 +143,28 @@ protected CompletableFuture onInvokeActivity(TurnContext turnCon case "task/fetch": result = onTeamsTaskModuleFetch( turnContext, - Serialization.safeGetAs( - turnContext.getActivity().getValue(), - TaskModuleRequest.class - ) + Serialization.safeGetAs(turnContext.getActivity().getValue(), TaskModuleRequest.class) ).thenApply(ActivityHandler::createInvokeResponse); break; case "task/submit": result = onTeamsTaskModuleSubmit( turnContext, - Serialization.safeGetAs( - turnContext.getActivity().getValue(), - TaskModuleRequest.class - ) + Serialization.safeGetAs(turnContext.getActivity().getValue(), TaskModuleRequest.class) ).thenApply(ActivityHandler::createInvokeResponse); break; case "tab/fetch": result = onTeamsTabFetch( turnContext, - Serialization.safeGetAs( - turnContext.getActivity().getValue(), - TabRequest.class - ) + Serialization.safeGetAs(turnContext.getActivity().getValue(), TabRequest.class) ).thenApply(ActivityHandler::createInvokeResponse); break; case "tab/submit": result = onTeamsTabSubmit( turnContext, - Serialization.safeGetAs( - turnContext.getActivity().getValue(), - TabSubmit.class - ) + Serialization.safeGetAs(turnContext.getActivity().getValue(), TabSubmit.class) ).thenApply(ActivityHandler::createInvokeResponse); break; @@ -208,17 +179,12 @@ protected CompletableFuture onInvokeActivity(TurnContext turnCon } return result.exceptionally(e -> { - if ( - e instanceof CompletionException && e.getCause() instanceof InvokeResponseException - ) { + if (e instanceof CompletionException && e.getCause() instanceof InvokeResponseException) { return ((InvokeResponseException) e.getCause()).createInvokeResponse(); } else if (e instanceof InvokeResponseException) { return ((InvokeResponseException) e).createInvokeResponse(); } - return new InvokeResponse( - HttpURLConnection.HTTP_INTERNAL_ERROR, - e.getLocalizedMessage() - ); + return new InvokeResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, e.getLocalizedMessage()); }); } @@ -255,9 +221,10 @@ protected CompletableFuture onTeamsSigninVerifyState(TurnContext turnConte /** * Invoked when a file consent card activity is received from the connector. * - * @param turnContext The current TurnContext. - * @param fileConsentCardResponse The response representing the value of the invoke activity sent - * when the user acts on a file consent card. + * @param turnContext The current TurnContext. + * @param fileConsentCardResponse The response representing the value of the + * invoke activity sent when the user acts on a + * file consent card. * @return An InvokeResponse depending on the action of the file consent card. */ protected CompletableFuture onTeamsFileConsent( @@ -266,14 +233,12 @@ protected CompletableFuture onTeamsFileConsent( ) { switch (fileConsentCardResponse.getAction()) { case "accept": - return onTeamsFileConsentAccept(turnContext, fileConsentCardResponse).thenApply( - aVoid -> createInvokeResponse(null) - ); + return onTeamsFileConsentAccept(turnContext, fileConsentCardResponse) + .thenApply(aVoid -> createInvokeResponse(null)); case "decline": - return onTeamsFileConsentDecline(turnContext, fileConsentCardResponse).thenApply( - aVoid -> createInvokeResponse(null) - ); + return onTeamsFileConsentDecline(turnContext, fileConsentCardResponse) + .thenApply(aVoid -> createInvokeResponse(null)); default: CompletableFuture result = new CompletableFuture<>(); @@ -290,9 +255,10 @@ protected CompletableFuture onTeamsFileConsent( /** * Invoked when a file consent card is accepted by the user. * - * @param turnContext The current TurnContext. - * @param fileConsentCardResponse The response representing the value of the invoke activity sent - * when the user accepts a file consent card. + * @param turnContext The current TurnContext. + * @param fileConsentCardResponse The response representing the value of the + * invoke activity sent when the user accepts a + * file consent card. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsFileConsentAccept( @@ -305,9 +271,10 @@ protected CompletableFuture onTeamsFileConsentAccept( /** * Invoked when a file consent card is declined by the user. * - * @param turnContext The current TurnContext. - * @param fileConsentCardResponse The response representing the value of the invoke activity sent - * when the user declines a file consent card. + * @param turnContext The current TurnContext. + * @param fileConsentCardResponse The response representing the value of the + * invoke activity sent when the user declines a + * file consent card. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsFileConsentDecline( @@ -318,10 +285,11 @@ protected CompletableFuture onTeamsFileConsentDecline( } /** - * Invoked when a Messaging Extension Query activity is received from the connector. + * Invoked when a Messaging Extension Query activity is received from the + * connector. * * @param turnContext The current TurnContext. - * @param query The query for the search command. + * @param query The query for the search command. * @return The Messaging Extension Response for the query. */ protected CompletableFuture onTeamsMessagingExtensionQuery( @@ -332,10 +300,11 @@ protected CompletableFuture onTeamsMessagingExtensio } /** - * Invoked when a O365 Connector Card Action activity is received from the connector. + * Invoked when a O365 Connector Card Action activity is received from the + * connector. * * @param turnContext The current TurnContext. - * @param query The O365 connector card HttpPOST invoke query. + * @param query The O365 connector card HttpPOST invoke query. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsO365ConnectorCardAction( @@ -349,7 +318,7 @@ protected CompletableFuture onTeamsO365ConnectorCardAction( * Invoked when an app based link query activity is received from the connector. * * @param turnContext The current TurnContext. - * @param query The invoke request body type for app-based link query. + * @param query The invoke request body type for app-based link query. * @return The Messaging Extension Response for the query. */ protected CompletableFuture onTeamsAppBasedLinkQuery( @@ -360,10 +329,11 @@ protected CompletableFuture onTeamsAppBasedLinkQuery } /** - * Invoked when a messaging extension select item activity is received from the connector. + * Invoked when a messaging extension select item activity is received from the + * connector. * * @param turnContext The current TurnContext. - * @param query The object representing the query. + * @param query The object representing the query. * @return The Messaging Extension Response for the query. */ protected CompletableFuture onTeamsMessagingExtensionSelectItem( @@ -374,10 +344,11 @@ protected CompletableFuture onTeamsMessagingExtensio } /** - * Invoked when a Messaging Extension Fetch activity is received from the connector. + * Invoked when a Messaging Extension Fetch activity is received from the + * connector. * * @param turnContext The current TurnContext. - * @param action The messaging extension action. + * @param action The messaging extension action. * @return The Messaging Extension Action Response for the action. */ protected CompletableFuture onTeamsMessagingExtensionFetchTask( @@ -388,10 +359,11 @@ protected CompletableFuture onTeamsMessagingEx } /** - * Invoked when a messaging extension submit action dispatch activity is received from the connector. + * Invoked when a messaging extension submit action dispatch activity is + * received from the connector. * * @param turnContext The current TurnContext. - * @param action The messaging extension action. + * @param action The messaging extension action. * @return The Messaging Extension Action Response for the action. */ protected CompletableFuture onTeamsMessagingExtensionSubmitActionDispatch( @@ -411,8 +383,7 @@ protected CompletableFuture onTeamsMessagingEx result.completeExceptionally( new InvokeResponseException( HttpURLConnection.HTTP_BAD_REQUEST, - action.getBotMessagePreviewAction() - + " is not a support BotMessagePreviewAction" + action.getBotMessagePreviewAction() + " is not a support BotMessagePreviewAction" ) ); return result; @@ -423,10 +394,11 @@ protected CompletableFuture onTeamsMessagingEx } /** - * Invoked when a messaging extension submit action activity is received from the connector. + * Invoked when a messaging extension submit action activity is received from + * the connector. * * @param turnContext The current TurnContext. - * @param action The messaging extension action. + * @param action The messaging extension action. * @return The Messaging Extension Action Response for the action. */ protected CompletableFuture onTeamsMessagingExtensionSubmitAction( @@ -437,10 +409,11 @@ protected CompletableFuture onTeamsMessagingEx } /** - * Invoked when a messaging extension bot message preview edit activity is received from the connector. + * Invoked when a messaging extension bot message preview edit activity is + * received from the connector. * * @param turnContext The current TurnContext. - * @param action The messaging extension action. + * @param action The messaging extension action. * @return The Messaging Extension Action Response for the action. */ protected CompletableFuture onTeamsMessagingExtensionBotMessagePreviewEdit( @@ -451,10 +424,11 @@ protected CompletableFuture onTeamsMessagingEx } /** - * Invoked when a messaging extension bot message preview send activity is received from the connector. + * Invoked when a messaging extension bot message preview send activity is + * received from the connector. * * @param turnContext The current TurnContext. - * @param action The messaging extension action. + * @param action The messaging extension action. * @return The Messaging Extension Action Response for the action. */ protected CompletableFuture onTeamsMessagingExtensionBotMessagePreviewSend( @@ -465,10 +439,11 @@ protected CompletableFuture onTeamsMessagingEx } /** - * Invoked when a messaging extension configuration query setting url activity is received from the connector. + * Invoked when a messaging extension configuration query setting url activity + * is received from the connector. * * @param turnContext The current TurnContext. - * @param query The Messaging extension query. + * @param query The Messaging extension query. * @return The Messaging Extension Response for the query. */ protected CompletableFuture onTeamsMessagingExtensionConfigurationQuerySettingUrl( @@ -479,10 +454,11 @@ protected CompletableFuture onTeamsMessagingExtensio } /** - * Override this in a derived class to provide logic for when a configuration is set for a messaging extension. + * Override this in a derived class to provide logic for when a configuration is + * set for a messaging extension. * * @param turnContext The current TurnContext. - * @param settings Object representing the configuration settings. + * @param settings Object representing the configuration settings. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsMessagingExtensionConfigurationSetting( @@ -493,9 +469,10 @@ protected CompletableFuture onTeamsMessagingExtensionConfigurationSetting( } /** - * Override this in a derived class to provide logic for when a task module is fetched. + * Override this in a derived class to provide logic for when a task module is + * fetched. * - * @param turnContext The current TurnContext. + * @param turnContext The current TurnContext. * @param taskModuleRequest The task module invoke request value payload. * @return A Task Module Response for the request. */ @@ -507,11 +484,11 @@ protected CompletableFuture onTeamsTaskModuleFetch( } /** - * Override this in a derived class to provide logic for when a card button - * is clicked in a messaging extension. + * Override this in a derived class to provide logic for when a card button is + * clicked in a messaging extension. * * @param turnContext The current TurnContext. - * @param cardData Object representing the card data. + * @param cardData Object representing the card data. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsMessagingExtensionCardButtonClicked( @@ -522,9 +499,10 @@ protected CompletableFuture onTeamsMessagingExtensionCardButtonClicked( } /** - * Override this in a derived class to provide logic for when a task module is submited. + * Override this in a derived class to provide logic for when a task module is + * submited. * - * @param turnContext The current TurnContext. + * @param turnContext The current TurnContext. * @param taskModuleRequest The task module invoke request value payload. * @return A Task Module Response for the request. */ @@ -537,18 +515,17 @@ protected CompletableFuture onTeamsTaskModuleSubmit( /** * Invoked when a conversation update activity is received from the channel. - * Conversation update activities are useful when it comes to responding to users being added to - * or removed from the channel. - * For example, a bot could respond to a user being added by greeting the user. + * Conversation update activities are useful when it comes to responding to + * users being added to or removed from the channel. For example, a bot could + * respond to a user being added by greeting the user. * * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ protected CompletableFuture onConversationUpdateActivity(TurnContext turnContext) { if (turnContext.getActivity().isTeamsActivity()) { - ResultPair channelData = turnContext.getActivity().tryGetChannelData( - TeamsChannelData.class - ); + ResultPair channelData = + turnContext.getActivity().tryGetChannelData(TeamsChannelData.class); if (turnContext.getActivity().getMembersAdded() != null) { return onTeamsMembersAddedDispatch( @@ -648,14 +625,14 @@ protected CompletableFuture onConversationUpdateActivity(TurnContext turnC } /** - * Override this in a derived class to provide logic for when members other than the bot - * join the channel, such as your bot's welcome logic. - * It will get the associated members with the provided accounts. + * Override this in a derived class to provide logic for when members other than + * the bot join the channel, such as your bot's welcome logic. It will get the + * associated members with the provided accounts. * - * @param membersAdded A list of all the accounts added to the channel, as described by - * the conversation update activity. - * @param teamInfo The team info object representing the team. - * @param turnContext The current TurnContext. + * @param membersAdded A list of all the accounts added to the channel, as + * described by the conversation update activity. + * @param teamInfo The team info object representing the team. + * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsMembersAddedDispatch( @@ -694,7 +671,7 @@ protected CompletableFuture onTeamsMembersAddedDispatch( throw ex; } } - } else { + } else { throw ex; } } @@ -716,14 +693,14 @@ protected CompletableFuture onTeamsMembersAddedDispatch( } /** - * Override this in a derived class to provide logic for when members other than the bot - * leave the channel, such as your bot's good-bye logic. - * It will get the associated members with the provided accounts. + * Override this in a derived class to provide logic for when members other than + * the bot leave the channel, such as your bot's good-bye logic. It will get the + * associated members with the provided accounts. * - * @param membersRemoved A list of all the accounts removed from the channel, as described by - * the conversation update activity. - * @param teamInfo The team info object representing the team. - * @param turnContext The current TurnContext. + * @param membersRemoved A list of all the accounts removed from the channel, as + * described by the conversation update activity. + * @param teamInfo The team info object representing the team. + * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsMembersRemovedDispatch( @@ -748,13 +725,13 @@ protected CompletableFuture onTeamsMembersRemovedDispatch( } /** - * Override this in a derived class to provide logic for when members other than the bot - * join the channel, such as your bot's welcome logic. + * Override this in a derived class to provide logic for when members other than + * the bot join the channel, such as your bot's welcome logic. * - * @param membersAdded A list of all the members added to the channel, as described by - * the conversation update activity. - * @param teamInfo The team info object representing the team. - * @param turnContext The current TurnContext. + * @param membersAdded A list of all the members added to the channel, as + * described by the conversation update activity. + * @param teamInfo The team info object representing the team. + * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsMembersAdded( @@ -766,13 +743,13 @@ protected CompletableFuture onTeamsMembersAdded( } /** - * Override this in a derived class to provide logic for when members other than the bot - * leave the channel, such as your bot's good-bye logic. + * Override this in a derived class to provide logic for when members other than + * the bot leave the channel, such as your bot's good-bye logic. * - * @param membersRemoved A list of all the members removed from the channel, as described by - * the conversation update activity. - * @param teamInfo The team info object representing the team. - * @param turnContext The current TurnContext. + * @param membersRemoved A list of all the members removed from the channel, as + * described by the conversation update activity. + * @param teamInfo The team info object representing the team. + * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ protected CompletableFuture onTeamsMembersRemoved( @@ -788,7 +765,7 @@ protected CompletableFuture onTeamsMembersRemoved( * Channel Created correspond to the user creating a new channel. * * @param channelInfo The channel info object which describes the channel. - * @param teamInfo The team info object representing the team. + * @param teamInfo The team info object representing the team. * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ @@ -805,7 +782,7 @@ protected CompletableFuture onTeamsChannelCreated( * Channel Deleted correspond to the user deleting an existing channel. * * @param channelInfo The channel info object which describes the channel. - * @param teamInfo The team info object representing the team. + * @param teamInfo The team info object representing the team. * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ @@ -822,7 +799,7 @@ protected CompletableFuture onTeamsChannelDeleted( * Channel Renamed correspond to the user renaming an existing channel. * * @param channelInfo The channel info object which describes the channel. - * @param teamInfo The team info object representing the team. + * @param teamInfo The team info object representing the team. * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ @@ -835,11 +812,12 @@ protected CompletableFuture onTeamsChannelRenamed( } /** - * Invoked when a Channel Restored event activity is received from the connector. - * Channel Restored correspond to the user restoring a previously deleted channel. + * Invoked when a Channel Restored event activity is received from the + * connector. Channel Restored correspond to the user restoring a previously + * deleted channel. * * @param channelInfo The channel info object which describes the channel. - * @param teamInfo The team info object representing the team. + * @param teamInfo The team info object representing the team. * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ @@ -882,8 +860,8 @@ protected CompletableFuture onTeamsTeamDeleted( } /** - * Invoked when a Team Hard Deleted event activity is received from the connector. - * Team Hard Deleted correspond to the user hard-deleting a team. + * Invoked when a Team Hard Deleted event activity is received from the + * connector. Team Hard Deleted correspond to the user hard-deleting a team. * * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. @@ -901,7 +879,7 @@ protected CompletableFuture onTeamsTeamHardDeleted( * Channel Renamed correspond to the user renaming an existing channel. * * @param channelInfo The channel info object which describes the channel. - * @param teamInfo The team info object representing the team. + * @param teamInfo The team info object representing the team. * @param turnContext The current TurnContext. * @return A task that represents the work queued to execute. */ @@ -945,8 +923,9 @@ protected CompletableFuture onTeamsTeamUnarchived( /** * Override this in a derived class to provide logic for when a tab is fetched. + * * @param turnContext The context object for this turn. - * @param tabRequest The tab invoke request value payload. + * @param tabRequest The tab invoke request value payload. * @return A Tab Response for the request. */ protected CompletableFuture onTeamsTabFetch(TurnContext turnContext, TabRequest tabRequest) { @@ -954,19 +933,101 @@ protected CompletableFuture onTeamsTabFetch(TurnContext turnContext } /** - * Override this in a derived class to provide logic for when a tab is submitted. + * Override this in a derived class to provide logic for when a tab is + * submitted. + * * @param turnContext The context object for this turn. - * @param tabSubmit The tab submit invoke request value payload. + * @param tabSubmit The tab submit invoke request value payload. * @return A Tab Response for the request. */ protected CompletableFuture onTeamsTabSubmit(TurnContext turnContext, TabSubmit tabSubmit) { return withException(new InvokeResponseException(HttpURLConnection.HTTP_NOT_IMPLEMENTED)); } + /** + * Invoked when a "tokens/response" event is received when the base behavior of + * {@link #onEventActivity(TurnContext)} is used. + * + *

+ * If using an OAuthPrompt, override this method to forward this + * {@link com.microsoft.bot.schema.Activity} to the current dialog. + *

+ * + *

+ * By default, this method does nothing. + *

+ *

+ * When the {@link #onEventActivity(TurnContext)} method receives an event with + * a {@link com.microsoft.bot.schema.Activity#getName()} of `tokens/response`, it calls this method. + * + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + @Override + protected CompletableFuture onEventActivity(TurnContext turnContext) { + if (StringUtils.equals(turnContext.getActivity().getChannelId(), Channels.MSTEAMS)) { + try { + switch (turnContext.getActivity().getName()) { + case "application/vnd.microsoft.meetingStart": + return onTeamsMeetingStart( + Serialization.safeGetAs( + turnContext.getActivity().getValue(), + MeetingStartEventDetails.class + ), + turnContext + ); + + case "application/vnd.microsoft.meetingEnd": + return onTeamsMeetingEnd( + Serialization.safeGetAs( + turnContext.getActivity().getValue(), + MeetingEndEventDetails.class + ), + turnContext + ); + + default: + break; + } + } catch (Throwable t) { + return Async.completeExceptionally(t); + } + } + + return super.onEventActivity(turnContext); + } + + /** + * Invoked when a Teams Meeting Start event activity is received from the + * connector. Override this in a derived class to provide logic for when a + * meeting is started. + * + * @param meeting The details of the meeting. + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture onTeamsMeetingStart(MeetingStartEventDetails meeting, TurnContext turnContext) { + return CompletableFuture.completedFuture(null); + } + + /** + * Invoked when a Teams Meeting End event activity is received from the + * connector. Override this in a derived class to provide logic for when a + * meeting is ended. + * + * @param meeting The details of the meeting. + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture onTeamsMeetingEnd(MeetingEndEventDetails meeting, TurnContext turnContext) { + return CompletableFuture.completedFuture(null); + } + /** * Invoke a new InvokeResponseException with a HTTP 501 code status. * - * @return true if this invocation caused this CompletableFuture to transition to a completed state, else false + * @return true if this invocation caused this CompletableFuture to transition + * to a completed state, else false */ protected CompletableFuture notImplemented() { return notImplemented(null); @@ -976,13 +1037,12 @@ protected CompletableFuture notImplemented() { * Invoke a new InvokeResponseException with a HTTP 501 code status. * * @param body The body for the InvokeResponseException. - * @return true if this invocation caused this CompletableFuture to transition to a completed state, else false + * @return true if this invocation caused this CompletableFuture to transition + * to a completed state, else false */ protected CompletableFuture notImplemented(String body) { CompletableFuture result = new CompletableFuture<>(); - result.completeExceptionally( - new InvokeResponseException(HttpURLConnection.HTTP_NOT_IMPLEMENTED, body) - ); + result.completeExceptionally(new InvokeResponseException(HttpURLConnection.HTTP_NOT_IMPLEMENTED, body)); return result; } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsInfo.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsInfo.java index 5b93c4688..f5d96da44 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsInfo.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsInfo.java @@ -270,6 +270,15 @@ public static CompletableFuture getMeetingParticipant( ); } + /** + * Gets the information for the given meeting id. + * @param turnContext Turn context. + * @return Meeting Details. + */ + public static CompletableFuture getMeetingInfo(TurnContext turnContext) { + return getMeetingInfo(turnContext, null); + } + /** * Gets the information for the given meeting id. * @param turnContext Turn context. diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java index 3d6ee347b..ab813f3fb 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java @@ -24,10 +24,13 @@ import com.microsoft.bot.schema.ConversationReference; import com.microsoft.bot.schema.ConversationResourceResponse; import com.microsoft.bot.schema.ResourceResponse; +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.FileUploadInfo; +import com.microsoft.bot.schema.teams.MeetingEndEventDetails; +import com.microsoft.bot.schema.teams.MeetingStartEventDetails; import com.microsoft.bot.schema.teams.MessagingExtensionAction; import com.microsoft.bot.schema.teams.MessagingExtensionActionResponse; import com.microsoft.bot.schema.teams.MessagingExtensionQuery; @@ -39,6 +42,7 @@ import com.microsoft.bot.schema.teams.TeamInfo; import com.microsoft.bot.schema.teams.TeamsChannelAccount; import com.microsoft.bot.schema.teams.TeamsChannelData; +import java.io.IOException; import org.apache.commons.lang3.NotImplementedException; import org.junit.Assert; import org.junit.Test; @@ -857,6 +861,80 @@ public void TestSigninVerifyState() { ); } + @Test + public void TestOnEventActivity() { + // Arrange + Activity activity = new Activity(ActivityTypes.EVENT); + activity.setChannelId(Channels.DIRECTLINE); + + TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(), activity); + + // Act + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + // Assert + Assert.assertEquals(1, bot.record.size()); + Assert.assertEquals("onEventActivity", bot.record.get(0)); + } + + @Test + public void TestMeetingStartEvent() throws IOException { + // Arrange + Activity activity = new Activity(ActivityTypes.EVENT); + activity.setChannelId(Channels.MSTEAMS); + activity.setName("application/vnd.microsoft.meetingStart"); + activity.setValue(Serialization.jsonToTree("{\"StartTime\": \"2021-06-05T00:01:02.0Z\"}")); + + AtomicReference> activitiesToSend = new AtomicReference<>(); + + TurnContext turnContext = new TurnContextImpl( + new SimpleAdapter(activitiesToSend::set), + activity + ); + + // Act + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + // Assert + Assert.assertEquals(2, bot.record.size()); + Assert.assertEquals("onEventActivity", bot.record.get(0)); + Assert.assertEquals("onTeamsMeetingStart", bot.record.get(1)); + + Assert.assertNotNull(activitiesToSend.get()); + Assert.assertEquals(1, activitiesToSend.get().size()); + Assert.assertTrue(activitiesToSend.get().get(0).getText().contains("00:01:02")); + } + + @Test + public void TestMeetingEndEvent() throws IOException { + // Arrange + Activity activity = new Activity(ActivityTypes.EVENT); + activity.setChannelId(Channels.MSTEAMS); + activity.setName("application/vnd.microsoft.meetingEnd"); + activity.setValue(Serialization.jsonToTree("{\"EndTime\": \"2021-06-05T01:02:03.0Z\"}")); + + AtomicReference> activitiesToSend = new AtomicReference<>(); + + TurnContext turnContext = new TurnContextImpl( + new SimpleAdapter(activitiesToSend::set), + activity + ); + + // Act + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + // Assert + Assert.assertEquals(2, bot.record.size()); + Assert.assertEquals("onEventActivity", bot.record.get(0)); + Assert.assertEquals("onTeamsMeetingEnd", bot.record.get(1)); + Assert.assertNotNull(activitiesToSend.get()); + Assert.assertEquals(1, activitiesToSend.get().size()); + Assert.assertTrue(activitiesToSend.get().get(0).getText().contains("1:02:03")); + } + private static class NotImplementedAdapter extends BotAdapter { @Override @@ -1209,6 +1287,34 @@ protected CompletableFuture onTeamsTeamUnarchived( record.add("onTeamsTeamUnarchived"); return super.onTeamsTeamUnarchived(channelInfo, teamInfo, turnContext); } + + @Override + protected CompletableFuture onEventActivity( + TurnContext turnContext + ) { + record.add("onEventActivity"); + return super.onEventActivity(turnContext); + } + + @Override + protected CompletableFuture onTeamsMeetingStart( + MeetingStartEventDetails meeting, + TurnContext turnContext + ) { + record.add("onTeamsMeetingStart"); + return turnContext.sendActivity(meeting.getStartTime().toString()) + .thenCompose(resourceResponse -> super.onTeamsMeetingStart(meeting, turnContext)); + } + + @Override + protected CompletableFuture onTeamsMeetingEnd( + MeetingEndEventDetails meeting, + TurnContext turnContext + ) { + record.add("onTeamsMeetingEnd"); + return turnContext.sendActivity(meeting.getEndTime().toString()) + .thenCompose(resourceResponse -> super.onTeamsMeetingEnd(meeting, turnContext)); + } } private static ConnectorClient getConnectorClient(String baseUri, AppCredentials credentials) { diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonAdapter.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonAdapter.java index a8a7f4048..c46ac4319 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonAdapter.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonAdapter.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.type.TypeBindings; @@ -143,7 +144,8 @@ private static ObjectMapper initializeObjectMapper(ObjectMapper mapper) { .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) .setSerializationInclusion(JsonInclude.Include.NON_NULL) .registerModule(new Jdk8Module()) .registerModule(new JavaTimeModule()) diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java index 9d50d0a99..e3353867d 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -27,6 +28,7 @@ private Serialization() { static { objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); objectMapper.findAndRegisterModules(); // NOTE: Undetermined if we should accommodate non-public fields. The normal diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingDetails.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingDetails.java index 04d309b6d..e933a6fa4 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingDetails.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingDetails.java @@ -4,59 +4,27 @@ package com.microsoft.bot.schema.teams; import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.OffsetDateTime; /** * Specific details of a Teams meeting. */ -public class MeetingDetails { - @JsonProperty(value = "id") - private String id; - +public class MeetingDetails extends MeetingDetailsBase { @JsonProperty(value = "msGraphResourceId") private String msGraphResourceId; @JsonProperty(value = "scheduledStartTime") - private String scheduledStartTime; + private OffsetDateTime scheduledStartTime; @JsonProperty(value = "scheduledEndTime") - private String scheduledEndTime; - - @JsonProperty(value = "joinUrl") - private String joinUrl; - - @JsonProperty(value = "title") - private String title; + private OffsetDateTime scheduledEndTime; @JsonProperty(value = "type") private String type; - /** - * Initializes a new instance. - */ - public MeetingDetails() { - } - - /** - * Gets the meeting's Id, encoded as a BASE64 String. - * - * @return The meeting's Id, encoded as a BASE64 String. - */ - public String getId() { - return id; - } - - /** - * Sets the meeting's Id, encoded as a BASE64 String. - * - * @param withId The meeting's Id, encoded as a BASE64 String. - */ - public void setId(String withId) { - id = withId; - } - /** * Gets the MsGraphResourceId, used specifically for MS Graph API calls. - * + * * @return The MsGraphResourceId, used specifically for MS Graph API calls. */ public String getMsGraphResourceId() { @@ -65,7 +33,7 @@ public String getMsGraphResourceId() { /** * Sets the MsGraphResourceId, used specifically for MS Graph API calls. - * + * * @param withMsGraphResourceId The MsGraphResourceId, used specifically for MS * Graph API calls. */ @@ -75,79 +43,43 @@ public void setMsGraphResourceId(String withMsGraphResourceId) { /** * Gets the meeting's scheduled start time, in UTC. - * + * * @return The meeting's scheduled start time, in UTC. */ - public String getScheduledStartTime() { + public OffsetDateTime getScheduledStartTime() { return scheduledStartTime; } /** * Sets the meeting's scheduled start time, in UTC. - * + * * @param withScheduledStartTime The meeting's scheduled start time, in UTC. */ - public void setScheduledStartTime(String withScheduledStartTime) { + public void setScheduledStartTime(OffsetDateTime withScheduledStartTime) { scheduledStartTime = withScheduledStartTime; } /** * Gets the meeting's scheduled end time, in UTC. - * + * * @return The meeting's scheduled end time, in UTC. */ - public String getScheduledEndTime() { + public OffsetDateTime getScheduledEndTime() { return scheduledEndTime; } /** * Sets the meeting's scheduled end time, in UTC. - * + * * @param withScheduledEndTime The meeting's scheduled end time, in UTC. */ - public void setScheduledEndTime(String withScheduledEndTime) { + public void setScheduledEndTime(OffsetDateTime withScheduledEndTime) { scheduledEndTime = withScheduledEndTime; } - /** - * Gets the URL used to join the meeting. - * - * @return The URL used to join the meeting. - */ - public String getJoinUrl() { - return joinUrl; - } - - /** - * Sets the URL used to join the meeting. - * - * @param withJoinUrl The URL used to join the meeting. - */ - public void setJoinUrl(String withJoinUrl) { - joinUrl = withJoinUrl; - } - - /** - * Gets the title of the meeting. - * - * @return The title of the meeting. - */ - public String getTitle() { - return title; - } - - /** - * Sets the title of the meeting. - * - * @param withTitle The title of the meeting. - */ - public void setTitle(String withTitle) { - title = withTitle; - } - /** * Gets the meeting's type. - * + * * @return The meeting's type. */ public String getType() { @@ -156,7 +88,7 @@ public String getType() { /** * Sets the meeting's type. - * + * * @param withType The meeting's type. */ public void setType(String withType) { diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingDetailsBase.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingDetailsBase.java new file mode 100644 index 000000000..0327a2eb6 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingDetailsBase.java @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Specific details of a Teams meeting. + */ +public class MeetingDetailsBase { + @JsonProperty(value = "id") + private String id; + + @JsonProperty(value = "joinUrl") + private String joinUrl; + + @JsonProperty(value = "title") + private String title; + + /** + * Initializes a new instance. + */ + public MeetingDetailsBase() { + } + + /** + * Gets the meeting's Id, encoded as a BASE64 String. + * + * @return The meeting's Id, encoded as a BASE64 String. + */ + public String getId() { + return id; + } + + /** + * Sets the meeting's Id, encoded as a BASE64 String. + * + * @param withId The meeting's Id, encoded as a BASE64 String. + */ + public void setId(String withId) { + id = withId; + } + + /** + * Gets the URL used to join the meeting. + * + * @return The URL used to join the meeting. + */ + public String getJoinUrl() { + return joinUrl; + } + + /** + * Sets the URL used to join the meeting. + * + * @param withJoinUrl The URL used to join the meeting. + */ + public void setJoinUrl(String withJoinUrl) { + joinUrl = withJoinUrl; + } + + /** + * Gets the title of the meeting. + * + * @return The title of the meeting. + */ + public String getTitle() { + return title; + } + + /** + * Sets the title of the meeting. + * + * @param withTitle The title of the meeting. + */ + public void setTitle(String withTitle) { + title = withTitle; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingEndEventDetails.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingEndEventDetails.java new file mode 100644 index 000000000..6bfcaa221 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingEndEventDetails.java @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.OffsetDateTime; + +/** + * Specific details of a Teams meeting end event. + */ +public class MeetingEndEventDetails extends MeetingEventDetails { + @JsonProperty(value = "EndTime") + private OffsetDateTime endTime; + + /** + * Gets the meeting's end time, in UTC. + * @return The meeting's end time, in UTC. + */ + public OffsetDateTime getEndTime() { + return endTime; + } + + /** + * Sets the meeting's end time, in UTC. + * @param withEndTime The meeting's end time, in UTC. + */ + public void setEndTime(OffsetDateTime withEndTime) { + endTime = withEndTime; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingEventDetails.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingEventDetails.java new file mode 100644 index 000000000..ff4f27319 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingEventDetails.java @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Specific details of a Teams meeting. + */ +public class MeetingEventDetails extends MeetingDetailsBase { + @JsonProperty(value = "MeetingType") + private String meetingType; + + /** + * Gets the meeting's type. + * @return The meeting's type. + */ + public String getMeetingType() { + return meetingType; + } + + /** + * Sets the meeting's type. + * @param withMeetingType The meeting's type. + */ + public void setMeetingType(String withMeetingType) { + meetingType = withMeetingType; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingStartEventDetails.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingStartEventDetails.java new file mode 100644 index 000000000..67ae7f4ab --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingStartEventDetails.java @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.OffsetDateTime; + +/** + * Specific details of a Teams meeting start event. + */ +public class MeetingStartEventDetails extends MeetingEventDetails { + @JsonProperty(value = "StartTime") + private OffsetDateTime startTime; + + /** + * Gets the meeting's start time, in UTC. + * @return The meeting's start time, in UTC. + */ + public OffsetDateTime getStartTime() { + return startTime; + } + + /** + * Sets the meeting's start time, in UTC. + * @param withStartTime The meeting's start time, in UTC. + */ + public void setStartTime(OffsetDateTime withStartTime) { + startTime = withStartTime; + } +} diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/SerializationTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/SerializationTest.java index b9f6ff4d1..754ce84e1 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/SerializationTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/SerializationTest.java @@ -177,13 +177,13 @@ private Activity createActivity() { ChannelAccount account1 = new ChannelAccount(); account1.setId("ChannelAccount_Id_1"); account1.setName("ChannelAccount_Name_1"); - account1.setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); + account1.setProperties("TestName", JsonNodeFactory.instance.objectNode().put("Name", "Value")); account1.setRole(RoleTypes.USER); ChannelAccount account2 = new ChannelAccount(); account2.setId("ChannelAccount_Id_2"); account2.setName("ChannelAccount_Name_2"); - account2.setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); + account2.setProperties("TestName", JsonNodeFactory.instance.objectNode().put("Name", "Value")); account2.setRole(RoleTypes.USER); ConversationAccount conversationAccount = new ConversationAccount(); @@ -191,7 +191,7 @@ private Activity createActivity() { conversationAccount.setId("123"); conversationAccount.setIsGroup(true); conversationAccount.setName("Name"); - conversationAccount.setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); + conversationAccount.setProperties("TestName", JsonNodeFactory.instance.objectNode().put("Name", "Value")); Activity activity = new Activity(); activity.setId("123");