Skip to content

Commit 89b1c95

Browse files
authored
Add Slack Python Example (#1)
This PR adds a basic Python example of integrating Browser Use SDK with Slack.
1 parent c00d691 commit 89b1c95

File tree

8 files changed

+1203
-0
lines changed

8 files changed

+1203
-0
lines changed

python/slack/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
BROWSER_USE_API_KEY=bu-your-api-key-here
2+
3+
SLACK_BOT_TOKEN=xoxb-your-bot-token-here
4+
SLACK_SIGNING_SECRET=your-signing-secret-here

python/slack/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.venv
2+
__pycache__

python/slack/.python-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.9.18

python/slack/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Slack Example
2+
3+
Dev setup:
4+
5+
```bash
6+
uv sync
7+
```
8+
9+
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.
10+
11+
The following Slack scopes are required for this application:
12+
13+
- `app_mentions:read` - View messages that directly mention @Browser-use in conversations that the app is in
14+
- `channels:read` - View basic information about public channels in a workspace
15+
- `chat:write` - Send messages as @Browser-use
16+
17+
The required event subscription is: `app_mentions`
18+
19+
Create a `.env` file in your project root by copying the `.env.example`.
20+
21+
For more detailed information, refer to the [Slack API documentation](https://api.slack.com/).

python/slack/app/main.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import os
2+
import logging
3+
import uvicorn
4+
from fastapi import FastAPI, HTTPException, Request
5+
from slack_sdk.signature import SignatureVerifier
6+
from service import SlackService
7+
8+
logger = logging.getLogger(__name__)
9+
logger.setLevel(logging.INFO)
10+
11+
app = FastAPI()
12+
13+
14+
@app.post("/slack/events")
15+
async def slack_events(request: Request):
16+
try:
17+
signing_secret = os.getenv("SLACK_SIGNING_SECRET")
18+
browser_use_api_key = os.getenv("BROWSER_USE_API_KEY")
19+
access_token = os.getenv("SLACK_ACCESS_TOKEN")
20+
if not signing_secret or not browser_use_api_key or not access_token:
21+
raise HTTPException(
22+
status_code=500, detail="Environment variables not configured"
23+
)
24+
25+
if not SignatureVerifier(signing_secret).is_valid_request(
26+
await request.body(), dict(request.headers)
27+
):
28+
logger.warning("Request verification failed")
29+
raise HTTPException(status_code=400, detail="Request verification failed")
30+
31+
event_data = await request.json()
32+
if "challenge" in event_data:
33+
return {"challenge": event_data["challenge"]}
34+
35+
slack_bot = SlackService(browser_use_api_key, access_token)
36+
if "event" in event_data:
37+
try:
38+
await slack_bot.handle_event(event_data)
39+
except Exception as e:
40+
logger.error(f"Error handling event: {str(e)}")
41+
42+
return {}
43+
except HTTPException:
44+
raise
45+
except Exception as e:
46+
logger.error(f"Error in slack_events: {str(e)}")
47+
raise HTTPException(status_code=500, detail="Failed to process Slack event")
48+
49+
50+
if __name__ == "__main__":
51+
uvicorn.run(app, host="0.0.0.0", port=8000)

python/slack/app/service.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import logging
2+
import asyncio
3+
from typing import Optional
4+
from slack_sdk.errors import SlackApiError
5+
from slack_sdk.web.async_client import AsyncWebClient
6+
from browser_use import BrowserUse
7+
8+
logger = logging.getLogger(__name__)
9+
logger.setLevel(logging.INFO)
10+
11+
12+
class SlackService:
13+
def __init__(self, api_key: str, access_token: str):
14+
self.client = BrowserUse(api_key=api_key)
15+
self.access_token = access_token
16+
17+
async def send_message(
18+
self, channel: str, text: str, thread_ts: Optional[str] = None
19+
):
20+
try:
21+
client = AsyncWebClient(token=self.access_token)
22+
response = await client.chat_postMessage(
23+
channel=channel, text=text, thread_ts=thread_ts
24+
)
25+
return response
26+
except SlackApiError as e:
27+
logger.error(f"Error sending message: {e.response['error']}")
28+
29+
async def update_message(self, channel: str, ts: str, text: str):
30+
try:
31+
client = AsyncWebClient(token=self.access_token)
32+
response = await client.chat_update(channel=channel, ts=ts, text=text)
33+
return response
34+
except SlackApiError as e:
35+
logger.error(f"Error updating message: {e.response['error']}")
36+
37+
async def handle_event(self, event_data):
38+
try:
39+
event_id = event_data.get("event_id")
40+
logger.info(f"Received event id: {event_id}")
41+
if not event_id:
42+
logger.warning("Event ID missing in event data")
43+
return
44+
45+
event = event_data.get("event")
46+
47+
text = event.get("text")
48+
channel_id = event.get("channel")
49+
50+
if text and channel_id:
51+
# Extract the task by taking only the part after the bot mention
52+
# The text format is: "anything before <@BOT_ID> task description"
53+
import re
54+
55+
mention_pattern = r"<@[A-Z0-9]+>"
56+
match = re.search(mention_pattern, text)
57+
58+
if match:
59+
# Take everything after the bot mention
60+
task = text[match.end() :].strip()
61+
else:
62+
return
63+
64+
# Only process if there's actually a task
65+
if not task:
66+
await self.send_message(
67+
channel_id,
68+
"Specify a task to execute.",
69+
thread_ts=event.get("ts"),
70+
)
71+
return
72+
73+
# Start the async task to process the agent task
74+
asyncio.create_task(self.process_agent_task_async(task, channel_id))
75+
76+
except Exception as e:
77+
logger.error(f"Error in handle_event: {str(e)}")
78+
79+
async def process_agent_task_async(self, task: str, channel_id: str):
80+
"""Async function to process the agent task and return share link immediately"""
81+
try:
82+
# Send initial "starting" message and capture its timestamp
83+
response = await self.send_message(
84+
channel_id, "Starting browser use task..."
85+
)
86+
if not response or not response.get("ok"):
87+
logger.error(f"Failed to send initial message: {response}")
88+
return
89+
90+
message_ts = response.get("ts")
91+
if not message_ts:
92+
logger.error("No timestamp received from Slack API")
93+
return
94+
95+
# Start the agent task using internal service
96+
task_result = await self.client.tasks.create(task=task)
97+
98+
if not task_result.session_id:
99+
# Error starting task
100+
error_message = f"Error: {task_result.message}"
101+
await self.update_message(channel_id, message_ts, error_message)
102+
return
103+
104+
share_url = await self.client.sessions.retrieve(task_result.session_id)
105+
106+
# Create final message with share link
107+
if share_url:
108+
final_message = f"Agent task started!\n\nShare URL: {share_url.public_share_url}\n\nTask: {task}"
109+
else:
110+
final_message = f"Agent task started!\n\nTask: {task}\n\nNote: Share link could not be created."
111+
112+
await self.update_message(channel_id, message_ts, final_message)
113+
114+
except Exception as e:
115+
error_message = f"Error during task execution: {str(e)}"
116+
logger.error(f"Error in process_agent_task_async: {error_message}")
117+
118+
# Send error message as a new message
119+
try:
120+
await self.send_message(
121+
channel_id, f"Error in task execution: {error_message}"
122+
)
123+
except Exception as send_error:
124+
logger.error(f"Failed to send error message: {str(send_error)}")

python/slack/pyproject.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[project]
2+
name = "slack"
3+
version = "0.1.0"
4+
description = "slack-example"
5+
readme = "README.md"
6+
requires-python = ">=3.9.18"
7+
dependencies = [
8+
"aiohttp>=3.12.15",
9+
"browser-use-sdk>=0.0.2",
10+
"fastapi>=0.116.1",
11+
"pydantic>=2.11.7",
12+
"slack-sdk>=3.36.0",
13+
"uvicorn>=0.35.0",
14+
]

0 commit comments

Comments
 (0)