Skip to content

Commit

Permalink
Add Socket Mode support #159
Browse files Browse the repository at this point in the history
  • Loading branch information
seratch committed Dec 14, 2020
1 parent d82a7d9 commit 3be6008
Show file tree
Hide file tree
Showing 13 changed files with 428 additions and 2 deletions.
39 changes: 39 additions & 0 deletions examples/socket_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# ------------------------------------------------
# instead of slack_bolt in requirements.txt
import sys

sys.path.insert(1, "..")
# ------------------------------------------------

import logging
import os

from slack_bolt import App
from slack_bolt.adapter.socket_mode.websocket_client import SocketModeHandler

# Install the Slack app and get xoxb- token in advance
app = App(token=os.environ["SLACK_BOT_TOKEN"])


@app.command("/hello-socket-mode")
def hello_command(ack, body):
user_id = body["user_id"]
ack(f"Hi <@{user_id}>!")


@app.event("app_mention")
def event_test(event, say):
say(f"Hi there, <@{event['user']}>!")


@app.shortcut("socket-mode")
def global_shortcut(ack):
ack()


if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)

# export SLACK_APP_TOKEN=xapp-***
# export SLACK_BOT_TOKEN=xoxb-***
SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()
46 changes: 46 additions & 0 deletions examples/socket_mode_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ------------------------------------------------
# instead of slack_bolt in requirements.txt
import sys


sys.path.insert(1, "..")
# ------------------------------------------------

import logging
import os

from slack_bolt.app.async_app import AsyncApp
from slack_bolt.adapter.socket_mode.aiohttp import AsyncSocketModeHandler

# Install the Slack app and get xoxb- token in advance
app = AsyncApp(token=os.environ["SLACK_BOT_TOKEN"])


@app.command("/hello-socket-mode")
async def hello_command(ack, body):
user_id = body["user_id"]
await ack(f"Hi <@{user_id}>!")


@app.event("app_mention")
async def event_test(event, say):
await say(f"Hi there, <@{event['user']}>!")


@app.shortcut("socket-mode")
async def global_shortcut(ack):
await ack()


# export SLACK_APP_TOKEN=xapp-***
# export SLACK_BOT_TOKEN=xoxb-***

async def main():
handler = AsyncSocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
await handler.start_async()


if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
import asyncio
asyncio.run(main())
52 changes: 52 additions & 0 deletions examples/socket_mode_oauth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ------------------------------------------------
# instead of slack_bolt in requirements.txt
import sys

sys.path.insert(1, "..")
# ------------------------------------------------

import logging
import os
from slack_bolt.app import App
from slack_bolt.oauth.oauth_settings import OAuthSettings
from slack_bolt.adapter.socket_mode.websocket_client import SocketModeHandler

app = App(
signing_secret=os.environ["SLACK_SIGNING_SECRET"],
oauth_settings=OAuthSettings(
client_id=os.environ["SLACK_CLIENT_ID"],
client_secret=os.environ["SLACK_CLIENT_SECRET"],
scopes=os.environ["SLACK_SCOPES"].split(","),
)
)


@app.command("/hello-socket-mode")
def hello_command(ack, body):
user_id = body["user_id"]
ack(f"Hi <@{user_id}>!")


@app.event("app_mention")
def event_test(event, say):
say(f"Hi there, <@{event['user']}>!")


@app.shortcut("socket-mode")
def global_shortcut(ack):
ack()


if __name__ == "__main__":
logging.basicConfig(level=logging.DEBUG)
SocketModeHandler(app, os.environ.get("SLACK_APP_TOKEN")).connect()
app.start()

