본문으로 건너뛰기

봇 만들기

이 문서를 따라 하면 DJ 방송의 채팅을 실시간으로 받고, 봇 이름으로 채팅을 보내는 봇을 만들 수 있어요. 동의를 받는 것부터 첫 발화까지 한 번에 이어져요.

준비물​

  • 앱 등록 에서 받은 Client ID 와 Client Secret
  • 앱에 등록한 로그인 리디렉션 URL
  • 앱에 등록한 권한(scope) — 이 문서는 events.chat 과 chat.send 를 써요
  • Node.js 22.6 이상 — 예제가 TypeScript 라 --experimental-strip-types 가 필요해요

API 주소​

아래 예제는 base URL 을 $SPOON_BASE_URL 로 적었어요. 연동할 지역의 주소를 넣고 실행해요.

export SPOON_BASE_URL="https://kr-openapi.spooncast.net"
지역base URL
한국https://kr-openapi.spooncast.net
일본https://jp-openapi.spooncast.net

내 앱이 등록된 지역의 주소를 써요. 어느 지역인지는 DJ 계정이 정하니, 다른 지역 주소로 부르면 그 DJ 의 방송을 찾지 못해요.

1. DJ 의 동의를 받아요​

DJ 브라우저를 동의 화면으로 보내고, 돌아온 code 를 받아요. 자세한 규칙은 사용자 동의 받기 에 있어요.

https://developers.spooncast.net/kr/oauth/authorize
?response_type=code
&client_id={Client ID}
&redirect_uri={로그인 리디렉션 URL}
&scope=events.chat%20chat.send
&state={내가 만든 임의 값}

authorize 주소에도 지역이 들어가요. 한국은 /kr, 일본은 /jp 예요.

DJ 가 허용하면 내 리디렉션 URL 로 code 가 실려 돌아와요. 이 값은 60초 안에 써야 해요.

2. code 를 토큰으로 바꿔요​

받은 code 를 access token 으로 교환해요. Client Secret 을 쓰는 호출이라 반드시 서버에서. 불러요.

curl -X POST "$SPOON_BASE_URL/v1/oauth/token" \
-u "{Client ID}:{Client Secret}" \
-d "grant_type=authorization_code" \
-d "code={받은 code}" \
-d "redirect_uri={로그인 리디렉션 URL}"
{
"access_token": "at_3f9c1e7b8d2a4056...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_7b2e5a9c4f10d386...",
"scope": "events.chat chat.send"
}
값수명알아둘 것
access_token1시간모든 봇 API 호출에 Authorization: Bearer 로 붙여요.
refresh_token30일새 access token 을 받을 때 써요.

상주 봇은 갱신을 반드시 구현해요. access token 이 만료되면 열려 있던 이벤트 스트림도 함께 끝나요. 만료 직전이 아니라 여유 있게(예: 만료 1시간 전). 갱신하고, 갱신 결과는 재시작해도 남도록 저장해요. 메모리에만 두면 프로세스를 다시 띄울 때마다 DJ 에게 다시 동의를 받아야 해요.

갱신하면 refresh token 도 새 값으로 바뀌어요. 응답의 새 refresh_token 을 저장하지 않으면 다음 갱신에서 실패해요 — 자세한 내용은 토큰 에 있어요.

갱신은 같은 엔드포인트에 grant_type=refresh_token 으로 불러요.

curl -X POST "$SPOON_BASE_URL/v1/oauth/token" \
-u "{Client ID}:{Client Secret}" \
-d "grant_type=refresh_token" \
-d "refresh_token={받은 refresh_token}"

발급받은 토큰도 환경변수로 내보내요.

export SPOON_ACCESS_TOKEN="{access_token}"

3. 이벤트 스트림에 연결해요​

연결하면 청취자 고지가 나가요

스트림에 연결하는 순간 방송에 봇 이름으로 청취자 고지가 올라가요. 봇 코드에서 할 일은 없어요 — 청취자 고지를 보세요.

DJ 가 방송 중이면 채팅이 곧바로 흘러 들어와요. 먼저 curl 로 확인해요.

