flowoodz.
← blog
9분 읽기글 flowoodz

Slack에서 누가 나를 부르면 「OO님~」이 울리게 만든 이야기

Slack 멘션이 오면 「OO님~」 커스텀 음성이 울리게 만든 자동화. Hammerspoon을 버리고 LaunchAgent로 옮긴 이유.

목차
  1. 최종 구조를 한 장으로
  2. Hammerspoon을 뺀 이유
  3. Slack 멘션을 어떻게 "감지"하나
  4. 알림음은 macOS TTS가 아니라 OmniVoice MLX
  5. state.json으로 같은 멘션에 소리가 두 번 안 나게
  6. 트러블슈팅 요약
  7. 직접 써보려면
  8. 1초 폴링을 운영하며 남은 기준

Slack에서 @김OO 멘션이 오면 기본 알림음 말고 내가 만든 「OO님~」 음성이 추가로 재생되게 하고 싶었다. Slack 자체 설정만으로는 멘션마다 커스텀 TTS를 넣을 수 없다. 백그라운드에서 Slack을 주기적으로 확인하고 새 멘션이면 소리를 틀어 주는 작은 자동화를 만들었다.


최종 구조를 한 장으로#

로그인하면 macOS LaunchAgent가 자동 실행
        ↓
3~1초마다 poll-slack.sh 실행
        ↓
Slack API로 "새 @멘션 있나?" 확인
        ↓
있으면 afplay로 음성.wav 재생
구성요소 역할 비유
LaunchAgent 맥이 켜져 있는 동안 스크립트를 주기적으로 실행 알람 시계
poll-slack.sh Slack API 호출 + 멘션 판별 경비원
config.json 토큰, 내 유저 ID, 감시 채널 목록 열쇠랑 설정표
state.json 마지막으로 본 멘션 시각 "이미 들은 알림" 메모
음성.wav OmniVoice MLX로 만든 TTS 실제로 울리는 소리

파일은 전부 ~/slack-mention/ 한 폴더에 모아 두었다.


Hammerspoon을 뺀 이유#

처음엔 Hammerspoon이라는 맥 자동화 도구를 썼다. Lua 스크립트로 타이머를 돌리고 Slack API를 치고 소리를 내는 방식.

문제가 연달아 터졌다.

  1. Slack API 호출이 조용히 실패: Hammerspoon 내장 HTTP가 응답을 제대로 못 받는 경우가 있었다. 로그에는 아무것도 안 남고 멘션은 왔는데 소리만 안 남.
  2. 타이머가 멈춤: 맥이 절전(App Nap)에 들어가면 주기 실행이 끊기기도 했다.
  3. 299개 채널을 전부 조회: 한때 "모든 대화방 history를 돌자"는 접근을 썼는데 API 호출 한 번에 1초씩 잡으면 한 바퀴 3분. 실시간 알림이 아니라 시험 채점이었다.

Hammerspoon 앱 자체를 uninstall하고 macOS 기본 백그라운드 서비스(LaunchAgent)로 옮겼다. "메뉴바 📣 테스트"는 되는데 Slack 멘션만 안 울리던 패턴. 재생은 되는데 감지 경로만 고장. 여기서 갈라졌다.


Slack 멘션을 어떻게 "감지"하나#

Slack은 멘션 푸시 알림을 주지만 내 커스텀 소리를 같이 트리거해 주진 않는다. API로 직접 확인해야 한다.

방법 A: 검색 API (search.messages)#

"<@내유저ID>" 로 검색. API 호출 1번이라 가볍다.

단점: Slack이 검색 인덱스에 메시지를 올리기까지 10~30초, 가끔 1분 걸린다. "알림은 왔는데 우리 소리는 15초 뒤"가 여기서 나온다. 폴링을 3초로 줄여도 검색 지연은 그대로.

방법 B: 대화 기록 API (conversations.history)#

특정 채널의 최근 메시지 몇 개를 직접 읽는다. 거의 실시간.

단점: 채널마다 API를 한 번씩 쳐야 해서 채널이 많으면 rate limit(호출 횟수 제한)에 걸린다.

최종 타협은 둘 다 쓰되 역할 분담#

Slack 멘션 감지 역할 분담. 자주 쓰는 채널과 DM은 1초마다 conversations.history로 조회해 1~3초에 감지하고, 그 외 채널은 30초마다 search.messages로 백업 조회합니다

