문서

Lua 5.5 · API v0.1

개요

스크립트는 서버를 확장하는 작은 Lua 프로그램입니다. 봇과 완전히 분리된 격리된 Lua 5.5 샌드박스 프로세스에서 실행됩니다. 스크립트는 데이터베이스, 파일 시스템, 다른 서버에 접근하지 않습니다. 모든 기능은 허용 목록에 있는 호스트 API를 통해 IPC로 봇에 제어를 넘기는 방식으로 제공됩니다.

스크립트마다 하나의 샌드박스

로드된 스크립트가 없으면 프로세스도 없습니다.

호출 횟수가 제한된 호스트 호출

bot:* 및 msg:* 호출은 Lua가 아니라 봇이 권한을 확인합니다.

기본 기능이 우선

오토모드, 환영 메시지, 티켓은 항상 실행됩니다. 스크립트는 보완할 뿐 대체하지 않습니다.

오류는 격리됩니다

충돌한 스크립트는 언로드되거나 격리되며, 봇은 계속 실행됩니다.

브라우저 내 에디터는 없습니다. 일반 에디터에서 .lua를 작성한 뒤 서버의 스크립트 페이지에서 업로드하세요.

시작하기

  1. 1

    로컬에서 .lua 파일 작성

    VS Code, Neovim 등 아무 에디터나 사용할 수 있습니다.
  2. 2

    로컬에서 확인 (선택)

    script_runner --check file.lua를 실행하면 check: ok가 출력됩니다.
  3. 3

    업로드

    서버의 스크립트 페이지를 열고 코드를 붙여넣거나 .lua 파일(최대 64 KiB)을 업로드하세요.
  4. 4

    시험 실행

    아무것도 실행하지 않고 크기, 인코딩, 바이트코드, 금지된 API를 검증합니다.
  5. 5

    활성화

    스태프가 버전을 승인할 때까지 기다린 뒤 활성화를 누르세요. 봇이 몇 초 안에 로드합니다.
  6. 6

    로그 채널 설정

    bot:log 출력과 오류 알림을 이곳에서 받습니다.

로컬 CLI

러너는 봇이 사용하는 것과 동일한 바이너리입니다.

  • script_runner --check file.lua

    실행하지 않고 정적 구문/금지 API를 검사합니다. 'check: ok'를 출력합니다.

  • script_runner --eval 'code'

    샌드박스에서 코드 조각을 로컬로 실행합니다. 오류는 종류와 메시지를 출력합니다.

  • script_runner --self-test

    샌드박스, 프로토콜, 소켓 자체 테스트를 실행합니다.

수명 주기 및 검토

스크립트는 버전으로 관리됩니다. 버전은 제출하는 순간 고정되며 SHA-256으로 식별됩니다. 승인된 버전만 활성화할 수 있고, 활성 버전만 봇이 로드합니다.

  1. 1

    작성

    VS Code, Neovim 등 아무 에디터에서 로컬로 .lua 파일을 만드세요. 브라우저 내 에디터는 의도적으로 제공하지 않습니다.
  2. 2

    시험 실행 / 업로드

    서버의 스크립트 페이지에서 코드를 붙여넣거나 .lua 파일(≤ 64 KiB, 바이너리 불가)을 업로드하세요. 시험 실행은 아무것도 실행하지 않고 크기, 인코딩, 바이트코드, 금지된 API를 검증합니다.
  3. 3

    검토

    모든 새 스크립트와 새 버전은 대기 중 상태로 시작합니다. 스태프가 해당 코드 수정본을 승인하거나 거부하기 전에는 실행될 수 없습니다.
  4. 4

    활성화

    승인된 버전을 활성화합니다. 대기 중이거나 거부된 버전은 활성화할 수 없습니다. 활성 버전만 봇이 로드합니다.
  5. 5

    실행

    봇이 버전(코드 + SHA-256, 러너가 검증)을 격리된 Lua 5.5 샌드박스에 로드합니다. 이벤트는 bot:on으로 등록된 핸들러에만 전달됩니다.
  6. 6

    업데이트 / 롤백

    새 버전을 업로드하거나(다시 대기 중 상태) 이전의 승인된 버전을 활성화하여 롤백하세요. KV 저장소는 유지됩니다.
  7. 7

    비활성화 / 삭제

    비활성화하면 즉시 로드가 중지되며, '모두 비활성화'는 서버의 모든 활성 스크립트에 적용됩니다. 삭제하면 스크립트와 해당 버전, KV, 오류가 제거됩니다.

