# イベント

> チャットルームのイベント名、受け取る値、beforeでfalseを返したときに実際に止まる処理をまとめた一覧です。

チャットルームで起きたことは `U.chat(チャットルーム).on(名前, function (room, data) { ... })` で受け取ります。このページでは、イベントごとにいつ届くのか、`data` に何が入っているのか、`before.` で `false` を返すと実際に何が止まるのかを、コードに基づいてまとめています。登録と解除の方法は [U.chat() と room オブジェクト](/ja/doc/3.2#on-off)をご覧ください。

## ルール {#rules}

- ほとんどのイベントは `before.名前` と `after.名前` の組で届きます。`before.` は既定の処理の前に、`after.` は処理の後に届きます。
- コールバックは `(room, data)` を受け取ります。`room` は [roomオブジェクト](/ja/doc/3.2)です。
- `before.` のコールバックが厳密に `false` を返すと、それ以降のコールバックは呼ばれず、下の表の「falseを返すと」欄に書かれた処理だけが止まります。**止まる範囲はイベントごとに異なります。**
- `before.` で `data` の値を書き換えると、その後の処理(画面表示や送信)は書き換えた値を使います。書き換えられるイベントは表に記載しています。
- 既定の処理が止まった場合は `after.` も届きません。

## イベント一覧 {#events}

| イベント | タイミング | `data` | `before` でfalseを返すと |
| --- | --- | --- | --- |
| `before.create` | チャットルームの設置開始(設定・スキンを読み込む前)。設置し直すたびに再度届きます | `{}` | 止められません(設置はそのまま進みます) |
| `after.create` | チャットルーム画面の準備完了(接続前)。設置し直す(`room.reload()`)たびに再度届きます | `{ id }` | — |
| `before.join` | サーバーへ入室をリクエストする直前。再接続のたびに再度届きます | `{ user }` | 会員連携でないルーム: 入室リクエストを送りません。会員連携ルーム: 下の注意を参照 |
| `after.join` | 入室完了、自分の情報を受信。再接続のたびに再度届きます | `{ my }` | (`before` なし) |
| `before.message`, `after.message` | チャットメッセージ1行(過去の会話、自分のメッセージを含む) | [メッセージ](#message) | 画面に表示しません |
| `before.whisper`, `after.whisper` | ウィスパー(自分が送ったウィスパーの確認を含む) | [ウィスパー](#whisper) | 画面に表示しません |
| `before.send`, `after.send` | 自分がメッセージを送るとき(`/コマンド`、`!コマンド` の入力は除く) | `{ message, bold, italic, underline, color }` | 送信しません。`room.action.send()` が `false` を返します |
| `before.command`, `after.command` | コマンドを実行するとき | `{ type, data }` | 実行しません。`room.action.command()` が `false` を返します |
| `before.plugin`, `after.plugin` | チャンネルのデータを受信したとき、または `!コマンド` を入力したとき | [チャンネル](#plugin) | 受信データ: `onReceived` コールバックを呼びません。`!コマンド`: `addParser` コールバックを呼ばず、`room.action.plugin()` が `false` を返します |
| `before.user.join`, `after.user.join` | 他の人が入室。届かない場合は[ユーザーの変化](#users)を参照 | `{ user }` | 画面に入室表示をしません。`room.user.list` にはすでに追加されています |
| `before.user.quit`, `after.user.quit` | 他の人が退室 | `{ user: { nick } }` | 画面に退室表示をせず、`room.user.list` からも削除しません |
| `before.user.modi`, `after.user.modi` | 誰か(自分を含む)のニックネーム・権限・ミュートが変わった | `{ target, changed }` | `room.my`、`room.user.list`、画面に反映しません。`room.user.get()` はすでに新しい値を返すことがあります。使わないことをおすすめします |
| `before.user.count`, `after.user.count` | 接続者数だけが変わった(入室・退室の代わりに届く場合は[ユーザーの変化](#users)を参照) | `{ count }` | 画面の接続者数を更新しません |
| `before.update`, `after.update` | チャットルームの設定が変わった | 変わった値 `{ キー: 値 }` | `room.info` と画面に反映しません |
| `before.system`, `after.system` | システム案内の行(お知らせ・BAN・ミュート・呼び出しなど、`room.print` を含む) | `{ type, data }` | 画面に表示しません |
| `before.control`, `after.control` | サーバーの制御信号(会話の消去、投票の状態など) | `{ type, data }` | スキンに渡しません |
| `before.error`, `after.error` | エラー(サーバーのエラーと、コマンド引数のチェックなどクライアント側のエラー) | `{ code }` | 画面にエラーを表示しません |
| `before.disconnect`, `after.disconnect` | 接続が切れた(ページを離れたときやタグを削除したときは届きません) | `{}` | スキンに知らせず、ネットワークエラー時の自動再接続も行いません |

`before.` で止めても、サーバーで起きたことはそのままです。たとえば `before.system` でBANの案内を隠してもBANは適用されますし、`before.user.quit` を止めてもその人はすでに退室しています。ほとんどの場合、変わるのは**自分の画面に見えるものだけ**です。

## 発生順序 {#order}

**チャットルームが開くとき**

1. `before.create` — 設置開始。`room.info`、`room.my` はまだ空です。
2. チャットルームの設定・言語・プラグイン・スキンを読み込みます(プラグインの実行ファイルはここで実行されます)。
3. `after.create` — スキン画面の準備完了。スキンUIとチャンネルはここで登録します。
4. `before.join` → サーバーへ入室リクエスト → `after.join`。
5. 過去の会話とお知らせが `message`、`whisper`、`system` イベントとして届きます。

入室確認(ボットチェック)画面が出るルームでは、確認を通過してから手順1が始まります。ネットワークの問題で再接続すると、手順4から再度届きます。

**自分がメッセージを送るとき**

1. `before.send` → サーバーへ送信 → `after.send`
2. サーバーがチャットルームの全員(自分を含む)にメッセージを送ります。
3. `before.message` → 画面に表示 → `after.message`

`after.send` は「サーバーへ送った」という意味にすぎず、受け付けられたという意味ではありません。サーバーが拒否するとメッセージは返ってこず、代わりに `error`(権限なし410、同じ内容の繰り返し430など)、`system`(`muted`、`bad_word`)、`control`(`slowWait`)イベントが届きます。実際に投稿されたかどうかは `after.message` で確認してください。

チャット入力が `/` で始まると `send` の代わりに `command`、`!` で始まると `plugin` イベントになります。基本スキンは、`!コマンド` を処理するチャンネルがないか止められた場合、その入力を通常のメッセージとして送ります。

**接続が切れるとき**

1. `before.disconnect` → スキンに通知 → 自動再接続の判断 → `after.disconnect`
2. ネットワークエラーで切れた場合は再接続し、`before.join` から再度届きます。

BANされた場合や、サーバーがエラーを送った直後に切れた場合は再接続しません。ブラウザを閉じたとき、別のページへ移動したとき、`<u-chat>` タグを削除したとき、`room.reload()` したときは `disconnect` イベントは**届きません**。離れる前に行う処理は、ブラウザの `pagehide` イベントで行ってください。

## 入室 {#join}

```js
U.chat('my-room').on('after.join', function (room, data) {
	// data.my は room.my と同じ形のユーザーオブジェクト
	room.print(data.my.nick + 'さん、ようこそ。');
});
```

`before.create` は画面も情報もない時点なので、使う機会はほとんどありません。止めることはできず、入室情報を変えるには `before.join` を使います。

`before.join` の `data.user`:

| ルーム | `data.user` | 変更できるもの |
| --- | --- | --- |
| 会員連携でないルーム | `{ nick }` — このブラウザに保存されたニックネーム(なければ空) | `data.user.nick` を変えると、そのニックネームで入室をリクエストします |
| 会員連携ルーム | `user_data`(または `user_data_url`)をデコードした値。`user_data` に入れたキーと `time`、`hash` | 変更しないでください。署名が合わなくなりエラー202になります |

> **注意:** 会員連携ルームで `before.join` が `false` を返しても、入室が止まるのではなく、空の入室情報でリクエストが送られてエラーで終わります。会員連携ルームでは `before.join` で入室を止めないでください。入室させたくない場合は、`user_data` を入れないか、タグを設置しないでください。

## メッセージ {#message}

```js
U.chat('*').on('after.message', function (room, data) {
	console.log(data.user.nick, data.content);
});
```

| キー | 説明 |
| --- | --- |
| `user` | 送信者の[ユーザーオブジェクト](/ja/doc/3.2#user-object) |
| `content` | 内容 |
| `style` | `{ bold, italic, underline, size, color }`。`color` がなければチャットルームの既定の文字色 |
| `created` | 送信時刻 `'YYYY-MM-DD HH:MM:SS'`(閲覧者のタイムゾーン) |
| `time` | 送信時刻(Unix秒) |
| `uid` | メッセージ固有の番号(16進数の文字列)。なければ空 |
| `past` | 入室・再接続時に改めて受け取った過去の会話なら `true`。新しく投稿されたメッセージにはありません |

- `before.message` で `data.content` を書き換えると、書き換えた内容が**自分の画面にだけ**表示されます。
- 自分が無視している人(モデレーター未満)のメッセージは `before.message` までしか届かず、画面表示と `after.message` はありません。

## ウィスパー {#whisper}

| キー | 説明 |
| --- | --- |
| `from`, `to` | 送信者、受信者の[ユーザーオブジェクト](/ja/doc/3.2#user-object) |
| `content` | 内容 |
| `style` | 現在は常に空文字列です。メッセージの `style` オブジェクトとは形が異なります |
| `created`, `time` | 送信時刻(文字列、Unix秒) |

- サーバーはウィスパーを送信者と受信者の両方に送ります。そのため、自分が送ったウィスパーもこのイベントで返ってきます。`data.from.nick` が自分のニックネームなら、自分が送ったものです。
- メッセージと違い、`uid` はありません。
- 入室時には、過去の会話と一緒に自分に関係するウィスパーが再度届きます。
- 自分が無視している人(モデレーター未満)からのウィスパー、または自分の設定がウィスパー拒否の場合は `before.whisper` までしか届かず、画面表示と `after.whisper` はありません。

## 送信とコマンド {#send-command}

```js
// 送信するメッセージの末尾に署名を付ける
U.chat('*').on('before.send#sign', function (room, data) {
	data.message += ' ~';
});

// ウィスパーコマンドを止める
U.chat('*').on('before.command#no-whisper', function (room, data) {
	if (data.type === 'whisper') return false;
});
```

- `before.send` で `message`、`bold`、`italic`、`underline`、`color` を書き換えると、書き換えた値で送信します。
- `before.command` の `type` は、小文字にして `_` を除いた名前です(`un_ignore` → `unignore`)。`data.data`(引数の配列)を書き換えると、書き換えた引数で実行します。

## チャンネル {#plugin}

`before.plugin`/`after.plugin` は2つの場合に届き、`data` の形が異なります。

| 場合 | `data` |
| --- | --- |
| 他の人やサーバーが送ったチャンネルのデータを受信 | `{ user, type, data }` — `user` は送信者、`type` は `0`(全員へ)または `1`(自分だけへ)、`data` は値の配列 |
| チャット入力欄に `!名前 引数` を入力(または `room.action.plugin()`) | `{ type: '名前', data: ['引数', ...] }` — `type` がチャンネル名 |

サーバー(REST API)が送ったデータと、入室時に再度受け取るチャンネルの保存値は、`user.auth` が `4` でニックネームが空です。サーバーからの送信かどうかは `data.user.auth === 4` だけで確認してください。

受信データにはチャンネル名が含まれていません。チャンネルごとに分けて受け取るには `room.plugin.add(チャンネル).onReceived` を使ってください。[チャンネルAPIと上限](/ja/doc/3.5)をご覧ください。

## ユーザーの変化 {#users}

```js
U.chat('*').on('after.user.modi', function (room, data) {
	// data.target  変更前のニックネーム
	// data.changed { nick?, auth?, mute? } のうち変わったもの
	if (data.changed.nick) room.print(data.target + ' → ' + data.changed.nick);
});
```

- 入室後の変化にだけ届きます。自分のニックネーム・権限・ミュートが変わったときも届き、そのとき `room.my` も変わります。入室時の権限は、`user.join` の `data.user` か `after.join` の `data.my` で確認します。
- `before.user.modi` の中では、`room.my` と `room.user.list` はまだ変わる前です。`room.user.get()` はすでに新しい値のことがあります。
- `user.join`、`user.quit` が届かず、`user.count`(3秒ごと)だけが届く場合があります。
  - **基本スキン。** ユーザー一覧が開いていても閉じていても、常にこうなります。
  - レガシースキンでユーザー一覧が閉じているとき
  - 入室時の接続者が100人以上のルーム
- この場合、入室・退室はユーザー一覧の画面にだけ反映され、ページスクリプトには誰が入退室したかを知らせるイベントがありません。`room.user.list` も更新されません。ページスクリプトが信頼できるのは接続者数(`user.count`)だけです。
- 入退室の通知のような機能は、レガシースキンでユーザー一覧が開いている小さなルームでしか動作しない前提で作ってください。サーバー側で知る必要がある場合は、[サーバーREST API](/ja/doc/3.6#stats) の接続者数を使います。
- `user.list` イベント(`{ list }`)はレガシースキンでのみ届きます。基本スキンはユーザー一覧を分割して受け取るため、このイベントは届きません。

## システム案内 {#system}

`data.type` は案内の種類、`data.data` は値の配列です。値の配列の形は種類ごとに異なります。

| `type` | 意味 |
| --- | --- |
| `notice` | お知らせ、`room.print()` |
| `ban` | BAN |
| `mute`, `permanent_mute` | ミュート(期間、永久) |
| `un_mute` | ミュート解除 |
| `to_call`, `from_call` | 呼び出しを送った、受けた |
| `invite`, `invite_notice` | 個人チャンネルへの招待を受けた、送った |
| `ignore`, `un_ignore` | 無視、無視の解除 |
| `frozen`, `unfrozen` | チャットルームの凍結、凍結解除 |
| `op`, `deop` | モデレーターの指定、解除 |
| `user_info` | `info` コマンドの結果 |
| `ip` | `ip` コマンドの結果 |
| `admin_login`, `admin_logout` | 誰かが `/login` で管理者になった、管理者をやめた |
| `clear_log` | 会話の消去 |
| `muted` | ミュート中のため自分のメッセージが拒否された |
| `bad_word` | 単語フィルターにかかり自分のメッセージが拒否された |
| `retry_connect` | 再接続を試みた(試行回数) |

```js
U.chat('*').on('after.system', function (room, data) {
	if (data.type === 'from_call') window.focus();
});
```

呼び出しの受信・招待の受信をオフにしているユーザーには、`from_call`、`invite` の `before.system` も届きません。

## 制御信号 {#control}

`data.type` は信号名、`data.data` は値の配列です。ほとんどはスキンが使う内部信号なので、名前や形が変わることがあります。ページスクリプトで役立つのは、おおむね次のものです。

| `type` | 意味 | `data` |
| --- | --- | --- |
| `clearLog` | 会話の消去 | `[]` |
| `removeChat` | 管理者がメッセージを削除した | 削除したメッセージの `uid` の一覧。`message` の `uid` と照合します |
| `slowWait` | スローモードのため自分のメッセージが拒否された | `[残りミリ秒, 送ろうとした内容]` |
| `pluginDataReplaced` | 無料ルームでチャンネルの保存値が置き換わった | `[]`。[チャンネルAPIと上限](/ja/doc/3.5#save) |

## エラー {#error}

`data.code` はエラー番号で、現在の言語の案内文は `room.language.error[code]` にあります。

```js
U.chat('*').on('after.error', function (room, data) {
	console.warn(data.code, room.language.error[data.code]);
});
```

| コード | 意味 |
| --- | --- |
| `101` | プロトコルエラー(不正な値、大きすぎるチャンネル保存値など) |
| `201` | 存在しないチャットルーム |
| `301` | すでに使われているニックネーム |
| `202` | 会員連携の署名が一致しない |
| `203` | 会員連携ルームに接続情報なしで接続 |
| `205` | 時刻連携の基準より古い接続情報、または最初に使ってから5分を過ぎた同じ `user_data` |
| `207` | チャットルームが満員 |
| `208`, `701` | パスワードが違う、パスワードが必要 |
| `402` | 権限なし |
| `404` | 対象が存在しない |
| `407` | 自分自身には使えないコマンド |
| `410` | チャット権限なし(機能の条件) |
| `412` | コマンドの引数が不正 |
| `415` | BANされている状態 |
| `416` | このチャットルームでは使えないコマンド |
| `421` | すでにその権限 |
| `422` | パスワードが違う |
| `413` | ミュート中 |
| `418` | 凍結されたチャットルーム |
| `424` | 自分がウィスパーを拒否中のため送れない |
| `425` | 送信が頻繁すぎる |
| `428` | チャンネルの送信上限を超えた |
| `429` | ログインの試行が多すぎる |
| `430` | 同じ内容を繰り返し送った |
| `501`, `502` | 個人チャンネルがない、個人チャンネルの中では個人チャンネルを作れない |

会員連携エラーの原因と解決方法は[会員連携](/ja/doc/2.1#troubleshooting)をご覧ください。

## dataの例 {#data-examples}

イベントごとに受け取る `data` の実際の形です。値は例で、サーバーが送らなかったキーは `undefined` のことがあります。自分で確認するには、`U.chat('*').on('after.message', function (room, data) { console.log(data); })` のようにコンソールに出力してみてください。

```js
// after.join
{ my: { nick: '홍길동', id: 'user1', level: 5, auth: 1, icons: '', nickcon: '',
        connected: '2026-10-17 12:00:00', time: 1792209600, mute: 0, session: 'a1b2c3d4e5f6', profileimg: '' } }

// before.join (会員連携でないルーム)
{ user: { nick: '홍길동' } }

// after.message
{ user: { nick: '홍길동', auth: 1, ... },
  content: '안녕하세요',
  style: { bold: false, italic: false, underline: false, size: '9pt', color: '#000000' },
  created: '2026-10-17 12:00:01', time: 1792209601, uid: '0a1b2c3d4e' }

// after.whisper
{ from: { nick: '홍길동', ... }, to: { nick: '김철수', ... }, style: '', content: '잠깐요',
  created: '2026-10-17 12:00:02', time: 1792209602 }

// before.send
{ message: '안녕하세요', bold: undefined, italic: undefined, underline: undefined, color: undefined }

// before.command  (/w 김철수 잠깐요)
{ type: 'whisper', data: ['김철수', '잠깐요'] }

// after.user.join / after.user.quit
{ user: { nick: '김철수', auth: 0, ... } }
{ user: { nick: '김철수' } }

// after.user.modi
{ target: '김철수', changed: { nick: '철수' } }
{ target: '김철수', changed: { auth: 2 } }
{ target: '김철수', changed: { mute: 1 } }

// after.user.count
{ count: 152 }

// after.update
{ slowSeconds: 5 }

// after.system
{ type: 'mute', data: ['관리자', '김철수', 10, '도배'] } // 管理者、対象、分、理由
{ type: 'from_call', data: ['김철수', '홍길동'] }       // 送信者、受信者

// after.control
{ type: 'removeChat', data: ['0a1b2c3d4e'] }

// after.plugin  (チャンネルのデータを受信 / !dice 1 6 を入力)
{ user: { nick: '김철수', auth: 1, ... }, type: 0, data: [4] }
{ type: 'dice', data: ['1', '6'] }

// after.error
{ code: 402 }
```