# export SLACK_APP_TOKEN=
# export SLACK_SIGNING_SECRET=
# export SLACK_CLIENT_ID=
# export SLACK_CLIENT_SECRET=
# export SLACK_SCOPES=
# pip install .[optional]
# pip install slack_bolt
# python socket_mode_oauth.py
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
exclude=["examples", "integration_tests", "tests", "tests.*",]
),
include_package_data=True, # MANIFEST.in
install_requires=["slack_sdk>=3.1.0,<3.2",],
install_requires=["slack_sdk>=3.2.0b1,<3.3",],
setup_requires=["pytest-runner==5.2"],
tests_require=test_dependencies,
test_suite="tests",
Expand Down
Empty file.
54 changes: 54 additions & 0 deletions slack_bolt/adapter/socket_mode/aiohttp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import os
from time import time
from typing import Optional

from slack_sdk.socket_mode.aiohttp import SocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest

from slack_bolt import App
from slack_bolt.adapter.socket_mode.async_base_handler import AsyncBaseSocketModeHandler
from slack_bolt.adapter.socket_mode.async_internals import (
send_async_response,
run_async_bolt_app,
)
from slack_bolt.adapter.socket_mode.internals import run_bolt_app
from slack_bolt.app.async_app import AsyncApp
from slack_bolt.response import BoltResponse


class SocketModeHandler(AsyncBaseSocketModeHandler):
app: App
app_token: str
client: SocketModeClient

def __init__(
self, app: App, app_token: Optional[str] = None,
):
self.app = app
self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
self.client = SocketModeClient(app_token=self.app_token)
self.client.socket_mode_request_listeners.append(self.handle)

async def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
start = time()
bolt_resp: BoltResponse = run_bolt_app(self.app, req)
await send_async_response(client, req, bolt_resp, start)


class AsyncSocketModeHandler(AsyncBaseSocketModeHandler):
app: AsyncApp
app_token: str
client: SocketModeClient

def __init__(
self, app: AsyncApp, app_token: Optional[str] = None,
):
self.app = app
self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
self.client = SocketModeClient(app_token=self.app_token)
self.client.socket_mode_request_listeners.append(self.handle)

async def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
start = time()
bolt_resp: BoltResponse = await run_async_bolt_app(self.app, req)
await send_async_response(client, req, bolt_resp, start)
29 changes: 29 additions & 0 deletions slack_bolt/adapter/socket_mode/async_base_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import asyncio
import logging

from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest


class AsyncBaseSocketModeHandler:
client: AsyncBaseSocketModeClient

async def handle(self, client: AsyncBaseSocketModeClient, req: SocketModeRequest) -> None:
raise NotImplementedError()

async def connect_async(self):
await self.client.connect()

async def disconnect_async(self):
await self.client.disconnect()

async def close_async(self):
await self.client.close()

async def start_async(self):
await self.connect_async()
if self.app.logger.level > logging.INFO:
print("⚡️ Bolt app is running!")
else:
self.app.logger.info("⚡️ Bolt app is running!")
await asyncio.sleep(float("inf"))
46 changes: 46 additions & 0 deletions slack_bolt/adapter/socket_mode/async_internals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import logging
from time import time

from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.socket_mode.response import SocketModeResponse

from slack_bolt.app.async_app import AsyncApp
from slack_bolt.request.async_request import AsyncBoltRequest
from slack_bolt.response import BoltResponse


async def run_async_bolt_app(app: AsyncApp, req: SocketModeRequest):
bolt_req: AsyncBoltRequest = AsyncBoltRequest(mode="socket_mode", body=req.payload)
bolt_resp: BoltResponse = await app.async_dispatch(bolt_req)
return bolt_resp