스크립트 상태

모든 스크립트에 배지로 표시됩니다.

  • 초안

    생성되었지만 아직 활성 버전이 없습니다.

  • 대기 중

    무언가를 기다리는 중(예: 첫 승인) — 초안 상태로는 실행되지 않습니다.

  • 활성

    승인된 버전이 활성화되어 봇이 로드한 상태입니다.

  • 거부됨

    스태프가 거부했으며 스크립트는 로드되지 않습니다.

  • 비활성화됨

    수동으로 비활성화됨(본인, 스태프, '모두 비활성화' 또는 할당량 축소) — 언로드됨.

  • 격리됨

    한도 남용(요청 폭주, 큐 폭주, 반복 오류) 후 자동 종료되었습니다. 언로드되었으며 스태프의 확인이 필요합니다.

버전 검토

모든 코드 수정본은 스태프의 검토를 거칩니다.

  • 대기 중

    스태프가 아직 결정하지 않았습니다. 활성화할 수 없습니다.

  • 승인됨

    활성화할 수 있습니다. 설치한 스토어 스크립트의 새 업로드는 대기열을 건너뜁니다(등록된 버전은 이미 승인됨).

  • 거부됨

    스태프가 이 코드 수정본을 거부했습니다. 대신 새 버전을 업로드하세요.

할당량: 서버당 활성 스크립트 2개(무료), 프리미엄은 10개입니다. 할당량이 줄어들면 초과분 중 가장 오래된 활성 스크립트부터 자동으로 비활성화되며, 스크립트 로그 채널로 알림이 전송됩니다.

핸들러 및 이벤트

bot:on(event, fn)으로 핸들러를 등록합니다. 스크립트는 로드 시 등록한 이벤트를 보고하며, 해당 이벤트만 전달됩니다. 알 수 없는 이벤트 이름은 로드 실패를 일으키고, 스크립트는 이벤트당 최대 8개의 핸들러를 등록할 수 있습니다.

luaLua
bot:on("message_create", function(msg)
  if msg.content == "!ping" then
    msg:reply("pong")
  end
end)

메시지 관련 이벤트(message_*, automod_check)는 메서드가 있는 메시지 객체를 전달합니다. 메시지 객체를 참고하세요.

호스트 호출은 제어를 넘깁니다. 핸들러가 bot:kv_get 등의 호출을 기다리는 동안 같은 스크립트에서 두 번째 이벤트는 실행되지 않습니다. 새 이벤트는 32개짜리 큐에서 대기하며, 넘치면 가장 오래된 이벤트가 버려지고, 지속적인 폭주는 스크립트를 격리합니다.

guild_id는 항상 페이로드에 추가됩니다. 이벤트는 bot:on으로 등록된 핸들러에만 전달됩니다. 알 수 없는 이벤트 이름은 정책 오류로 로드에 실패합니다. voice_state 이벤트는 없습니다.

이벤트 페이로드