curl -N \
-H "Authorization: Bearer {access_token}" \
-H "Accept: text/event-stream" \
"$SPOON_BASE_URL/v1/live/events"
:heartbeat

id:1788328046114-0
event:chat
data:{"user":{"id":"Ab1Cd2Ef3Gh4Ij5Kl6Mn7O","nickname":"스푼청취자"},"isDj":false,"message":"안녕하세요","sentTime":"2026-09-02T05:47:26.114Z"}

방송 중이 아니면 404 가 와요. 오류가 아니라 상태이니, DJ 가 방송을 시작할 때까지 기다렸다 다시 연결해요.

아래는 같은 일을 하는 Node 예제예요. 그대로 저장해 실행하면 채팅이 콘솔에 찍혀요.

// bot.ts — node --experimental-strip-types bot.ts

interface EventUser {
id: string;
nickname: string | null;
}

interface ChatEvent {
user: EventUser;
isDj: boolean;
message: string;
sentTime: string;
}

const BASE = process.env.SPOON_BASE_URL;
const TOKEN = process.env.SPOON_ACCESS_TOKEN;

const res = await fetch(`${BASE}/v1/live/events`, {
headers: { Authorization: `Bearer ${TOKEN}`, Accept: 'text/event-stream' },
});

if (!res.ok || !res.body) throw new Error(`스트림을 열지 못했어요: ${res.status}`);

const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';

for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += value;

// SSE 는 빈 줄로 이벤트를 가르고, 마지막 조각은 아직 안 끝났으니 버퍼에 남겨요
const frames = buffer.split('\n\n');
buffer = frames.pop() ?? '';

for (const frame of frames) {
const lines = frame.split('\n');
const event = lines.find((l) => l.startsWith('event:'))?.slice(6);
const data = lines.find((l) => l.startsWith('data:'))?.slice(5);
if (!data) continue; // `:heartbeat` 처럼 data 가 없는 프레임이에요

if (event === 'chat') {
const chat = JSON.parse(data) as ChatEvent;
console.log(`${chat.user.nickname ?? '익명'}: ${chat.message}`);
}
}
}
node --experimental-strip-types bot.ts

브라우저 EventSource 로는 연결할 수 없어요. 토큰을 Authorization 헤더로 보내는데 EventSource 는 헤더를 붙이지 못해요. 서버에서 위 코드를 돌리고 결과만 브라우저로 중계해요.

4. 채팅을 보내요​

봇 계정 이름으로 방송에 채팅을 보내요. 성공하면 본문 없이 204 가 와요.

curl -i -X POST "$SPOON_BASE_URL/v1/live/chat" \
-H "Authorization: Bearer {access_token}" \
-H "Content-Type: application/json" \
-d '{"message":"안녕하세요! 봇이 인사드려요"}'
HTTP/2 204

잘 됐는지 확인해요​

DJ 가 방송 중인 상태에서 아래가 모두 맞으면 봇이 제대로 붙은 거예요.

  1. 스트림 연결이 200 으로 열리고, 15초마다 :heartbeat 줄이 와요.
  2. 청취자가 채팅을 치면 event: chat 프레임이 바로 와요.
  3. POST /v1/live/chat 이 204 를 주고, 스푼 앱 채팅창에 봇 계정 이름으로 메시지가 보여요.
  4. 봇이 보낸 메시지는 내 스트림으로 되돌아오지 않아요 — 에코 루프를 막기 위한 규칙이에요.

다음 단계​

  • 토큰 — 발급 · 갱신 · 폐기의 전체 명세예요.
  • API 레퍼런스 — 각 엔드포인트의 요청 · 응답 · 예시예요.
  • 현재 방송 조회 — 제목 · 청취자 수 · isChatFrozen 이에요.
  • 이벤트 스트림 — 이벤트 종류, 페이로드 스키마, 재연결 전략을 확인해요.
  • 채팅 보내기 — 길이 제한과 전송 실패 처리를 확인해요.
  • 권한 고르기 — 어떤 권한이 무엇을 주는지 확인해요.
  • 에러 코드 — 어떤 응답에 어떻게 대응할지 확인해요.