Обзор
Скрипты — это небольшие программы на Lua, которые расширяют возможности вашего сервера. Они выполняются в изолированном процессе-песочнице Lua 5.5, полностью отделённом от бота. Скрипт никогда не обращается к базе данных, файловой системе или другим серверам. Любая возможность доступна только через API хоста из белого списка, который передаёт управление обратно боту по IPC.
Нет загруженного скрипта — нет и процесса.
Вызовы bot:* и msg:* проверяются на права ботом, а не Lua.
АвтоМод, приветствия и тикеты работают всегда. Скрипты дополняют их, но не заменяют.
Упавший скрипт выгружается или помещается в карантин, а бот продолжает работать.
Редактора в браузере нет. Пишите .lua в обычном редакторе и загружайте файл на странице «Скрипты» вашего сервера.
Начало работы
- 1
Напишите файл .lua локально
VS Code, Neovim или любой другой редактор. - 2
Проверьте его локально (необязательно)
script_runner --check file.luaвыводитcheck: ok. - 3
Загрузите его
Откройте страницу «Скрипты» вашего сервера и вставьте код или загрузите файл .lua (до 64 КиБ). - 4
Пробный запуск
Проверяет размер, кодировку, байткод и запрещённые API, ничего не выполняя. - 5
Активируйте
Дождитесь одобрения версии персоналом, затем нажмите «Активировать». Бот загрузит её в течение нескольких секунд. - 6
Укажите канал журнала
Туда будут приходить выводbot:logи уведомления об ошибках.
Локальный CLI
Раннер — тот же бинарный файл, который использует бот.
- script_runner --check file.lua
Статическая проверка синтаксиса и запрещённых API без запуска. Выводит 'check: ok'.
- script_runner --eval 'code'
Запускает фрагмент в песочнице локально. Ошибки выводятся с типом и сообщением.
- script_runner --self-test
Запускает самотесты песочницы, протокола и сокета.
Жизненный цикл и проверка
Скрипты версионируются. Версия фиксируется при отправке и идентифицируется по SHA-256. Активировать можно только одобренные версии, и только активные версии загружаются ботом.
- 1
Написание
Создайте файл .lua локально в VS Code, Neovim или любом другом редакторе. Редактора в браузере намеренно нет. - 2
Пробный запуск / загрузка
Вставьте код или загрузите файл .lua на странице «Скрипты» сервера (≤ 64 КиБ, без двоичных данных). Пробный запуск проверяет размер, кодировку, байткод и запрещённые API, ничего не выполняя. - 3
Проверка
Каждый новый скрипт и каждая новая версия начинаются со статуса «ожидает». Персонал одобряет или отклоняет конкретную ревизию кода, прежде чем она сможет запуститься. - 4
Активация
Вы активируете одобренную версию. Активация версии, ожидающей проверки или отклонённой, запрещена. Ботом загружаются только активные версии. - 5
Запуск
Бот загружает версию (код + SHA-256, проверяется раннером) в изолированную песочницу Lua 5.5. События доставляются только обработчикам, зарегистрированным через bot:on. - 6
Обновление / откат
Загрузите новую версию (снова в статусе «ожидает») или активируйте более старую одобренную версию для отката. KV-хранилище сохраняется. - 7
Отключение / удаление
Отключение немедленно останавливает загрузку, «Отключить все» затрагивает все активные скрипты сервера. Удаление убирает скрипт, его версии, KV и ошибки.
Статус скрипта
Отображается значком у каждого скрипта.
- черновик
Создан, активной версии пока нет.
- ожидает
Ожидает чего-либо (например, первого одобрения) — как черновик никогда не запускается.
- активен
Одобренная версия активирована и загружена ботом.
- отклонён
Персонал отклонил скрипт; он не будет загружен.
- отключён
Отключён вручную (вами, персоналом, «Отключить все» или из-за снятия квоты) — выгружен.
- в карантине
Автоматическое отключение после злоупотребления лимитами (лавина запросов, переполнение очереди, повторные сбои). Выгружен и требует внимания персонала.
Проверка версии
Каждая ревизия кода проходит проверку персоналом.
- ожидает
Персонал ещё не принял решение. Активировать нельзя.
- одобрена
Можно активировать. Новые загрузки установленных скриптов из магазина обходят очередь (версия записи уже одобрена).
- отклонена
Персонал отклонил эту ревизию кода. Вместо этого загрузите новую версию.
Квота: 2 активных скрипта на сервер бесплатно, 10 с Premium. Если квота уменьшается, самые старые активные скрипты сверх неё отключаются автоматически, а вы получаете уведомление в канале журнала скриптов.
Обработчики и события
Регистрируйте обработчики с помощью bot:on(event, fn). Скрипт сообщает о зарегистрированных событиях при загрузке, и ему доставляются только они. Неизвестные имена событий приводят к ошибке загрузки, а на одно событие скрипт может зарегистрировать не более 8 обработчиков.
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 — массив идентификаторов удалённых сообщений.
- 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 — название, для эмодзи Unicode 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 — время Unix.
API хоста
Эти вызовы передают управление боту по IPC. Лимиты частоты на скрипт: 30 вызовов хоста / 10 с плюс 5 отправок в Discord / 10 с (reply, channel_send, react, edit, delete, pin, escalate, http_get). Чтение из кэша, например member_get и guild_get, никогда не обращается к Discord напрямую.
- bot:log(msg)
Записывает в канал журнала скриптов и журнал ошибок. Учитывается в лимите 30 вызовов / 10 с.
- bot:kv_get(key)
Возвращает сохранённое значение JSON или nil. Ключи: 1–64 символа [A-Za-z0-9_-].
- bot:kv_set(key, value)
Сохраняет до 8 КиБ JSON. 256 ключей на скрипт, 1 МиБ на скрипт (8 МиБ с Premium), 8 МиБ на сервер.
- 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 КиБ. Время ожидания не учитывается в 100 мс реального времени Lua.
- 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()
Время Unix в секундах.
- bot:id()
UUID этого скрипта. Не передаёт управление и не учитывается в лимитах частоты.
- bot:message_get(channel_id, message_id)
Чтение из кэша сообщений бота (без обращения к сети).
- bot:can_send(channel_id)
Возвращает { ok: true|false } — может ли бот отправлять сообщения в этот канал.
Что принимает bot:http_get
- Только HTTPS
URL с http:// отклоняются. Без userinfo (user:pass@host).
- Белый список сервера
Хост должен входить в HTTP-белый список сервера (страница «Скрипты», до 32 хостов). Вы управляете им самостоятельно.
- Никаких инфраструктурных хостов
IP-адреса, localhost, *.local и *.internal всегда запрещены.
- Ответ
{ status, body, truncated }, тело ограничено 16 КиБ. Тайм-аут 3 с. Использует общий лимит Discord — 5 за 10 с.
Объект сообщения
События сообщений передают таблицу 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.
- Вычисления
~100 мс реального времени на обработчик, 10 000 000 инструкций (хук подсчёта).
- Память
2 МБ на скрипт бесплатно, 8 МБ с Premium.
- Размер кода
64 КиБ UTF-8 .lua, без NUL-байтов и байткода.
- Вызовы хоста
30 за 10 с на скрипт (включая bot:log).
- Отправки в Discord
5 за 10 с на скрипт (reply, channel_send, react/unreact, edit, delete, pin/unpin, escalate, http_get). 8 отказов за 10 с → карантин.
- Обработчики
Не более 8 на событие на скрипт. Неизвестные имена событий отклоняются при загрузке.
- Очередь событий
Глубина 32 на скрипт, при переполнении удаляется самое старое событие. 8+ потерь за 10 с → карантин.
- HTTP
HTTPS + белый список сервера, тайм-аут 3 с, тело 16 КиБ, без IP/localhost/внутренних хостов, без userinfo.
- KV
Ключи 1–64 [A-Za-z0-9_-], значения ≤ 8 КиБ JSON, 256 ключей/скрипт, 1 МиБ/скрипт (8 МиБ с Premium), 8 МиБ/сервер.
- База данных
Один файл SQLite на сервер, общий для всех его скриптов: 1 МиБ (8 МиБ с Premium), один запрос за вызов, SQL ≤ 2 КиБ, ≤ 8 параметров, ≤ 50 строк, 20 мс на запрос, 64 таблицы и индекса. Без ATTACH, PRAGMA, транзакций, триггеров, представлений и WITH. Каждый скрипт на сервере может читать и удалять любую таблицу.
- Активные скрипты
2 на сервер бесплатно, 10 с Premium. Лишние самые старые активные скрипты отключаются автоматически.
- Версии
Количество версий на скрипт не ограничено; каждая новая версия ожидает одобрения персоналом.
- Упоминания
Только автор события. @everyone / @here удаляются. Без файлов и стикеров.
- Изоляция
3 сбоя (тайм-аут/память/политика/выполнение) → отдельный изолированный воркер (максимум 4), иначе карантин и выгрузка.
- Журнал ошибок
Последние 24 ч видны в панели, записи удаляются через 7 дней.
- Запрещено
Без voice_state, без API mod_* (бан/кик/тайм-аут), без доступа между серверами, без базы данных/файловой системы, без монетизации.
- АвтоМод
Встроенный АвтоМод работает первым и всегда приоритетнее скриптов.
Валидация
Перед сохранением версии бэкенд выполняет статические проверки. Пробный запуск делает то же самое без сохранения. Сама песочница — вторая линия защиты: запрещённых глобальных объектов во время выполнения просто нет.
- Размер / кодировка
Не пустой, ≤ 64 КиБ, UTF-8 без NUL-байтов.
- Байткод
Всё, что начинается с сигнатуры байткода 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
Обработчик превысил 100 мс реального времени или истёк тайм-аут IPC-вызова (1 с).
- memory
Скрипт превысил лимит памяти.
- policy
Скрипт попытался сделать что-то недопустимое: неизвестное событие, слишком много обработчиков, запрещённый API, слишком большие данные.
Повторные нарушения приводят к автоматической эскалации: 3 сбоя (тайм-аут, память, политика, выполнение) переносят скрипт в отдельный изолированный воркер. Если нарушения продолжаются или нет свободного изолированного слота (максимум 4), скрипт помещается в карантин: выгружается и помечается в панели. Лавина запросов (8 отклонённых отправок в Discord или 8 сброшенных событий очереди за 10 с) отправляет в карантин немедленно. Вы получаете уведомление в канале журнала скриптов или в личных сообщениях.
Со стороны эксплуатации: бота можно глобально приостановить аварийным выключателем без перезапуска, а сигнал scripts:updated перезагружает изменённые версии в течение нескольких секунд. Оба действия доступны только персоналу.
Магазин
Магазин позволяет публично делиться скриптами и устанавливать скрипты с других серверов. И публикация, и установка создают копии. Установка записи создаёт независимый скрипт на вашем сервере и не остаётся связанной с кодом автора.
- Публикация
Требуются активный скрипт с одобренной версией, описание (10–512 символов) и 1–8 тегов ([a-z0-9-], 1–24 символа). Перед первой публикацией необходимо один раз принять условия магазина.
- Установка
При установке запись копируется на ваш сервер как отдельный скрипт (черновик, заранее одобренная версия, собственное имя с суффиксом, если оно занято). KV не копируется. Счётчик установок записи увеличивается.
- Снятие с публикации
Удаляет запись из магазина. Существующие копии остаются и продолжают работать; они просто больше не получают обновлений.
- Никаких денег
Продажи, платный доступ, ссылки на пожертвования/криптовалюту и подобная монетизация в описании или коде отклоняются при публикации и могут привести к блокировке скрипта.
- Жалобы
Любой, кто имеет доступ к серверу, может пожаловаться на запись, указав причину. Персонал видит очередь открытых жалоб и закрывает их после проверки.
- Лимиты
Список магазина показывает до 50 записей на запрос (поиск, тег, сортировка по установкам или новизне). На установки действует обычная квота скриптов сервера.
Примеры
Выберите пример, чтобы увидеть его код.