이벤트
채팅방 이벤트의 이름, 받는 값, before에서 false를 돌려줄 때 실제로 막히는 것을 정리한 목록이에요.
마지막 수정 2026년 10월 20일
채팅방에서 일어나는 일을 U.chat(채팅방).on(이름, function (room, data) { ... })으로 받아요. 이 문서는 이벤트마다 언제 오는지, data에 무엇이 있는지, before.에서 false를 돌려주면 실제로 무엇이 막히는지를 코드 기준으로 정리해요. 등록·해제 방법은 U.chat()과 room 객체를 보세요.
규칙
- 대부분의 이벤트는
before.이름과after.이름짝으로 와요.before.는 기본 처리 전에,after.는 처리 뒤에 와요. - 콜백은
(room, data)를 받아요.room은 room 객체이에요. before.콜백이false를 정확히 돌려주면 그 뒤의 콜백은 불리지 않고, 아래 표의 "false를 돌려주면" 칸에 적힌 처리만 멈춰요. 이벤트마다 막히는 범위가 달라요.before.에서data의 값을 고치면 이어지는 처리(화면 표시, 전송)가 고친 값을 써요. 고칠 수 있는 이벤트는 표에 적었어요.- 막혀서 기본 처리가 멈추면
after.도 오지 않아요.
이벤트 목록
| 이벤트 | 언제 | data |
before에서 false를 돌려주면 |
|---|---|---|---|
before.create |
채팅방 설치를 시작함(설정·스킨을 받기 전). 다시 설치할 때마다 다시 옴 | {} |
막을 수 없어요(설치는 그대로 진행) |
after.create |
채팅방 화면 준비 완료(접속 전). 다시 설치(room.reload())할 때마다 다시 옴 |
{ id } |
— |
before.join |
서버에 입장을 요청하기 직전. 다시 접속할 때마다 다시 옴 | { user } |
회원 연동이 아닌 방: 입장 요청을 보내지 않아요. 회원 연동 방: 아래 주의 참고 |
after.join |
입장 완료, 내 정보를 받음. 다시 접속할 때마다 다시 옴 | { my } |
(before 없음) |
before.message, after.message |
채팅 메시지 한 줄(지난 대화, 내 메시지 포함) | 메시지 | 화면에 표시하지 않아요 |
before.whisper, after.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 |
채널 데이터를 받거나, !명령을 입력할 때 |
채널 | 받은 데이터: onReceived 콜백을 부르지 않아요. !명령: addParser 콜백을 부르지 않고 room.action.plugin()이 false를 돌려줘요 |
before.user.join, after.user.join |
다른 사람이 들어옴. 오지 않는 경우는 사용자 변화 참고 | { 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 |
접속자 수만 바뀜(입장·퇴장 대신 오는 경우는 사용자 변화 참고) | { count } |
화면의 접속자 수를 바꾸지 않아요 |
before.update, after.update |
채팅방 설정이 바뀜 | 바뀐 값 { 키: 값 } |
room.info와 화면에 반영하지 않아요 |
before.system, after.system |
시스템 안내 줄(공지·차단·채팅금지·호출 등, room.print 포함) |
{ type, data } |
화면에 표시하지 않아요 |
before.control, after.control |
서버 제어 신호(대화 청소, 투표 상태 등) | { type, data } |
스킨에 전달하지 않아요 |
before.error, after.error |
오류(서버 오류와, 명령 인자 검사 같은 내 쪽 오류) | { code } |
화면에 오류를 표시하지 않아요 |
before.disconnect, after.disconnect |
연결이 끊김(페이지를 떠나거나 태그를 지울 때는 오지 않음) | {} |
스킨에 알리지 않고, 네트워크 오류일 때의 자동 재접속도 하지 않아요 |
before.에서 막아도 서버에서 일어난 일은 그대로예요. 예를 들어 before.system으로 차단 안내를 숨겨도 차단은 적용되고, before.user.quit을 막아도 그 사람은 이미 나갔어요. 대부분 내 화면에 보이는 것만 바뀌어요.
일어나는 순서
채팅방이 열릴 때
before.create— 설치 시작.room.info,room.my는 아직 비어 있어요.- 채팅방 설정·언어·플러그인·스킨을 불러와요(플러그인 실행 파일이 여기서 실행됨).
after.create— 스킨 화면 준비 완료. 스킨 UI와 채널은 여기서 등록해요.before.join→ 서버에 입장 요청 →after.join.- 지난 대화와 공지가
message,whisper,system이벤트로 와요.
입장 확인(봇 검사) 화면이 뜨는 방은 확인을 통과한 뒤 1번부터 시작해요. 네트워크 문제로 다시 접속하면 4번부터 다시 와요.
내가 메시지를 보낼 때
before.send→ 서버로 전송 →after.send- 서버가 채팅방 모두에게(나 포함) 메시지를 보내요.
before.message→ 화면에 표시 →after.message
after.send는 "서버로 보냈다"는 뜻일 뿐, 받아들여졌다는 뜻이 아니에요. 서버가 거절하면 메시지가 돌아오지 않고 error(권한 없음 410, 같은 내용 반복 430 등), system(muted, bad_word), control(slowWait) 이벤트가 대신 와요. 실제로 올라갔는지는 after.message로 확인하세요.
채팅창 입력이 /로 시작하면 send 대신 command, !로 시작하면 plugin 이벤트로 가요. 기본 스킨은 !명령을 처리할 채널이 없거나 막히면 그 입력을 일반 메시지로 보내요.
연결이 끊길 때
before.disconnect→ 스킨에 알림 → 자동 재접속 판단 →after.disconnect- 네트워크 오류로 끊겼으면 다시 접속하고
before.join부터 다시 와요.
차단되었거나 서버가 오류를 보낸 직후 끊겼으면 다시 접속하지 않아요. 브라우저를 닫거나, 다른 페이지로 가거나, <u-chat> 태그를 지우거나, room.reload()할 때는 disconnect 이벤트가 오지 않아요. 떠나기 전에 할 일은 브라우저의 pagehide 이벤트에서 하세요.
입장
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를 넣지 않거나 태그를 넣지 않아요.
메시지
U.chat('*').on('after.message', function (room, data) {
console.log(data.user.nick, data.content);
});
| 키 | 설명 |
|---|---|
user |
보낸 사람 사용자 객체 |
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는 없어요.
귓속말
| 키 | 설명 |
|---|---|
from, to |
보낸 사람, 받는 사람 사용자 객체 |
content |
내용 |
style |
지금은 늘 빈 문자열이에요. 메시지의 style 객체와 모양이 달라요 |
created, time |
보낸 시각(문자열, Unix 초) |
- 서버는 귓속말을 보낸 사람과 받는 사람 모두에게 보내요. 그래서 내가 보낸 귓속말도 이 이벤트로 돌아와요.
data.from.nick이 내 닉네임이면 내가 보낸 거예요. - 메시지와 달리
uid가 없어요. - 입장할 때 지난 대화와 함께 나와 관련된 귓속말이 다시 와요.
- 내가 무시한 사람(부관리자 미만)의 귓속말이거나 내 설정이 귓속말 거부이면
before.whisper까지만 오고 화면 표시와after.whisper는 없어요.
보내기와 명령
// 보내는 메시지 끝에 서명 붙이기
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(인자 배열)를 고치면 고친 인자로 실행해요.
채널
before.plugin/after.plugin은 두 경우에 오며 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와 한도를 보세요.
사용자 변화
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의 접속자 수를 써요.
user.list이벤트({ list })는 레거시 스킨에서만 와요. 기본 스킨은 사용자 목록을 나눠 받으므로 이 이벤트가 오지 않아요.
시스템 안내
data.type은 안내 종류, data.data는 값 배열이에요. 값 배열의 모양은 종류마다 달라요.
type |
뜻 |
|---|---|
notice |
공지, room.print() |
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 |
다시 접속을 시도함(시도 횟수) |
U.chat('*').on('after.system', function (room, data) {
if (data.type === 'from_call') window.focus();
});
호출 받기·초대 받기를 꺼 둔 사용자에게는 from_call, invite의 before.system도 오지 않아요.
제어 신호
data.type은 신호 이름, data.data는 값 배열이에요. 대부분 스킨이 쓰는 내부 신호이므로 이름과 모양이 바뀔 수 있어요. 페이지 스크립트에서 쓸 만한 것은 아래 정도예요.
type |
뜻 | data |
|---|---|---|
clearLog |
대화 청소 | [] |
removeChat |
관리자가 메시지를 지움 | 지운 메시지의 uid 목록. message의 uid와 맞춰 봐요 |
slowWait |
슬로우 모드라 내 메시지가 거절됨 | [남은 밀리초, 보내려던 내용] |
pluginDataReplaced |
무료 방에서 채널 저장값이 바뀜 | []. 채널 API와 한도 |
오류
data.code는 오류 번호이고, 현재 언어의 안내 문구는 room.language.error[code]에 있어요.
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 |
차단된 상태 |
416 |
이 채팅방에서는 쓸 수 없는 명령 |
421 |
이미 그 권한임 |
422 |
비밀번호가 틀림 |
413 |
채팅금지 중 |
418 |
얼린 채팅방 |
424 |
내가 귓속말 거부 중이라 보낼 수 없음 |
425 |
너무 자주 보냄 |
428 |
채널 전송 한도 초과 |
429 |
로그인 시도가 너무 많음 |
430 |
같은 내용을 반복해 보냄 |
501, 502 |
개인채널이 없음, 개인채널 안에서는 개인채널을 만들 수 없음 |
회원 연동 오류의 원인과 해결은 회원 연동을 보세요.
data 예시
이벤트마다 받는 data의 실제 모양이에요. 값은 예시이고, 서버가 보내지 않은 키는 undefined일 수 있어요. 직접 확인하려면 U.chat('*').on('after.message', function (room, data) { console.log(data); })처럼 콘솔에 찍어 보세요.
// 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 }
봐도 잘 이해가 되지 않나요? 문의 게시판에 질문을 남겨 주세요.