# 서버에서 채팅방으로 알림 보내기

> 내 서버에서 서버 API(v2)로 채팅방 채널에 데이터를 보내고, 페이지 스크립트나 플러그인이 서버가 보낸 것만 골라 표시하는 흐름을 설명해요.

내 서버에서 일어난 일(결제 완료, 새 글, 방송 시작 등)을 서버 API로 채팅방 채널에 보내면, 페이지 스크립트가 받아 채팅방에 표시할 수 있어요. 요청 형식과 오류 코드 전체는 [서버 REST API](/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_…`)는 발급할 때 한 번만 보이니 바로 복사해 둬요. 같은 화면에서 주소에 넣을 **API 채팅방 아이디**(`room_…`)도 복사해요.

API 키는 내 서버에만 둬요. 브라우저 자바스크립트에서 서버 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]);
```

## 한 사람에게만 보내기 {#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":[]`로 저장해요. 받는 쪽에는 빈 값이 가요.
- 한도는 채널 저장과 같아요(무료 채팅방은 채널 하나·1KB). → [채널 API와 한도](/doc/3.5#save)
- 저장값은 채팅방이 비거나 서버가 다시 시작돼도 남아요.
- 위 받는 쪽 예제는 `msg.type !== 0`을 버리므로, 저장값까지 받으려면 그 줄을 빼요.

## 자주 나는 오류 {#errors}

| 오류 | 뜻 | 할 일 |
| --- | --- | --- |
| 401 `missing_key`, `invalid_key` | 키가 없거나 이 채팅방의 키가 아님 | `Authorization: Bearer` 헤더와 API 채팅방 아이디를 확인해요. 회원 연동 토큰이 아니라 API 키를 넣어요 |
| 403 `insufficient_scope` | 키에 이벤트 권한이 없음 | **API** 메뉴에서 그 키의 권한을 고쳐요 |
| 400 `invalid_data` | `data`가 배열이 아니거나 줄바꿈 같은 제어 문자가 있음 | `[...]` 모양으로, 줄바꿈 없이 보내요 |
| 404 `target_not_found` | `to`의 닉네임이 채팅방에 없음 | 닉네임을 확인해요 |
| 429 `rate_limited`, `chat_rate_limited` | 호출·채널 전송 한도 초과 | 보내는 횟수를 줄여요. → [서버 REST API](/doc/3.6#response) |

요청 본문 앞부분과 요청 IP는 **API** 메뉴의 호출 기록에 30일 동안 남아요. 개인정보나 결제 정보는 보내지 말고, 표시할 문구만 보내세요. 완성된 예제는 [결제 완료 알림](/doc/4.4)에 있어요. 예전 v1 방식(`/rooms/{채팅방}/plugin/...`)은 [서버 REST API](/doc/3.6#v1)에 정리해 두었어요.