async def send_async_response(
client: AsyncBaseSocketModeClient,
req: SocketModeRequest,
bolt_resp: BoltResponse,
start_time: int,
):
if bolt_resp.status == 200:
if bolt_resp.body is None or len(bolt_resp.body) == 0:
await client.send_socket_mode_response(
SocketModeResponse(envelope_id=req.envelope_id)
)
elif bolt_resp.body.startswith("{"):
await client.send_socket_mode_response(
SocketModeResponse(envelope_id=req.envelope_id, payload=bolt_resp.body,)
)
else:
await client.send_socket_mode_response(
SocketModeResponse(
envelope_id=req.envelope_id, payload={"text": bolt_resp.body},
)
)
if client.logger.level <= logging.DEBUG:
spent_time = int((time() - start_time) * 1000)
client.logger.debug(f"Response time: {spent_time} milliseconds")
else:
client.logger.info(
f"Unsuccessful Bolt execution result (status: {bolt_resp.status}, body: {bolt_resp.body})"
)
29 changes: 29 additions & 0 deletions slack_bolt/adapter/socket_mode/base_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import logging
from threading import Event

from slack_sdk.socket_mode.client import BaseSocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest


class BaseSocketModeHandler:
client: BaseSocketModeClient

def handle(self, client: BaseSocketModeClient, req: SocketModeRequest) -> None:
raise NotImplementedError()

def connect(self):
self.client.connect()

def disconnect(self):
self.client.disconnect()

def close(self):
self.client.close()

def start(self):
self.connect()
if self.app.logger.level > logging.INFO:
print("⚡️ Bolt app is running!")
else:
self.app.logger.info("⚡️ Bolt app is running!")
Event().wait()
47 changes: 47 additions & 0 deletions slack_bolt/adapter/socket_mode/internals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import logging
from time import time

from slack_sdk.socket_mode.client import BaseSocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.socket_mode.response import SocketModeResponse

from slack_bolt.app import App
from slack_bolt.request import BoltRequest
from slack_bolt.response import BoltResponse


def run_bolt_app(app: App, req: SocketModeRequest):
bolt_req: BoltRequest = BoltRequest(mode="socket_mode", body=req.payload)
bolt_resp: BoltResponse = app.dispatch(bolt_req)
return bolt_resp


def send_response(
client: BaseSocketModeClient,
req: SocketModeRequest,
bolt_resp: BoltResponse,
start_time: int,
):
if bolt_resp.status == 200:
if bolt_resp.body is None or len(bolt_resp.body) == 0:
client.send_socket_mode_response(
SocketModeResponse(envelope_id=req.envelope_id)
)
elif bolt_resp.body.startswith("{"):
client.send_socket_mode_response(
SocketModeResponse(envelope_id=req.envelope_id, payload=bolt_resp.body,)
)
else:
client.send_socket_mode_response(
SocketModeResponse(
envelope_id=req.envelope_id, payload={"text": bolt_resp.body}
)
)

if client.logger.level <= logging.DEBUG:
spent_time = int((time() - start_time) * 1000)
client.logger.debug(f"Response time: {spent_time} milliseconds")
else:
client.logger.info(
f"Unsuccessful Bolt execution result (status: {bolt_resp.status}, body: {bolt_resp.body})"
)
30 changes: 30 additions & 0 deletions slack_bolt/adapter/socket_mode/websocket_client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import os
from time import time
from typing import Optional

from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.socket_mode.websocket_client import SocketModeClient

from slack_bolt import App
from slack_bolt.adapter.socket_mode.base_handler import BaseSocketModeHandler
from slack_bolt.adapter.socket_mode.internals import run_bolt_app, send_response
from slack_bolt.response import BoltResponse


class SocketModeHandler(BaseSocketModeHandler):
app: App
app_token: str
client: SocketModeClient

def __init__(
self, app: App, app_token: Optional[str] = None,
):
self.app = app
self.app_token = app_token or os.environ["SLACK_APP_TOKEN"]
self.client = SocketModeClient(app_token=self.app_token)
self.client.socket_mode_request_listeners.append(self.handle)

def handle(self, client: SocketModeClient, req: SocketModeRequest) -> None:
start = time()
bolt_resp: BoltResponse = run_bolt_app(self.app, req)
send_response(client, req, bolt_resp, start)
Loading

0 comments on commit 3be6008

Please sign in to comment.