멘션 위치 감지 방식 체감 속도
watchChannelIds에 넣은 채널 매 1초 history 조회 1~3초
나와의 DM history 1~3초
그 외 채널 search (백업, 30초에 1번) 10~30초+

자주 멘션 받는 채널 ID(C0B23PU8GTG 같은 것)를 config에 넣으면 검색 인덱스 기다릴 필요가 없어진다.

12초 지연이 또 난 이유 (두 번째 함정)#

watch 채널을 넣었는데도 ~12초 걸렸다. 로그를 보니 감지→재생은 2초였다. 진짜 원인: watch 채널을 "매번 확인"이 아니라 활동 중인 채널 6개와 라운드로빈으로 섞어 돌리고 있었다. 7번에 1번만 watch를 보면 7×1초 ≈ 12초. 고친 뒤 watch는 매 폴링마다 무조건 history를 본다.


알림음은 macOS TTS가 아니라 OmniVoice MLX#

첫 시도는 맥 기본 say 명령. 「OO님」이 로봇 같고 한 번은 0.016초짜리 깨진 파일이 나왔다. 바로 버렸다.

OmniVoice MLX는 Apple Silicon 맥에서 로컬로 돌아가는 신경망 TTS다. 인터넷·API 과금 없이 ~/omnivoice-local/에 설치해 둔 상태였다.

최종 생성 명령:

omnivoice-clone "OO님" "" "female, korean accent, moderate pitch"
인자 의미
1번 읽을 텍스트
2번 클론 참조 음성 (빈 문자열 = 없음)
3번 목소리 속성 instruct

Voice Design 모드는 녹음 없이 "여성, 한국어 억양, 중간 톤"만 지정한다. 결과 wav를 ~/slack-mention/sounds/음성.wav에 넣었다.

나중에 래퍼 스크립트(omnivoice-clone)도 손봤다. 원래는 instruct가 있으면 참조 음성을 완전히 무시하는 if/else였는데 모델은 둘 다 받을 수 있다. 참조 음성 + instruct 동시 지원으로 바꿨다. (충돌하면 참조 음성이 이긴다. 남자 녹음에 female instruct면 여자 목소리가 안 나올 수 있다.)


state.json으로 같은 멘션에 소리가 두 번 안 나게#

스크립트를 처음 켜면 Slack에 이미 쌓인 옛 멘션까지 "새 것"으로 오인할 수 있다. 첫 실행은 타임스탬프만 저장하고 소리는 안 낸다.

그다음부터는 state.json의 othersTs, selfDmTs보다 더 최신 멘션만 TRIGGER.


트러블슈팅 요약#

증상 원인 해결
메뉴바 테스트는 되는데 Slack 멘션만 무음 재생 OK, 감지 경로 고장 Hammerspoon → LaunchAgent + curl
15초쯤 늦게 울림 search API 인덱싱 지연 watchChannelIds + history
watch 넣었는데도 ~12초 watch가 RR에 섞여 7번에 1번만 조회 watch는 매 폴링마다 항상 조회
자기 DM @멘션 테스트 안 됨 search는 자기 메시지 느림 self-DM은 history API
TTS 퀄리티 형편없음 macOS say OmniVoice MLX
Hammerspoon 켜져 있는데 안 됨 타이머/App Nap/HTTP Hammerspoon 제거, LaunchAgent만

직접 써보려면#

# 소리만 테스트
bash ~/slack-mention/test-sound.sh
 
# 폴링 1회
bash ~/slack-mention/poll-slack.sh
 
# 로그
tail -f ~/slack-mention/poll.log

Slack 토큰은 config.json에만 두고 git에는 올리지 않는다.


1초 폴링을 운영하며 남은 기준#

  1. "알림이 왔다" ≠ "내 스크립트가 안다": Slack 푸시와 API 가시성은 타이밍이 다르다.
  2. 빠르게 만들려면 search 한 방이 아니라, 중요한 채널만 history로 직접 본다.
  3. 자동화 도구(Hammerspoon)가 편해도, 백그라운드 신뢰성은 LaunchAgent가 낫다: 이 use case에서는.
  4. TTS는 기본 내장보다 로컬 MLX 한 번 세팅해 두는 게 낫다: 특히 짧은 커스텀 알림음.

Slack 기본 알림은 그대로 두고 그 위에 「OO님~」이 얹히는 구조다. 과하지 않게, 근데 확실하게. 그게 목표였다.