From 5ae89f485c874c7ff49a2f5dc2d886201445fd2c Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 8 Jun 2021 11:36:42 -0700 Subject: [PATCH 1/2] add adaptive card invoke action --- .../botbuilder/core/activity_handler.py | 70 ++++++++++++++ .../botbuilder/schema/__init__.py | 6 ++ .../botbuilder/schema/_models_py3.py | 93 ++++++++++++++++++- .../botbuilder/schema/teams/_models_py3.py | 2 +- 4 files changed, 168 insertions(+), 3 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/activity_handler.py b/libraries/botbuilder-core/botbuilder/core/activity_handler.py index 28c924e0f..3124589fa 100644 --- a/libraries/botbuilder-core/botbuilder/core/activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/activity_handler.py @@ -6,6 +6,8 @@ from botbuilder.schema import ( Activity, ActivityTypes, + AdaptiveCardInvokeResponse, + AdaptiveCardInvokeValue, ChannelAccount, MessageReaction, SignInConstants, @@ -453,6 +455,14 @@ async def on_invoke_activity( # pylint: disable=unused-argument await self.on_sign_in_invoke(turn_context) return self._create_invoke_response() + if turn_context.activity.name == "adaptiveCard/action": + invokeValue = self._get_adaptive_card_invoke_value( + turn_context.activity + ) + return self._create_invoke_response( + await self.on_adaptive_card_invoke(turn_context, invokeValue) + ) + if turn_context.activity.name == "healthcheck": return self._create_invoke_response( await self.on_healthcheck(turn_context) @@ -493,10 +503,70 @@ async def on_healthcheck(self, turn_context: TurnContext) -> HealthCheckResponse turn_context.turn_state.get(BotAdapter.BOT_CONNECTOR_CLIENT_KEY) ) + async def on_adaptive_card_invoke( + self, turn_context: TurnContext, invoke_value: AdaptiveCardInvokeValue + ) -> AdaptiveCardInvokeResponse: + """ + Invoked when the bot is sent an Adaptive Card Action Execute. + + When the on_invoke_activity method receives an Invoke with a Activity.name of `adaptiveCard/action`, it + calls this method. + + :param turn_context: A context object for this turn. + :type turn_context: :class:`botbuilder.core.TurnContext` + :param invoke_value: A string-typed object from the incoming activity's value. + :type invoke_value: :class:`botframework.schema.models.AdaptiveCardInvokeValue` + :return: The HealthCheckResponse object + """ + raise _InvokeResponseException(HTTPStatus.NOT_IMPLEMENTED) + @staticmethod def _create_invoke_response(body: object = None) -> InvokeResponse: return InvokeResponse(status=int(HTTPStatus.OK), body=serializer_helper(body)) + def _get_adaptive_card_invoke_value(self, activity: Activity): + if activity.value is None: + response = self._create_adaptive_card_invoke_error_response( + HTTPStatus.BAD_REQUEST, "BadRequest", "Missing value property" + ) + raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response) + + invokeValue = None + try: + invokeValue = AdaptiveCardInvokeValue(**activity.value) + except: + response = self._create_adaptive_card_invoke_error_response( + HTTPStatus.BAD_REQUEST, + "BadRequest", + "Value property is not properly formed", + ) + raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response) + + if invokeValue.action is None: + response = self._create_adaptive_card_invoke_error_response( + HTTPStatus.BAD_REQUEST, "BadRequest", "Missing action property" + ) + raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response) + + if invokeValue.action.get("type") != "Action.Execute": + response = self._create_adaptive_card_invoke_error_response( + HTTPStatus.BAD_REQUEST, + "NotSupported", + f"The action '{invokeValue.action.get('type')}' is not supported.", + ) + raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response) + + return invokeValue + + def _create_adaptive_card_invoke_error_response( + self, status_code: HTTPStatus, code: str, message: str + ): + return AdaptiveCardInvokeResponse( + status_code=status_code, + type="application/vnd.microsoft.error", + value=Exception(code, message), + ) + class _InvokeResponseException(Exception): def __init__(self, status_code: HTTPStatus, body: object = None): diff --git a/libraries/botbuilder-schema/botbuilder/schema/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/__init__.py index 734d6d91c..f2faba9b9 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/__init__.py +++ b/libraries/botbuilder-schema/botbuilder/schema/__init__.py @@ -3,6 +3,9 @@ from ._models_py3 import Activity from ._models_py3 import ActivityEventNames +from ._models_py3 import AdaptiveCardInvokeAction +from ._models_py3 import AdaptiveCardInvokeResponse +from ._models_py3 import AdaptiveCardInvokeValue from ._models_py3 import AnimationCard from ._models_py3 import Attachment from ._models_py3 import AttachmentData @@ -76,6 +79,9 @@ __all__ = [ "Activity", "ActivityEventNames", + "AdaptiveCardInvokeAction", + "AdaptiveCardInvokeResponse", + "AdaptiveCardInvokeValue", "AnimationCard", "Attachment", "AttachmentData", diff --git a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py index 472ff51ce..7bc8e77ce 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/_models_py3.py @@ -242,7 +242,7 @@ class Activity(Model): :type semantic_action: ~botframework.connector.models.SemanticAction :param caller_id: A string containing an IRI identifying the caller of a bot. This field is not intended to be transmitted over the wire, but is - instead populated by bots and clients based on cryptographically + instead populated by bots and clients based on cryptographically verifiable data that asserts the identity of the callers (e.g. tokens). :type caller_id: str """ @@ -2365,7 +2365,7 @@ class TokenResponse(Model): "2007-04-05T14:30Z") :type expiration: str :param channel_id: The channelId of the TokenResponse - :type channel_id: str + :type channel_id: str """ _attribute_map = { @@ -2486,3 +2486,92 @@ def __init__( self.aspect = aspect self.duration = duration self.value = value + + +class AdaptiveCardInvokeAction(Model): + """AdaptiveCardInvokeAction. + + Defines the structure that arrives in the Activity.Value.Action for Invoke activity with + name of 'adaptiveCard/action'. + + :param type: The Type of this Adaptive Card Invoke Action. + :type type: str + :param id: The Id of this Adaptive Card Invoke Action. + :type id: str + :param verb: The Verb of this Adaptive Card Invoke Action. + :type verb: str + :param data: The data of this Adaptive Card Invoke Action. + :type data: dict[str, object] + """ + + _attribute_map = { + "type": {"key": "type", "type": "str"}, + "id": {"key": "id", "type": "str"}, + "verb": {"key": "verb", "type": "str"}, + "data": {"key": "data", "type": "{object}"}, + } + + def __init__( + self, *, type: str = None, id: str = None, verb: str = None, data=None, **kwargs + ) -> None: + super(AdaptiveCardInvokeAction, self).__init__(**kwargs) + self.type = type + self.id = id + self.verb = verb + self.data = data + + +class AdaptiveCardInvokeResponse(Model): + """AdaptiveCardInvokeResponse. + + Defines the structure that is returned as the result of an Invoke activity with Name of 'adaptiveCard/action'. + + :param status_code: The Card Action Response StatusCode. + :type status_code: int + :param type: The type of this Card Action Response. + :type type: str + :param value: The JSON response object. + :type value: dict[str, object] + """ + + _attribute_map = { + "status_code": {"key": "statusCode", "type": "int"}, + "type": {"key": "type", "type": "str"}, + "value": {"key": "value", "type": "{object}"}, + } + + def __init__( + self, *, status_code: int = None, type: str = None, value=None, **kwargs + ) -> None: + super(AdaptiveCardInvokeResponse, self).__init__(**kwargs) + self.status_code = status_code + self.type = type + self.value = value + + +class AdaptiveCardInvokeValue(Model): + """AdaptiveCardInvokeResponse. + + Defines the structure that arrives in the Activity.Value for Invoke activity with Name of 'adaptiveCard/action'. + + :param action: The action of this adaptive card invoke action value. + :type action: :class:`botframework.schema.models.AdaptiveCardInvokeAction` + :param authentication: The TokenExchangeInvokeRequest for this adaptive card invoke action value. + :type authentication: :class:`botframework.schema.models.TokenExchangeInvokeRequest` + :param state: The 'state' or magic code for an OAuth flow. + :type state: str + """ + + _attribute_map = { + "action": {"key": "action", "type": "{object}"}, + "authentication": {"key": "authentication", "type": "{object}"}, + "state": {"key": "state", "type": "str"}, + } + + def __init__( + self, *, action=None, authentication=None, state: str = None, **kwargs + ) -> None: + super(AdaptiveCardInvokeValue, self).__init__(**kwargs) + self.action = action + self.authentication = authentication + self.state = state diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index 3dc6f397e..6f86092aa 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -2192,7 +2192,7 @@ class TabResponsePayload(Model): :param type: Gets or sets choice of action options when responding to the tab/fetch message. Possible values include: 'continue', 'auth' or 'silentAuth' :type type: str - :param value: Gets or sets the TabResponseCards when responding to + :param value: Gets or sets the TabResponseCards when responding to tab/fetch activity with type of 'continue'. :type value: TabResponseCards :param suggested_actions: Gets or sets the Suggested Actions for this card tab. From 76886979b23f767469fd93e53d3c86ee487f49d1 Mon Sep 17 00:00:00 2001 From: Michael Richardson Date: Tue, 8 Jun 2021 11:47:28 -0700 Subject: [PATCH 2/2] pylint fix --- .../botbuilder/core/activity_handler.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/activity_handler.py b/libraries/botbuilder-core/botbuilder/core/activity_handler.py index 3124589fa..7dcb37f4d 100644 --- a/libraries/botbuilder-core/botbuilder/core/activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/activity_handler.py @@ -456,11 +456,11 @@ async def on_invoke_activity( # pylint: disable=unused-argument return self._create_invoke_response() if turn_context.activity.name == "adaptiveCard/action": - invokeValue = self._get_adaptive_card_invoke_value( + invoke_value = self._get_adaptive_card_invoke_value( turn_context.activity ) return self._create_invoke_response( - await self.on_adaptive_card_invoke(turn_context, invokeValue) + await self.on_adaptive_card_invoke(turn_context, invoke_value) ) if turn_context.activity.name == "healthcheck": @@ -531,9 +531,9 @@ def _get_adaptive_card_invoke_value(self, activity: Activity): ) raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response) - invokeValue = None + invoke_value = None try: - invokeValue = AdaptiveCardInvokeValue(**activity.value) + invoke_value = AdaptiveCardInvokeValue(**activity.value) except: response = self._create_adaptive_card_invoke_error_response( HTTPStatus.BAD_REQUEST, @@ -542,21 +542,21 @@ def _get_adaptive_card_invoke_value(self, activity: Activity): ) raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response) - if invokeValue.action is None: + if invoke_value.action is None: response = self._create_adaptive_card_invoke_error_response( HTTPStatus.BAD_REQUEST, "BadRequest", "Missing action property" ) raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response) - if invokeValue.action.get("type") != "Action.Execute": + if invoke_value.action.get("type") != "Action.Execute": response = self._create_adaptive_card_invoke_error_response( HTTPStatus.BAD_REQUEST, "NotSupported", - f"The action '{invokeValue.action.get('type')}' is not supported.", + f"The action '{invoke_value.action.get('type')}' is not supported.", ) raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response) - return invokeValue + return invoke_value def _create_adaptive_card_invoke_error_response( self, status_code: HTTPStatus, code: str, message: str