Skip to content

Commit 51af9e0

Browse files
committed
Add Socket Mode support #159
1 parent 3eabe68 commit 51af9e0

File tree

13 files changed

+428
-2
lines changed

13 files changed

+428
-2
lines changed

examples/socket_mode.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# ------------------------------------------------
2+
# instead of slack_bolt in requirements.txt
3+
import sys
4+
5+
sys.path.insert(1, "..")
6+
# ------------------------------------------------
7+
8+
import logging
9+
import os
10+
11+
from slack_bolt import App
12+
from slack_bolt.adapter.socket_mode.websocket_client import SocketModeHandler
13+
14+
# Install the Slack app and get xoxb- token in advance
15+
app = App(token=os.environ["SLACK_BOT_TOKEN"])
16+
17+
18+
@app.command("/hello-socket-mode")
19+
def hello_command(ack, body):
20+
user_id = body["user_id"]
21+
ack(f"Hi <@{user_id}>!")
22+
23+
24+
@app.event("app_mention")
25+
def event_test(event, say):
26+
say(f"Hi there, <@{event['user']}>!")
27+
28+
29+
@app.shortcut("socket-mode")
30+
def global_shortcut(ack):
31+
ack()
32+
33+
34+
if __name__ == "__main__":
35+
logging.basicConfig(level=logging.DEBUG)
36+
37+
# export SLACK_APP_TOKEN=xapp-***
38+
# export SLACK_BOT_TOKEN=xoxb-***
39+
SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

examples/socket_mode_async.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# ------------------------------------------------
2+
# instead of slack_bolt in requirements.txt
3+
import sys
4+
5+
6+
sys.path.insert(1, "..")
7+
# ------------------------------------------------
8+
9+
import logging
10+
import os
11+
12+
from slack_bolt.app.async_app import AsyncApp
13+
from slack_bolt.adapter.socket_mode.aiohttp import AsyncSocketModeHandler
14+
15+
# Install the Slack app and get xoxb- token in advance
16+
app = AsyncApp(token=os.environ["SLACK_BOT_TOKEN"])
17+
18+
19+
@app.command("/hello-socket-mode")
20+
async def hello_command(ack, body):
21+
user_id = body["user_id"]
22+
await ack(f"Hi <@{user_id}>!")
23+
24+
25+
@app.event("app_mention")
26+
async def event_test(event, say):
27+
await say(f"Hi there, <@{event['user']}>!")
28+
29+
30+
@app.shortcut("socket-mode")
31+
async def global_shortcut(ack):
32+
await ack()
33+
34+
35+
# export SLACK_APP_TOKEN=xapp-***
36+
# export SLACK_BOT_TOKEN=xoxb-***
37+
38+
async def main():
39+
handler = AsyncSocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
40+
await handler.start_async()
41+
42+
43+
if __name__ == "__main__":
44+
logging.basicConfig(level=logging.DEBUG)
45+
import asyncio
46+
asyncio.run(main())

examples/socket_mode_oauth.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# ------------------------------------------------
2+
# instead of slack_bolt in requirements.txt
3+
import sys
4+
5+
sys.path.insert(1, "..")
6+
# ------------------------------------------------
7+
8+
import logging
9+
import os
10+
from slack_bolt.app import App
11+
from slack_bolt.oauth.oauth_settings import OAuthSettings
12+
from slack_bolt.adapter.socket_mode.websocket_client import SocketModeHandler
13+
14+
app = App(
15+
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
16+
oauth_settings=OAuthSettings(
17+
client_id=os.environ["SLACK_CLIENT_ID"],
18+
client_secret=os.environ["SLACK_CLIENT_SECRET"],
19+
scopes=os.environ["SLACK_SCOPES"].split(","),
20+
)
21+
)
22+
23+
24+
@app.command("/hello-socket-mode")
25+
def hello_command(ack, body):
26+
user_id = body["user_id"]
27+
ack(f"Hi <@{user_id}>!")
28+
29+
30+
@app.event("app_mention")
31+
def event_test(event, say):
32+
say(f"Hi there, <@{event['user']}>!")
33+
34+
35+
@app.shortcut("socket-mode")
36+
def global_shortcut(ack):
37+
ack()
38+
39+
40+
if __name__ == "__main__":
41+
logging.basicConfig(level=logging.DEBUG)
42+
SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).connect()
43+
app.start()
44+
45+
# export SLACK_APP_TOKEN=
46+
# export SLACK_SIGNING_SECRET=
47+
# export SLACK_CLIENT_ID=
48+
# export SLACK_CLIENT_SECRET=
49+
# export SLACK_SCOPES=
50+
# pip install .[optional]
51+
# pip install slack_bolt
52+
# python socket_mode_oauth.py

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
exclude=["examples", "integration_tests", "tests", "tests.*",]
3434
),
3535
include_package_data=True, # MANIFEST.in
36-
install_requires=["slack_sdk>=3.1.1,<3.2",],
36+
install_requires=["slack_sdk>=3.2.0b1,<3.3",],
3737
setup_requires=["pytest-runner==5.2"],
3838
tests_require=test_dependencies,
3939
test_suite="tests",

