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 diff --git a/docs/docs/connectors/slack.mdx b/docs/docs/connectors/slack.mdx index 2766e16f7a89..c85b5491c874 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 (it might be smart to handle if a user has already encountered a chatbot and not react to this event every single + 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 65bff1ae893e..031e8f277715 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -235,6 +235,28 @@ def _is_user_message(slack_event: Dict[Text, Any]) -> bool: ) and slack_event.get("event", {}).get("text") 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: + """ + 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" ) @staticmethod @@ -297,6 +319,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 +332,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 +361,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, @@ -519,7 +551,9 @@ 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']}" @@ -564,6 +598,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( diff --git a/tests/core/channels/test_slack.py b/tests/core/channels/test_slack.py index f24d1ddb5429..20a3d82d9626 100644 --- a/tests/core/channels/test_slack.py +++ b/tests/core/channels/test_slack.py @@ -356,6 +356,20 @@ def test_is_slack_message_false(): 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