pythonslackslack-apislack-bot

I'm creating a Slackbot in Python and want to repeat the message until a reaction is added to that message. What am I doing wrong?


as the title states, I'm writing a Slack Bot in Python and using NGROK to host it locally. I'm not super experienced with decorators, and I can get the bot posting messages in slack, however I can't seem to handle two events at once. For example, I want to handle a message and have the message keep repeating in slack until a thumbs up reaction is added to that message. The issue is I cannot figure out how to handle an event while another event is still running, please see the following code:

rom slack import WebClient
import os
import time
from pathlib import Path
from dotenv import load_dotenv
from flask import Flask
from slackeventsapi import SlackEventAdapter

env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)

app = Flask(__name__)
slack_event_adapter = SlackEventAdapter(
    os.environ['SIGNING_SECRET'],'/slack/events',app)

client = WebClient(token=os.environ['SLACK_TOKEN'])
BOT_ID = client.api_call("auth.test")['user_id']

state = {}

@slack_event_adapter.on('message')
def handle_message(event_data):
    message = event_data.get('event', {})
    channel_id = message.get('channel')
    user_id = message.get('user')
    text = message.get('text')
    messageid = message.get('ts')
    state[messageid] = {"channel_id": channel_id, "user_id": user_id, "text": text}

    if BOT_ID != user_id:
        if text[0:12] == ":red_circle:":
            time.sleep(5)
            client.chat_postMessage(channel=channel_id, text=text)
        if text[0:21] == ":large_yellow_circle:":
            client.chat_postMessage(channel=channel_id, text="it's a yellow question!")
        if text[0:14] == ":white_circle:":
            client.chat_postMessage(channel=channel_id, text="it's a white question!")


@slack_event_adapter.on('reaction_added')
def reaction_added(event_data):
    reaction = event_data.get('event',{})
    emoji = reaction.get('reaction')
    emoji_id = reaction.get('item',{}).get('ts')
    emoji_channel_id = reaction.get('item',{}).get('channel')
    client.chat_postMessage(channel=emoji_channel_id, text=emoji)


for message_id, message_data in state.items():
    channel_id = message_data["channel_id"]
    text = message_data["text"]
    client.chat_postMessage(channel=channel_id, text=text)
    print(message_id,message_data)

if __name__ == "__main__":
    app.run(debug=True)

I can handle individual events, but I cannot handle them while another is running. Please help! :)


Solution

  • Flask is a synchronous web framework.

    When it's running a view handler, it uses up a web worker thread. If you does something like time.sleep(...), that worker thread will still be occupied and unavailable to handle other requests until the sleep finishes.

    There are a couple options you can do here.

    You can use Bolt for Python, which is a Python Slack library that natively support asynchronous even processing. Instead of time.sleep(), you can do await asyncio.sleep(...), which returns the thread to the async loop, and allow the worker thread to process other events.

    If you already have an existing slack application and don't want to rewrite your entire codebase to Bolt, then you'll need to handle the event processing yourself. You can do this by doing your work in an ThreadLoopExecutor, or by building your own async event Queue mechanism, or use Celery. Or if your slack bot has very low volume, you can probably just add more web workers, and hope for the best that you don't run out of workers.