모든 페이로드에는 guild_id가 포함됩니다. 필드는 소문자 snake_case 테이블입니다.

  • message_create

    id, guild_id, channel_id, author_id, author, content, attachments, referenced_id

    태그/자동 응답보다 먼저 발생합니다. 첨부 파일: 최대 8개 항목 { id, filename, size }. 답장 참조가 없으면 referenced_id는 0입니다.

  • message_update

    same as message_create

  • message_delete

    channel_id, id

  • message_delete_bulk

    channel_id, ids

    ids는 삭제된 메시지 ID 배열입니다.

  • guild_member_add

    user_id

    봇 계정은 건너뜁니다.

  • guild_member_remove

    user_id

  • guild_member_update

    user_id

  • reaction_add

    channel_id, id, author_id, user_id, emoji, emoji_id

    봇의 반응은 건너뜁니다. emoji는 이름이며, 유니코드 이모지의 emoji_id는 0입니다.

  • reaction_remove

    channel_id, id, author_id, user_id, emoji, emoji_id

  • channel_create

    id, name, type

  • channel_update

    id, name, type

  • channel_delete

    id, name

  • role_create

    id, name

  • role_update

    id, name

  • role_delete

    id, name

  • thread_create

    id, name

  • thread_update

    id, name

  • thread_delete

    id

  • invite_create

    code, uses

  • invite_delete

    code

  • guild_ban_add

    user_id

  • guild_ban_remove

    user_id

  • welcome_before

    user_id, username

    기본 환영 메시지보다 먼저 발생합니다. 봇이 아닌 계정의 참여에만 해당합니다.

  • goodbye_before

    user_id

    기본 작별 메시지보다 먼저 발생합니다.

  • ticket_created

    ticket_id, user_id, channel_id

  • automod_check

    content, channel_id, author_id, id

    기본 오토모드보다 먼저 발생합니다. msg:escalate()는 처리 중인 메시지를 삭제하며, 기본 차단이 항상 우선합니다. msg:allow()는 아무 동작도 하지 않습니다.

  • webhooks_update

    channel_id

  • emoji_update

    count, ids

    ids는 최대 8개로 제한됩니다.

  • tick

    unix

    60초마다 발생하며 bot:on("tick")을 등록한 스크립트에만 전달됩니다. unix는 유닉스 시간입니다.

호스트 API

이 호출들은 IPC를 통해 봇에 제어를 넘깁니다. 스크립트당 호출 제한: 호스트 호출 10초당 30회, 그리고 Discord 전송 10초당 5회(reply, channel_send, react, edit, delete, pin, escalate, http_get). member_get, guild_get 같은 캐시 읽기는 Discord에 직접 접근하지 않습니다.

  • bot:log(msg)

    스크립트 로그 채널과 오류 로그에 기록합니다. 10초당 30회 호출 한도에 포함됩니다.

  • bot:kv_get(key)

    저장된 JSON 값 또는 nil을 반환합니다. 키: 1~64자 [A-Za-z0-9_-].

  • bot:kv_set(key, value)

    최대 8 KiB의 JSON을 저장합니다. 스크립트당 256개 키, 스크립트당 1 MiB(프리미엄 8 MiB), 서버당 8 MiB.

  • bot:kv_del(key)

    키 하나를 삭제합니다.

  • bot:kv_keys()

    정렬된 키를 최대 256개까지 반환합니다.

  • bot:kv_incr(key, delta?)

    저장된 JSON 숫자를 증가시킵니다. 기본 증분은 1이며 증분은 ±1,000,000,000으로 제한됩니다.

  • bot:db_exec(sql, binds?)

    서버 스크립트 데이터베이스에서 쓰기 또는 스키마 문 하나(CREATE TABLE/INDEX, INSERT, UPDATE, DELETE, DROP)를 실행합니다. 선택적 바인드: nil, boolean, number, string의 배열, 최대 8개. { changes, last_insert_rowid }를 반환합니다. 할당량, 사용 중, 한도 문제는 예외를 발생시키는 대신 { error, kind }를 반환하며, 금지된 문은 정책 오류를 발생시킵니다.

  • bot:db_query(sql, binds?)

    선택적 바인드와 함께 SELECT 하나를 실행하고 최대 50행을 반환합니다. 각 행은 열 이름을 키로 갖는 테이블입니다. 항상 LIMIT을 사용하세요. 실패하면 { error, kind }를 반환하므로 rows.error를 확인하세요.

  • bot:http_get(url)

    HTTPS GET만 가능합니다. 호스트는 서버 허용 목록에 있어야 합니다. { status, body, truncated }를 반환하며 본문은 16 KiB로 제한됩니다. 대기 시간은 Lua의 실제 시간 100ms에 포함되지 않습니다.

  • bot:channel_send(channel_id, text, embed?)

    봇이 쓸 수 있는 자신의 서버 채널 어디로든 전송합니다. 콘텐츠 규칙은 msg:reply와 같습니다.

  • bot:channel_get(channel_id)

    캐시 읽기: { id, name, type, parent_id }. 자신의 서버만 가능합니다.

  • bot:member_get(user_id)

    캐시 읽기: { id, nick, bot, roles (최대 20개) }.

  • bot:role_get(role_id)

    캐시 읽기: { id, name, position, color }.

  • bot:guild_get()

    자신의 서버 캐시: { id, name, owner_id, member_count }.

  • bot:self()

    봇 사용자: { id, username, bot }.

  • bot:has_role(user_id, role_id)

    캐시 읽기, { has: true|false }를 반환합니다.

  • bot:channels()

    서버 채널의 캐시 목록(최대 50개).

  • bot:roles()

    서버 역할의 캐시 목록(최대 50개).

  • bot:now()

    초 단위 유닉스 시간.

  • bot:id()

    이 스크립트의 UUID. 제어를 넘기지 않으며 호출 횟수 제한에 포함되지 않습니다.

  • bot:message_get(channel_id, message_id)

    봇 메시지 캐시 읽기(네트워크 요청 없음).

  • bot:can_send(channel_id)

    봇이 해당 채널에 전송할 수 있는지 { ok: true|false }로 반환합니다.

