Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions python/slack/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BROWSER_USE_API_KEY=bu-your-api-key-here

SLACK_BOT_TOKEN=xoxb-your-bot-token-here
SLACK_SIGNING_SECRET=your-signing-secret-here
2 changes: 2 additions & 0 deletions python/slack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv
__pycache__
1 change: 1 addition & 0 deletions python/slack/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.9.18
21 changes: 21 additions & 0 deletions python/slack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Slack Example

Dev setup:

```bash
uv sync
```

Slack requires a HTTPS endpoint for its events. For local development, you have to use a service like `ngrok` to get your uvicorn server a public IP and a HTTPS endpoint.

The following Slack scopes are required for this application:

- `app_mentions:read` - View messages that directly mention @Browser-use in conversations that the app is in
- `channels:read` - View basic information about public channels in a workspace
- `chat:write` - Send messages as @Browser-use

The required event subscription is: `app_mentions`

Create a `.env` file in your project root by copying the `.env.example`.

For more detailed information, refer to the [Slack API documentation](https://api.slack.com/).
51 changes: 51 additions & 0 deletions python/slack/app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import os
import logging
import uvicorn
from fastapi import FastAPI, HTTPException, Request
from slack_sdk.signature import SignatureVerifier
from service import SlackService

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

app = FastAPI()


@app.post("/slack/events")
async def slack_events(request: Request):
try:
signing_secret = os.getenv("SLACK_SIGNING_SECRET")
browser_use_api_key = os.getenv("BROWSER_USE_API_KEY")
access_token = os.getenv("SLACK_ACCESS_TOKEN")
if not signing_secret or not browser_use_api_key or not access_token:
raise HTTPException(
status_code=500, detail="Environment variables not configured"
)

if not SignatureVerifier(signing_secret).is_valid_request(
await request.body(), dict(request.headers)
):
logger.warning("Request verification failed")
raise HTTPException(status_code=400, detail="Request verification failed")

event_data = await request.json()
if "challenge" in event_data:
return {"challenge": event_data["challenge"]}

slack_bot = SlackService(browser_use_api_key, access_token)
if "event" in event_data:
try:
await slack_bot.handle_event(event_data)
except Exception as e:
logger.error(f"Error handling event: {str(e)}")

return {}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error in slack_events: {str(e)}")
raise HTTPException(status_code=500, detail="Failed to process Slack event")


if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
124 changes: 124 additions & 0 deletions python/slack/app/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import logging
import asyncio
from typing import Optional
from slack_sdk.errors import SlackApiError
from slack_sdk.web.async_client import AsyncWebClient
from browser_use import BrowserUse

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


class SlackService:
def __init__(self, api_key: str, access_token: str):
self.client = BrowserUse(api_key=api_key)
self.access_token = access_token

async def send_message(
self, channel: str, text: str, thread_ts: Optional[str] = None
):
try:
client = AsyncWebClient(token=self.access_token)
response = await client.chat_postMessage(
channel=channel, text=text, thread_ts=thread_ts
)
return response
except SlackApiError as e:
logger.error(f"Error sending message: {e.response['error']}")

async def update_message(self, channel: str, ts: str, text: str):
try:
client = AsyncWebClient(token=self.access_token)
response = await client.chat_update(channel=channel, ts=ts, text=text)
return response
except SlackApiError as e:
logger.error(f"Error updating message: {e.response['error']}")

async def handle_event(self, event_data):
try:
event_id = event_data.get("event_id")
logger.info(f"Received event id: {event_id}")
if not event_id:
logger.warning("Event ID missing in event data")
return

event = event_data.get("event")

text = event.get("text")
channel_id = event.get("channel")

if text and channel_id:
# Extract the task by taking only the part after the bot mention
# The text format is: "anything before <@BOT_ID> task description"
import re

mention_pattern = r"<@[A-Z0-9]+>"
match = re.search(mention_pattern, text)

if match:
# Take everything after the bot mention
task = text[match.end() :].strip()
else:
return

# Only process if there's actually a task
if not task:
await self.send_message(
channel_id,
"Specify a task to execute.",
thread_ts=event.get("ts"),
)
return

# Start the async task to process the agent task
asyncio.create_task(self.process_agent_task_async(task, channel_id))

except Exception as e:
logger.error(f"Error in handle_event: {str(e)}")

async def process_agent_task_async(self, task: str, channel_id: str):
"""Async function to process the agent task and return share link immediately"""
try:
# Send initial "starting" message and capture its timestamp
response = await self.send_message(
channel_id, "Starting browser use task..."
)
if not response or not response.get("ok"):
logger.error(f"Failed to send initial message: {response}")
return

message_ts = response.get("ts")
if not message_ts:
logger.error("No timestamp received from Slack API")
return

# Start the agent task using internal service
task_result = await self.client.tasks.create(task=task)

if not task_result.session_id:
# Error starting task
error_message = f"Error: {task_result.message}"
await self.update_message(channel_id, message_ts, error_message)
return

share_url = await self.client.sessions.retrieve(task_result.session_id)

# Create final message with share link
if share_url:
final_message = f"Agent task started!\n\nShare URL: {share_url.public_share_url}\n\nTask: {task}"
else:
final_message = f"Agent task started!\n\nTask: {task}\n\nNote: Share link could not be created."

await self.update_message(channel_id, message_ts, final_message)

except Exception as e:
error_message = f"Error during task execution: {str(e)}"
logger.error(f"Error in process_agent_task_async: {error_message}")

# Send error message as a new message
try:
await self.send_message(
channel_id, f"Error in task execution: {error_message}"
)
except Exception as send_error:
logger.error(f"Failed to send error message: {str(send_error)}")
14 changes: 14 additions & 0 deletions python/slack/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[project]
name = "slack"
version = "0.1.0"
description = "slack-example"
readme = "README.md"
requires-python = ">=3.9.18"
dependencies = [
"aiohttp>=3.12.15",
"browser-use-sdk>=0.0.2",
"fastapi>=0.116.1",
"pydantic>=2.11.7",
"slack-sdk>=3.36.0",
"uvicorn>=0.35.0",
]
Loading