diff --git a/.gitignore b/.gitignore
index cf72631..62453c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ venv/
dist/
docs/_build/
*.egg-info/
+.DS_Store
diff --git a/docs/images/cards_sample.png b/docs/images/cards_sample.png
new file mode 100644
index 0000000..66eb05b
Binary files /dev/null and b/docs/images/cards_sample.png differ
diff --git a/docs/index.rst b/docs/index.rst
index e10232e..517a02a 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -33,6 +33,7 @@ The User Guide
user/intro
user/quickstart
user/api
+ user/cards
The Development Community
diff --git a/docs/user/api.rst b/docs/user/api.rst
index 44c45f1..ff4a7e8 100644
--- a/docs/user/api.rst
+++ b/docs/user/api.rst
@@ -281,5 +281,93 @@ Warnings
:show-inheritance:
:members:
+.. _CardsAPI:
+
+Cards and Buttons
+=================
+
+.. autoclass:: webexteamssdk.cards.card.AdaptiveCard()
+
+Components
+----------
+
+.. autoclass:: webexteamssdk.cards.components.Image()
+ :members:
+
+ .. automethod:: __init__
+
+.. autoclass:: webexteamssdk.cards.components.TextBlock()
+ :members:
+
+ .. automethod:: __init__
+
+.. autoclass:: webexteamssdk.cards.components.Column()
+
+.. autoclass:: webexteamssdk.cards.components.Fact()
+
+.. autoclass:: webexteamssdk.cards.components.Choice()
+
+Options
+-------
+
+.. autoclass:: webexteamssdk.cards.options.VerticalContentAlignment()
+
+.. autoclass:: webexteamssdk.cards.options.Colors()
+
+.. autoclass:: webexteamssdk.cards.options.HorizontalAlignment()
+
+.. autoclass:: webexteamssdk.cards.options.FontSize()
+
+.. autoclass:: webexteamssdk.cards.options.FontWeight()
+
+.. autoclass:: webexteamssdk.cards.options.BlockElementHeight()
+
+.. autoclass:: webexteamssdk.cards.options.Spacing()
+
+.. autoclass:: webexteamssdk.cards.options.ImageSize()
+
+.. autoclass:: webexteamssdk.cards.options.ImageStyle()
+
+.. autoclass:: webexteamssdk.cards.options.ContainerStyle()
+
+.. autoclass:: webexteamssdk.cards.options.TextInputStyle()
+
+.. autoclass:: webexteamssdk.cards.options.ChoiceInputStyle()
+
+
+Container
+---------
+
+.. autoclass:: webexteamssdk.cards.container.Container()
+
+.. autoclass:: webexteamssdk.cards.container.ColumnSet()
+
+.. autoclass:: webexteamssdk.cards.container.FactSet()
+
+.. autoclass:: webexteamssdk.cards.container.ImageSet()
+
+Inputs
+------
+
+.. autoclass:: webexteamssdk.cards.inputs.Text()
+
+.. autoclass:: webexteamssdk.cards.inputs.Number()
+
+.. autoclass:: webexteamssdk.cards.inputs.Date()
+
+.. autoclass:: webexteamssdk.cards.inputs.Time()
+
+.. autoclass:: webexteamssdk.cards.inputs.Toggle()
+
+.. autoclass:: webexteamssdk.cards.inputs.Choices()
+
+Actions
+-------
+
+.. autoclass:: webexteamssdk.cards.actions.OpenUrl
+
+.. autoclass:: webexteamssdk.cards.actions.Submit
+
+.. autoclass:: webexteamssdk.cards.actions.ShowCard
*Copyright (c) 2016-2019 Cisco and/or its affiliates.*
diff --git a/docs/user/cards.rst b/docs/user/cards.rst
new file mode 100644
index 0000000..9c7a369
--- /dev/null
+++ b/docs/user/cards.rst
@@ -0,0 +1,39 @@
+.. _Cards:
+
+=================
+Cards and Buttons
+=================
+
+Webex Teams supports `AdaptiveCards `_ to allow
+new levels of interactivity for bots and integrations. You can read more about
+how cards and buttons work `in the official guide `_.
+
+In this guide I want to cover the abstraction build into the webexteamssdk that
+lets you author adaptive cards in pure python without having to touch the
+underlying json of a adaptive card.
+
+Lets dive into a simple example that sends a card to a room
+
+.. code-block:: python
+
+ from webexteamssdk import WebexTeamsAPI
+ from webexteamssdk.cards.card import AdaptiveCard
+ from webexteamssdk.cards.inputs import Text, Number
+ from webexteamssdk.cards.components import TextBlock
+ from webexteamssdk.cards.actions import Submit
+
+ greeting = TextBlock("Hey hello there! I am a adaptive card")
+ first_name = Text('first_name', placeholder="First Name")
+ age = Number('age', placeholder="Age")
+
+ submit = Submit(title="Send me!")
+
+ card = AdaptiveCard(body=[greeting, first_name, age], actions=[submit])
+
+ api = WebexTeamsAPI()
+ api.messages.create(text="fallback", roomId="...", cards=card)
+
+The message we send with this code then looks like this in our Webex Teams
+client:
+
+.. image:: ../images/cards_sample.png
diff --git a/webexteamssdk/api/messages.py b/webexteamssdk/api/messages.py
index 37c6a7a..49eeac2 100644
--- a/webexteamssdk/api/messages.py
+++ b/webexteamssdk/api/messages.py
@@ -39,8 +39,9 @@
from ..restsession import RestSession
from ..utils import (
check_type, dict_from_items_with_values, is_local_file, is_web_url,
- open_local_file,
+ open_local_file, make_card_attachment
)
+from ..cards.card import AdaptiveCard
API_ENDPOINT = 'messages'
@@ -208,6 +209,17 @@ def create(self, roomId=None, toPersonId=None, toPersonEmail=None,
attachments=attachments,
)
+ # Add cards
+ if cards is not None:
+ cards_list = []
+
+ if isinstance(cards, list):
+ cards_list = [make_card_attachment(c) for c in cards]
+ else:
+ cards_list = [make_card_attachment(cards)]
+
+ post_data['attachments'] = cards_list
+
# API request
if not files or is_web_url(files[0]):
# Standard JSON post
diff --git a/webexteamssdk/cards/__init__.py b/webexteamssdk/cards/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/webexteamssdk/cards/abstract_components.py b/webexteamssdk/cards/abstract_components.py
new file mode 100644
index 0000000..90906c5
--- /dev/null
+++ b/webexteamssdk/cards/abstract_components.py
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+"""Webex Teams Access-Tokens API wrapper.
+
+Copyright (c) 2016-2019 Cisco and/or its affiliates.
+
+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.
+"""
+
+import json
+
+class Serializable:
+ """Parent class for all components of adaptive cards.
+
+ Each component should inherit from this class and then specify, from
+ its properties, which fall into the following two categories:
+
+ * Simple properties are text properties like "type" or "id"
+ * Serializable properties are properties that can themselfes be serilized.
+ This includes lists of items (i.e. the 'body' field of the adaptive card) or
+ single objects that also inherit from Serializable
+ """
+ def __init__(self, serializable_properties, simple_properties):
+ """Creates a serializable object.
+
+ See class docstring for an explanation what the different types of
+ properties are.
+
+ Args:
+ serializable_properties(list): List of all serializable properties
+ simple_properties(list): List of all simple properties.
+ """
+ self.serializable_properties = serializable_properties
+ self.simple_properties = simple_properties
+
+ def to_json(self, pretty=False):
+ """Create json from a serializable component
+
+ This function is used to render the json from a component. While all
+ components do support this operation it is mainly used on the
+ AdaptiveCard to generate the json for the attachment.
+
+ Args:
+ pretty(boolean): If true, the returned json will be sorted by keys
+ and indented with 4 spaces to make it more human-readable
+
+ Returns:
+ A Json representation of this component
+ """
+ ret = None
+ if pretty:
+ ret = json.dumps(self.to_dict(), indent=4, sort_keys=True)
+ else:
+ ret = json.dumps(self.to_dict())
+
+ return ret
+
+ def to_dict(self):
+ """Export a dictionary representation of this card/component by
+ parsing all simple and serializable properties.
+
+ A simple_component is a single-text property of the exported card
+ (i.e. {'version': "1.2"}) while a serializable property is another
+ subcomponent that also implements a to_dict() method.
+
+ The to_dict() method is used to recursively create a dict representation
+ of the adaptive card. This dictionary representation can then be
+ converted into json for usage with the API.
+
+ Returns:
+ dict: Dictionary representation of this component.
+ """
+ export = {}
+
+ # Export simple properties (i.e. properties that are only single text)
+ for sp in self.simple_properties:
+ o = getattr(self, sp, None)
+
+ if o is not None:
+ export[sp] = str(o)
+
+ # Export all complex properties by calling its respective serialization
+ for cp in self.serializable_properties:
+ o = getattr(self, cp, None)
+
+ if o is not None:
+ # Check if it is a list or a single component
+ l = []
+ if isinstance(o, list):
+ for i in o:
+ l.append(i.to_dict())
+ else:
+ l.append(o.to_dict())
+ export[cp] = l
+
+ return export
diff --git a/webexteamssdk/cards/actions.py b/webexteamssdk/cards/actions.py
new file mode 100644
index 0000000..7ef4e35
--- /dev/null
+++ b/webexteamssdk/cards/actions.py
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+"""Webex Teams Access-Tokens API wrapper.
+
+Copyright (c) 2016-2019 Cisco and/or its affiliates.
+
+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.
+"""
+
+from .abstract_components import Serializable
+
+class OpenUrl(Serializable):
+ def __init__(self, url, title=None,
+ iconURL=None):
+ self.type = "Action.OpenUrl"
+ self.title = title
+ self.iconURL = iconURL
+
+ super().__init__(serializable_properties=[],
+ simple_properties=['type', 'title', 'iconURL'])
+
+class Submit(Serializable):
+ def __init__(self, data=None,
+ title=None,
+ iconURL=None,
+ ):
+ self.type = "Action.Submit"
+ self.data = data
+ self.title = title
+ self.iconURL = iconURL
+
+ super().__init__(serializable_properties=['data'],
+ simple_properties=['title', 'iconURL', 'type'])
+
+class ShowCard(Serializable):
+ def __init__(self, card=None,
+ title=None,
+ iconURL=None):
+ self.type = "Action.ShowCard"
+ self.card = card
+ self.title = title
+ self.iconURL = iconURL
+
+ super().__init__(serializable_properties=['card'],
+ simple_properties=['title', 'type', 'iconURL'])
diff --git a/webexteamssdk/cards/card.py b/webexteamssdk/cards/card.py
new file mode 100644
index 0000000..512e42b
--- /dev/null
+++ b/webexteamssdk/cards/card.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+"""Webex Teams Access-Tokens API wrapper.
+
+Copyright (c) 2016-2019 Cisco and/or its affiliates.
+
+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.
+"""
+
+from .abstract_components import Serializable
+from .actions import OpenUrl, ShowCard, Submit
+
+from .utils import check_type
+
+class AdaptiveCard(Serializable):
+ """AdaptiveCard class that represents a adaptive card python object.
+
+ Note:
+ Webex Teams currently supports version 1.1 of adaptive cards and thus
+ only features from that release are supported in this abstraction.
+ """
+ def __init__(self, body=None,
+ actions=None,
+ selectAction=None,
+ fallbackText=None,
+ lang=None):
+ """Creates a new adaptive card object.
+
+ Args:
+ body(list): The list of components and containers making up the
+ body of this adaptive card.
+ actions(list): The list of actions this adaptive card should contain
+ selectAction(action): The action that should be invoked when this
+ adaptive card is selected. Can be any action other then
+ 'ShowCard'
+ fallbackText(str): The text that should be displayed on clients that
+ can't render adaptive cards
+ lang(str): The 2-letter ISO-639-1 language used in the card. This is
+ used for localization of date/time functions
+
+ """
+ # Check types
+ check_type(actions, (ShowCard, Submit, OpenUrl), True, True)
+ check_type(selectAction, (Submit, OpenUrl), False, True)
+ check_type(fallbackText, str, False, True)
+ check_type(lang, str, False, True)
+
+ # Set properties
+ self.type = "AdaptiveCard"
+ self.version = "1.1" # This is the version currently supported in Teams
+ self.body = body
+ self.actions = actions
+ self.selectAction = selectAction
+ self.fallbackText = fallbackText
+ self.lang = lang
+
+ super().__init__(serializable_properties=[
+ 'body', 'actions', 'selectAction'
+ ],
+ simple_properties=[
+ 'version', 'fallbackText', 'lang', 'type'
+ ])
+ def to_dict(self):
+ # We need to overwrite the to_dict method to add the $schema
+ # property that can't be specified the normal way due to the
+ # $ in the beginning
+ ret = super().to_dict()
+ ret["$schema"] = "http://adaptivecards.io/schemas/adaptive-card.json"
+
+ return ret
diff --git a/webexteamssdk/cards/components.py b/webexteamssdk/cards/components.py
new file mode 100644
index 0000000..669ae84
--- /dev/null
+++ b/webexteamssdk/cards/components.py
@@ -0,0 +1,239 @@
+# -*- coding: utf-8 -*-
+"""Webex Teams Access-Tokens API wrapper.
+
+Copyright (c) 2016-2019 Cisco and/or its affiliates.
+
+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.
+"""
+
+from .abstract_components import Serializable
+from .utils import check_type
+from .options import BlockElementHeight, Spacing, ImageSize, ImageStyle
+from .actions import OpenUrl, ShowCard, Submit
+
+class MediaSource(Serializable):
+ """Defines the source of a Media element."""
+ def __init__(self,
+ mimeType,
+ url):
+ """Create a new MediaSource component.
+
+ Args:
+ mimeType(str): Mime type of the associated media(i.e. 'video/mp4')
+ url(str): URL of the media.
+ """
+ # Check types
+ check_type(mimeType, str, False, False)
+ check_type(url, str, False, False)
+
+ self.mimeType = mimeType
+ self.url = url
+
+ super().__init__(serializable_properties=[],
+ simple_properties=['mimeType', 'url'])
+
+class Media(Serializable):
+ """Displays a media player for audio or video content"""
+ def __init__(self,
+ sources,
+ poster=None,
+ altText=None,
+ height=None,
+ separator=None,
+ spacing=None,
+ id=None):
+ """Create a new Media component.
+
+ Args:
+ sources(list): A list of media sources to be played
+ poster(str): The url to the image that is displayed before playing
+ altText(str): Alternative text for this component
+ height(BlockElementHeight): The height of this block element
+ separator(bool): Draw a separating line when set to true
+ spacing(Spacing): Specify the spacing of this component
+ id(str): The id of this component
+ """
+ # Check types
+ check_type(sources, MediaSource, True, False)
+ check_type(poster, str, False, True)
+ check_type(altText, str, False, True)
+ check_type(height, BlockElementHeight, False, True)
+ check_type(separator, bool, False, True)
+ check_type(spacing, Spacing, False, True)
+ check_type(id, str, False, True)
+
+ self.type = "Media"
+ self.sources = sources #Needs to be a list of media sources
+ self.poster = poster
+ self.altText = altText
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+ self.id = id
+
+ super().__init__(serializable_properties=['sources'],
+ simple_properties=[
+ 'type', 'poster', 'altText', 'height',
+ 'separator', 'spacing', 'id'
+ ])
+class Image(Serializable):
+ """Displays a image object"""
+
+ def __init__(self,
+ url,
+ altText=None,
+ backgroundColor=None,
+ height=None,
+ horizontalAlignment=None,
+ selectAction=None,
+ size=None,
+ style=None,
+ width=None,
+ separator=None,
+ spacing=None,
+ id=None):
+ """Create a new image component
+
+ Args:
+ url(str): The URL to the image
+ altText(str): Alternative text describing the image
+ backgroundColor(str): Background color for transparent images.
+ height(str, BlockElementHeight): Height of the image either as a
+ pixel value(i.e. '50px') or as an instance of BlockElementHeight
+ horizontalAlignmnet(HorizontalAlignment): Controls how the component
+ is positioned within its parent.
+ selectAction(OpenUrl, Submit): Option that is caried out when the
+ card is selected.
+ size(ImageSize): Controls the approximate size of the image.
+ style(ImageStyle): The display style of this image.
+ width(str): Width of the image as a pixel value (i.e. '50px')
+ separator(bool): Draw a separating line when set to true
+ spacing(Spacing): Specify the spacing of this component
+ id(str): The id of this component
+
+ """
+ check_type(url, str, False, False)
+ check_type(altText, str, False, True)
+ check_type(backgroundColor, str, False, True)
+ check_type(height, (str, BlockElementHeight), False, True)
+ check_type(horizontalAlignment, horizontalAlignment, False, True)
+ check_type(selectAction, (OpenUrl, Submit), False, True)
+ check_type(size, ImageSize, False, True)
+ check_type(style, ImageStyle, False, True)
+ check_type(width, str, False, True)
+ check_type(separator, bool, False, True)
+ check_type(spacing, Spacing, False, True)
+ check_type(id, str, False, True)
+
+ self.type = "Image"
+ self.url = url
+ self.altText = altText
+ self.backgroundColor = backgroundColor
+ self.height = height
+ self.horizontalAlignment = horizontalAlignment
+ self.selectAction = selectAction
+ self.size = size
+ self.style = style
+ self.width = width
+ self.separator = separator
+ self.spacing = spacing
+ self.id = id
+
+ super().__init__(serializable_properties=[],
+ simple_properties=[
+ 'type', 'url', 'altText', 'backgroundColor',
+ 'height', 'horizontalAlignment', 'selectAction',
+ 'size', 'style', 'width', 'separator', 'spacing',
+ 'id'
+ ])
+class TextBlock(Serializable):
+ def __init__(self,
+ text,
+ color=None,
+ horizontalAlignment=None,
+ isSubtle=None,
+ maxLines=None,
+ size=None,
+ weight=None,
+ wrap=None,
+ separator=None,
+ spacing=None,
+ id=None):
+
+
+ #ToDo(mneiding): Type check
+ self.type = "TextBlock"
+ self.text = text
+ self.color = color
+ self.horizontalAlignment = horizontalAlignment
+ self.isSubtle = isSubtle
+ self.maxLines = maxLines
+ self.size = size
+ self.weight = weight
+ self.wrap = wrap
+ self.separator = separator
+ self.spacing = spacing
+ self.id = id
+
+ super().__init__(serializable_properties=[],
+ simple_properties=[
+ 'type', 'text', 'color', 'horizontalAlignment',
+ 'isSubtle', 'maxLines', 'size', 'weight', 'wrap',
+ 'spacing', 'id', 'separator'
+ ])
+class Column(Serializable):
+ def __init__(self, items=None,
+ separator=None,
+ spacing=None,
+ selectAction=None,
+ style=None,
+ verticalContentAlignment=None,
+ width=None,
+ id=None):
+ self.type = "Column"
+ self.items = items
+ self.separator = separator
+ self.spacing = spacing
+ self.selectAction = selectAction
+ self.style = style
+ self.verticalContentAlignment = verticalContentAlignment
+ self.width = width
+ self.id = id
+
+ super().__init__(serializable_properties=['items'],
+ simple_properties=[
+ 'type', 'separator', 'spacing', 'selectAction',
+ 'style', 'verticalContentAlignment', 'width', 'id'
+ ])
+
+class Fact(Serializable):
+ def __init__(self, title, value):
+ self.title = title
+ self.value = value
+
+ super().__init__(serializable_properties=[],
+ simple_properties=['title', 'value'])
+
+class Choice(Serializable):
+ def __init__(self, title, value):
+ self.title = title
+ self.value = value
+
+ super().__init__(serializable_properties=[],
+ simple_properties=['title', 'value'])
diff --git a/webexteamssdk/cards/container.py b/webexteamssdk/cards/container.py
new file mode 100644
index 0000000..b0f7cc0
--- /dev/null
+++ b/webexteamssdk/cards/container.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+"""Webex Teams Access-Tokens API wrapper.
+
+Copyright (c) 2016-2019 Cisco and/or its affiliates.
+
+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.
+"""
+
+from .abstract_components import Serializable
+
+class Container(Serializable):
+ def __init__(self, items, selectAction=None,
+ style=None,
+ verticalContentAlignment=None,
+ height=None,
+ separator=None,
+ spacing=None,
+ id=None):
+ self.type = "Container"
+ self.items = items
+ self.selectAction = selectAction
+ self.style = style
+ self.verticalContentAlignment = verticalContentAlignment
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+ self.id = id
+
+ super().__init__(serializable_properties=['items'],
+ simple_properties=[
+ 'selectAction', 'style', 'verticalContentAlignment',
+ 'height', 'separator', 'spacing', 'id', 'type'
+ ])
+
+class ColumnSet(Serializable):
+ def __init__(self, columns=None,
+ selectAction=None,
+ height=None,
+ separator=None,
+ spacing=None,
+ id=None):
+ self.type = "ColumnSet"
+ self.columns = columns
+ self.selectAction = selectAction
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+ self.id = id
+
+ super().__init__(serializable_properties=['columns'],
+ simple_properties=[
+ 'selectAction', 'height', 'separator', 'spacing',
+ 'id', 'type'
+ ])
+
+class FactSet(Serializable):
+ def __init__(self, facts, height=None,
+ separator=None,
+ spacing=None,
+ id=None):
+ self.type = "FactSet"
+ self.facts = facts
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+ self.id = id
+
+ super().__init__(serializable_properties=['facts'],
+ simple_properties=[
+ 'type', 'height', 'separator', 'id', 'spacing'
+ ])
+
+class ImageSet(Serializable):
+ def __init__(self, images, imageSize=None,
+ height=None,
+ separator=None,
+ spacing=None,
+ id=None):
+ self.type = "ImageSet"
+ self.images = images
+ self.imageSize = imageSize
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+ self.id = id
+
+ super().__init__(serializable_properties=['images'],
+ simple_properties=[
+ 'imageSize', 'height', 'separator', 'spacing', 'id',
+ 'type'
+ ])
diff --git a/webexteamssdk/cards/inputs.py b/webexteamssdk/cards/inputs.py
new file mode 100644
index 0000000..f8060e3
--- /dev/null
+++ b/webexteamssdk/cards/inputs.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+"""Webex Teams Access-Tokens API wrapper.
+
+Copyright (c) 2016-2019 Cisco and/or its affiliates.
+
+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.
+"""
+
+from .abstract_components import Serializable
+
+class Text(Serializable):
+ def __init__(self, id, isMultiline=None,
+ maxLength=None,
+ placeholder=None,
+ style=None,
+ value=None,
+ height=None,
+ separator=None,
+ spacing=None):
+
+ self.type = "Input.Text"
+ self.id = id
+ self.isMultiline = isMultiline
+ self.maxLength = maxLength
+ self.placeholder = placeholder
+ self.style = style
+ self.value = value
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+
+ super().__init__(serializable_properties=[],
+ simple_properties=[
+ 'id', 'type', 'isMultiline', 'maxLength',
+ 'placeholder', 'style', 'value', 'height',
+ 'separator', 'spacing'
+ ])
+
+class Number(Serializable):
+ def __init__(self, id, max=None,
+ min=None,
+ placeholder=None,
+ value=None,
+ height=None,
+ separator=None,
+ spacing=None):
+ self.type = "Input.Number"
+ self.id = id
+ self.max = max
+ self.min = min
+ self.placeholder = placeholder
+ self.value = value
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+
+ super().__init__(serializable_properties=[],
+ simple_properties=[
+ 'type', 'id', 'max', 'min', 'placeholder', 'value',
+ 'height', 'separator', 'spacing'
+ ])
+
+class Date(Serializable):
+ def __init__(self, id, max=None,
+ min=None,
+ placeholder=None,
+ value=None,
+ height=None,
+ separator=None,
+ spacing=None):
+ self.type = "Input.Date"
+ self.id = id
+ self.max = max
+ self.min = min
+ self.placeholder = placeholder
+ self.value = value
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+
+ super().__init__(serializable_properties=[],
+ simple_properties=[
+ 'type', 'id', 'max', 'min', 'placeholder', 'value',
+ 'height', 'separator', 'spacing'
+ ])
+class Time(Serializable):
+ def __init__(self, id, max=None,
+ min=None,
+ placeholder=None,
+ value=None,
+ height=None,
+ separator=None,
+ spacing=None):
+ self.id = id
+ self.type = "Input.Time"
+ self.max = max
+ self.min = min
+ self.placeholder = placeholder
+ self.value = value
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+
+ super().__init__(serializable_properties=[],
+ simple_properties=[
+ 'id', 'type', 'max', 'min', 'placeholder', 'value',
+ 'height', 'separator', 'spacing'
+ ])
+
+class Toggle(Serializable):
+ def __init__(self, title, id, value=None,
+ valueOff=None,
+ valueOn=None,
+ height=None,
+ separator=None,
+ spacing=None):
+ self.title = title
+ self.type = "Input.Toggle"
+ self.id = id
+ self.value = value
+ self.valueOff = valueOff
+ self.valueOn = valueOn
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+
+ super().__init__(serializable_properties=[],
+ simple_properties=[
+ 'type', 'id', 'title', 'value', 'valueOff',
+ 'valueOn', 'height', 'separator', 'spacing'
+ ])
+
+class Choices(Serializable):
+ def __init__(self, choices, id, isMultiSelect=None,
+ style=None,
+ value=None,
+ height=None,
+ separator=None,
+ spacing=None):
+ self.choices = choices
+ self.type = "Input.ChoiceSet"
+ self.id = id
+ self.isMultiSelect = isMultiSelect
+ self.style = style
+ self.value = value
+ self.height = height
+ self.separator = separator
+ self.spacing = spacing
+
+ super().__init__(serializable_properties=['choices'],
+ simple_properties=[
+ 'id', 'type', 'isMultiSelect', 'style', 'value',
+ 'height', 'separator', 'spacing'
+ ])
diff --git a/webexteamssdk/cards/options.py b/webexteamssdk/cards/options.py
new file mode 100644
index 0000000..be6af3a
--- /dev/null
+++ b/webexteamssdk/cards/options.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+"""Webex Teams Access-Tokens API wrapper.
+
+Copyright (c) 2016-2019 Cisco and/or its affiliates.
+
+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.
+"""
+
+from enum import Enum
+
+class AbstractOption(Enum):
+ def to_value(self):
+ return str(self.name).lower()
+
+ def __str__(self):
+ return self.to_value()
+
+ def __repr__(self):
+ return self.to_value()
+
+class VerticalContentAlignment:
+ TOP = 1
+ CENTER = 2
+ BOTTOM = 3
+
+class Colors(AbstractOption):
+ DEFAULT = 1
+ DARK = 2
+ LIGHT = 3
+ ACCENT = 4
+ GOOD = 5
+ WARNING = 6
+ ATTENTION = 7
+
+class HorizontalAlignment(AbstractOption):
+ LEFT = 1
+ CENTER = 2
+ RIGHT = 3
+
+class FontSize(AbstractOption):
+ DEFAULT = 1
+ SMALL = 2
+ MEDIUM = 3
+ LARGE = 4
+ EXTRALARGE = 5
+
+class FontWeight(AbstractOption):
+ DEFAULT = 1
+ LIGHTER = 2
+ BOLDER = 3
+
+class BlockElementHeight(AbstractOption):
+ AUTO = 1
+ STRETCH = 2
+
+class Spacing(AbstractOption):
+ DEFAULT = 1
+ NONE = 2
+ SMALL = 3
+ MEDIUM = 4
+ LARGE = 5
+ EXTRALARGE = 6
+ PADDING = 7
+
+class ImageSize(AbstractOption):
+ AUTO = 1
+ STRETCH = 2
+ SMALL = 3
+ MEDIUM = 4
+ LARGE = 5
+
+class ImageStyle(AbstractOption):
+ DEFAULT = 1
+ PERSON = 2
+
+class ContainerStyle(AbstractOption):
+ DEFAULT = 1
+ EMPHASIS = 2
+
+class TextInputStyle(AbstractOption):
+ TEXT = 1
+ TEL = 2
+ URL = 3
+ EMAIL = 4
+
+class ChoiceInputStyle(AbstractOption):
+ COMPACT = 1
+ EXPANDED = 2
diff --git a/webexteamssdk/cards/utils.py b/webexteamssdk/cards/utils.py
new file mode 100644
index 0000000..4e8cfab
--- /dev/null
+++ b/webexteamssdk/cards/utils.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+"""Webex Teams Access-Tokens API wrapper.
+
+Copyright (c) 2016-2019 Cisco and/or its affiliates.
+
+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.
+"""
+
+def set_if_not_none(property_name, property, export):
+ if property is not None:
+ export[property_name] = property.to_dict()
+
+def check_type(obj, acceptable_types, is_list=False, may_be_none=False):
+ """Object is an instance of one of the acceptable types or None.
+
+ Args:
+ obj: The object to be inspected.
+ acceptable_types: A type or tuple of acceptable types.
+ is_list(bool): Whether or not we expect a list of objects of acceptable
+ type
+ may_be_none(bool): Whether or not the object may be None.
+
+ Raises:
+ TypeError: If the object is None and may_be_none=False, or if the
+ object is not an instance of one of the acceptable types.
+
+ """
+ error_message = None
+ if not isinstance(acceptable_types, tuple):
+ acceptable_types = (acceptable_types,)
+
+ if may_be_none and obj is None:
+ pass
+ elif is_list:
+ # Check that all objects in that list are of the required type
+ if not isinstance(obj, list):
+ error_message = (
+ "We were expecting to receive a list of one of the following "
+ "types: {types}{none}; but instead we received {o} which is a "
+ "{o_type}.".format(
+ types=", ".join([repr(t.__name__) for t in acceptable_types]),
+ none="or 'None'" if may_be_none else "",
+ o=obj,
+ o_type=repr(type(obj).__name__)
+ )
+ )
+ else:
+ for o in obj:
+ if not isinstance(o, acceptable_types):
+ error_message = (
+ "We were expecting to receive an instance of one of the following "
+ "types: {types}{none}; but instead we received {o} which is a "
+ "{o_type}.".format(
+ types=", ".join([repr(t.__name__) for t in acceptable_types]),
+ none="or 'None'" if may_be_none else "",
+ o=o,
+ o_type=repr(type(o).__name__)
+ )
+ )
+ elif isinstance(obj, acceptable_types):
+ pass
+ else:
+ # Object is something else.
+ error_message = (
+ "We were expecting to receive an instance of one of the following "
+ "types: {types}{none}; but instead we received {o} which is a "
+ "{o_type}.".format(
+ types=", ".join([repr(t.__name__) for t in acceptable_types]),
+ none="or 'None'" if may_be_none else "",
+ o=obj,
+ o_type=repr(type(obj).__name__)
+ )
+ )
+ if error_message is not None:
+ raise TypeError(error_message)
diff --git a/webexteamssdk/utils.py b/webexteamssdk/utils.py
index 04773ee..809ebb4 100644
--- a/webexteamssdk/utils.py
+++ b/webexteamssdk/utils.py
@@ -257,6 +257,22 @@ def json_dict(json_data):
"received: {!r}".format(json_data)
)
+def make_card_attachment(card):
+ """Given a card, makes a card attachment by attaching the correct
+ content type and content.
+
+ Args:
+ card(AdaptiveCard): Adaptive Card object that should be attached
+
+ Returns:
+ A Python dictionary containing the card attachment dictionary
+ """
+ ret = {
+ "contentType": "application/vnd.microsoft.card.adaptive",
+ "content": card.to_dict()
+ }
+
+ return ret
class ZuluTimeZone(tzinfo):
"""Zulu Time Zone."""