# チャンネルAPIと上限

> room.pluginチャンネルでリアルタイムにデータをやり取りする関数、値の形式、保存と送信の上限をまとめた正式なリファレンスです。

チャンネル(`room.plugin`)は、同じチャットルームにいる人同士、または自分のサーバーとチャットルームの間で、チャットメッセージ以外のデータをリアルタイムにやり取りする通り道です。このページは関数・値の形式・上限の正式なリファレンスです。全体の流れは[リアルタイムデータの送受信](/ja/doc/2.4)、サーバーから送る方法は[サーバーREST API](/ja/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)` | ニックネーム、値の配列 | 1人だけに送ります。自分のニックネームを対象にしない限り、自分は受け取りません。対象がいなければ何も起きません(エラーなし) |
| `save(values)` | 値の配列 | 最新の状態をサーバーに保存します。モデレーター以上。下の[保存](#save)を参照 |
| `clear()` | — | このチャンネルの保存値を消します。モデレーター以上 |
| `onReceived(fn)` | `fn(room, data)` | 受信コールバックを追加。下の[受信](#receive)を参照 |
| `addParser(fn)` | `fn(args)` | `!チャンネル名 引数` の入力を処理するコールバックを追加。下の[コマンド入力の処理](#parser)を参照 |
| `destroy()` | — | チャンネルを一覧から削除します。登録したコールバックも一緒に破棄されます |

- 関数に戻り値はありません。接続前に呼ぶと送信されません。サーバーが拒否すると `error` イベントが届きます(権限402、値のエラー101、上限428)。
- `onReceived`、`addParser` で追加したコールバックは1つずつ外すことができません。`add` は同じオブジェクトを返すため、他のスクリプトとチャンネル名が重複するとコールバックが混ざります。固有の名前を使ってください。

## 値の形式 {#values}

送る値は**配列**で、各要素は下の形式だけがそのまま渡されます。

| 送った値 | 受け取る値 |
| --- | --- |
| 文字列 | 文字列 |
| 0以上の整数 | 数値 |
| 負の数、小数 | 数値 |
| `true`, `false` | `true`, `false` |
| `undefined` | `undefined`(空の要素) |
| オブジェクト、配列、`null`、関数 | **除外されます。** 後ろの値が1つずつ前に詰められます |

構造のあるデータは要素に分けて送るか、`JSON.stringify` で文字列1つにまとめてください。受け取った文字列を `JSON.parse` するときは `try` で囲んでください。

```js
ch.send(['move', 3, 5]);                                  // 要素に分ける
ch.send(['state', JSON.stringify({ x: 3, y: 5 })]);       // 文字列1つ
```

## 受信 {#receive}

`onReceived` コールバックは `(room, data)` を受け取ります。

| キー | 説明 |
| --- | --- |
| `data.user` | 送信者の[ユーザーオブジェクト](/ja/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` コールバックは呼ばれません([イベント](/ja/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) |
| チャンネル1つの保存値 | 無料ルーム1KB、有料ルーム4KB |
| チャットルーム全体の保存値 | 60KB |
| 無料ルームのチャンネル数 | 実質1つ。別のチャンネルに保存すると、既存のチャンネルの保存値が消えます |
| 値の形式 | 文字列・数値・真偽値 |

- チャンネル1つのサイズは、値の配列をJSONにしたときのUTF-8バイト数です。全体の60KBは、チャンネル名を含む `{ チャンネル: 値の配列 }` 全体をJSONにしたサイズです。超えた場合やチャンネル名がルールに合わない場合は、エラー101で拒否されます。
- 無料ルームで保存値が別のチャンネルのものに置き換わると、保存した人に `control` イベント(`type: 'pluginDataReplaced'`)が届きます。
- `save` で保存した値はチャットサーバーのメモリにだけあります。チャットルームが閉じたりサーバーが再起動したりすると消えることがあり、有料ルームが無料に変わると削除されます。
- 自分のサーバーから[サーバーREST API](/ja/doc/3.6#events)の `"save": true` で保存すると、uchatのサーバーにも残るため、チャットルームが閉じたりサーバーが再起動したりしても、次にチャットルームが開いたときに再び読み込まれます。この場合はいま接続している人にも同時に送り、上限は上の表と同じです。長く残す必要がある値（配信状態・スコア・目標金額など）にはこの方法を使ってください。

## 送信の上限 {#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` せず、まとめて送るか、変わったときだけ送ってください。
