From 819eff65da22c27b280bee5d69bab2baeb3ae446 Mon Sep 17 00:00:00 2001 From: Pavel Kostelnik Date: Wed, 25 Nov 2020 12:33:54 +0100 Subject: [PATCH 1/8] implementing slack init message functionality --- docs/docs/connectors/slack.mdx | 7 +++++++ rasa/core/channels/slack.py | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/docs/connectors/slack.mdx b/docs/docs/connectors/slack.mdx index 2766e16f7a89..d549fcca4586 100644 --- a/docs/docs/connectors/slack.mdx +++ b/docs/docs/connectors/slack.mdx @@ -136,6 +136,13 @@ your bot and tell you about new messages. If you are running locally, you can sending of messages, you'll be prompted to **reinstall your app** which you will need to do. Otherwise, Slack will confirm your change with a **Success!**) +## Optional: Initial message + If you want to have the bot greet message appear every time when user opens its chat (e.g. for the first time) you want + to send some subscribe to `app_home_opened` event. This will automatically redirect to expected `init` intent in your + domain. + + + Your bot is now ready to go and will receive webhook notifications about new messages. ## Optional: Interactive Components After you've completed [Sending Messages](./slack.mdx#sending-messages) and diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 2c7b04e02093..088cb4c76967 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -232,11 +232,17 @@ def _is_user_message(slack_event: Dict[Text, Any]) -> bool: and ( slack_event.get("event", {}).get("type") == "message" or slack_event.get("event", {}).get("type") == "app_mention" + or SlackInput._is_init_message(slack_event) ) and slack_event.get("event", {}).get("text") and not slack_event.get("event", {}).get("bot_id") ) - + @staticmethod + def _is_init_message(slack_event: Dict[Text, Any]) -> bool: + return ( + slack_event.get("event") is not None + and slack_event.get("event", {}).get("type") == "app_home_opened" + ) @staticmethod def _sanitize_user_message( text: Text, uids_to_remove: Optional[List[Text]] @@ -516,7 +522,11 @@ async def webhook(request: Request) -> HTTPResponse: "a user message. Skipping message." ) return response.text("Bot message delivered.") - + if self._is_init_message(output): + logger.debug( + "Init message recieved - sending to /init intent" + ) + user_message = "/init" if not self._is_supported_channel(output, metadata): logger.warning( f"Received message on unsupported channel: {metadata['out_channel']}" From 46a9e3160ded298981a88a912059f7f5eb4c0ad4 Mon Sep 17 00:00:00 2001 From: Pavel Kostelnik Date: Wed, 25 Nov 2020 13:14:09 +0100 Subject: [PATCH 2/8] small update to docs + verified implementation --- docs/docs/connectors/slack.mdx | 3 ++- rasa/core/channels/slack.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/docs/connectors/slack.mdx b/docs/docs/connectors/slack.mdx index d549fcca4586..4224651094ef 100644 --- a/docs/docs/connectors/slack.mdx +++ b/docs/docs/connectors/slack.mdx @@ -139,7 +139,8 @@ your bot and tell you about new messages. If you are running locally, you can ## Optional: Initial message If you want to have the bot greet message appear every time when user opens its chat (e.g. for the first time) you want to send some subscribe to `app_home_opened` event. This will automatically redirect to expected `init` intent in your - domain. + domain (it might be smart to handle if a user has already encountered a chatbot and not react to this event every single + time). diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 088cb4c76967..253dd89fd3cb 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -228,14 +228,14 @@ def _is_direct_message(slack_event: Dict) -> bool: @staticmethod def _is_user_message(slack_event: Dict[Text, Any]) -> bool: return ( - slack_event.get("event") is not None + (slack_event.get("event") is not None and ( slack_event.get("event", {}).get("type") == "message" or slack_event.get("event", {}).get("type") == "app_mention" - or SlackInput._is_init_message(slack_event) ) and slack_event.get("event", {}).get("text") - and not slack_event.get("event", {}).get("bot_id") + and not slack_event.get("event", {}).get("bot_id")) + or SlackInput._is_init_message(slack_event) ) @staticmethod def _is_init_message(slack_event: Dict[Text, Any]) -> bool: @@ -570,6 +570,7 @@ def _is_supported_channel(self, slack_event: Dict, metadata: Dict) -> bool: self._is_direct_message(slack_event) or self._is_app_mention(slack_event) or metadata["out_channel"] == self.slack_channel + or self._is_init_message(slack_event) ) def get_output_channel( From 1b2b39c51826104e7b402047f3d5c2481325e28e Mon Sep 17 00:00:00 2001 From: Pavel Kostelnik Date: Wed, 25 Nov 2020 13:35:30 +0100 Subject: [PATCH 3/8] change log added --- changelog/7366.feature.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/7366.feature.md diff --git a/changelog/7366.feature.md b/changelog/7366.feature.md new file mode 100644 index 000000000000..70be845e4041 --- /dev/null +++ b/changelog/7366.feature.md @@ -0,0 +1,2 @@ +Allow to reaction of rasa chatbot to slack opening / switching window of conversation. This allows the chatbot to provide +a "welcome message" to the user. \ No newline at end of file From 0d643fd3dc39f3ab449ffee1adb5b8ad792f04d9 Mon Sep 17 00:00:00 2001 From: Pavel Kostelnik Date: Wed, 25 Nov 2020 13:49:46 +0100 Subject: [PATCH 4/8] remove whitespace --- docs/docs/connectors/slack.mdx | 1 - rasa/core/channels/slack.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/connectors/slack.mdx b/docs/docs/connectors/slack.mdx index 4224651094ef..c85b5491c874 100644 --- a/docs/docs/connectors/slack.mdx +++ b/docs/docs/connectors/slack.mdx @@ -143,7 +143,6 @@ your bot and tell you about new messages. If you are running locally, you can time). - Your bot is now ready to go and will receive webhook notifications about new messages. ## Optional: Interactive Components After you've completed [Sending Messages](./slack.mdx#sending-messages) and diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 253dd89fd3cb..e5d090b73edb 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -570,6 +570,7 @@ def _is_supported_channel(self, slack_event: Dict, metadata: Dict) -> bool: self._is_direct_message(slack_event) or self._is_app_mention(slack_event) or metadata["out_channel"] == self.slack_channel + or self._is_init_message(slack_event) ) From d5456bd7ec1f2f161cfc2aab208838449b107c1d Mon Sep 17 00:00:00 2001 From: Pavel Kostelnik Date: Wed, 25 Nov 2020 13:49:57 +0100 Subject: [PATCH 5/8] formatted black --- rasa/core/channels/slack.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index e5d090b73edb..5a723cea60e0 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -228,21 +228,22 @@ def _is_direct_message(slack_event: Dict) -> bool: @staticmethod def _is_user_message(slack_event: Dict[Text, Any]) -> bool: return ( - (slack_event.get("event") is not None + slack_event.get("event") is not None and ( slack_event.get("event", {}).get("type") == "message" or slack_event.get("event", {}).get("type") == "app_mention" ) and slack_event.get("event", {}).get("text") - and not slack_event.get("event", {}).get("bot_id")) - or SlackInput._is_init_message(slack_event) - ) + and not slack_event.get("event", {}).get("bot_id") + ) or SlackInput._is_init_message(slack_event) + @staticmethod def _is_init_message(slack_event: Dict[Text, Any]) -> bool: return ( slack_event.get("event") is not None and slack_event.get("event", {}).get("type") == "app_home_opened" ) + @staticmethod def _sanitize_user_message( text: Text, uids_to_remove: Optional[List[Text]] @@ -523,9 +524,7 @@ async def webhook(request: Request) -> HTTPResponse: ) return response.text("Bot message delivered.") if self._is_init_message(output): - logger.debug( - "Init message recieved - sending to /init intent" - ) + logger.debug("Init message recieved - sending to /init intent") user_message = "/init" if not self._is_supported_channel(output, metadata): logger.warning( @@ -570,7 +569,6 @@ def _is_supported_channel(self, slack_event: Dict, metadata: Dict) -> bool: self._is_direct_message(slack_event) or self._is_app_mention(slack_event) or metadata["out_channel"] == self.slack_channel - or self._is_init_message(slack_event) ) From 41dec584447492b6569bcdcbbd65e3c5edef38c1 Mon Sep 17 00:00:00 2001 From: Pavel Kostelnik Date: Sat, 28 Nov 2020 09:54:43 +0100 Subject: [PATCH 6/8] adding docs + test --- rasa/core/channels/slack.py | 15 +++++++++++++++ tests/core/channels/test_slack.py | 13 +++++++++++++ 2 files changed, 28 insertions(+) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 5a723cea60e0..4631b2b01e2b 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -239,6 +239,21 @@ def _is_user_message(slack_event: Dict[Text, Any]) -> bool: @staticmethod def _is_init_message(slack_event: Dict[Text, Any]) -> bool: + """ + This method determines if a message sent is an initial message + from Slack when the user opens up the window with chatbot conversation. + + This can happen multiple times throughout the conversation and the user + needs to handle this on the chatbot side only actually invoking + init intent e.g. if a certain time (like 24 hours) have passed since + last time welcome message (=init intent) display. + + By default the init intent is not invoked as this message is normally + not invoked by Slack. For this method to work it needs to be subscribed + to in event subscription. + Args: + slack_event: event from slack saying that user has started chatbot interaction + """ return ( slack_event.get("event") is not None and slack_event.get("event", {}).get("type") == "app_home_opened" diff --git a/tests/core/channels/test_slack.py b/tests/core/channels/test_slack.py index f24d1ddb5429..0e449ebffc30 100644 --- a/tests/core/channels/test_slack.py +++ b/tests/core/channels/test_slack.py @@ -355,6 +355,19 @@ def test_is_slack_message_false(): slack_message = json.loads(payload) assert SlackInput._is_user_message(slack_message) is False +def test_is_slack_message_true_init(): + + event = { + "type": "app_home_opened", + "user": "UFDM2MG77", + "channel": "A01G7854KUU", + "tab": "messages", + "event_ts": "1606315650.276505" + } + payload = json.dumps({"event": event}) + slack_message = json.loads(payload) + assert SlackInput._is_user_message(slack_message) is True + def test_slackbot_init_one_parameter(): from rasa.core.channels.slack import SlackBot From 61a7a8f2dde75d453d641060939c5f6fc74ba1bc Mon Sep 17 00:00:00 2001 From: Pavel Kostelnik Date: Sat, 28 Nov 2020 10:27:35 +0100 Subject: [PATCH 7/8] reformatted --- tests/core/channels/test_slack.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/core/channels/test_slack.py b/tests/core/channels/test_slack.py index 0e449ebffc30..20a3d82d9626 100644 --- a/tests/core/channels/test_slack.py +++ b/tests/core/channels/test_slack.py @@ -355,6 +355,7 @@ def test_is_slack_message_false(): slack_message = json.loads(payload) assert SlackInput._is_user_message(slack_message) is False + def test_is_slack_message_true_init(): event = { @@ -362,11 +363,11 @@ def test_is_slack_message_true_init(): "user": "UFDM2MG77", "channel": "A01G7854KUU", "tab": "messages", - "event_ts": "1606315650.276505" + "event_ts": "1606315650.276505", } payload = json.dumps({"event": event}) slack_message = json.loads(payload) - assert SlackInput._is_user_message(slack_message) is True + assert SlackInput._is_user_message(slack_message) is True def test_slackbot_init_one_parameter(): From 4ace5e5cacb987ed28fd37aa8407ff8b1a46f587 Mon Sep 17 00:00:00 2001 From: Pavel Kostelnik Date: Sat, 10 Apr 2021 10:21:29 +0200 Subject: [PATCH 8/8] adding support for slack multi_select --- rasa/core/channels/slack.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 65bff1ae893e..50a2fb1720b2 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -297,6 +297,7 @@ def _is_interactive_message(payload: Dict) -> bool: "channels_select", "overflow", "datepicker", + "multi_static_select", ] if payload.get("actions"): action_type = payload["actions"][0].get("type") @@ -309,6 +310,13 @@ def _is_interactive_message(payload: Dict) -> bool: ) return False + @staticmethod + def _get_text_from_multi_select(action: Dict) -> Optional[Text]: + values = [] + for val in action.get("selected_options"): + values.append(val.get("value")) + return ",".join(values) + @staticmethod def _get_interactive_response(action: Dict) -> Optional[Text]: """Parse the payload for the response value.""" @@ -331,6 +339,8 @@ def _get_interactive_response(action: Dict) -> Optional[Text]: return action.get("selected_option", {}).get("value") elif action["type"] == "datepicker": return action.get("selected_date") + elif action["type"] == "multi_static_select": + return SlackInput._get_text_from_multi_select(action) async def process_message( self,