err-backend-slackv3 icon indicating copy to clipboard operation
err-backend-slackv3 copied to clipboard

Support Slack Message Buttons

Open ebpitts opened this issue 9 years ago • 13 comments

Slack recently introduced the concept of message buttons.

Example Message Button

Blog post: https://slackhq.com/get-more-done-with-message-buttons-5fa5b283a59 Developer documentation: https://api.slack.com/docs/message-buttons

Here's their summary of the interaction model:

  1. Your application produces a message containing buttons. Maybe the message originated in response to an invoked slash command, or in response to a bot user's trigger phrase. Or maybe your app posted the message manually using an incoming webhook or chat.postMessage. In any case, your Slack app produced a message with buttons, offering users a chance to interact with it.
  2. Users encounter your message and, inspired by its call to action, clicks one of your buttons. This triggers an invocation of your application's associated Action URL.
  3. Slack sends a request to your Action URL, sending all the context needed to identify the originating message, the user that executed the action, and the specific values you've associated with the button. This request also contains a response_url you can use to continue interacting with the user or channel.
  4. Your application responds to the action. If you respond directly to the incoming invocation request, your provided message will replace the existing message. It's also possible to respond with an ephemeral message, visible only to the invoking user. Or you can just respond with HTTP 200 OK and wait to continue the interaction until later using the response_url provided as part of the action.
  5. Meanwhile: Your application does whatever it does as a result of the intended action to be taken: enqueue a process, save a database row, or continue interacting with users through additional message buttons.
  6. By using the response_url, your app can continue interacting with users up to 5 times within 30 minutes of the action invocation. Use this to continue through a workflow until it is complete.
  7. Messages can evolve. By using chat.update and your created message's message_ts field, you can modify the original interactive message (including all of its attachments) to add or remove buttons based on user interactions.

I opened this issue so there would be a place for conversation about if and how errbot could support message buttons.

ebpitts avatar Jun 22 '16 19:06 ebpitts

We are considering this. But we will have to emulate that for the other backends that don't support it probably like what we did for the cards with some markdown.

gbin avatar Jul 11 '16 20:07 gbin

Any idea when could this feature be implemented for Slack ?

ab9-er avatar Jun 20 '17 15:06 ab9-er

Nothing concrete is currently being worked on (to the best of my knowledge) so don't get your hopes up too much.

zoni avatar Jun 20 '17 16:06 zoni

Any news or progress on this?

i-nikolov avatar Nov 08 '17 09:11 i-nikolov

@i-nikolov I am using Slack's API to make use of this feature instead of relying on any functions from errbot.

ab9-er avatar Nov 08 '17 09:11 ab9-er

@ab9-er Will you be willing to share some code examples with us?

eran-totango avatar Nov 11 '17 22:11 eran-totango

@eran-totango check this out https://gist.github.com/ab9-er/e47695429262f8274d1b0a9b19114db0

(I have initiated a variable with a mutable value which is not the best of the practices but it was a dirty quick example) 😈

The function should also return something, which it doesn't in the basic example, best would be to return the result of the API call which should be a 200 OK along with the ts value of the message and other details as per Slack's API documentation.

ab9-er avatar Nov 13 '17 09:11 ab9-er

@ab9-er cool, I'd really love to integrate that to flows: they basically match this with button answers being fed to the context and unlocking steps. I don't have much time for myself at the moment but I can guide anyone willing to do that.

gbin-argo avatar Nov 13 '17 12:11 gbin-argo

@gbin-argo I could help chip in but sporadically.

ab9-er avatar Nov 15 '17 17:11 ab9-er

I'm a bit keen on this too, but I also see authentication being "fun" (not). @ab9-er how did you handle that?

lingfish avatar Aug 25 '18 08:08 lingfish

@ab9-er @gbin-argo Is there any update regarding this feature? if not then I would like to take up the task of implementing this feature :smile:

Buffer0x7cd avatar Apr 02 '19 14:04 Buffer0x7cd

You guys may want to take a quick glance at https://api.slack.com/changelog/2018-12-a-bric-a-brac-of-broadcasts-built-with-blocks before starting any work. I don't know if it impacts buttons, but it might.

Blocks also introduce more interactive options for app developers. They provide visually cleaner ways to incorporate buttons or menus, as well as new interactive elements like date pickers or overflow menus.

sheluchin avatar Apr 03 '19 01:04 sheluchin

I guess this is more meant to be provided by Slack Events errbotio/errbot#1451 . The way I implemented it in my custom Slack Event Backend was by parsing the button event (interactive message) as a text message with extra fields.

    def _interactive_message_event_handler(self, event):
        """
        Event handler for the 'interactive' event, used in attachments / buttons.
        """
        msg = Message(
            frm=SlackPerson(event['user']['id'], event['channel']['id'], bot=self),
            to=self.bot_identifier,
            extras={
                'actions': [{x['name']: x} for x in event['actions']],
                'url': event['response_url'],
                'trigger_id': event.get('trigger_id', None),
                'callback_id': event.get('callback_id', None),
                'slack_event': event
            }
        )

        flow, _ = self.flow_executor.check_inflight_flow_triggered(msg.extras['callback_id'], msg.frm)
        if flow:
            log.debug("Reattach context from flow %s to the message", flow._root.name)
            msg.ctx = flow.ctx

        self.callback_message(msg)

Then on callback_message of the plugin:

    def callback_message(self, msg):
        callback = msg.extras.get('callback_id', None)
        if (
            callback and callback in dir(self) and
            callable(getattr(self, callback))
        ):
            self.log.info(f'Calling function {callback}')
            return getattr(self, callback)(msg)

Finally on the command:

    @botcmd
    def approval_request(self, msg, args=None):
        """ Check response of a request. """
        if msg.extras.get('callback_id', None) == 'approval_request':
            answer = msg.extras.get('actions')[0]['question']['value']
            answer = True if answer == 'Approve' else False

duhow avatar Dec 10 '20 19:12 duhow