Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add adaptive card invoke action handler #1697

Merged
merged 4 commits into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 70 additions & 0 deletions libraries/botbuilder-core/botbuilder/core/activity_handler.py
Expand Up @@ -6,6 +6,8 @@
from botbuilder.schema import (
Activity,
ActivityTypes,
AdaptiveCardInvokeResponse,
AdaptiveCardInvokeValue,
ChannelAccount,
MessageReaction,
SignInConstants,
Expand Down Expand Up @@ -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":
invoke_value = self._get_adaptive_card_invoke_value(
turn_context.activity
)
return self._create_invoke_response(
await self.on_adaptive_card_invoke(turn_context, invoke_value)
)

if turn_context.activity.name == "healthcheck":
return self._create_invoke_response(
await self.on_healthcheck(turn_context)
Expand Down Expand Up @@ -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)

invoke_value = None
try:
invoke_value = 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 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 invoke_value.action.get("type") != "Action.Execute":
response = self._create_adaptive_card_invoke_error_response(
HTTPStatus.BAD_REQUEST,
"NotSupported",
f"The action '{invoke_value.action.get('type')}' is not supported.",
)
raise _InvokeResponseException(HTTPStatus.BAD_REQUEST, response)

return invoke_value

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):
Expand Down
6 changes: 6 additions & 0 deletions libraries/botbuilder-schema/botbuilder/schema/__init__.py
Expand Up @@ -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
Expand Down Expand Up @@ -76,6 +79,9 @@
__all__ = [
"Activity",
"ActivityEventNames",
"AdaptiveCardInvokeAction",
"AdaptiveCardInvokeResponse",
"AdaptiveCardInvokeValue",
"AnimationCard",
"Attachment",
"AttachmentData",
Expand Down
93 changes: 91 additions & 2 deletions libraries/botbuilder-schema/botbuilder/schema/_models_py3.py
Expand Up @@ -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
"""
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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
Expand Up @@ -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.
Expand Down