Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Event Matching Specific Reaction #284

Closed
2 of 4 tasks
Beartime234 opened this issue Apr 8, 2021 · 7 comments
Closed
2 of 4 tasks

Event Matching Specific Reaction #284

Beartime234 opened this issue Apr 8, 2021 · 7 comments
Labels
area:async question Further information is requested

Comments

@Beartime234
Copy link

Hi Bolt Python,

I am currently trying to create listeners for specific reactions and not just the event as a whole.

If someone reacts with the reaction white_check_mark, it will run a specific listener. If someone reacts with eyes it will run an alternative listener.

I have noticed that this doesn't occur and it will just match the event type and run the first listener found. I can inspect the body and then disperse from their but it would be nice if I was able to just match the type of reaction.

Something like this

@app.event({"type": "reaction_added", "reaction": "eyes"})
async def handle_add_eyes():
    # This one will be run when someone reacts with eyes

@app.event({"type": "reaction_added", "reaction": "white_check_mark"})
async def handle_add_white_check_mark():
    # This one will be run when someone reacts with white_check_mark

Category (place an x in each of the [ ])

  • slack_bolt.App and/or its core components
  • slack_bolt.async_app.AsyncApp and/or its core components
  • Adapters in slack_bolt.adapter
  • Others

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.

@seratch
Copy link
Member

seratch commented Apr 8, 2021

Hi @Beartime234, thanks for taking the time to write in!

The app.event listener registration currently supports only type and subtype in dict value constraints. This is not clearly mentioned in docstring and the document website. I will update them later.

For this use case, the recommended way is to utilize listener matchers as below. A listener matcher is a simplified wrapper of listener middleware. You can pass an array of Callable[..., Awaitable[bool]] in addition to the standard constraints for filter incoming requests from Slack. As with listener functions, you can take any arguments (e.g., body, client, event) in its method signature.

# listener matcher: async function that returns bool
async def is_eyes(event: dict) -> bool:
    return event["reaction"] == "eyes"

@app.event("reaction_added", matchers=[is_eyes])
async def handle_eyes_reactions_only(body, logger):
    logger.info(body)

# common utility example
def is_target_reaction(reaction: str):
    async def is_target(event) -> bool:
        return event["reaction"] == reaction
    return is_target

@app.event(
    event="reaction_added",
    matchers=[is_target_reaction("white_check_mark")],
)
async def handle_white_check_mark_reactions_only(body, logger):
    logger.info(body)

If Python supports async lambda syntax in the future, it can be much simpler like matchers=[async lambda event: event["reaction"] == "eyes"]. But it's not the thing as of today. The only way to use this along with AsyncApp for now is to set an array of async functions Callable[..., Awaitable[bool]] for additional conditions as above.

Also, if you would like to learn more about listener middleware, check the module document and the section in the website.

@seratch seratch added area:async question Further information is requested labels Apr 8, 2021
@Beartime234
Copy link
Author

HI @seratch,

Thanks for your reply that makes sense. After delving into the code I realized that subtype was the only thing supported which is why I raised it as a feature request, but I see now that matchers is better suited for what I am wanting.

I honestly didn't really click that matchers and middleware were the same thing!

@seratch
Copy link
Member

seratch commented Apr 8, 2021

This is not clearly mentioned in docstring and the document website. I will update them later.

The latest revision already has a relevant sentence in docstring.

If you pass a dict for this, you can have type, subtype in the constraint.

@Beartime234 I'm glad to hear that listener matchers sound reasonable for you! Let me close this issue.

@mvilanova
Copy link

mvilanova commented Mar 17, 2023

Hi,

Is there a way to tell bolt to execute another function or stop looking for a listener function when the reaction value in the event doesn't match what we want?

We're getting the following error when it does not match. We register the event here.

Unhandled request ({'type': 'event_callback', 'event': {'type': 'reaction_added'}})
---
[Suggestion] You can handle this type of event with the following listener function:

@app.event("reaction_added")
def handle_reaction_added_events(body, logger):
    logger.info(body)

@mvilanova
Copy link

mvilanova commented Mar 17, 2023

This seemed to do the trick: Netflix/dispatch#3124, but I wish there was a better way to handle it.

@seratch
Copy link
Member

seratch commented Mar 17, 2023

@mvilanova My recommendation is to have two listeners as below:

# listener matcher to check if the reaction should be handled
def target_reactions_only(event) -> bool:
    return event.get("reaction") in ["eyes", "white_check_mark"]

@app.event(event="reaction_added", matchers=[target_reactions_only])
def handle_target_reactions(body, logger):
    # do something meaningful here
    logger.info(body)

# Note that this listener must be defined after the above one
@app.event(event="reaction_added")
def just_ack_the_rest_of_reaciton_events():
    pass

@erwynesantos
Copy link

@mvilanova My recommendation is to have two listeners as below:

# listener matcher to check if the reaction should be handled
def target_reactions_only(event) -> bool:
    return event.get("reaction") in ["eyes", "white_check_mark"]

@app.event(event="reaction_added", matchers=[target_reactions_only])
def handle_target_reactions(body, logger):
    # do something meaningful here
    logger.info(body)

# Note that this listener must be defined after the above one
@app.event(event="reaction_added")
def just_ack_the_rest_of_reaciton_events():
    pass

This is useful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:async question Further information is requested
Projects
None yet
Development

No branches or pull requests

4 participants