-
Python으로 점심 메뉴 Slack 봇 만들기개발 이야기 2026. 6. 16. 11:30

카카오 오픈채팅에는 공식 API가 없습니다.
Mac 로컬 DB, Slack 파일 업로드, launchd 스케줄링을 엮으면서 겪은 문제와 해결 방법을 정리했습니다.
목차
1. 문제 정의
카카오 오픈채팅에 올라오는 메뉴 사진을 Slack 채널에 자동으로 보내는 파이프라인이 필요했습니다.
제약 조건
- 카카오 오픈채팅 공식 API 없음
- Mac 카카오톡 로컬 DB만 읽을 수 있음
- 평일 정해진 시간에 수집 → 알림
- 오늘 메뉴가 없으면 알림 자체를 보내지 않음
- 특정 오픈채팅방 하나만 접근
2. 아키텍처
카카오 오픈채팅
↓
Mac + kakaocli (로컬 DB)
↓
로컬 캐시 (data/menu/)
↓
Slack Bot API
실행 스케줄
시간 작업
10:58 pmset repeat wake — 잠자기 상태 Mac 깨우기 11:00 메뉴 사진 수집·캐시 11:29 캐시 기반 Slack 알림 재직중인 회사에서의 11:30분부터 점심시간이기에 11:00 수집과 11:29 알림을 분리했습니다.
알림 시점에 재수집(--fetch-now)하지 않고, 11:00에 저장한 캐시만 사용합니다.
3. Slack 연동 이슈
3-1. Bot Token 종류 혼동
xapp-(App-Level Token)을 넣으면 not_allowed_token_type 오류가 납니다.
💡 정리
- 채널 메시지·파일 업로드 → xoxb-(Bot User OAuth Token)
- xapp-은 Socket Mode용이라 이번 용도와 맞지 않습니다.
3-2. Scope 설정
Scope 용도
chat:write 알림 메시지 files:write 사진 업로드 텍스트만 보낼 때는 chat:write로 충분하지만, 이미지 업로드에는 files:write가 필요합니다.
3-3. 다중 이미지 업로드 실패
메뉴 사진 20장을 한 번에 올리면 Slack에서 internal_error가 발생했습니다.
해결: 헤더 메시지를 chat_postMessage로 보낸 뒤, thread_ts를 이용해 스레드에 사진을 순서대로 업로드합니다.
header = client.chat_postMessage(channel=channel, text=text) thread_ts = header["ts"] for i, image in enumerate(images, start=1): client.files_upload_v2( channel=channel, file=str(image), title=f"메뉴 {i}/{len(images)}", thread_ts=thread_ts, )
4. 카카오 연동 이슈
4-1. 공식 API 부재
오픈채팅 메시지·이미지를 읽을 공식 API가 없습니다.
kakaocli로 Mac 카카오톡 SQLite DB를 직접 조회합니다.⚠️ 전제 조건
Mac 카카오톡에 해당 오픈채팅이 가입·동기화되어 있어야 합니다.
서버 API가 아니라 로컬 DB 스냅샷을 읽는 구조입니다.4-2. kakaocli auth 실패
No candidate user ID produced a valid key.
- AlertKakaoIDsList의 ID는 로그인 계정 ID가 아닙니다.
- User ID가 큰 계정은 kakaocli 자동 탐색(10초 타임아웃)에서 실패할 수 있습니다.
해결: plist SHA-512 해시를 brute-force로 User ID를 복원한 뒤, DB 키 파생 스크ript를 따로 두었습니다.
python scripts/find_kakao_user_id.py # 3~5분
python scripts/derive_kakao_key.py
kakaocli auth --user-id <찾은_ID>
4-3. Mac DB 미동기화
오픈채팅에 사진이 있어도 Mac 카카오톡이 꺼져 있거나 동기화가 안 되면 수집에 실패합니다.
해결 순서
- 11:00 수집 전 pgrep으로 카카오톡 실행 여부 확인 → 꺼져 있으면 open -a로 자동 실행
- has_today_menu_photos()를 15초 간격으로 폴링, 최대 120초 대기
- 동기화 확인 후 fetch_today_menus() 실행
4-4. allowlist — 다른 채팅방 접근 차단
계정에 여러 채팅방이 있으므로 URL과 방 이름으로 하나의 방만 허용합니다.
- KAKAO_OPEN_LINK_URL — URL 정확히 일치
- KAKAO_CHAT_NAME — 방 이름 부분 일치
- SQL은 검증된 chatId 하나에만 실행
- 다른 방 검색·전송 로직 없음
4-5. attachment에서 이미지 URL 추출
kakaocli SQL 결과의 attachment JSON에서 imageUrls / thumbnailUrls를 파싱한 뒤 HTTP로 다운로드합니다.
5. macOS 스케줄링
5-1. launchd vs schedule
Python schedule 모듈 대신 launchd를 사용합니다. Mac 재부팅·로그인 후에도 안정적으로 동작합니다.
bash scripts/install_launchd.sh # 11:00 수집, 11:29 알림
bash scripts/install_wake_schedule.sh # 10:58 깨우기
5-2. 잠자기 / 로그아웃 / 화면 잠금
상태 launchd 실행
화면 잠금 ✅ 가능 로그아웃 ❌ 불가 잠자기 ❌ → pmset repeat wake 필요 pmset repeat wake는 한 번만 등록할 수 있습니다.
11:00·11:29 각각 깨우기는 불가하므로 10:58 한 번으로 두 작업을 모두 커버합니다.5-3. 전체 디스크 접근
launchd에서 kakaocli가 카카오 DB를 읽으려면 Python 바이너리에 전체 디스크 접근 권한이 필요합니다.
💡 .venv/bin/python은 심볼릭 링크라 권한 설정 UI에서 회색으로 보일 수 있습니다.
실제 바이너리 경로(/opt/homebrew/opt/python@3.14/bin/python3.14)를 직접 추가하세요.
6. 비즈니스 로직
오늘 메뉴 없으면 알림 없음
수집에 실패해도 «메뉴를 찾지 못했습니다» 알림은 보내지 않습니다. 어제 캐시로 대체 전송하지도 않습니다.업로드 후 캐시 삭제
Slack 업로드 성공 후 data/menu/ 로컬 캐시를 삭제합니다. 실패 시에는 캐시를 남깁니다.주말·공휴일 스킵
holidays 패키지로 KR 공휴일과 토·일을 제외합니다.
7. 환경 변수
SLACK_BOT_TOKEN=xoxb-... SLACK_CHANNEL_ID=C... KAKAO_CHAT_NAME=오픈채팅방이름 KAKAO_OPEN_LINK_URL=https://open.kakao.com/o/... KAKAO_CLI=kakaocli KAKAO_APP_PATH=/Applications/KakaoTalk.app KAKAO_SYNC_POLL_SECONDS=15 KAKAO_SYNC_MAX_WAIT_SECONDS=120 TZ=Asia/Seoul
8. 테스트 방법
.venv/bin/python scripts/collect_menu.py --dry-run
.venv/bin/python scripts/send_lunch_alert.py --force --dry-run
.venv/bin/python scripts/send_lunch_alert.py --force
Mac 기본 python 명령이 없을 수 있으므로 .venv/bin/python을 사용하는 편이 안전합니다.
9. 이슈 요약표
이슈 해결
카카오 오픈채팅 공식 API 없음 kakaocli로 Mac 로컬 DB 읽기 kakaocli auth 실패 User ID brute-force + 키 파생 스크립트 Mac DB 미동기화 카카오톡 자동 실행 + 폴링 대기 Slack 다중 이미지 internal_error 헤더 + 스레드 순차 업로드 xapp- 토큰 오류 xoxb- Bot Token 사용 잠자기 중 스케줄 미실행 pmset repeat wake 10:58 pmset wake 1개 제한 10:58 한 번으로 11:00·11:29 커버 다른 채팅방 오접근 URL + 방 이름 allowlist launchd DB 접근 거부 Python 실제 바이너리에 전체 디스크 접근
마무리
한번 세팅하여 평일마다 정해진 시간에 Slack 채널로 메뉴 사진이 자동 전송되는 알림 봇을 구현하니
활발하지 않던 슬랙 채널의 채팅이 동료분들과 점심시간에 소통도 하게 되고 만족스러운 결과였습니다.'개발 이야기' 카테고리의 다른 글
JSX의 불편함을 해결한다는 TSRX, 직접 따져봤습니다 (0) 2026.06.22 재직중인 회사에서 법인카드 정산·영수증 처리 자동화하기 (0) 2026.06.19 WebView에서 iOS처럼 스와이프 뒤로가기 넣기 — Next.js App Router 실전기 (0) 2026.06.19