slack_bolt/adapter/socket_mode/__init__.py

Whitespace-only changes.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import os
2+
from time import time
3+
from typing import Optional
4+
5+
from slack_sdk.socket_mode.aiohttp import SocketModeClient
6+
from slack_sdk.socket_mode.request import SocketModeRequest
7+
8+
from slack_bolt import App
9+
from slack_bolt.adapter.socket_mode.async_base_handler import AsyncBaseSocketModeHandler
10+
from slack_bolt.adapter.socket_mode.async_internals import (
11+
send_async_response,
12+
run_async_bolt_app,
13+
)
14+
from slack_bolt.adapter.socket_mode.internals import run_bolt_app
15+
from slack_bolt.app.async_app import AsyncApp
16+
from slack_bolt.response import BoltResponse
17+
18+
19+
class SocketModeHandler(AsyncBaseSocketModeHandler):
20+
app: App
21+
app_token: str
22+
client: SocketModeClient
23+
24+
def __init__(
25+
self, app: App, app_token: Optional[str] = None,
26+
):
27+
self.app = app
28+
self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
29+
self.client = SocketModeClient(app_token=self.app_token)
30+
self.client.socket_mode_request_listeners.append(self.handle)
31+
32+
async def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
33+
start = time()
34+
bolt_resp: BoltResponse = run_bolt_app(self.app, req)
35+
await send_async_response(client, req, bolt_resp, start)
36+
37+
38+
class AsyncSocketModeHandler(AsyncBaseSocketModeHandler):
39+
app: AsyncApp
40+
app_token: str
41+
client: SocketModeClient
42+
43+
def __init__(
44+
self, app: AsyncApp, app_token: Optional[str] = None,
45+
):
46+
self.app = app
47+
self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
48+
self.client = SocketModeClient(app_token=self.app_token)
49+
self.client.socket_mode_request_listeners.append(self.handle)
50+
51+
async def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
52+
start = time()
53+
bolt_resp: BoltResponse = await run_async_bolt_app(self.app, req)
54+
await send_async_response(client, req, bolt_resp, start)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import asyncio
2+
import logging
3+
4+
from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
5+
from slack_sdk.socket_mode.request import SocketModeRequest
6+
7+
8+
class AsyncBaseSocketModeHandler:
9+
client: AsyncBaseSocketModeClient
10+
11+
async def handle(self, client: AsyncBaseSocketModeClient, req: SocketModeRequest) -> None:
12+
raise NotImplementedError()
13+
14+
async def connect_async(self):
15+
await self.client.connect()
16+
17+
async def disconnect_async(self):
18+
await self.client.disconnect()
19+
20+
async def close_async(self):
21+
await self.client.close()
22+
23+
async def start_async(self):
24+
await self.connect_async()
25+
if self.app.logger.level > logging.INFO:
26+
print("⚡️ Bolt app is running!")
27+
else:
28+
self.app.logger.info("⚡️ Bolt app is running!")
29+
await asyncio.sleep(float("inf"))
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import logging
2+
from time import time
3+
4+
from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
5+
from slack_sdk.socket_mode.request import SocketModeRequest
6+
from slack_sdk.socket_mode.response import SocketModeResponse
7+
8+
from slack_bolt.app.async_app import AsyncApp
9+
from slack_bolt.request.async_request import AsyncBoltRequest
10+
from slack_bolt.response import BoltResponse
11+
12+
13+
async def run_async_bolt_app(app: AsyncApp, req: SocketModeRequest):
14+
bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload)
15+
bolt_resp: BoltResponse = await app.async_dispatch(bolt_req)
16+
return bolt_resp
17+
18+
19+
async def send_async_response(
20+
client: AsyncBaseSocketModeClient,
21+
req: SocketModeRequest,
22+
bolt_resp: BoltResponse,
23+
start_time: int,
24+
):
25+
if bolt_resp.status == 200:
26+
if bolt_resp.body is None or len(bolt_resp.body) == 0:
27+
await client.send_socket_mode_response(
28+
SocketModeResponse(envelope_id=req.envelope_id)
29+
)
30+
elif bolt_resp.body.startswith("{"):
31+
await client.send_socket_mode_response(
32+
SocketModeResponse(envelope_id=req.envelope_id, payload=bolt_resp.body,)
33+
)
34+
else:
35+
await client.send_socket_mode_response(
36+
SocketModeResponse(
37+
envelope_id=req.envelope_id, payload={"text": bolt_resp.body},
38+
)
39+
)
40+
if client.logger.level <= logging.DEBUG:
41+
spent_time = int((time() - start_time) * 1000)
42+
client.logger.debug(f"Response time: {spent_time} milliseconds")
43+
else:
44+
client.logger.info(
45+
f"Unsuccessful Bolt execution result (status: {bolt_resp.status}, body: {bolt_resp.body})"
46+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import logging
2+
from threading import Event
3+
4+
from slack_sdk.socket_mode.client import BaseSocketModeClient
5+
from slack_sdk.socket_mode.request import SocketModeRequest
6+
7+
8+
class BaseSocketModeHandler:
9+
client: BaseSocketModeClient
10+
11+
def handle(self, client: BaseSocketModeClient, req: SocketModeRequest) -> None:
12+
raise NotImplementedError()
13+
14+
def connect(self):
15+
self.client.connect()
16+
17+
def disconnect(self):
18+
self.client.disconnect()
19+
20+
def close(self):
21+
self.client.close()
22+
23+
def start(self):
24+
self.connect()
25+
if self.app.logger.level > logging.INFO:
26+
print("⚡️ Bolt app is running!")
27+
else:
28+
self.app.logger.info("⚡️ Bolt app is running!")
29+
Event().wait()
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import logging
2+
from time import time
3+
4+
from slack_sdk.socket_mode.client import BaseSocketModeClient
5+
from slack_sdk.socket_mode.request import SocketModeRequest
6+
from slack_sdk.socket_mode.response import SocketModeResponse
7+
8+
from slack_bolt.app import App
9+
from slack_bolt.request import BoltRequest
10+
from slack_bolt.response import BoltResponse
11+
12+
13+
def run_bolt_app(app: App, req: SocketModeRequest):
14+
bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload)
15+
bolt_resp: BoltResponse = app.dispatch(bolt_req)
16+
return bolt_resp
17+
18+
19+
def send_response(
20+
client: BaseSocketModeClient,
21+
req: SocketModeRequest,
22+
bolt_resp: BoltResponse,
23+
start_time: int,
24+
):
25+
if bolt_resp.status == 200:
26+
if bolt_resp.body is None or len(bolt_resp.body) == 0:
27+
client.send_socket_mode_response(
28+
SocketModeResponse(envelope_id=req.envelope_id)
29+
)
30+
elif bolt_resp.body.startswith("{"):
31+
client.send_socket_mode_response(
32+
SocketModeResponse(envelope_id=req.envelope_id, payload=bolt_resp.body,)
33+
)
34+
else:
35+
client.send_socket_mode_response(
36+
SocketModeResponse(
37+
envelope_id=req.envelope_id, payload={"text": bolt_resp.body}
38+
)
39+
)
40+
41+
if client.logger.level <= logging.DEBUG:
42+
spent_time = int((time() - start_time) * 1000)
43+
client.logger.debug(f"Response time: {spent_time} milliseconds")
44+
else:
45+
client.logger.info(
46+
f"Unsuccessful Bolt execution result (status: {bolt_resp.status}, body: {bolt_resp.body})"
47+
)

0 commit comments

Comments
 (0)