bot:http_get이 허용하는 것

  • HTTPS만 허용

    http:// URL은 거부됩니다. userinfo(user:pass@host)는 사용할 수 없습니다.

  • 서버 허용 목록

    호스트는 서버의 HTTP 허용 목록(스크립트 페이지, 최대 32개 호스트)에 있어야 합니다. 허용 목록은 직접 관리합니다.

  • 인프라 호스트 불가

    IP 주소, localhost, *.local, *.internal은 항상 거부됩니다.

  • 응답

    { status, body, truncated }, 본문은 16 KiB로 제한됩니다. 타임아웃 3초. 10초당 5회의 Discord 버킷을 공유합니다.

메시지 객체

메시지 이벤트는 msg 테이블과 페이로드 필드를 전달합니다. 메서드는 처리 중인 이벤트 메시지에만 작용하며, 임의의 메시지를 가져와 수정할 수는 없습니다.

모든 곳에 적용되는 콘텐츠 규칙: 텍스트 1~2000바이트, 임베드는 최대 하나 { title, description, color }, 멘션은 이벤트 작성자에 대해서만 해석되며 @everyone과 @here는 제거됩니다. 파일과 스티커는 사용할 수 없습니다.

  • msg:reply(text, embed?)

    이벤트 채널에 답장합니다. 멘션은 이벤트 작성자에 대해서만 해석되며 @everyone / @here는 제거됩니다. 텍스트 1~2000바이트, 임베드는 최대 하나.

  • msg:react(emoji)

    이벤트 메시지에 반응을 추가합니다.

  • msg:unreact(emoji)

    스크립트 자신의 반응을 제거합니다.

  • msg:edit(text, embed?)

    처리 중인 메시지를 수정합니다. 콘텐츠 규칙은 답장과 같습니다.

  • msg:delete()

    처리 중인 메시지를 삭제합니다.

  • msg:pin() / msg:unpin()

    처리 중인 메시지를 고정하거나 고정 해제합니다.

  • msg:escalate()

    automod_check 전용: 처리 중인 메시지를 삭제합니다(추가 작업, 호출 횟수 제한). 기본 오토모드의 판단이 항상 우선합니다.

  • msg:allow()

    automod_check 전용: 메시지를 허용된 것으로 표시합니다. 기본 차단을 되돌릴 수는 없으며, 사실상 아무 동작도 하지 않는 표시입니다.

