# 채널 API와 한도

> room.plugin 채널로 실시간 데이터를 주고받는 함수, 값 형식, 저장과 전송 한도의 정본이에요.

채널(`room.plugin`)은 같은 채팅방에 있는 사람끼리, 또는 내 서버와 채팅방 사이에 채팅 메시지가 아닌 데이터를 실시간으로 주고받는 통로예요. 이 문서는 함수·값 형식·한도의 정본이에요. 흐름은 [실시간 데이터 주고받기](/doc/2.4), 서버에서 보내는 방법은 [서버 REST API](/doc/3.6)를 보세요.

## room.plugin {#room-plugin}

| 이름 | 설명 |
| --- | --- |
| `room.plugin.add(channel)` | 채널 객체를 돌려줘요. 같은 이름으로 다시 부르면 **같은 객체**를 돌려줘요 |
| `room.plugin.TO_ALL` | `0`. 받은 데이터의 `type`이 이 값이면 모두에게 보낸 것 |
| `room.plugin.ONLY_ME` | `1`. 받은 데이터의 `type`이 이 값이면 나에게만 보낸 것 |

채널 이름은 채팅방 안에서 겹치지 않게 정해요. 저장(`save`)까지 쓰려면 영문·숫자로 시작하고 영문·숫자·`_`·`-`만 쓴 64자 이하여야 해요.

```js
U.chat('*').on('after.create#dice', function (room) {
	if (room.id !== 'my-room') return;
	var ch = room.plugin.add('dice');
	ch.onReceived(function (room, data) {
		room.print(data.user.nick + '님의 주사위: ' + data.data[0]);
	});
	ch.addParser(function (args) {
		ch.send([Math.floor(Math.random() * 6) + 1]);
	});
});
```

## 채널 객체 {#channel}

