# サーバーからチャットルームに通知を送る

> 自分のサーバーからサーバーAPI（v2）でチャットルームのチャンネルにデータを送り、ページスクリプトやプラグインでサーバーが送ったものだけを選んで表示する流れを説明します。

自分のサーバーで起きたこと（決済完了、新しい投稿、配信開始など）をサーバーAPIでチャットルームのチャンネルに送ると、ページスクリプトがそれを受け取ってチャットルームに表示できます。リクエスト形式とエラーコードの全体は[サーバーREST API](/ja/doc/3.6)にあります。

コードを書かずに表示だけしたい場合は、**イベント通知ウィンドウ**プラグインをオンにして、`plugin` を `alertBox` にして送ります。投稿・寄付・購読の通知をチャット一覧の中に色付きの枠で表示します。寄付目標バー・配信状態・スコアボード・カウントダウン・ランキングのプラグインも同じ方式です。

## 流れ {#flow}

1. ページスクリプト（またはプラグイン）がチャットルームでチャンネル（例: `notice-bot`）を開いて待ちます。
2. 自分のサーバーが `POST https://api.uchat.io/v2/rooms/{id}/events` に `{"plugin":"notice-bot","data":[...]}` を送ります。
3. チャットルームにいま接続している人が、そのチャンネルでデータを受け取ります。送信者は `auth` が4のサーバーユーザーです。
4. ページスクリプトが `auth` を確認し、画面に表示します。

そのまま送ると、いま接続している人にだけ届きます。誰もいなければ受け取る人がいないだけで、エラーにはなりません。あとから入室した人にも表示する必要がある場合は `"save": true` で保存します（下の[あとから入室した人にも](#save)）。

## APIキー {#api-key}

管理ページの **API** メニューで **APIキーを発行** を押します。名前を決め、権限で **イベント** をオンにしておきます。キー（`uchat_…`）は発行時に一度だけ表示されるので、すぐにコピーしておきます。同じ画面で、URLに入れる **APIチャットルームID**（`room_…`）もコピーします。

APIキーは自分のサーバーにだけ置いてください。ブラウザのJavaScriptからサーバーAPIを呼ぶとキーが露出し、誰でもサーバーユーザーとしてデータを送れるようになります。キーが漏れたら、**API** メニューですぐに無効化するか、値を再発行します。

## 1. 受信側の準備 {#receive}

```html
<script>U=window.U=window.U||{},U.events=U.events||[],U.chat=function(n){return{on:function(e,t){U.events.push([n,e,t])},off:function(e){for(var t=U.events.length;t>0;)U.events[--t][0]==n&&U.events[t][1]==e&&U.events.splice(t,1)}}};</script>
<script async src="https://client.uchat.io/uchat.js"></script>
<u-chat room="my-room" style="display:inline-block; width:100%; max-width:500px; height:500px;"></u-chat>
<script>
function escapeHtml(text) {
	return String(text).replace(/[&<>"']/g, function (c) {
		return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
	});
}

U.chat('my-room').on('after.create', function (room) {
	room.plugin.add('notice-bot').onReceived(function (room, msg) {
		if (!msg.user || msg.user.auth !== 4) return; // サーバー API または保存値だけ
		if (msg.type !== 0) return; // サーバー API で全員に送られたものだけ
		if (typeof msg.data[0] !== 'string') return;
		room.print('[お知らせ] ' + escapeHtml(msg.data[0]));
	});
});
</script>
```

- チャットルームの参加者も同じチャンネル名でデータを送れます。サーバーが送ったものだけを信用するには `msg.user.auth === 4` を確認します。`auth` 4は、サーバーAPIで送られたデータ、またはモデレーター以上が保存した値です。保存値は入室時に `type` 1で届くため、全員に送られたサーバーAPIのデータ（`type` 0）だけを受け取るには `msg.type === 0` も確認します。ニックネームを指定して送ったサーバーAPIのデータも `type` 1なので、保存値と区別できません。サーバーユーザーにはニックネームがないため、ニックネームで確認しないでください。
- `room.print` はHTMLとして解釈するため、受け取った文字列はエスケープしてから入れます。

## 2. サーバーから送る {#send}

curl:

```sh
curl -X POST https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events \
  -H "Authorization: Bearer uchat_..." \
  -H "Content-Type: application/json" \
  -d '{"plugin":"notice-bot","data":["注文 #1024 の決済が完了しました",12000]}'
```

- `data` は値の配列です。文字列・数値・真偽値を使い、受信側にはすべて文字列で届きます。
- 成功するとHTTP 202と `{"data":{"sent":true}}`、失敗するとHTTPステータスと `{"error":{"code":…,"message":…}}` が返ります。
- 送る前に、**API** メニューの **試す** で同じ本文を実行してみることができます。

Node.js（18以上）:

```js
const ROOM = 'room_xxxxxxxxxxxxxxxx';

async function sendToRoom(plugin, data, options = {}) {
	const res = await fetch(`https://api.uchat.io/v2/rooms/${ROOM}/events`, {
		method: 'POST',
		headers: {
			Authorization: `Bearer ${process.env.UCHAT_API_KEY}`,
			'Content-Type': 'application/json'
		},
		body: JSON.stringify({ plugin, data, ...options })
	});
	const result = await res.json();
	if (!res.ok) console.warn('uchat api error', res.status, result.error);
	return result;
}

