Integrating with Slack and Block Kit
This mini-project is based on the following use-case:
Integration from Home Assistant sends a message to a slack channel with Yes/No options for the user to send a message back to Home Assistant to then be actioned.
This is based heavily on a post by SteveDinn
https://community.home-assistant.io/t/how-to-get-actionable-notifications-using-slack/145035
Sorry as a new user I cannot post clickable links.
however the modern way that Slack would like you to do this is with their Block Kit https://api.slack.com/block-kit
.
General Requirements - Webhooks
This will need to use externally facing webhooks to receive messages in so ideally Nabu Casa makes this area work easily.
General Requirements - Slack App
You will need a Slack App that is correctly set up. The information provided by SteveDinn works well.
Configuration Files
Obviously how users split (or not) their configuration file can make a big difference to how to copy a project. I tend to use the format:
automation slack: !include_dir_merge_list ./automations/slack
The world slack after automations is only a name to help describe the group and means you can have other automation lines within you configuration.yaml file. This method then allows for separate automation files to be in the ./automations/slack folder.
Likewise I will have separate script files in a folder called scripts with their and then
script: !include_dir_merge_named ./scripts
Initial Post to Slack
This is the easiest part of the project.
This needs a rest_command
slack_api:
url: https://slack.com/api/{{ api }}
content_type: 'application/json; charset=utf-8'
verify_ssl: true
method: 'post'
timeout: 20
headers:
Authorization: 'Bearer XXXInsert Token HereXXX'
payload: '{{ payload }}'
This is used a general script to post a payload to an api end point. So we can set up a script below that takes a channel, question, yes_value and no_value. The yes_value and no_value will be returned to us after the button is clicked in Slack.
notify_slack_block:
sequence:
- service: rest_command.slack_api
data_template:
api: 'chat.postMessage'
payload: >
{
"channel": "{{ channel }}",
"blocks":
[
{
"type": "section",
"text": {"type": "plain_text", "text": "{{ question }}"}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "Yes",
"emoji": true
},
"value": "{{ yes_value }}"
},
{
"type": "button",
"text": {
"type": "plain_text",
"text": "No",
"emoji": true
},
"value": "{{ no_value }}"
}
]
}
],
"text": "{{ question }}"
}
As a test I created a custom button-card to send data to the script:
type: custom:button-card
color_type: card
color: rgb(223, 255, 97)
icon: mdi:slack
tap_action:
action: call-service
service: script.notify_slack_block
service_data:
channel: ChannelId
question: Do you want to open door ?
yes_value: yes_open_door
no_value: no_open_door
This should appear in Slack:
Response from Slack
The documentation for Slack is pretty comprehensive and should be read at
https://api.slack.com/reference/interaction-payloads/block-actions
At a high level when a button is clicked the block_action payload is sent via a POST message to an endpoint (set up in the slack app) and this has information on what was clicked (we will get back the yes_value or no_value) and also a unique endpoint to acknowledge the button click.
Response Script
We will need a fairly basic response script to Slack. This does not need any authentication and the responseUrl is provided by Slack when they send back the button press.
This is used by Home Assistant to acknowledge that the button has been clicked and will actually replace the button with the text provided:
notify_slack_response:
sequence:
- service: rest_command.slack_api_response
data_template:
responseUrl: '{{ responseUrl }}'
payload: >
{
"text": "{{ text }}"
}
Home Assistant Webhooks
The setup for webhooks in Home Assistant can be a bit complicated. In a way you work backwards, create the trigger based on a webhook, this will then create the webhook, that can then be added to Nabu Casa and then finally added to Slack as the end point.
So create an automation:
alias: Slack Incoming
description: ''
trigger:
- platform: webhook
webhook_id: Slack_Incoming_Webhook
condition: []
action:
- service: script.notify_slack_response
data_template:
responseUrl: >
{%- set myPayload = trigger.data.payload | from_json -%}
{{ myPayload.response_url }}
text: >
{%- set myPayload = trigger.data.payload | from_json -%}
{%- set ts_message = myPayload.message.ts | float -%}
{%- set ts_response = myPayload.actions[0].action_ts | float -%}
{%- set ts_difference = ts_response - ts_message -%}
{% if ts_difference > 10 %}
Message received {{ myPayload.actions[0].value }} {{ ts_difference |round|int }} seconds too late to be processed
{% else %}
Message received {{ myPayload.actions[0].value }} {{ ts_difference |round|int }} seconds
{% endif %}
- service: script.process_slack_response
data_template:
response: >
{%- set myPayload = trigger.data.payload | from_json -%}
{%- set ts_message = myPayload.message.ts | float -%}
{%- set ts_response = myPayload.actions[0].action_ts | float -%}
{%- set ts_difference = ts_response - ts_message -%}
{% if ts_difference > 10 %}
Too late
{% else %}
{{ myPayload.actions[0].value }}
{% endif %}
mode: single
Now under Configuration and Home Assistant Cloud, the new webhook will appear. This needs externally enabling via Nabu Casa and then you will get a external url like:
https://hooks.nabu.casa/XXXXtokenXXX
This needs to go into Slack. Under ‘Interactivity & Shortcuts’ in the ‘Settings’ of the app, add this to the Request Url.
So the flow is this:
- Home Assistant send interactive message to Slack (via a button or other automation)
- User click button
- Slack does HTTP POST to our webhook
- This is sent to Home Assistant and picked up by Automation above
The automation above is broken into two separate actions, one to acknowledge the request back to Slack and then to carry out any supporting actions. This is shown below is a simpler format
alias: Slack Incoming
description: ''
trigger:
- platform: webhook
webhook_id: Slack_Incoming_Webhook
condition: []
action:
- service: script.notify_slack_response
data_template:
responseUrl: >
text: >
- service: script.process_slack_response
data_template:
response: >
mode: single
so for the response back to Slack we are sent URL to use in the payload (see example on the Slack Developers website). The response_url is provided and then passed back. Then there is a bit of calculation of the time difference between when the button was created to being pressed. This is set to 10 seconds to ensure that buttons are not clicked much late after the questions but can be changed to suit. Note that there is no other authentication so this might be something to consider, depending on the automations that are being used.
responseUrl: >
{%- set myPayload = trigger.data.payload | from_json -%}
{{ myPayload.response_url }}
text: >
{%- set myPayload = trigger.data.payload | from_json -%}
{%- set ts_message = myPayload.message.ts | float -%}
{%- set ts_response = myPayload.actions[0].action_ts | float -%}
{%- set ts_difference = ts_response - ts_message -%}
{% if ts_difference > 10 %}
Message received {{ myPayload.actions[0].value }} {{ ts_difference |round|int }} seconds too late to be processed
{% else %}
Message received {{ myPayload.actions[0].value }} {{ ts_difference |round|int }} seconds
{% endif %}
The second part of the automation extracts out the value of the button passed in (our yes_value or no_value) and this is passed to script.process_slack_response. This separates any local actions to a different script.
response: >
{%- set myPayload = trigger.data.payload | from_json -%} {%- set
ts_message = myPayload.message.ts | float -%} {%- set ts_response =
myPayload.actions[0].action_ts | float -%} {%- set ts_difference =
ts_response - ts_message -%} {% if ts_difference > 10 %}
Too late
{% else %}
{{ myPayload.actions[0].value }}
{% endif %}
Then finally the actual script for local actions. The choose option allows a if…then…else type structure. So here we are checking the response being passed in matches the action so no_open_door and yes_open_door.
For this example we are actually posting back to Slack to say that we are either opening the door or not (in reality you would have the actual local sequence to open to door) :
process_slack_response:
sequence:
- choose:
- conditions: "{{ response == 'no_open_door' }}"
sequence:
- service: rest_command.slack_api
data_template:
api: 'chat.postMessage'
payload: >
{
"channel": "C1U0VDLPM",
"text": "No to opening the door"
}
- choose:
- conditions: "{{ response == 'yes_open_door' }}"
sequence:
- service: rest_command.slack_api
data_template:
api: 'chat.postMessage'
payload: >
{
"channel": "C1U0VDLPM",
"text": "Yes to opening the door"
}