한도 및 할당량

모든 한도는 봇과 러너가 공유하는 하나의 설정 파일에 있습니다. 여기에 문서로만 존재하는 것이 아니라 런타임에 실제로 적용됩니다. 표시된 수치는 현재 기본값입니다.

  • 언어

    Lua 5.5, 텍스트 청크만 가능(바이트코드는 거부됨). io/os.file/require/load/debug/coroutine 사용 불가.

  • 연산

    핸들러당 실제 시간 약 100ms, 명령어 10,000,000개(카운트 훅).

  • 메모리

    스크립트당 무료 2 MB, 프리미엄 8 MB.

  • 코드 크기

    64 KiB UTF-8 .lua, NUL 바이트 및 바이트코드 불가.

  • 호스트 호출

    스크립트당 10초에 30회(bot:log 포함).

  • Discord 전송

    스크립트당 10초에 5회(reply, channel_send, react/unreact, edit, delete, pin/unpin, escalate, http_get). 10초 안에 거부 8회 → 격리.

  • 핸들러

    스크립트당 이벤트마다 최대 8개. 알 수 없는 이벤트 이름은 로드 시 거부됩니다.

  • 이벤트 큐

    스크립트당 깊이 32, 넘치면 가장 오래된 것부터 버립니다. 10초 안에 8회 이상 버려지면 → 격리.

  • HTTP

    HTTPS + 서버 허용 목록, 타임아웃 3초, 본문 16 KiB, IP/localhost/내부 호스트 불가, userinfo 불가.

  • KV

    키 1~64자 [A-Za-z0-9_-], 값 ≤ 8 KiB JSON, 스크립트당 키 256개, 스크립트당 1 MiB(프리미엄 8 MiB), 서버당 8 MiB.

  • 데이터베이스

    서버당 SQLite 파일 하나를 모든 스크립트가 공유: 1 MiB(프리미엄 8 MiB), 호출당 문 하나, SQL ≤ 2 KiB, 바인드 ≤ 8개, 행 ≤ 50개, 문당 20ms, 테이블과 인덱스 합계 64개. ATTACH, PRAGMA, 트랜잭션, 트리거, 뷰, WITH 불가. 서버의 모든 스크립트가 모든 테이블을 읽고 삭제할 수 있습니다.

  • 활성 스크립트

    서버당 무료 2개, 프리미엄 10개. 초과분 중 가장 오래된 활성 스크립트는 자동으로 비활성화됩니다.

  • 버전

    스크립트당 개수 제한 없음. 모든 새 버전은 스태프가 승인할 때까지 대기합니다.

  • 멘션

    이벤트 작성자만 해당. @everyone / @here는 제거됨. 파일과 스티커 불가.

  • 격리

    오류 3회(타임아웃/메모리/정책/런타임) → 자체 격리 워커(최대 4개), 아니면 격리 + 언로드.

  • 오류 로그

    대시보드에서 최근 24시간 확인 가능, 항목은 7일 후 삭제.

  • 금지 사항

    voice_state 없음, mod_* API(밴/추방/타임아웃) 없음, 서버 간 접근 없음, 데이터베이스/파일 시스템 없음, 수익화 없음.

  • 오토모드

    기본 오토모드가 먼저 실행되며 항상 스크립트보다 우선합니다.

검증

