156 lines
5.2 KiB
Python
156 lines
5.2 KiB
Python
"""Slack Socket Mode 이벤트 핸들링."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
from collections.abc import Callable
|
|
|
|
from slack_bolt import App
|
|
from slack_bolt.adapter.socket_mode import SocketModeHandler
|
|
from slack_sdk import WebClient
|
|
|
|
from lazy_enter.config import Config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SlackHandler:
|
|
"""Slack 앱과의 통신을 담당한다."""
|
|
|
|
def __init__(self, config: Config) -> None:
|
|
self.config = config
|
|
self.app = App(token=config.slack_bot_token)
|
|
self._handler: SocketModeHandler | None = None
|
|
self._stop_requested = False
|
|
self._on_message_callback: Callable[[str, str], None] | None = None
|
|
self._on_command_callback: Callable[[str, str, str], None] | None = None
|
|
|
|
self._register_listeners()
|
|
|
|
@property
|
|
def client(self) -> WebClient:
|
|
return self.app.client
|
|
|
|
def _is_authorized(self, user_id: str, channel_id: str) -> bool:
|
|
"""허가된 사용자·채널인지 확인한다."""
|
|
if self.config.allowed_user_id and user_id != self.config.allowed_user_id:
|
|
return False
|
|
if (
|
|
self.config.allowed_channel_id
|
|
and channel_id != self.config.allowed_channel_id
|
|
):
|
|
return False
|
|
return True
|
|
|
|
def _register_listeners(self) -> None:
|
|
"""Slack 이벤트 리스너를 등록한다."""
|
|
|
|
@self.app.event("message")
|
|
def handle_message(event: dict, say: Callable[..., None]) -> None:
|
|
user_id = event.get("user", "")
|
|
channel_id = event.get("channel", "")
|
|
text = event.get("text", "")
|
|
|
|
if not self._is_authorized(user_id, channel_id):
|
|
return
|
|
|
|
if self._on_message_callback:
|
|
self._on_message_callback(text, channel_id)
|
|
|
|
@self.app.command("/start-claude")
|
|
def handle_start_claude(ack: Callable[..., None], body: dict) -> None:
|
|
ack()
|
|
user_id = body.get("user_id", "")
|
|
channel_id = body.get("channel_id", "")
|
|
|
|
if not self._is_authorized(user_id, channel_id):
|
|
return
|
|
|
|
if self._on_command_callback:
|
|
self._on_command_callback("start", "claude", channel_id)
|
|
|
|
@self.app.command("/stop-claude")
|
|
def handle_stop_claude(ack: Callable[..., None], body: dict) -> None:
|
|
ack()
|
|
user_id = body.get("user_id", "")
|
|
channel_id = body.get("channel_id", "")
|
|
|
|
if not self._is_authorized(user_id, channel_id):
|
|
return
|
|
|
|
if self._on_command_callback:
|
|
self._on_command_callback("stop", "claude", channel_id)
|
|
|
|
@self.app.command("/start-codex")
|
|
def handle_start_codex(ack: Callable[..., None], body: dict) -> None:
|
|
ack()
|
|
user_id = body.get("user_id", "")
|
|
channel_id = body.get("channel_id", "")
|
|
|
|
if not self._is_authorized(user_id, channel_id):
|
|
return
|
|
|
|
if self._on_command_callback:
|
|
self._on_command_callback("start", "codex", channel_id)
|
|
|
|
@self.app.command("/stop-codex")
|
|
def handle_stop_codex(ack: Callable[..., None], body: dict) -> None:
|
|
ack()
|
|
user_id = body.get("user_id", "")
|
|
channel_id = body.get("channel_id", "")
|
|
|
|
if not self._is_authorized(user_id, channel_id):
|
|
return
|
|
|
|
if self._on_command_callback:
|
|
self._on_command_callback("stop", "codex", channel_id)
|
|
|
|
def on_message(self, callback: Callable[[str, str], None]) -> None:
|
|
"""메시지 수신 콜백을 등록한다."""
|
|
self._on_message_callback = callback
|
|
|
|
def on_command(self, callback: Callable[[str, str, str], None]) -> None:
|
|
"""슬래시 커맨드 콜백을 등록한다."""
|
|
self._on_command_callback = callback
|
|
|
|
def send_message(
|
|
self, channel: str, text: str, thread_ts: str | None = None
|
|
) -> None:
|
|
"""슬랙 채널에 메시지를 전송한다."""
|
|
self.client.chat_postMessage(
|
|
channel=channel,
|
|
text=text,
|
|
thread_ts=thread_ts,
|
|
)
|
|
|
|
def start(self) -> None:
|
|
"""Socket Mode 핸들러를 시작한다. 연결이 끊기면 재연결한다."""
|
|
self._stop_requested = False
|
|
logger.info("Slack Socket Mode 시작")
|
|
|
|
while not self._stop_requested:
|
|
try:
|
|
self._handler = SocketModeHandler(self.app, self.config.slack_app_token)
|
|
self._handler.start()
|
|
except Exception:
|
|
if self._stop_requested:
|
|
break
|
|
logger.exception("Socket Mode 연결이 종료되었습니다.")
|
|
|
|
if self._stop_requested:
|
|
break
|
|
|
|
logger.warning(
|
|
"Socket Mode 재연결 시도 (%s초 후)",
|
|
self.config.reconnect_delay_seconds,
|
|
)
|
|
time.sleep(self.config.reconnect_delay_seconds)
|
|
|
|
def stop(self) -> None:
|
|
"""Socket Mode 핸들러를 종료한다."""
|
|
logger.info("Slack Socket Mode 종료")
|
|
self._stop_requested = True
|
|
if self._handler is not None:
|
|
self._handler.close()
|