slackapi / bolt-python

A framework to build Slack apps using Python
https://slack.dev/bolt-python/
MIT License
1.02k stars 236 forks source link

is it possible to forward event to another URL by using slack_bolt ? #1090

Closed liucgbj closed 4 weeks ago

liucgbj commented 1 month ago

Description:

this is regarding slack event request URL which defined in https://api.slack.com/apps//event-subscriptions.

Scenario:

we would like to integrate AI with slack bot, those AI products will provide a request URL to receive and handle event message, then generate content and send it back to slack user. meanwhile we would like to use the same slack bot to build home page or get user ID from event message and send notification to them, we have already developed those functions (based on python slack_bolt and ngrok), but this definitely will need another request URL.

Question:

The question here are,

Q1 : if only one URL can be added in slack event subscriptions, is it possible to forward slack event to another URL by using python slack_bolt ? Q2: is it possible to add two request URLs in slack event subscriptions ?

please let me know, thanks.

Reproducible in:

Try to add two URLs in slack api event subscriptions.

The slack_bolt version

slack-bolt==1.18.1 slack_sdk==3.27.1

Python runtime version

Python 3.10.12

OS info

Ubuntu 22.04.4 LTS

Steps to reproduce:

  1. try to add two URLs in slack api event subscriptions.

Expected result:

don't know if we can add two URLs in slack api event subscriptions or if we can forward event to another URL by using python slack_bolt ?

Actual result:

only one URL can be added in slack api event subscriptions

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 commented 1 month ago

Hi @liucgbj, thanks for asking the question. AI integration using bolt-python is one of the hottest use cases recently!

Q2: Is it possible to add two request URLs in Slack event subscriptions?

No, it isn't. The Request URL for Event Subscriptions must be a single URL.

Q1: If only one URL can be added in Slack event subscriptions, is it possible to forward Slack events to another URL using Python Slack_Bolt?

If you send a different request to a different URL within your @app.event listener, it may work like that. But it's not a built-in bolt-python feature, so you need to do it manually, like sending an HTTP request using requests library or similar.

Also, if you're fine with having different logic within the same project, utilizing lazy listeners could be a good option: https://slack.dev/bolt-python/concepts#lazy-listeners. This feature was originally implemented for FaaS use cases, but you can use it to run a few operations asynchronously (using either threads or the asyncio event loop if your app is not on a FaaS platform).

I hope this helps you out.

liucgbj commented 1 month ago

Hi @seratch , thank you for your information, from my point of view, sending an HTTP request using requests library is a better choice for me.

Goal

Slack API --> slack bot (based on python slack_bolt + ngrok) --> requests library post --> AI request URL

[Description]: slack api send event message to my slack bot (ngrok request URL), add a new function in app.py to use requests library to forward same event message to AI request URL.

What i did

Step 1: config ngrok request URL in Slack event subscriptions

Step 2: this document mentioned that slack will send an HTTP POST request to the Request URL, below is the entire body of the request, my understanding is i should forward it to AI request URL.

image

Step 3: code snippet

import json
import requests
from slack_bolt import App

# initialize your app
app = App(token='xxxx', signing_secret='xxxx')

def forward_event(body):
    url = "<AI_REQUEST_URL>"
    header = { "Content-Type": "application/json" }
    resp = requests.post(url, data=json.dumps(body), headers=header)
    print('Info: below is requests message: ') 
    print(resp.text)

@app.event("message")
def get_event(client, event, logger, body):
    print("Info: received event, ID is " + body['event_id'])
    forward_event(body)

if __name__ == '__main__':
    app.start(port=3000)

Step 4: launch app.py

⚡️ Bolt app is running! (development server)

Step 5: send a message to my slack bot I sent a 'hello' message to my slack bot, and i can see the event id from terminal console, looks like slack have successfully sent event message to ngrok request URL.

Step6: verify HTTP POST request At same time, I received following error message from terminal console, would you please let me know if anything is incorrect ? thanks.

Info: below is requests message: 
{"error":"user verification failed"}

btw: enjoy your weekend, feel free to reply to me on Monday.

seratch commented 4 weeks ago

@liucgbj This code should work for you, but please note it only works if your forwarded host shares the same app settings (especially the signing secret). If your forwarded host is a different Slack app with a different signing secret, it never works. Also, this approach only works for Events API. It never works for any other patterns such as app.command, app.action, and app.view listeners (there is no workaround for this limitation).

import logging
import requests
from slack_bolt import App, BoltRequest

logging.basicConfig(level=logging.DEBUG)

app = App()

def forward_event(request: BoltRequest, logger: logging.Logger):
    # If this post is a different app, which has a different signing secret,
    # this requset fails because x-slack-signature never matches
    # if you share the same app settings among a few server instances for a reason,
    # this approach can work only for Events API patterns
    # (this means it does not work property for slash commands and interactivity)
    forward_url = "https://your-forwared-request-url/slack/events"
    headers = dict(map(lambda kv: (kv[0], kv[1][0]), request.headers.items()))
    forwared_response = requests.post(
        forward_url,
        data=request.raw_body,
        headers=headers,
    )
    logger.info(forwared_response)

@app.event("message")
def get_event(request: BoltRequest, logger: logging.Logger):
    forward_event(request, logger)

if __name__ == "__main__":
    app.start(port=3000)

In general, I don't recommend mimicking a Slack event request this way. Instead, you can send the payload to a custom endpoint with your custom authentication for security (e.g., having an authorization header, etc.). What I meant by sending a request using the requests library is something like this, not enabling app.event listeners to work as-is.

Since we generally don't recommend this approach and don't have any further information on this topic, I will close this issue now. I hope you find a reasonable solution for your use case!

liucgbj commented 3 weeks ago

@seratch , sorry for late reply, just back from public holiday, again, thank you for your information.

Yes, I'm using the same slack App, the forwarded host shares the same app settings (using the same bot token and signing secret).

I tried your solution, below is the result. if I use ngrok request URL as forward_url, everything is good, i can see the event has successfully been forwarded and handled.

127.0.0.1 - - [11/Jun/2024 14:18:50] "POST /slack/events HTTP/1.1" 200 -
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): xxxx-xx-xx-xxx-xx.ngrok-free.app:443
DEBUG:urllib3.connectionpool:https://xxxx-xx-xx-xxx-xx.ngrok-free.app:443 "POST /slack/events HTTP/1.1" 200 0
INFO:get_active_user.py:get_event:<Response [200]>

the bad news is if i use AI request URL as forward_url, it will return a 400 error message, the forwared_response.text told me that the URL is Invalid.

127.0.0.1 - - [11/Jun/2024 14:10:16] "POST /slack/events HTTP/1.1" 200 -
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): <host>:<port>
DEBUG:urllib3.connectionpool:https://<host>:<port> "POST /public/slack HTTP/1.1" 400 310
INFO:get_active_user.py:get_event:<Response [400]>
INFO:get_active_user.py:get_event:<HTML><HEAD>
<TITLE>Invalid URL</TITLE>
</HEAD><BODY>
<H1>Invalid URL</H1>
The requested URL "&#91;no&#32;URL&#93;", is invalid.<p>
Reference&#32;&#35;9&#46;2f017c68&#46;1718115344&#46;11a606f9
</BODY></HTML>

anyway, thank you for your assistance.