본인 명의 휴대폰(통신사 인증)으로 1~2분이면 끝나요. 인증하면 성인 캐릭터 이용과 매일 무료 채팅 지급이 열립니다.
해외 거주자 인증인증 정보는 성인 여부 확인에만 사용되며, 인증은 1년간 유지됩니다.
JSX 는 채팅 답변 안에 그려지는 작은 화면입니다. 호감도 막대, 선택지 버튼, 상태 패널처럼 제작자가 HTML·JS·React 로 만들고, 캐릭터가 답변 끝에 한 줄로 불러 씁니다.
기능과 함수 하나하나를 개념 → 사용법 → 예시 → 제한 순서로 적었습니다. 왼쪽 검색 칸에 함수 이름이나 하고 싶은 일(예: setValue, 확인창, 높이)을 넣으면 그 항목만 남습니다.
JSX 는 에셋의 한 종류입니다. 제작자가 코드를 등록해 두면, 캐릭터가 답변 안에 호출 태그 한 줄을 쓰고, 화면이 그 자리에 격리된 작은 창을 그립니다. 창 안의 코드는 LT 라는 다리를 통해서만 채팅과 이야기합니다.
| 에셋 | 제작자가 등록하는 코드 묶음. 고유 ID 로 불립니다. 유형은 그림(SVG)·JSX·Worker 셋입니다. |
|---|---|
| 호출 태그 | 답변 안에 쓰는 <lunatalk_asset id="…" … /> 한 줄. 어떤 에셋을 어떤 값으로 그릴지 정합니다. |
| 격리 창 | 태그 자리에 그려지는 iframe. 채팅 화면과 출처가 달라 쿠키·저장소·화면에 손댈 수 없습니다. |
다리 LT | 격리 창 안에 준비된 전역 객체. 메시지 보내기·재생성·문맥 읽기·값 저장·높이 맞춤을 이 함수들로만 합니다. |
| 방 저장값 | 채팅방 하나마다 따로 있는 작은 저장소. JSX 가 LT.setValue() 로 쓰고, 다음 턴부터 {{{키}}} 가 그 값으로 바뀝니다. |
| 숨은 데이터 | 모델이 답변에 쓰지만 화면에는 안 보이는 {{hidden:키=값}}. JSX 가 읽어 저장값으로 옮기는 통로입니다. |
panel)<lunatalk_asset id="panel" af="{{{AF}}}" />LT.vars.af 로 읽힙니다.LT.sendToAI() 가 이용자 메시지를 보냅니다. 확인창이 뜨고, 확인하면 한 턴이 나갑니다.LT.setValue("AF", 72) 로 값을 저장하면, 다음 턴부터 프롬프트와 태그의 {{{AF}}} 가 72 로 바뀝니다.| 구분 | 그림(SVG) 에셋 | JSX |
|---|---|---|
| 내용 | SVG 그림(+스크립트) | HTML·SVG·JS 화면. SVG 그림도 그대로 됨 |
| React | 없음 | React 18 포함. <Panel /> 같은 JSX 문법 자동 변환 |
| 호출 태그 | <lunatalk_asset …/> | 같음 |
| 채팅에 작용 | 없음(보여주기만) | 메시지 보내기·재생성·값 저장·직전 메시지 읽기 |
| 외부 접속 | 그림 허용 | 통신 전부 차단. 그림·소리·글꼴만 허용 목록 호스트 |
| 크기 | 태그의 w/h 또는 비율 | 같음 + 창이 스스로 높이를 맞출 수 있음 |
이미 등록한 SVG 에셋은 그대로 동작합니다. 편집기에서 열면 JSX 로 보이고, 저장하면 JSX 가 됩니다.
JSX 는 단계적으로 열립니다. 지금 상태와, 등록한 에셋이 남에게 보이기까지의 절차입니다.
| 편집기 입장 | 로그인한 회원 누구나 에셋 편집기를 열 수 있어요. 제작자 인증은 필요 없습니다. |
|---|---|
| JSX 유형 저장 | 로그인한 회원 누구나 JSX 유형으로 저장됩니다. |
| JSX 실행 | 모든 회원의 채팅에서 그려집니다. |
| 지금 단계 | 로그인한 회원 전체에 열려 있습니다. |
eval 같은 항목이 걸리면 승인 대기로 들어가 운영진이 본 뒤 승인해요. 문구: "에셋이 등록되었습니다. 관리자 승인 후 공개됩니다." Worker 유형은 외부 통신을 하므로 항상 운영진 확인을 거칩니다.에셋 편집기에서 타입 JSX · 그림·상호작용을 고르고 코드를 넣어 등록합니다. 칸마다 규칙이 있습니다.
| 칸 | 규칙 |
|---|---|
| 에셋 이름 * | 1~100자. 목록에 보이는 이름. |
| 고유 ID * | 3~50자, 영문 소문자·숫자·-·_ 만. 숫자만으로는 불가(번호와 충돌). 예약어 불가: id w h width height src style class _t idx key. [중복 확인] 버튼으로 미리 확인. 호출 태그의 id 에 이 값을 씁니다. |
| 짧은 설명 | 255자 이내. 카드·목록에 표시. |
| 상세 설명 | 20,000자까지. 상세 페이지에 표시. |
| 사용 예시 (변수 조합) | 최대 10줄. 호출 태그 예를 적어 두면 다른 제작자가 보고 씁니다. |
| 공개 범위 | 새 에셋은 비공개로 시작합니다. 공개는 갤러리에 올라가고 누구든 자기 캐릭터에서 부를 수 있습니다. 비공개 · 내 캐릭터 전용은 갤러리·상세에 보이지 않고, 내가 만든 캐릭터의 채팅방에서만 작동합니다. 다른 제작자의 캐릭터가 부르면 "제작자 전용 에셋입니다" 안내만 나가고, 캐릭터를 복제해 가도 복제본에서는 작동하지 않습니다. 안전 검사와 승인은 공개와 같습니다. |
| 코드 | HTML·SVG·<script> 그대로. 순수 JS 도, React 코드도 됩니다. 500KB 이하. |
LT.vars 가 채워진 상태를 봅니다.미리보기에서는 LT 가 가짜로 들어갑니다. 화면은 그려지지만 sendToAI·reroll·setValue 는 {ok:false, error:"preview"} 를 돌려주고, getContext 는 빈 값, resize 는 아무것도 하지 않습니다. 실제 동작은 채팅방에서 확인하세요.
eval 이 있으면 승인 대기로 들어가 운영진이 본 뒤 승인합니다. 그 밖의 확인 항목(스크립트 있음 등)만 있으면 바로 승인됩니다.가장 작은 JSX 를 등록하고, 프롬프트 한 줄로 불러, 채팅방에서 확인하는 순서입니다.
hello, 코드에 아래를 붙여 넣고 등록합니다.매 답변 맨 끝에 아래 한 줄을 그대로 쓴다: <lunatalk_asset id="hello" name="유나" />hello<div id="box" style="font:13px/1.5 sans-serif;color:#222;background:#fff;border:1px solid #ddd;border-radius:10px;padding:10px 12px;">
안녕, <b id="who"></b>!
</div>
<script>
document.getElementById('who').textContent = LT.vars.name || '이름 없음';
LT.resize(document.getElementById('box').offsetHeight + 4);
</script>
<lunatalk_asset id="hello" name="유나" /> → 상자에 안녕, 유나! 가 그려지고, 높이는 내용에 맞춰집니다.답변 안에 쓰는 한 줄입니다. 어떤 에셋을, 어떤 값으로, 어떤 크기로 그릴지 정합니다.
| 항목 | 규칙 |
|---|---|
id | 필수. 에셋의 고유 ID 또는 번호(idx). 영문·숫자·-·_ 1~50자, 대소문자 구분 없음(소문자로 맞춰집니다). |
| 속성 이름 | 영문자나 _ 로 시작, 그 뒤 영문·숫자·_·-. 대소문자는 보존되지만 LT.vars 조회는 대소문자 무관. |
| 속성 값 | 큰따옴표나 작은따옴표 안. 500자에서 잘립니다. 값 안에 같은 종류의 따옴표는 쓸 수 없어요. 여러 항목은 | 같은 구분자를 정해 한 값에 넣습니다. |
| 예약 속성 | id w h width height ratio — 크기·식별에 쓰이고 LT.vars 로는 넘어가지 않아요. 서버는 idx key _t _r 도 뺍니다. |
| 고정 크기 | w·h(또는 width·height) 둘 다 적으면 px 고정. 각각 16~1,200 으로 잘립니다. |
| 비율 크기 | 크기를 안 적으면 폭 100% 에 ratio 비율로 시작합니다. ratio="16:9" 또는 "16/9", 기본 1/1. 창이 열리면 내용 높이에 맞춰 자동으로 바뀝니다. 에셋이 스스로 비율을 알려 오면(비율 알리기) 그 비율로 잡힙니다. |
| 치환 | 값 안의 {{{키}}} 는 화면이 그릴 때 방 저장값으로 바뀝니다. 저장값이 없으면 빈 글자. (치환) |
| 개수·자리 | 한 답변에 여러 개 가능, 각각 따로 그려집니다. 답변 어디에나 쓸 수 있지만 보통 맨 끝. 첫 메시지(인사말)에도 됩니다. |
| 스트리밍 | 답변이 생성되는 동안에는 자리만 잡히고, 답변이 끝난 뒤 그려집니다. |
| 다시 그리기 | 말풍선이 다시 그려져도 창은 새로 불러오지 않습니다(화면 바깥에 두고 자리만 연결). 턴이 바뀌면 새 창입니다. |
<lunatalk_asset id="panel" af="{{{AF}}}" mood="차분" items="말을 건다|조용히 본다|자리를 뜬다" />
<lunatalk_asset id="map" x="12" y="40" w="320" h="200" />
LT.vars.af = 저장값 AF(없으면 ""), LT.vars.mood = "차분", LT.vars.items = "말을 건다|조용히 본다|자리를 뜬다". 둘째 줄 → 320×200 고정, LT.vars.x = "12".values="{{{AF}}}" 처럼 쓰고 AF 에 JSON 문자열을 저장해 두면, 치환은 태그를 해석한 뒤에 일어나므로 따옴표가 태그를 깨지 않습니다. 모델에게 JSON 을 태그 값에 직접 쓰게 하면 따옴표 때문에 자주 깨집니다.격리 창 안에는 전역 객체 LT 가 준비되어 있습니다. 값을 돌려주는 함수는 전부 Promise 라서 .then() 으로 받습니다.
| 함수 | 하는 일 | 돌려주는 것 |
|---|---|---|
LT.vars | 호출 태그 속성값 읽기 | 객체 (Promise 아님) |
LT.sendToAI(text) | 이용자 메시지로 한 턴 보내기 (확인창) | {ok} |
LT.reroll(guidance) | 마지막 답변 재생성 (확인창) | {ok} |
LT.getContext() | 이 답변·직전 메시지·저장값 전체 읽기 | {ok, current, prevUser, prevChar, values} |
LT.getCurrentMessage() | 이 답변 원문만 | 문자열 |
LT.getPrevUserMessage() | 직전 이용자 메시지만 | 문자열 |
LT.getPrevCharMessage() | 하나 전 캐릭터 답변만 | 문자열 |
LT.getValues() | 방 저장값 전체만 | 객체 |
LT.setValue(key, value) | 방 저장값 쓰기 | {ok} |
LT.resize(height) | 창 높이 직접 지정 — 보통 필요 없음(자동) | 없음 |
LT.autosize(on) | 자동 높이 켜기/끄기 (기본 켜짐) | 없음 |
LT.hidden(text) | 글 속 {{hidden:…}} 를 객체로 | 객체 (Promise 아님) |
{ ok: true, …결과 } // 성공
{ ok: false, error: "코드" } // 실패 — 코드는 아래 표
| error | 뜻 | 대처 |
|---|---|---|
cancelled | 이용자가 확인창에서 취소함 | 조용히 넘기기 |
busy | 다른 확인창이 떠 있거나, 답변 생성 중이거나, 직전 요청 뒤 3초가 안 지남 | "잠시 뒤 다시" 안내 |
empty | 보낼 글이 비어 있음 | 글을 채워서 다시 |
no_target | 재생성할 마지막 답변을 못 찾음 | 드묾. 새로 고침 뒤 다시 |
bad_key / too_long | 저장 키 형식 오류 / 값 2,000자 초과 | 키·값 규칙(방 저장값) 확인 |
| (서버 거절 문구) "방 하나의 저장값 합계는 10,000자까지입니다." | 이 방의 값을 다 합쳐 10,000자를 넘김 | 안 쓰는 키를 비우거나 값을 줄이세요. 합계 규칙은 방 저장값 |
network / fail | 저장 요청이 서버에 닿지 못함 / 서버가 거절함 | 다시 시도. 서버 거절 문구는 error 에 그대로 옴 |
timeout | 부모 창이 60초(보내기·재생성은 120초) 안에 답하지 않음 | 새로 고침 |
no_parent | 채팅방 밖(직접 연 창 등)에서 실행됨 | 채팅방에서 확인 |
preview | 편집기 미리보기의 가짜 다리 | 채팅방에서 확인 |
LT 는 동작하지 않아요.LT.setValue() 로 저장합니다.호출 태그에 적힌 속성값이 들어 있는 객체입니다. 전부 문자열이라 숫자는 parseInt, JSON 은 JSON.parse 로 바꿔 씁니다. 이름은 대소문자 무관(LT.vars.af 와 LT.vars.AF 는 같음). 없는 이름은 undefined.
var af = parseInt(LT.vars.af, 10); if (isNaN(af)) af = 0; // 숫자
var items = String(LT.vars.items || '').split('|'); // 목록
var data = {}; try { data = JSON.parse(LT.vars.values || '{}'); } catch (e) {} // JSON (저장값을 거쳐 받은 경우)
<lunatalk_asset id="panel" af="72" Mood="차분" items="a|b" w="320" h="200" />
LT.vars.af = "72" · LT.vars.mood = "차분" (대소문자 무관) · LT.vars.items = "a|b" · LT.vars.w = undefined (예약 속성){{{키}}} 자리는 빈 글자("")가 옵니다. 빈 값일 때의 기본값을 꼭 두세요.window.ASSET_VARS 로도 읽을 수 있고, 대소문자를 보존한 원본은 window.__ASSET_VARS_RAW 입니다.text 를 이용자가 직접 쓴 메시지로 보냅니다. 부모 창이 확인창을 띄우고(JSX 가 끌 수 없음), 확인하면 입력창에 글이 들어가고 전송됩니다. 그 뒤는 보통 턴과 똑같이 처리되고 요금도 같습니다.
확인창 문구: JSX가 아래 메시지를 보내려고 합니다. 보내면 한 턴으로 처리됩니다. + 보낼 글.
btn.onclick = function () {
LT.sendToAI('말을 건다').then(function (r) {
if (r.ok) note.textContent = '보냈습니다.';
else if (r.error === 'cancelled') note.textContent = '취소했습니다.';
else if (r.error === 'busy') note.textContent = '지금은 답변 중입니다. 잠시 뒤 다시.';
else note.textContent = '보내지 못했습니다: ' + r.error;
});
};
['도망친다','맞선다','말을 건다'].forEach(function (label) {
var b = document.createElement('button'); b.textContent = label;
b.onclick = function () { LT.sendToAI(label); };
list.appendChild(b);
});
empty.busy. 한 번에 하나만 보낼 수 있어요.ok:true 는 "전송을 시작했다"는 뜻입니다. 답변 내용은 돌려주지 않아요. 답변은 새 말풍선에 나옵니다.timeout.채팅의 맨 마지막 캐릭터 답변을 다시 만듭니다. guidance 를 주면 "이렇게 다시 써라"는 지시로 함께 전달됩니다. 확인창이 뜨고, 확인하면 보통 재생성과 같은 경로·요금으로 처리됩니다.
확인창 문구: JSX가 마지막 답변을 다시 만들려고 합니다. 재생성 한 턴으로 처리됩니다. 지시가 있으면 그 아래 지시: … 가 붙습니다.
LT.reroll('더 짧게, 대사 위주로').then(function (r) {
if (!r.ok && r.error !== 'cancelled') note.textContent = '다시 만들지 못했습니다: ' + r.error;
});document.getElementById('lighter').onclick = function () { LT.reroll('같은 장면을 더 밝은 분위기로'); };
no_target.busy, 3초 간격, 120초 확인 대기)은 sendToAI 와 같습니다.current | 이 창이 들어 있는 답변의 원문 전체. 화면 표시용 가공 전이라 {{hidden:…}} 가 들어 있습니다. |
|---|---|
prevUser | 그 답변 바로 앞의 이용자 메시지. |
prevChar | 그 앞의 캐릭터 답변(이 답변 말고 하나 전). |
values | 이 방의 저장값 전체 {키: 값}. |
줄임 함수 네 개는 각각 한 필드만 돌려줍니다.
LT.getContext().then(function (c) {
if (!c.ok) return;
var h = LT.hidden(c.current); // 이 답변의 숨은 데이터
var said = c.prevUser; // 이용자가 방금 한 말
var saved = c.values.AF; // 저장값
});LT.getPrevUserMessage().then(function (t) {
if (/고마워|감사/.test(t || '')) document.getElementById('heart').style.display = 'block';
});
values 는 페이지를 열 때 받은 값에 이 탭에서 setValue 한 것을 더한 것입니다. 다른 탭·기기에서 바꾼 값은 새로 고침해야 보입니다.이 방의 저장값에 씁니다. 문자열이 아닌 값(숫자·객체)은 JSON.stringify 로 문자열이 됩니다. 저장되면 다음 턴부터 프롬프트와 태그의 {{{key}}} 가 이 값으로 치환됩니다. 저장소 규칙은 방 저장값.
LT.setValue('AF', 72); // "72"
LT.setValue('STATS', { hp: 80, mp: 20 }); // '{"hp":80,"mp":20}'
LT.setValue('AF', next).then(function (r) {
note.textContent = r.ok ? '저장됨' : '저장 실패: ' + r.error;
});plus.onclick = function () {
af = Math.min(100, af + 5); show(af);
LT.setValue('AF', String(af));
};
values.AF 가 바뀌고, 다음 턴에 서버가 프롬프트의 {{{AF}}} 를 새 값으로 바꿔 모델에게 보냅니다. 이미 그려진 답변의 태그 값은 바뀌지 않아요._ 1~40자, 대소문자 구분. 아니면 bad_key.too_long. 방 하나에 키 50개까지(서버 거절 문구: "방 하나에 키는 50개까지입니다."). 방 하나의 값을 다 합쳐 10,000자까지 — 넘으면 서버가 거절하고 error 에 "방 하나의 저장값 합계는 10,000자까지입니다." 문구가 옵니다. 프롬프트의 {{{키}}} 치환도 한 턴에 합계 10,000자까지만 꽂힙니다.network. JSX 가 열리지 않은 계정이면 서버가 거절하고 error 에 안내 문구가 옵니다.창 높이는 자동입니다. 창 안의 내용 크기가 바뀔 때마다(목록이 늘거나, React 상태가 바뀌거나, 그림·글꼴이 늦게 로드되거나) 본문 높이를 다시 재서 창을 맞춥니다. 16~1,200px, 1px 차이는 무시, 0.06초 단위로 묶어 보냅니다. 폭은 말풍선 폭을 따릅니다.
LT.resize(h) 는 직접 높이를 지정하고 싶을 때만 씁니다. 자동이 켜져 있으면 내용이 바뀔 때 다시 측정값으로 돌아오므로, 고정하려면 먼저 LT.autosize(false) 로 자동을 끄세요.
// 보통은 아무것도 안 해도 됩니다.
// 높이를 직접 고정하고 싶을 때:
LT.autosize(false);
LT.resize(240);
// 다시 자동으로(멈춘 상태도 풀림):
LT.autosize(true);toggle.onclick = function () { more.hidden = !more.hidden; };
내용 높이를 재는 자동 맞춤 대신 비율을 직접 알릴 수 있습니다. 아래 한 줄을 보내면 창이 폭 100% 에 그 비율로 잡히고, 자동 높이는 이미 맞은 높이를 건드리지 않습니다. 편집기 미리보기·갤러리·채팅 모두 같은 메시지를 받습니다. 태그에 w·h 를 둘 다 적은 창에서는 무시됩니다.
parent.postMessage({ type: 'lt_asset_size', w: 380, h: 760 }, '*');
그림이 그려진 뒤(load)에 한 번 더 보내면 안전합니다. 비율만 정하는 것이라 크기(px)는 말풍선 폭에 따라 달라집니다.
height:100vh + 여백)는 끝없이 자랄 수 있어, 같은 폭으로 짧은 시간에 3번 연속 커지거나 두 값을 번갈아 오가면 자동 맞춤이 멈춥니다. 최상위 요소 높이는 내용에 맡기세요. LT.autosize(true) 를 부르면 다시 켜집니다.max-height; overflow:auto 를 주세요.w·h 둘 다 적은 고정 크기 창도 높이는 자동으로 바뀝니다. 폭은 못 바꿉니다.채팅방 하나, 회원 하나마다 따로 있는 {키: 값} 저장소입니다. JSX 가 쓰고, 서버와 화면이 {{{키}}} 자리에 읽어 넣습니다.
| 범위 | 방 단위. 같은 캐릭터라도 방이 다르면 값이 다르고, 같은 방에서는 턴이 지나도 이어집니다. |
|---|---|
| 키 | 영문·숫자·_, 1~40자. 대소문자 구분. (예: AF, stats_v2) |
| 값 | 문자열 2,000자까지. 객체는 JSON 문자열로. |
| 개수 | 방 하나에 키 50개까지. |
| 합계 | 방 하나의 값을 다 합쳐 10,000자까지. 프롬프트 치환도 한 턴에 같은 합계까지만(저장값은 상태·수치용이지 긴 글을 넣는 통로가 아닙니다). |
| 기본값 | 없음. 저장 전에는 {{{키}}} 가 빈 글자. 프롬프트에 "비어 있으면 50으로 본다"처럼 적거나, JSX 에서 빈 값을 기본값으로 처리하세요. |
| 새 방 | 비어 있음. 방을 복사해도 값은 따라가지 않습니다. |
| 쓰기 | JSX 의 LT.setValue() 만. 모델은 직접 못 쓰고 숨은 데이터로 JSX 에 넘깁니다. |
| 읽기 시점 | 채팅 화면을 열 때 한 번 받고, 이후엔 이 탭에서 저장한 것만 더해집니다. 다른 탭·기기에서 바꾼 값은 새로 고침해야 보입니다. |
{ "AF": "72", "MOOD": "기쁨", "STATS": "{\"hp\":80,\"mp\":20}", "FLAG_met": "1" }
{{{AF}}} → 72, {{{STATS}}} → {"hp":80,"mp":20}, 없는 {{{GOLD}}} → (빈 글자)세 겹 중괄호 {{{키}}} 는 방 저장값으로 바뀝니다. 바뀌는 자리는 딱 두 곳입니다.
| 자리 | 누가 언제 | 효과 |
|---|---|---|
| 캐릭터 프롬프트 (설명·세계관·시작 대사 등 글 칸 전부) | 서버가 턴을 보내기 직전 | 모델이 "지금 호감도: 72" 를 보고 답합니다. |
| 답변 속 호출 태그의 속성값 | 화면이 창을 그릴 때 | 모델은 {{{AF}}} 를 그대로 쓰기만 하면 됩니다. |
_ 1~40자. 중괄호 안 공백은 무시됩니다.{{{ 가 있어도 다시 치환되지 않습니다(안전 처리).턴 N 모델: … 그녀가 살짝 웃었다. {{hidden:AF=72}} <lunatalk_asset id="panel" af="{{{AF}}}" />
화면: {{hidden:…}} 은 지우고, 태그 속성의 {{{AF}}} 는 저장값(아직 없으면 빈 글자)으로 바꿔 JSX 창을 그림
JSX : LT.getContext() → LT.hidden(current).AF = "72" → LT.setValue('AF', '72')
턴 N+1 서버: 캐릭터 프롬프트의 {{{AF}}} → 72 (모델이 "지금 호감도 72" 를 보고 답함)
화면: 새 답변 태그의 {{{AF}}} → 72
{{{키}}} 는 화면에서 바뀌지 않습니다(태그 속성값만). 본문에 값을 보이게 하려면 프롬프트 쪽에서 치환된 값을 모델이 읽고 쓰게 하세요.JSX 는 출처가 없는 iframe(sandbox="allow-scripts") 안에서 돕니다. 채팅 화면의 쿠키·화면·저장소에 손댈 수 없고, 바깥과는 LT 로만 이야기합니다. 통신 정책(CSP)이 아래처럼 걸려 있습니다.
| 할 수 있음 | 할 수 없음 |
|---|---|
HTML·SVG·CSS·JavaScript 전부. <style>, 인라인 스타일, 애니메이션, 입력칸, 버튼 | 외부 통신 전부 — fetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon |
React 18 (React·ReactDOM 전역) 과 JSX 문법 | 바깥 스크립트 불러오기(우리 서버 주소 제외). 다른 라이브러리는 500KB 상한 안에서 코드에 직접 포함 |
그림·소리 — 우리 서버와 허용 호스트(itimg.kr, *.itimg.kr)의 주소, data:·blob: URL | 그 밖의 외부 주소 그림·소리 (표시되지 않음) |
글꼴 — Google Fonts(스타일시트·글꼴 파일), data: 글꼴 | 그 밖의 외부 스타일시트·글꼴 |
LT.* 함수 | alert·confirm·prompt (격리 창에서는 뜨지 않음. 화면 안에 글로 보여줄 것) |
부모 창 접근, 쿠키, localStorage, 새 창 열기, 주소 이동, 폼 전송, import/export |
그림은 itimg.kr 에 올려 쓰면 됩니다. LUNATALK 공식 이미지 호스팅이라 올린 그림의 주소가 그대로 허용됩니다.
html, body { margin:0; padding:0; width:100%; background:transparent !important; }
body { overflow:hidden; } * { box-sizing:border-box; } svg { display:block; max-width:100%; }
body 가 스크롤되지 않습니다. 긴 내용은 안쪽 요소에 max-height; overflow:auto.data: URL 로 코드에 넣으세요(500KB 상한 주의).React 18 이 들어 있고, <Panel /> 같은 JSX 문법은 창이 열릴 때 자동으로 변환됩니다. 아무 설정도 켤 필요가 없습니다.
| React 읽기 | 코드에 React 또는 ReactDOM 글자가 보일 때만 React 18 을 불러옵니다. 순수 JS 만 쓰면 아무것도 안 읽어 가볍습니다. |
|---|---|
| 변환 대상 | <script type="text/babel"> 또는 type="text/jsx" 는 무조건 변환. 일반 <script> 는 태그 모양(<Abc)이 있고 그대로 실행하면 문법 오류가 나는 경우에만 변환합니다. |
| 변환 결과 | JSX 문법 → React.createElement 호출(클래식 방식). 그래서 React 전역이 꼭 있어야 합니다. |
| 안 되는 것 | import·export. React 와 ReactDOM 은 전역으로 바로 씁니다. |
| 실행 순서 | 창이 다 불러온 뒤 변환 → 변환된 스크립트를 순서대로 실행 → LT.resize 를 아직 안 불렀으면 50ms 뒤 자동 높이 맞춤. |
<div id="root"></div>
<script type="text/babel">
function Card() {
const [n, setN] = React.useState(0);
return <button onClick={() => setN(n + 1)}>눌린 횟수 {n}</button>;
}
ReactDOM.createRoot(document.getElementById("root")).render(<Card />);
</script>
변환·실행에 실패하면 창 안에 빨간 상자로 원인이 뜹니다(코드 앞 300자 포함). 브라우저 콘솔에는 [LT JSX] 로 남습니다.
| JSX 변환 오류 (스크립트 N) | JSX 문법 자체가 틀림. 닫히지 않은 태그, 속성 따옴표 누락이 흔함. |
|---|---|
| 실행 오류 (스크립트 N) | 변환은 됐지만 실행 중 예외. 없는 id, undefined 접근 등. |
| 라이브러리 로드 실패 | React 나 변환기를 못 불러옴. 새로 고침. |
| 불러오지 못함: (주소) | 외부 스크립트 주소가 막힘. 코드에 직접 포함할 것. |
<script> 에 JSX 문법을 써도 변환되지만, 그 경우 콘솔에 문법 오류 한 줄이 먼저 찍힙니다(동작엔 지장 없음). type="text/babel" 을 권합니다.저장할 때(그리고 [🛡 안전 검사] 버튼으로 미리) 코드를 검사합니다. 항목마다 등급이 있고, 최종 등급은 차단이 하나라도 있으면 차단, 아니면 확인이 있으면 확인, 둘 다 없으면 통과입니다.
| ⛔ 차단 | 저장되지 않습니다. 패널 문구: "⛔ 차단 항목 N — 아래를 고쳐야 저장할 수 있습니다." |
|---|---|
| ⚠ 확인 | 저장은 됩니다. 사칭 문구·깜빡임·난독화·eval 이면 운영진이 본 뒤 승인하고, 그 밖(스크립트 있음·이벤트 속성·외부 주소 등)은 바로 승인됩니다. 패널 문구는 자동 승인 대상이면 "⚠ 확인 항목 N — 자동 승인 대상입니다. 저장하면 바로 사용할 수 있습니다.", 운영진 확인이 필요하면 "⚠ 확인 필요 N — 저장은 되지만 관리자가 아래 항목을 확인한 뒤 승인합니다." |
| ℹ 안내 | 등급에 영향 없음. 격리 때문에 어차피 동작하지 않는 코드를 알려 줍니다. |
| 통과 | "🛡 안전 검사 통과 — 막히는 항목이 없습니다." |
<) 5,000개 초과 — "요소가 너무 많습니다 (…개, 상한 5,000)."<fe…>) 60개 초과location(window·document·top·parent 포함)에 대입, .href=, assign(, replace(, reload(. 같은 이름의 변수를 직접 선언해 쓰는 경우는 제외.<meta http-equiv="refresh"># 이나 javascript: 가 아닌 곳으로 가는 <a href><script> 가 있음 — JSX 는 사실상 항상 해당. "스크립트가 들어 있습니다 — 관리자가 내용을 확인합니다."onclick= 같은 이벤트 속성, javascript: 주소eval(, new Function(, 문자열을 넘기는 setTimeout/setInterval\x/\u 이스케이프 20개 이상, data: 없이 한 줄 3,000자 초과, atob/fromCharCode/unescapelocalStorage·sessionStorage·IndexedDB 접근fetch·XHR·sendBeacon·WebSocket·EventSource — "SVG 에셋에서는 외부 통신(fetch·XMLHttpRequest 등)이 동작하지 않습니다. 필요하면 Worker 형식으로 만드세요."import(, importScripts, new Worker, window.openiframe·object·embed·base·foreignObject, form·input⚠ 확인 필요 1 — 저장은 되지만 관리자가 아래 항목을 확인한 뒤 승인합니다.
⚠ 스크립트가 들어 있습니다 — 관리자가 내용을 확인합니다. (12행)
ℹ SVG 에셋에서는 외부 통신(fetch·XMLHttpRequest 등)이 동작하지 않습니다. … (30행)
fetch 는 실행돼도 막히니 지우는 편이 깔끔합니다.width:100%; box-sizing:border-box; 를 두고 고정 폭은 피하세요.height:100vh 같은 창 비례 높이만 피하세요.-apple-system, 'Malgun Gothic', sans-serif 정도면 폰·PC 모두 무난합니다.overflow:hidden; text-overflow:ellipsis 로 보호하세요.max-height; overflow:auto 를 주세요.<div id="box" style="width:100%;box-sizing:border-box;font:13px/1.5 -apple-system,'Malgun Gothic',sans-serif;color:#1f2937;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:12px 14px;">
…
</div>
서비스는 JSX 설명을 프롬프트에 끼워 넣지 않습니다. 언제, 어떤 줄을 쓸지를 제작자가 프롬프트에 적어야 모델이 따릅니다. 잘 되는 틀은 "조건 + 그대로 쓸 한 줄" 입니다.
[화면 JSX]
- 매 답변 맨 끝에 아래 한 줄을 그대로 쓴다. af 는 지금 호감도(0~100), mood 는 지금 기분 한 단어, items 는 이용자가 고를 행동 2~3개를 | 로 구분한다:
<lunatalk_asset id="panel" af="{{{AF}}}" mood="차분" items="말을 건다|조용히 본다|자리를 뜬다" />
- 호감도가 달라진 턴에는 그 줄 바로 앞에 {{hidden:AF=새 숫자}} 를 쓴다.
- 지금 호감도: {{{AF}}} (비어 있으면 50으로 본다)[화면 JSX]
- 이용자가 고를 행동이 뚜렷한 장면이 끝나면 답변 맨 끝에 선택지를 쓴다(2~4개). 매 턴 쓰지 않는다:
<lunatalk_asset id="choice" items="도망친다|맞선다|말을 건다" />| 목록으로 제한하세요. 긴 문장이나 JSON 을 모델이 태그 안에 쓰게 하면 깨지기 쉽습니다.{{{키}}} 로 받기만 하면 됩니다.{{{키}}} 는 서버가 저장값으로 바꿔 모델에게 보냅니다. "비어 있으면 50으로 본다"처럼 빈 값 규칙도 함께 적으세요.편집기에 그대로 붙여 넣고 고유 ID 만 맞추면 됩니다. 각 샘플은 아래에 따로 항목이 있습니다.
| 샘플 | 고유 ID | 보여 주는 것 | 쓰는 함수 |
|---|---|---|---|
| ① 호감도 패널 | affinity | JSON 저장값을 막대로 | vars, sendToAI, resize |
| ② 선택지 버튼 | choice | 버튼 누르면 메시지 전송 | vars, sendToAI, resize |
| ③ 상태 패널 | panel | 숨은 데이터 읽어 저장까지 | vars, getContext, hidden, setValue, sendToAI, resize |
| ④ React 카드 | rcard | React 18 + JSX 문법 | React.useState, setValue, sendToAI, resize |
affinity저장값 AF 에 {"이름":수치,…} JSON 이 들어 있을 때 이름별 막대로 그립니다. 호출: <lunatalk_asset id="affinity" values="{{{AF}}}" />
<div id="box" style="font:13px/1.5 sans-serif;color:#222;background:#fff;border:1px solid #ddd;border-radius:10px;padding:10px 12px;">
<div style="font-weight:700;margin-bottom:6px;">호감도</div>
<div id="rows"></div>
<div style="display:flex;gap:6px;margin-top:8px;">
<button type="button" id="talk" style="flex:1;padding:8px;border:1px solid #cbd5e1;border-radius:8px;background:#f8fafc;cursor:pointer;">말 걸기</button>
</div>
</div>
<script>
// 태그에서 받은 값: <lunatalk_asset id="affinity" values="{{{AF}}}" /> → LT.vars.values 가 저장된 AF 값(JSON 문자열)
var data = {};
try { data = JSON.parse(LT.vars.values || "{}"); } catch (e) { data = {}; }
var rows = document.getElementById("rows");
Object.keys(data).forEach(function (name) {
var v = Math.max(0, Math.min(100, parseInt(data[name], 10) || 0));
var r = document.createElement("div"); r.style.cssText = "display:flex;align-items:center;gap:8px;margin:3px 0;";
r.innerHTML = "<span style='width:64px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'></span><div style='flex:1;height:8px;background:#e5e7eb;border-radius:4px;overflow:hidden;'><div style='height:100%;width:" + v + "%;background:#f472b6;'></div></div><b style='width:32px;text-align:right;'>" + v + "</b>";
r.firstChild.textContent = name;
rows.appendChild(r);
});
// 버튼: 이용자가 눌렀을 때만 한 턴을 보낸다(부모 창이 확인창을 띄운다)
document.getElementById("talk").onclick = function () {
LT.sendToAI("잠깐 이야기 좀 하자.").then(function (r) { if (!r.ok) console.log("보내지 않음:", r.error); });
};
LT.resize(document.getElementById("box").offsetHeight + 4);
</script>LT.vars.values 를 JSON 으로 풀어 막대를 만들고, [말 걸기]를 누르면 확인창 뒤 메시지가 나갑니다. AF 가 비어 있으면 막대가 없고 버튼만 보입니다.
choiceitems 를 | 로 나눠 버튼으로 만들고, 누른 글을 이용자 메시지로 보냅니다. 호출: <lunatalk_asset id="choice" items="도망친다|맞선다|말을 건다" />
<div id="box" style="font:13px/1.5 sans-serif;color:#222;background:#fff;border:1px solid #ddd;border-radius:10px;padding:10px 12px;">
<div style="font-weight:700;margin-bottom:8px;">어떻게 할까?</div>
<div id="list" style="display:flex;flex-direction:column;gap:6px;"></div>
<div id="note" style="font-size:11px;color:#888;margin-top:6px;"></div>
</div>
<script>
var items = String(LT.vars.items || '').split('|').map(function (s) { return s.trim(); }).filter(Boolean).slice(0, 4);
var list = document.getElementById('list'), note = document.getElementById('note');
if (!items.length) note.textContent = '선택지가 없습니다 (items="가|나|다")';
items.forEach(function (label) {
var b = document.createElement('button');
b.type = 'button'; b.textContent = label;
b.style.cssText = 'padding:9px 10px;border:1px solid #cbd5e1;border-radius:8px;background:#f8fafc;color:#111;text-align:left;cursor:pointer;';
b.onclick = function () {
note.textContent = '보내는 중…';
LT.sendToAI(label).then(function (r) {
note.textContent = r.ok ? '보냈습니다.' : (r.error === 'cancelled' ? '취소했습니다.' : (r.error === 'busy' ? '지금은 답변 중입니다. 잠시 뒤 다시 눌러 주세요.' : '보내지 못했습니다: ' + r.error));
});
};
list.appendChild(b);
});
LT.resize(document.getElementById('box').offsetHeight + 4);
</script>최대 4개까지 버튼을 만듭니다. 결과(ok·cancelled·busy)를 아래 글로 보여 주므로 이용자가 영문을 압니다. items 가 비면 안내 문구가 뜹니다.
panel호감도 막대 + 기분 + 선택지. 이 답변에 {{hidden:AF=숫자}} 가 있으면 그 값을 쓰고 저장까지 합니다. 호출: <lunatalk_asset id="panel" af="{{{AF}}}" mood="차분" items="말을 건다|조용히 본다|자리를 뜬다" />
<div id="box" style="font:13px/1.5 -apple-system,'Malgun Gothic',sans-serif;color:#222;background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:12px 14px;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;">
<b>상태</b>
<span id="mood" style="font-size:12px;color:#6b7280;"></span>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span style="width:44px;font-size:12px;color:#6b7280;">호감도</span>
<div style="flex:1;height:10px;background:#f3f4f6;border-radius:5px;overflow:hidden;">
<div id="bar" style="height:100%;width:0%;background:linear-gradient(90deg,#f9a8d4,#ec4899);transition:width .4s;"></div>
</div>
<b id="num" style="width:36px;text-align:right;">0</b>
</div>
<div id="list" style="display:flex;flex-direction:column;gap:6px;margin-top:10px;"></div>
<div id="note" style="font-size:11px;color:#9ca3af;margin-top:6px;min-height:14px;"></div>
</div>
<script>
(function () {
var $ = function (id) { return document.getElementById(id); };
var note = $('note');
function clamp(n) { n = parseInt(n, 10); return isNaN(n) ? null : Math.max(0, Math.min(100, n)); }
function show(af) { $('bar').style.width = af + '%'; $('num').textContent = af; }
function fit() { LT.resize($('box').offsetHeight + 4); }
// 1) 태그 값
var af = clamp(LT.vars.af);
$('mood').textContent = LT.vars.mood ? '기분: ' + LT.vars.mood : '';
show(af === null ? 0 : af);
// 2) 선택지 버튼 → 이용자 메시지로 보내기 (부모 창이 확인창을 띄움, 한 턴)
var items = String(LT.vars.items || '').split('|').map(function (s) { return s.trim(); }).filter(Boolean).slice(0, 4);
items.forEach(function (label) {
var b = document.createElement('button');
b.type = 'button'; b.textContent = label;
b.style.cssText = 'padding:9px 10px;border:1px solid #d1d5db;border-radius:8px;background:#f9fafb;color:#111;text-align:left;cursor:pointer;font:inherit;';
b.onclick = function () {
note.textContent = '보내는 중…';
LT.sendToAI(label).then(function (r) {
note.textContent = r.ok ? '보냈습니다.' : (r.error === 'cancelled' ? '취소했습니다.' : (r.error === 'busy' ? '지금은 답변 중입니다. 잠시 뒤 다시 눌러 주세요.' : '보내지 못했습니다: ' + r.error));
});
};
$('list').appendChild(b);
});
fit();
// 3) 이 답변의 숨은 데이터 {{hidden:AF=72}} 가 있으면 그 값을 쓰고 방 저장값으로 남긴다 → 다음 턴의 {{{AF}}} 가 이 값이 된다
LT.getContext().then(function (c) {
if (!c || !c.ok) return;
var h = LT.hidden(c.current || '');
var fromHidden = clamp(h.AF);
if (fromHidden === null) return;
show(fromHidden);
if (String(c.values && c.values.AF) !== String(fromHidden)) {
LT.setValue('AF', String(fromHidden)).then(function (r) { note.textContent = r.ok ? '호감도 ' + fromHidden + ' 저장됨' : '저장 실패: ' + r.error; fit(); });
}
});
})();
</script>af·mood·items 로 먼저 그립니다.getContext() 로 원문을 받아 hidden().AF 가 있으면 막대를 그 값으로 바꾸고, 저장값과 다르면 setValue('AF', …) 합니다.{{{AF}}} 가 새 값이 됩니다.rcardReact 18 + JSX 문법 그대로. <script type="text/babel"> 안에 쓰면 자동 변환됩니다. 호출: <lunatalk_asset id="rcard" name="유나" af="{{{AF}}}" mood="차분" />
<div id="root"></div>
<script type="text/babel">
function Bar({ value }) {
const v = Math.max(0, Math.min(100, value));
return (
<div style={{ flex: 1, height: 10, background: '#f3f4f6', borderRadius: 5, overflow: 'hidden' }}>
<div style={{ width: v + '%', height: '100%', background: 'linear-gradient(90deg,#f9a8d4,#ec4899)', transition: 'width .4s' }} />
</div>
);
}
function Card() {
const initial = parseInt(LT.vars.af, 10);
const [af, setAf] = React.useState(isNaN(initial) ? 0 : initial);
const [note, setNote] = React.useState('');
const boxRef = React.useRef(null);
// 높이 맞추기 — 내용이 바뀔 때마다
React.useEffect(() => { if (boxRef.current) LT.resize(boxRef.current.offsetHeight + 4); });
const bump = (d) => {
const next = Math.max(0, Math.min(100, af + d));
setAf(next);
LT.setValue('AF', String(next)).then((r) => setNote(r.ok ? '호감도 ' + next + ' 저장됨' : '저장 실패: ' + r.error));
};
const talk = () => {
setNote('보내는 중…');
LT.sendToAI('잠깐 이야기 좀 하자.').then((r) => setNote(r.ok ? '보냈습니다.' : (r.error === 'cancelled' ? '취소했습니다.' : '보내지 못했습니다: ' + r.error)));
};
const btn = { padding: '8px 10px', border: '1px solid #d1d5db', borderRadius: 8, background: '#f9fafb', color: '#111', cursor: 'pointer', font: 'inherit' };
return (
<div ref={boxRef} style={{ font: "13px/1.5 -apple-system,'Malgun Gothic',sans-serif", color: '#1f2937', background: '#fff', border: '1px solid #e5e7eb', borderRadius: 12, padding: '12px 14px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<b>{LT.vars.name || '이름 없음'}</b>
<span style={{ fontSize: 12, color: '#6b7280' }}>{LT.vars.mood ? '기분: ' + LT.vars.mood : ''}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ width: 44, fontSize: 12, color: '#6b7280' }}>호감도</span>
<Bar value={af} />
<b style={{ width: 36, textAlign: 'right' }}>{af}</b>
</div>
<div style={{ display: 'flex', gap: 6, marginTop: 10 }}>
<button type="button" style={btn} onClick={() => bump(-5)}>−5</button>
<button type="button" style={btn} onClick={() => bump(5)}>+5</button>
<button type="button" style={{ ...btn, flex: 1, background: '#4f46e5', borderColor: '#4f46e5', color: '#fff' }} onClick={talk}>말 걸기</button>
</div>
<div style={{ fontSize: 11, color: '#9ca3af', marginTop: 6, minHeight: 14 }}>{note}</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<Card />);
</script>−5/+5 버튼이 상태를 바꾸고 setValue 로 저장합니다. useEffect 가 그릴 때마다 LT.resize 를 불러 높이를 맞춥니다. [말 걸기]는 메시지를 보냅니다.
LT.vars 값이 비었을 때(첫 턴)도 깨지지 않는지error 를 화면에 글로 보여 주는지(alert 는 안 뜸)fetch·외부 그림·외부 링크를 쓰지 않았는지| 증상 | 원인과 대처 |
|---|---|
| JSX 자리가 비어 있음 | 고유 ID 철자, 에셋 상태(본인 것 또는 승인됨), 태그 따옴표 확인. 답변 생성 중에는 자리만 잡히고 끝난 뒤 그려짐. 로그아웃 상태면 안 그려짐. |
| "JSX 기능이 아직 열리지 않았습니다" | 보는 계정에 JSX 가 열려 있지 않음. |
| 창 안에 빨간 상자 "JSX 변환 오류" | JSX 문법 오류. 닫히지 않은 태그·따옴표 확인. "실행 오류"면 변환은 됐고 코드 실행 중 예외. |
| 버튼을 눌러도 아무 일 없음 | 결과의 error 를 화면에 보여 주세요. busy 면 답변 중이거나 3초 안 연타, cancelled 면 확인창 취소. |
| 값이 다음 턴에 안 바뀜 | LT.setValue() 결과가 ok 였는지, 프롬프트의 키 철자가 같은지(대소문자 구분). 저장은 되는데 모델이 못 읽으면 프롬프트에 {{{키}}} 가 실제로 적혀 있는지. |
| 이미 그려진 창의 값이 안 바뀜 | 정상. 치환은 그릴 때 한 번. 새 턴의 창부터 새 값. |
| 높이가 잘림 | 자동 맞춤은 내용이 바뀌면 따라갑니다. LT.autosize(false) 를 불렀거나, 최상위 요소 높이가 창에 비례(100vh 등)해 자동이 멈춘 경우. 내용 높이로 바꾸고 LT.autosize(true). |
| 다크 모드에서 글이 안 보임 | 배경색·글자색을 직접 지정. |
| 그림이 안 나옴 | 우리 서버·허용 호스트·data: 만 됨. 다른 주소는 CSP 로 막힘. |
| 저장 시 "차단" | 검사 메시지의 항목을 지우기. 창 이동·외부 링크·500KB 초과가 흔한 원인. |
| 미리보기에서 보내기·저장이 안 됨 | 정상. 미리보기의 LT 는 가짜(error: "preview"). 채팅방에서 확인. |
Q. 값을 캐릭터 전체나 계정 전체에 공유할 수 있나요?
지금은 방 단위만 됩니다.
Q. React 를 쓸 수 있나요?
됩니다. React 18 이 항상 들어 있어서 React.useState 와 JSX 문법을 그대로 쓰면 되고, 아무것도 켤 필요가 없습니다. 다른 라이브러리는 들어 있지 않습니다.
Q. JSX 가 보낸 메시지도 요금이 드나요?
이용자가 직접 쓴 메시지와 똑같이 한 턴으로 처리됩니다. 그래서 확인창이 반드시 뜹니다. 재생성도 보통 재생성과 같습니다.
Q. 다른 제작자의 JSX 를 내 캐릭터에서 쓸 수 있나요?
제작자가 공개로 둔 에셋만 됩니다. 갤러리에 보이는 에셋이 그것이고, 고유 ID 로 부르면 됩니다. 비공개 에셋은 그 제작자의 캐릭터에서만 작동합니다.
Q. 한 답변에 JSX 를 두 개 넣으면?
각각 따로 그려지고 따로 돕니다. 둘이 서로 이야기할 수는 없고, 저장값을 통해 간접으로 주고받습니다.
Q. 모델이 저장값을 직접 바꿀 수 있나요?
아니요. 모델은 숨은 데이터로 값을 적고, JSX 가 읽어 setValue 로 저장합니다. 그래서 상태 패널 샘플처럼 "읽고 저장"하는 코드가 필요합니다.
Q. 외부 자료(날씨·시세)를 가져올 수 있나요?
JSX 에서는 외부 통신이 전부 막힙니다. 그런 용도는 에셋 유형 중 Worker 를 쓰세요. Worker 의 외부 호출은 우리 서버 중계를 거치며 https 만 되고, 받는 호스트는 *.r2.dev, *.workers.dev, *.pages.dev, *.cloudflare.com, raw.githubusercontent.com, cdn.jsdelivr.net, *.itimg.kr, itimg.kr, *.lunatalk.chat, lunatalk.chat 입니다. 응답 크기는 5MB 까지입니다. Worker 는 자동 승인 없이 운영진이 코드를 본 뒤 승인합니다.