| 함수 | 인자 | 설명 |
| --- | --- | --- |
| `send(values)` | 값 배열 | 채팅방의 모두에게 보내요. 보낸 나도 받아요 |
| `sendTo(nick, values)` | 닉네임, 값 배열 | 한 사람에게만 보내요. 내 닉네임을 대상으로 하지 않으면 나는 받지 않아요. 대상이 없으면 아무 일도 일어나지 않아요(오류 없음) |
| `save(values)` | 값 배열 | 마지막 상태를 서버에 저장해요. 부관리자 이상. 아래 [저장](#save) |
| `clear()` | — | 이 채널의 저장값을 지워요. 부관리자 이상 |
| `onReceived(fn)` | `fn(room, data)` | 받기 콜백 추가. 아래 [받기](#receive) |
| `addParser(fn)` | `fn(args)` | `!채널이름 인자` 입력 처리 콜백 추가. 아래 [명령 입력 처리](#parser) |
| `destroy()` | — | 채널을 목록에서 지워요. 등록한 콜백도 함께 버려져요 |

- 함수는 반환값이 없어요. 연결 전에 부르면 보내지지 않아요. 서버가 거절하면 `error` 이벤트가 와요(권한 402, 값 오류 101, 한도 428).
- `onReceived`, `addParser`로 넣은 콜백은 하나씩 뺄 수 없어요. `add`가 같은 객체를 돌려주므로 다른 스크립트와 채널 이름이 겹치면 콜백이 섞여요. 고유한 이름을 쓰세요.

## 값 형식 {#values}

보내는 값은 **배열**이고, 각 칸은 아래 형식만 그대로 전달돼요.

| 보낸 값 | 받는 값 |
| --- | --- |
| 문자열 | 문자열 |
| 0 이상의 정수 | 숫자 |
| 음수, 소수 | 숫자 |
| `true`, `false` | `true`, `false` |
| `undefined` | `undefined`(빈 칸) |
| 객체, 배열, `null`, 함수 | **빠져요.** 뒤의 값이 한 칸씩 앞으로 당겨져요 |

구조가 있는 데이터는 칸을 나눠 보내거나 `JSON.stringify`로 문자열 한 칸에 넣으세요. 받은 문자열을 `JSON.parse`할 때는 `try`로 감싸세요.

```js
ch.send(['move', 3, 5]);                                  // 칸으로 나누기
ch.send(['state', JSON.stringify({ x: 3, y: 5 })]);       // 문자열 한 칸
```

## 받기 {#receive}

`onReceived` 콜백은 `(room, data)`를 받아요.

| 키 | 설명 |
| --- | --- |
| `data.user` | 보낸 사람 [사용자 객체](/doc/3.2#user-object) |
| `data.type` | `0`(`TO_ALL`, `send`) 또는 `1`(`ONLY_ME`, `sendTo`·서버의 대상 전송·저장값) |
| `data.data` | 값 배열 |

- **서버 발신:** REST API로 보낸 데이터와 입장할 때 다시 받는 저장값은 `data.user.auth`가 `4`이고 닉네임은 비어 있어요. 서버 발신인지는 `data.user.auth === 4`로만 확인하세요.
- 저장값도 `auth` 4로 오므로, `auth` 4라고 해서 REST API가 보낸 것이라고 단정할 수는 없어요. 저장값은 부관리자 이상만 쓸 수 있어요.
- 그 밖의 데이터는 누구나 보낼 수 있어요. 닉네임만 보고 믿지 말고, 값의 형식과 범위를 검사하세요.
- `before.plugin`에서 `false`를 돌려주면 `onReceived` 콜백은 불리지 않아요([이벤트](/doc/3.3)).

## 명령 입력 처리 {#parser}

기본 스킨에서 채팅창에 `!채널이름 인자1 인자2`를 입력하면 그 채널의 `addParser` 콜백이 `(['인자1', '인자2'])`로 불려요. 인자는 공백으로 나눈 문자열이에요. `room.action.plugin('채널이름', ['인자1'])`로 같은 처리를 코드에서 부를 수 있어요.

| 경우 | 결과 |
| --- | --- |
| 그 이름의 채널이 없음 | `room.action.plugin()`이 `false`. 기본 스킨은 입력을 일반 메시지로 보내요 |
| `before.plugin`이 `false`를 돌려줌 | `false`. 콜백을 부르지 않아요 |
| 어떤 콜백이 `false`를 돌려줌 | `false`. 뒤 콜백은 불리지 않고, 기본 스킨은 입력을 일반 메시지로 보내요 |
| 그 밖 | `true`. 입력은 메시지로 보내지 않아요 |

parser는 내 브라우저에서만 돌아가요. 다른 사람에게 알리려면 콜백 안에서 `send`를 부르세요.

## 저장 {#save}

`save(values)`는 채널의 마지막 상태를 서버에 남겨요. 나중에 들어온 사람은 입장할 때 저장값을 받아요(`type` 1, `auth` 4). **지금 접속한 사람에게는 보내지 않으므로** 필요하면 `send`도 함께 부르세요.

| 항목 | 한도 |
| --- | --- |
| 쓸 수 있는 사람 | 부관리자 이상(아니면 오류 402) |
| 채널 하나의 저장값 | 무료 방 1KB, 유료 방 4KB |
| 채팅방 전체 저장값 | 60KB |
| 무료 방의 채널 수 | 사실상 하나. 다른 채널에 저장하면 기존 채널들의 저장값을 지워요 |
| 값 형식 | 문자열·숫자·불리언 |

- 채널 하나의 크기는 값 배열을 JSON으로 만들었을 때의 UTF-8 바이트 수예요. 전체 60KB는 채널 이름까지 포함한 `{ 채널: 값 배열 }` 전체를 JSON으로 만든 크기예요. 넘거나 채널 이름이 규칙에 맞지 않으면 오류 101로 거절돼요.
- 무료 방에서 저장값이 다른 채널 것으로 바뀌면 저장한 사람에게 `control` 이벤트(`type: 'pluginDataReplaced'`)가 와요.
- `save`로 저장한 값은 채팅 서버 메모리에만 있어요. 채팅방이 닫히거나 서버가 다시 시작되면 사라질 수 있고, 유료 방이 무료로 바뀌면 지워져요.
- 내 서버에서 [서버 REST API](/doc/3.6#events)의 `"save": true`로 저장하면 유챗 서버에도 남아서, 채팅방이 닫히거나 서버가 다시 시작돼도 다음에 채팅방이 열릴 때 다시 읽혀요. 이때는 지금 접속한 사람에게도 함께 보내고, 한도는 위 표와 같아요. 오래 남아야 하는 값(방송 상태·점수·목표 금액 등)은 이 방법을 쓰세요.

## 전송 한도 {#limits}

| 한도 | 내용 |
| --- | --- |
| 무료 방 채널 전송 | 최근 60초 동안 데이터를 받은 사람 수의 합이 100을 넘을 수 없어요. 넘으면 오류 428 |
| 유료 방 | 채널 전송 한도 없음 |
| 패킷 수 | 같은 IP에서 1초에 보내는 패킷(메시지·명령·채널 전송 합계)이 10개에 이르면 오류 425, 더 넘으면 연결이 끊겨요 |

받은 사람 수는 이렇게 세어요.

| 전송 | 차감 |
| --- | --- |
| `send`, REST API 전체 전송 | 그때 접속자 수 |
| `sendTo`, REST API 대상 전송 | 1 |

- 차감한 값은 60초 뒤에 돌아와요. 예를 들어 접속자 20명인 무료 방에서는 1분에 `send`를 5번까지 보낼 수 있어요.
- 페이지 스크립트의 `send`, `sendTo`와 REST API 전송이 같은 한도를 나눠 써요.
- 한도에 걸린 무료 방에서는 `save`도 오류 428로 거절돼요.

자주 바뀌는 상태(위치, 타이머)는 매번 `send`하지 말고 묶어서 보내거나, 바뀔 때만 보내세요.