await sendToRoom('notice-bot', ['注文 #1024 の決済が完了しました', 12000]);
```

PHP:

```php
<?php
function uchat_send($plugin, array $data, array $options = []) {
	$ch = curl_init('https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events');
	curl_setopt_array($ch, [
		CURLOPT_POST => true,
		CURLOPT_HTTPHEADER => [
			'Authorization: Bearer ' . getenv('UCHAT_API_KEY'),
			'Content-Type: application/json',
		],
		CURLOPT_POSTFIELDS => json_encode(['plugin' => $plugin, 'data' => $data] + $options),
		CURLOPT_RETURNTRANSFER => true,
	]);
	$result = json_decode(curl_exec($ch), true);
	curl_close($ch);
	return $result;
}

uchat_send('notice-bot', ['注文 #1024 の決済が完了しました', 12000]);
```

## 1人だけに送る {#send-to-one}

本文の `"to"` にニックネームを入れると、その人にだけ送ります。受信側の `msg.type` は1です。日本語のニックネームもそのまま使えます。

```sh
curl -X POST https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events \
  -H "Authorization: Bearer uchat_..." \
  -H "Content-Type: application/json" \
  -d '{"plugin":"notice-bot","to":"みなみ","data":["みなみさん、クーポンが発行されました"]}'
```

そのニックネームの人がいまチャットルームにいなければ、HTTP 404 `target_not_found` が返ります。

## あとから入室した人にも {#save}

`"save": true` を付けると、そのチャンネルの最後の値として保存されます。いまいる人に送り、あとから入室する人は入室時に受け取ります（`msg.type` 1）。チャットルームに誰もいないときに送っても保存されます。

```sh
curl -X POST https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events \
  -H "Authorization: Bearer uchat_..." \
  -H "Content-Type: application/json" \
  -d '{"plugin":"notice-bot","save":true,"data":["今夜12時にメンテナンスがあります"]}'
```

- 削除するには `"data":[]` で保存します。受信側には空の値が届きます。
- 上限はチャンネルの保存と同じです（無料のチャットルームはチャンネル1つ・1KB）。→ [チャンネルAPIと上限](/ja/doc/3.5#save)
- 保存値はチャットルームが空になってもサーバーが再起動しても残ります。
- 上の受信側の例は `msg.type !== 0` を捨てるため、保存値も受け取るにはその行を外します。

## よくあるエラー {#errors}

| エラー | 意味 | 対処 |
| --- | --- | --- |
| 401 `missing_key`、`invalid_key` | キーがない、またはこのチャットルームのキーではない | `Authorization: Bearer` ヘッダーとAPIチャットルームIDを確認します。会員連携のトークンではなくAPIキーを入れます |
| 403 `insufficient_scope` | キーにイベント権限がない | **API** メニューでそのキーの権限を直します |
| 400 `invalid_data` | `data` が配列ではない、または改行などの制御文字がある | `[...]` の形で、改行なしで送ります |
| 404 `target_not_found` | `to` のニックネームがチャットルームにいない | ニックネームを確認します |
| 429 `rate_limited`、`chat_rate_limited` | 呼び出し・チャンネル送信の上限を超えた | 送信回数を減らします。→ [サーバーREST API](/ja/doc/3.6#response) |

リクエスト本文の先頭部分とリクエストIPは、**API** メニューの呼び出し履歴に30日間残ります。個人情報や決済情報は送らず、表示する文言だけを送ってください。完成した例は[決済完了の通知](/ja/doc/4.4)にあります。以前のv1方式（`/rooms/{チャットルーム}/plugin/...`）は[サーバーREST API](/ja/doc/3.6#v1)にまとめています。
