프로젝트/슬랙봇

슬랙봇 개발기 ② - python slack sdk + flask 서버 구축

뽀글보리 2022. 12. 11. 13:23
반응형

 

 

슬랙봇 개발기 ① - 소켓 모드란 무엇일까?

슬랙봇을 개발할 경우에는 HTTP통신을 기반으로 하거나 웹소켓 통신을 기반으로 하는 두가지 방법이 있다. 먼저 HTTP 통신을 기반으로 하는 방법은 public으로 열어놓은 REST API를 기반으로 하는 방

bboglebbogle.tistory.com

이전 글에서 소켓모드를 사용해서 웹소켓 기반의 슬랙봇을 구축해야 하는 이유에 대해서 살펴보았다.

 

 

Socket Mode Client — Python Slack SDK

Socket Mode is a method of connecting your app to Slack’s APIs using WebSockets instead of HTTP. You can use slack_sdk.socket_mode.SocketModeClient for managing Socket Mode connections and performing interactions with Slack. SocketModeClient First off, l

slack.dev

슬랙에서는 웹소켓 구축을 조금 더 쉽게할 수 있도록 만들어진 라이브러리를 제공한다.

 

바로 Python Slack SDK의 SocketModeClient 기능인 데, 나는 이것을 사용해서 구축하기로 결정했다.

Slack bolt를 사용할 수도 있지만, 조금 더 로우 레벨이면서 커스텀하기에 편하다고 생각하여 결정했다.

 

웹소켓 통신을 사용하면서 flask 서버도 사용하는 이유는 간단한 데,

- 쿠버네티스 헬스 체크를 위하여 api/ping url을 열어줘야 하기도 하고,

- 나중에 Oauth를 할 때 redirect uri가 필요하기도 하므로

결론적으로는 웹소켓 통신 + http 통신을 둘 다 사용할 것 같다.

 

pip3 명령어를 사용하여 slack-sdk와 flask를 설치한다.

from flask import Flask
from slack_sdk.socket_mode import SocketModeClient
from slack_sdk import WebClient

app = Flask(__name__)

@app.route('/api/ping')
def ping():
    return "pong"

clinet = WebClient(
    token=SLACK_BOT_TOKEN,
    proxy=PROXY_URL
)

socket = SocketModeClient(
    app_token=APP_TOKEN,
    web_client=client,
    on_close_listeners=[error_listner],
    on_message_listeners=[message_listener],
)
socket.connect()

socket.connect()를 사용하면 flask app context를 block하지 않기 때문에,

flask와 web socket 통신을 둘 다 사용할 수 있다.

 

만약 ssl 관련 오류가 발생한다면,

import ssl
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
client = WebClient(token='yourtoken',
                         ssl=ssl_context)

와 같은 ssl verify하지 않는 ssl_context를 넣을 수 있다.

def error_listener(app, ex: Exception):
  logging.error(e)

  if isinstance(ex, WebSocketConnectionClosedException):
    socket.close()
    socket.conenct_to_new_endpoint(True)

Error listener는 에러가 발생할 때마다 호출되는 함수라고 볼 수 있다.

나는 여러번의 시도 끝에, 어플리케이션을 장시간 켜놓을 경우, 웹 소켓이 간헐적으로 끊어지는 에러가 발생하는 것을 알게 되었고,

이를 해결 하기 위해 웹소켓 연결이 끊어지는 WebSocketConnectionClosedException이 발생할 경우,

새로운 endpoint로 강제로 연결을 갱신해주는 함수를 호출하는 것으로 이 문제를 해결했다.

이처럼 에러가 발생할 경우 할 코드들을 이 곳에 적어주면 된다.

def message_listener(req: str):
  req = json.loads(req)
  if req.get("type") == "slash_commands" and req.get("payload").get("command") == "/test":
    //do something

그리고 message_listener는 웹소켓을 통한 메시지가 올때마다 호출되는 함수이다.

연결이 될 때, disconnect될 때, 커맨드를 호출할 때, 슬랙봇을 멘션할 때 등등,

모든 이벤트가 발생할 때마다 message_listener를 통해 메시지가 전달된다.

따라서 먼저 json.loads를 통해서 메시지를 dictionary 형태로 변형한 후에,

해당 메시지의 타입에 따라서 할 동작들을 수행하도록 구조를 잡았다.

 

 

반응형