버전을 저장하기 전에 백엔드가 정적 검사를 실행합니다. 시험 실행은 저장 없이 같은 검사를 수행합니다. 샌드박스 자체가 두 번째 방어선입니다. 금지된 전역 객체는 런타임에 아예 존재하지 않습니다.

  • 크기 / 인코딩

    비어 있지 않아야 하며, ≤ 64 KiB, NUL 바이트 없는 UTF-8.

  • 바이트코드

    Lua 바이트코드 시그니처로 시작하는 것은 모두 거부됩니다.

  • 금지된 API

    정적 검사가 다음을 거부합니다: load(, loadfile, dofile, require(, os.execute, io.open, debug., string.dump, loadstring, getfenv, setfenv, package., coroutine., ffi., jit.

  • 런타임 샌드박스

    정적 검사를 빠져나가는 것이 있더라도 샌드박스가 이러한 전역 객체를 제공하지 않으므로 호출은 정책 오류로 실패합니다.

사용 가능한 내장 기능: 파일 접근이 없는 표준 Lua 라이브러리. string, table, math, tostring, tonumber, type, pairs, ipairs, os.time, os.clock, os.difftime, bot.

오류 및 격리

모든 핸들러 오류는 종류와 함께 기록되어 스크립트 상세 페이지(최근 24시간)에 표시됩니다. 항목은 7일 후 삭제됩니다.

  • runtime

    핸들러의 Lua 오류(nil 접근, 잘못된 인수 등). 메시지에는 Lua 오류 텍스트가 포함됩니다.

  • timeout

    핸들러가 실제 시간 100ms를 초과했거나 IPC 호출이 시간 초과(1초)되었습니다.

  • memory

    스크립트가 메모리 상한을 초과했습니다.

  • policy

    스크립트가 허용되지 않는 동작을 시도했습니다: 알 수 없는 이벤트, 과도한 핸들러, 허용되지 않은 API, 너무 큰 페이로드.

반복적인 위반은 자동으로 단계가 올라갑니다. 오류 3회(타임아웃, 메모리, 정책, 런타임)가 발생하면 스크립트는 자체 격리 워커로 옮겨집니다. 문제가 계속되거나 비어 있는 격리 슬롯이 없으면(최대 4개) 스크립트는 격리됩니다. 언로드되고 대시보드에 표시됩니다. 요청 폭주(10초 안에 거부된 Discord 전송 8회 또는 큐 폐기 8회)는 즉시 격리됩니다. 스크립트 로그 채널 또는 DM으로 알림을 받습니다.

운영 측면: 봇은 재시작 없이 킬 스위치로 전역 일시 중지할 수 있고, scripts:updated 신호는 변경된 버전을 몇 초 안에 다시 로드합니다. 둘 다 스태프 전용 작업입니다.

스토어

스토어에서는 스크립트를 공개로 공유하고 다른 서버의 스크립트를 설치할 수 있습니다. 게시와 설치는 모두 사본을 만듭니다. 등록된 스크립트를 설치하면 내 서버에 독립된 스크립트가 만들어지며 작성자의 코드와 연결된 상태로 남지 않습니다.

  • 게시

    승인된 버전이 있는 활성 스크립트, 설명(10~512자), 태그 1~8개([a-z0-9-], 1~24자)가 필요합니다. 첫 게시 전에 스토어 이용 약관에 한 번 동의해야 합니다.

  • 설치

    설치하면 등록된 스크립트가 내 서버에 별도의 스크립트로 복사됩니다(초안, 사전 승인된 버전, 이름이 이미 있으면 접미사가 붙은 고유 이름). KV는 복사되지 않습니다. 등록 항목의 설치 횟수가 증가합니다.

  • 게시 취소

    스토어에서 등록 항목을 제거합니다. 이미 설치된 사본은 그대로 유지되며 계속 실행되지만 더 이상 업데이트를 받지 않습니다.

  • 금전 거래 금지

    설명이나 코드에 판매, 유료 결제, 후원/암호화폐 링크 등 수익화 요소가 있으면 게시 시 거부되며 스크립트가 차단될 수 있습니다.

  • 신고

    서버에 접근할 수 있는 누구나 사유와 함께 등록 항목을 신고할 수 있습니다. 스태프는 미처리 신고 대기열을 확인하고 검토 후 신고를 닫습니다.

  • 한도

    스토어 목록은 쿼리당 최대 50개 항목을 표시합니다(검색, 태그, 설치 수 또는 최신순 정렬). 설치에는 일반 서버 스크립트 할당량이 적용됩니다.

예제

예제를 선택하면 코드가 표시됩니다.