BSSM 방송부 Discord 서버의 #명령 채널에서 슬래시 명령으로 ONAIR를 제어하는 봇.
ONAIR 서버 코드에 의존하지 않고 HTTP(REST)로만 통신하므로 독립 실행된다.
- 시작 시: 역할 6개 자동 생성(
관리자/부장/부원,4기/5기/6기),#알림채널 읽기전용 잠금, 슬래시 명령 길드 동기화. - 슬래시 명령 (모두
#명령채널에서만, 역할로 게이팅):/상태— 부원 이상. 스피커(정렬됨)·송출(파일명)·현수막·시보 그룹·매트릭스 상세 조회./미리보기— 부원 이상. 지금 송출 중인 이미지(현수막/채널)를 봇이 받아 디코에 첨부해 보여줌./시보— 부원 이상. 등록된 시보(예약) 목록 조회./시보그룹 [그룹] [특별]— 부장 이상. 송출할 시보 그룹(활성 그룹) 변경. 그룹은 검색 자동완성./tts [내용] [대상]— 부장 이상. TTS 방송 송출./스피커 [대상] [동작]— 부장 이상. 스피커 ON/OFF./미디어 [파일] [채널]— 부장 이상. 파일을 **이름으로 검색(자동완성)**해 송출./현수막 [대상]— 부장 이상. 빈화면/기본/업로드 이미지를 **검색(자동완성)**해 변경.- 모든 봇 출력은 이모지 없이. 파일/그룹 선택은 슬래시 자동완성 검색(개수 제한·오타 없이). 한글 파일명은 NFC로 정규화해 검색.
- ChatOps (
#chatops채널, 자연어 ·GROQ_API_KEY있을 때만):- 예) "3학년 2반 스피커 켜줘", "모두 꺼", "방송실에 '점심시간입니다' 방송해줘", "지금 상태 어때?"
- 흐름: 말 → Groq LLM이 기능·파라미터 판별 → 부족하면 되묻고(예: "스피커 켜줘" → "어디를 켤까요?") → 충분하면 실행.
- 대상 변환:
N학년 M반→N-M,모두/전교→전체. 유효 이름/그룹 밖은 임의로 만들지 않고 되묻는다. - 권한: 조회(get_status)=부원+, 스피커·TTS=부장+. 도구 실행 직전에 역할을 확인.
알림(웹훅) 기능은 봇이 아니라 ONAIR 서버(
notification_service)가 담당한다. 이 저장소는 명령 봇만.
.env에GROQ_API_KEY채우기 (console.groq.com).- Discord 개발자 포털 → 앱 → Bot → Privileged Gateway Intents → "MESSAGE CONTENT INTENT" 켜기 (자연어 메시지 본문을 읽으려면 필수. 이걸 안 켜고 키만 넣으면 봇 시작 시 인텐트 오류가 난다.) 봇이 100개 미만 서버에 있으면 심사 없이 토글만으로 켜진다.
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # Windows: copy .env.example .env
# .env 에 DISCORD_BOT_TOKEN 등을 채운다
python discord_bot.py같은 봇 토큰으로 여러 PC에서 동시에 띄우지 말 것. 봇은 토큰 하나 = Discord 상 하나의 정체성이라, 인스턴스를 여러 개 띄우면:
- 슬래시 명령 하나를 모든 인스턴스가 동시에 수신해서 서로 응답하려고 경쟁한다. 먼저 응답한 하나만 성공하고 나머지는 "이미 처리된 상호작용" 에러.
- 더 나쁜 건, 각 인스턴스가 자기
.env의ONAIR_API로 독립적으로 요청을 쏜다 → 같은 명령이 여러 번 실행될 수 있다(예: TTS가 두 번 나감).
즉 "어느 서버를 기준으로 가는가"는 각 봇 인스턴스의 .env 안 ONAIR_API가 결정한다. 기본값 127.0.0.1:8000은 "봇이 돌고 있는 그 PC의 서버"를 뜻한다.
권장 구성: ONAIR 서버가 있는 PC(또는 그 서버에 tailnet으로 닿는 PC) 한 곳에서만 봇을 실행한다.
- 봇을 서버와 같은 PC에서 →
ONAIR_API=http://127.0.0.1:8000/api - 봇을 다른 PC에서 →
ONAIR_API=http://<서버_Tailscale_IP>:8000/api
가용성이 걱정되면 인스턴스를 늘리지 말고 자동 재시작(Windows: 작업 스케줄러/nssm, Linux: systemd)으로 단일 인스턴스를 살려두는 방식을 쓴다. discord.py는 게이트웨이가 끊겨도 스스로 재접속한다.