From ff63e5b16037c656e47b98149ecff2f03e36b26a Mon Sep 17 00:00:00 2001 From: Omid Jafari Date: Mon, 29 Mar 2021 12:27:19 -0600 Subject: [PATCH 1/4] Fix for properly processing Slack payloads 1. Added SlackText class to represent a text object of Slack 2. Added SlackAction class to represent an action block object of Slack 3. Changed SlackPayload class to use SlackAction for actions 4. Changed SlackEvent class to use SlackAction for actions 5. The user ID of a received event from Slack is in event.user and not in event.user_id 6. Populated the timestamp property of even objects from event.event_ts by converting to UTC datetime 7. Typo and type mismatch fixes --- .../botbuilder/adapters/slack/slack_action.py | 15 +++++++++++++++ .../botbuilder/adapters/slack/slack_event.py | 8 ++++++-- .../botbuilder/adapters/slack/slack_helper.py | 14 ++++++++------ .../botbuilder/adapters/slack/slack_payload.py | 11 +++++++---- .../botbuilder/adapters/slack/slack_text.py | 7 +++++++ 5 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_action.py create mode 100644 libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_text.py diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_action.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_action.py new file mode 100644 index 000000000..29169aacf --- /dev/null +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_action.py @@ -0,0 +1,15 @@ +from botbuilder.adapters.slack.slack_text import SlackText +from typing import Optional + + +# Slack action block (https://api.slack.com/reference/block-kit/block-elements) +class SlackAction: + def __init__(self, **kwargs): + self.action_id: str = kwargs.get("action_id") + self.block_id: str = kwargs.get("block_id") + self.value: str = kwargs.get("value") + self.type: str = kwargs.get("type") + self.action_ts: str = kwargs.get("action_ts") + self.text: Optional[SlackText] = ( + None if "text" not in kwargs else SlackText(**(kwargs.get("text"))) + ) diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_event.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_event.py index 66b810ffb..e48774709 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_event.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_event.py @@ -1,7 +1,8 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import List +from typing import Optional, List +from botbuilder.adapters.slack.slack_action import SlackAction from botbuilder.adapters.slack.slack_message import SlackMessage @@ -25,10 +26,13 @@ def __init__(self, **kwargs): self.user = kwargs.get("user") self.user_id = kwargs.get("user_id") self.bot_id = kwargs.get("bot_id") - self.actions: List[str] = kwargs.get("actions") + self.actions: Optional[List[SlackAction]] = None self.item = kwargs.get("item") self.item_channel = kwargs.get("item_channel") self.files: [] = kwargs.get("files") self.message = ( None if "message" not in kwargs else SlackMessage(**kwargs.get("message")) ) + + if "actions" in kwargs: + self.actions = [SlackAction(**action) for action in kwargs.get("actions")] diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py index d71fd7852..229dc3987 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py @@ -3,6 +3,7 @@ import json import urllib.parse +from datetime import datetime, timezone from aiohttp.web_request import Request from aiohttp.web_response import Response @@ -124,9 +125,9 @@ def payload_to_activity(payload: SlackPayload) -> Activity: activity = Activity( channel_id="slack", - conversation=ConversationAccount(id=payload.channel.id, properties={}), + conversation=ConversationAccount(id=payload.channel.get("id"), properties={}), from_property=ChannelAccount( - id=payload.message.bot_id if payload.message.bot_id else payload.user.id + id=payload.message.bot_id if payload.message.bot_id else payload.user.get("id") ), recipient=ChannelAccount(), channel_data=payload, @@ -141,7 +142,7 @@ def payload_to_activity(payload: SlackPayload) -> Activity: payload.type == "block_actions" or payload.type == "interactive_message" ): activity.type = ActivityTypes.message - activity.text = payload.actions.value + activity.text = payload.actions[0].value return activity @@ -168,12 +169,13 @@ async def event_to_activity(event: SlackEvent, client: SlackClient) -> Activity: id=event.channel if event.channel else event.channel_id, properties={} ), from_property=ChannelAccount( - id=event.bot_id if event.bot_id else event.user_id + id=event.bot_id if event.bot_id else event.user_id if event.user_id else event.user ), recipient=ChannelAccount(id=None), channel_data=event, text=event.text, type=ActivityTypes.event, + timestamp=datetime.fromtimestamp(float(event.event_ts), tz=timezone.utc) ) if event.thread_ts: @@ -254,7 +256,7 @@ def query_string_to_dictionary(query: str) -> {}: for pair in pairs: key_value = pair.split("=") key = key_value[0] - value = urllib.parse.unquote(key_value[1]) + value = json.loads(urllib.parse.unquote(key_value[1])) values[key] = value @@ -287,7 +289,7 @@ def deserialize_body(content_type: str, request_body: str) -> SlackRequestBody: return SlackRequestBody(**request_dict) if "payload=" in request_body: - payload = SlackPayload(**request_dict) + payload = SlackPayload(**(request_dict.get("payload"))) return SlackRequestBody(payload=payload, token=payload.token) return SlackRequestBody(**request_dict) diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_payload.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_payload.py index 9b7438619..5aeb0e380 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_payload.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_payload.py @@ -2,19 +2,19 @@ # Licensed under the MIT License. from typing import Optional, List -from slack.web.classes.actions import Action +from botbuilder.adapters.slack.slack_action import SlackAction from botbuilder.adapters.slack.slack_message import SlackMessage class SlackPayload: def __init__(self, **kwargs): - self.type: List[str] = kwargs.get("type") + self.type: [str] = kwargs.get("type") self.token: str = kwargs.get("token") self.channel: str = kwargs.get("channel") self.thread_ts: str = kwargs.get("thread_ts") self.team: str = kwargs.get("team") self.user: str = kwargs.get("user") - self.actions: Optional[List[Action]] = None + self.actions: Optional[List[SlackAction]] = None self.trigger_id: str = kwargs.get("trigger_id") self.action_ts: str = kwargs.get("action_ts") self.submission: str = kwargs.get("submission") @@ -26,6 +26,9 @@ def __init__(self, **kwargs): message = kwargs.get("message") self.message = ( message - if isinstance(message) is SlackMessage + if isinstance(message, SlackMessage) else SlackMessage(**message) ) + + if "actions" in kwargs: + self.actions = [SlackAction(**action) for action in kwargs.get("actions")] diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_text.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_text.py new file mode 100644 index 000000000..0dc39e507 --- /dev/null +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_text.py @@ -0,0 +1,7 @@ +# Slack text object (https://api.slack.com/reference/block-kit/composition-objects#text) +class SlackText: + def __init__(self, **kwargs): + self.text: str = kwargs.get("text") + self.type: str = kwargs.get("type") + self.emoji: bool = kwargs.get("emoji") + self.verbatim: bool = kwargs.get("varbatim") From 1c1dc082193e6edb80f511f86751f82b89d19b9c Mon Sep 17 00:00:00 2001 From: Omid Date: Wed, 7 Apr 2021 11:56:04 -0600 Subject: [PATCH 2/4] Added support for simple HeroCards (with only text and buttons) in Slack --- .../botbuilder/adapters/slack/slack_helper.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py index 229dc3987..41740c251 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py @@ -55,6 +55,19 @@ def activity_to_slack(activity: Activity) -> SlackMessage: for att in activity.attachments: if att.name == "blocks": message.blocks = att.content + + elif att.content_type == "application/vnd.microsoft.card.hero": + message.blocks = [ + {"type": "section", + "text": {"type": "mrkdwn", "text": att.content.text} + }, + {"type": "actions", + "elements": [{"type": "button", + "text": {"type": "plain_text", "text": i.title}, + "value": i.value} for i in att.content.buttons] + } + ] + else: new_attachment = Attachment( author_name=att.name, thumb_url=att.thumbnail_url, text="", From a0e27852f44ccb858257871cb03f48703a3e18a6 Mon Sep 17 00:00:00 2001 From: Omid Date: Wed, 7 Apr 2021 14:10:57 -0600 Subject: [PATCH 3/4] Black formatting --- .../botbuilder/adapters/slack/slack_helper.py | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py index 41740c251..68dc9ab25 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_helper.py @@ -58,19 +58,28 @@ def activity_to_slack(activity: Activity) -> SlackMessage: elif att.content_type == "application/vnd.microsoft.card.hero": message.blocks = [ - {"type": "section", - "text": {"type": "mrkdwn", "text": att.content.text} - }, - {"type": "actions", - "elements": [{"type": "button", - "text": {"type": "plain_text", "text": i.title}, - "value": i.value} for i in att.content.buttons] - } + { + "type": "section", + "text": {"type": "mrkdwn", "text": att.content.text}, + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": {"type": "plain_text", "text": i.title}, + "value": i.value, + } + for i in att.content.buttons + ], + }, ] else: new_attachment = Attachment( - author_name=att.name, thumb_url=att.thumbnail_url, text="", + author_name=att.name, + thumb_url=att.thumbnail_url, + text="", ) attachments.append(new_attachment) @@ -138,9 +147,13 @@ def payload_to_activity(payload: SlackPayload) -> Activity: activity = Activity( channel_id="slack", - conversation=ConversationAccount(id=payload.channel.get("id"), properties={}), + conversation=ConversationAccount( + id=payload.channel.get("id"), properties={} + ), from_property=ChannelAccount( - id=payload.message.bot_id if payload.message.bot_id else payload.user.get("id") + id=payload.message.bot_id + if payload.message.bot_id + else payload.user.get("id") ), recipient=ChannelAccount(), channel_data=payload, @@ -182,13 +195,17 @@ async def event_to_activity(event: SlackEvent, client: SlackClient) -> Activity: id=event.channel if event.channel else event.channel_id, properties={} ), from_property=ChannelAccount( - id=event.bot_id if event.bot_id else event.user_id if event.user_id else event.user + id=event.bot_id + if event.bot_id + else event.user_id + if event.user_id + else event.user ), recipient=ChannelAccount(id=None), channel_data=event, text=event.text, type=ActivityTypes.event, - timestamp=datetime.fromtimestamp(float(event.event_ts), tz=timezone.utc) + timestamp=datetime.fromtimestamp(float(event.event_ts), tz=timezone.utc), ) if event.thread_ts: From c7b9c65e1d2f26ce14a126efae03f9aa83e29146 Mon Sep 17 00:00:00 2001 From: Omid Date: Wed, 7 Apr 2021 19:42:55 -0600 Subject: [PATCH 4/4] Pylint fix --- .../botbuilder/adapters/slack/slack_action.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_action.py b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_action.py index 29169aacf..e3482a560 100644 --- a/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_action.py +++ b/libraries/botbuilder-adapters-slack/botbuilder/adapters/slack/slack_action.py @@ -1,5 +1,5 @@ -from botbuilder.adapters.slack.slack_text import SlackText from typing import Optional +from botbuilder.adapters.slack.slack_text import SlackText # Slack action block (https://api.slack.com/reference/block-kit/block-elements)