Документация · всё в одном месте


Как настроить модули сервера и как с нуля научиться собирать свои команды: от переменных до игр с прокачкой, магазинами и таблицами лидеров. Каждый пример ниже запущен на настоящем движке бота, а кнопки в превью можно нажимать.

Уроков и проектов
28
Функций движка
190
Слэш-команд
77
Модулей в панели
8

Маршрут обучения

Идите по порядку: каждый урок опирается на предыдущий. Прочитанные главы отмечаются галочкой, прогресс сохраняется в этом браузере.

0 / 28
Часть 1

Знакомство

Новичок 3 мин

Что такое SilentBot

SilentBot — многофункциональный бот для Discord. В нём есть готовые модули (модерация, уровни, экономика, кланы, музыка, тикеты и другие) и конструктор своих команд с собственным языком шаблонов. Всё настраивается на этом сайте, а в Discord вы просто пользуетесь результатом.

Как устроена документация

Документация разделена на пять частей. Читать всё подряд не обязательно, выбирайте свою дорогу:

  • Администратору сервера хватит частей «Знакомство» и «Модули сервера»: там описаны все слэш-команды и каждая настройка панели.
  • Тем, кто хочет делать свои команды, подойдёт «Учебник». Он начинается с самых простых вещей и шаг за шагом доходит до игр с сохранением прогресса. Знать программирование заранее не нужно.
  • Тем, кто уже умеет, пригодятся «Проекты» и «Справочник» со всеми функциями движка.

За пять минут: добавить бота, войти на сайт и разобраться, где какие настройки.

  1. Добавьте бота на сервер. На главной странице нажмите «Добавить на сервер» и выберите сервер. Нужны права «Управлять сервером». Оставьте боту все права, которые он запрашивает: без них не будут работать модерация, роли и тикеты.
  2. Войдите на сайт через Discord. Кнопка «Войти» вверху страницы. В разделе «Мои серверы» появятся серверы, где вы администратор.
  3. Поднимите роль бота. В настройках сервера Discord → «Роли» перетащите роль SilentBot выше ролей, которые бот должен выдавать. Discord не даёт ботам управлять ролями выше их собственной.
  4. Откройте панель сервера и пройдитесь по вкладкам ниже.

Вкладки панели управления

ОсновноеПрефикс текстовых команд (по умолчанию !), язык ответов и язык названий команд (русский или английский), часовой пояс, цвет системных сообщений. Здесь же включаются слэш-команды и текстовые команды на сервере.
Команды и триггерыВаши команды (создание, импорт и экспорт), триггеры событий и входящие вебхуки.
Модерация и журналЖурнал аудита: какие события записывать и в какой канал.
АналитикаСколько раз вызывали ваши команды, топ команд и ошибок, самые активные участники за 7 или 30 дней.
УровниОпыт за сообщения, кулдаун, множитель, объявления о новом уровне и топ участников.
МодулиПриветствия, награды за уровни, тикеты, голосовые комнаты, лестница наказаний, проверка новичков, антирейд, публичная страница, роли по кнопкам и оповещения YouTube.
АвтомодАвтоматические фильтры: спам, капс, инвайты, стоп-слова и массовые пинги.
ПеременныеОбщие переменные сервера (servervar_*) и макросы: кусочки шаблонов, которые можно вызывать из разных команд.
Данные игроковВсё, что команды запомнили через store_*: поиск, правка и удаление значений, таблицы лидеров с закрытием сезона и экспорт в JSON.
ЗадачиОтложенные и повторяющиеся задачи (schedule_in, schedule_cron) и журнал запусков команд с ошибками.

Два способа вызвать команду

Слэш-командаНаберите / в чате, и Discord покажет меню команд с подсказками и полями ввода. Это удобнее всего.
Текстовая команда!баланс, !привет @друг: префикс, имя и аргументы через пробел. Работает, только если текстовые команды включены во вкладке «Основное».
Часть 2

Модули сервера

Модерация состоит из трёх слоёв. Первый — команды для модераторов. Второй — журнал аудита, который записывает, что происходит на сервере. Третий — автомод и лестница наказаний, которые работают сами, без участия людей.

Команды модераторов

Каждое наказание сохраняется как дело с номером. Его можно найти в /дела история и отменить через /дела отменить: снять предупреждение, досрочно закончить мут или разбанить. Длительность пишется так: 30m, 2h, 1d2h, также понимаются 30м и 2ч. У /бан можно указать срок, и тогда бан снимется сам.

/бан <пользователь> [причина] [дней_удаления] [длительность] Забанить пользователя.
/кик <пользователь> [причина] Кикнуть пользователя.
/анбан <id_пользователя> [причина] Снять бан с пользователя по ID.
/мут <пользователь> <длительность> [причина] Выдать тайм-аут пользователю.
/предупреждение <пользователь> [причина] Выдать предупреждение пользователю.
/преды <пользователь> Показать список предупреждений пользователя.
/дела история <пользователь> Все наказания участника.
/дела отменить <номер> [причина] Отменить дело: снять предупреждение, мут или бан.
/очистить <количество> Удалить сообщения в текущем канале.
/медленный_режим <секунды> Установить медленный режим в текущем канале.

В угловых скобках указаны обязательные параметры, в квадратных — необязательные. Наведите на параметр, чтобы увидеть подсказку.

Лестница наказаний

Команда /предупреждение копит предупреждения, а бот сам наказывает, когда их становится слишком много. Например: 3 предупреждения дают мут на час, 5 — кик, 7 — бан. Значение 0 выключает ступень. Предупреждения могут сгорать через заданное число дней, чтобы старые нарушения не тянулись вечно.

Лестница наказаний Панель → Модули
  • Мут после N предупреждений
  • Длительность мута, минут
  • Кик после N предупреждений
  • Бан после N предупреждений
  • Предупреждения сгорают через, дней (0 — никогда)
  • Сообщать участнику в личку о наказании

Журнал аудита

Вкладка «Модерация и журнал». Укажите канал и отметьте нужные события:

Новые участникиУход и кикИзменение ролейУдаление и правка сообщенийКаналыГолосовая активностьДействия модераторов ботаБаны и разбаныНики и таймаутыПриглашенияВеткиЭмодзи

Автомод

Автомод настраивается на вкладке «Автомод» и включает пять фильтров. Каждый включается отдельно:

Анти-спамБольше N сообщений за M секунд от одного участника.
Анти-капсПроцент заглавных букв в сообщении не короче заданной длины.
Анти-инвайтыУдаляет приглашения на чужие серверы (discord.gg).
Анти-пингиСлишком много упоминаний в одном сообщении.
Стоп-словаВаш список запрещённых слов.
Что делатьТолько удалить, удалить с предупреждением, или предупреждение с мутом при достижении лимита.

Для доверенных ролей и каналов есть исключения: например, модераторы могут кидать инвайты, а в канале #мемы можно писать капсом.

Два модуля во вкладке «Модули» защищают сервер от ботов и массовых набегов.

Проверка новичков

Бот публикует панель с кнопкой «Я не бот». Новичок нажимает её и решает простой пример вроде «Сколько будет 4 + 7?», после чего получает роль доступа. Можно заодно снимать роль «Новичок». Если пример не нужен, выключите его, и хватит одного нажатия.

  1. Создайте роль, например «Проверен», и откройте ей каналы сервера. У роли @everyone оставьте доступ только к каналу проверки.
  2. Заполните модуль «Проверка новичков» и нажмите «Опубликовать панель проверки».
Проверка новичков Панель → Модули
  • Проверка включена
  • Канал с панелью проверки
  • Роль после проверки
  • Снять роль после проверки (например «Новичок»)
  • Текст панели
  • Спрашивать пример (иначе достаточно нажать кнопку)

Защита от рейдов

Если за короткое время заходит слишком много аккаунтов, например 10 за 15 секунд, бот поднимает тревогу. Он может только оповестить модераторов, кикнуть всех, кто зашёл во время рейда, или поднять уровень проверки сервера до «Высокого». Отдельная настройка кикает слишком молодые аккаунты, созданные меньше N дней назад.

Защита от рейдов Панель → Модули
  • Защита включена
  • Сколько входов считать рейдом
  • За сколько секунд
  • Что делать при рейде
  • Кикать аккаунты моложе N дней (0 — выключено)
  • Канал для оповещений

Модуль встречает новичков сообщением и картинкой-карточкой с аватаркой, может написать им в личку, прощается с ушедшими и сразу выдаёт стартовые роли.

КарточкаКартинка 900×300 с аватаркой, ником и вашим заголовком. Цвет можно выбрать.
АвторолиОтдельные списки ролей для людей и для ботов, которых добавили на сервер.

В текстах можно использовать подстановки: {user} (упоминание), {user.name}, {server}, {members} (номер участника) и \n для переноса строки.

Пример текста приветствия
Добро пожаловать на **{server}**, {user}! Ты наш {members}-й участник 🎉
Загляни в #правила и выбери роли в #роли.
Приветствия и автороли Панель → Модули
  • Приветствовать новых участников
  • Канал приветствий
  • Текст приветствия
  • Картинка-карточка с аватаркой
  • Заголовок карточки
  • Цвет карточки
  • Также написать новичку в личку
  • Текст в личку
  • Прощаться с ушедшими
  • Канал прощаний (пусто — канал приветствий)
  • Текст прощания
  • Роли новым участникам
  • Роли добавленным ботам

В текстах: {user} — упоминание, {user.name} — имя, {user.id}, {server} — сервер, {members} — число участников, \n — перенос строки

Новичок 4 мин

Уровни и награды

Участники получают опыт за сообщения и растут в уровнях. За уровни можно выдавать роли, а некоторым ролям давать ускоренный опыт.

Опыт — вкладка «Уровни»

Здесь задаются опыт за одно сообщение, кулдаун (сообщения чаще, чем раз в N секунд, опыта не дают, так спамом не прокачаться) и общий множитель. Там же включаются объявления о новом уровне и выбирается их канал.

Награды — модуль «Награды за уровни»

  • Роль за уровень: например, «Активный» на 5 уровне и «Ветеран» на 20.
  • Оставлять прошлые роли или держать только роль за самый высокий уровень.
  • Множители ролей: бустерам 150% опыта. Если подходит несколько ролей, берётся самый большой множитель.
  • Каналы без опыта: флуд-каналы, боты, #spam.
  • Поздравление с подстановкой {level} и карточка ранга для /ранг.
Награды за уровни Панель → Модули
  • Роль за достижение уровня (роль → уровень)
  • Оставлять роли за прошлые уровниВыключите, чтобы у участника была только роль за самый высокий достигнутый уровень.
  • Множитель опыта для ролей, % (роль → процент)Берётся самый большой множитель из ролей участника, например бустерам 150%.
  • Каналы без опыта
  • Поздравление с новым уровнем
  • Показывать /ранг картинкой
  • Цвет карточки ранга

В текстах: {user} — упоминание, {user.name} — имя, {level} — новый уровень, {server} — сервер

Команды

/ранг [user] Показать ваш уровень и опыт.
/топ-уровней Топ пользователей по уровню.
Новичок 4 мин

Экономика и казино

Встроенная экономика: кошелёк и банк, ежедневные и еженедельные награды, работа, магазин, переводы, азартные игры и достижения. Новичок начинает с 500 монет.

200 + серия/ежедневно: раз в 24 часа, +25 за каждый день подряд (до +300)
1300+/еженедельно: раз в 7 дней, больше с уровнем
120–300/работа: раз в час
2 часаперерыв между ограблениями, у жертвы должно быть от 250 монет

Кошелёк и банк

Монеты в кошельке можно потратить, проиграть или потерять при ограблении. Монеты в банке в безопасности. Переводите их командами /депозит и /снять.

Слоты

Три одинаковых символа умножают ставку: 🍒 ×2, 🍋 ×3, 🍇 ×4, ⭐ ×5, 💎 ×8, 👑 ×12. Чем ценнее символ, тем реже он выпадает. Ещё есть /монетка (50 на 50), /дуэль против другого игрока и рискованное /ограбить.

Все команды экономики

/баланс [пользователь] Показать баланс пользователя.
/ежедневно Получить ежедневную награду.
/еженедельно Получить еженедельную награду.
/работа Заработать монеты работой.
/перевести <пользователь> <сумма> Перевести монеты другому пользователю.
/топ [страница] Топ пользователей по капиталу.
/депозит <сумма> Положить монеты в банк.
/снять <сумма> Снять монеты из банка.
/инвентарь [пользователь] Показать инвентарь пользователя.
/магазин Показать товары магазина.
/купить <товар> [количество] Купить товар в магазине.
/продать <товар> [количество] Продать товар из инвентаря.
/профиль [пользователь] Показать профиль экономики.
/достижения [пользователь] Показать прогресс достижений экономики.
/промокод <код> Активировать промокод на монеты.
/монетка <ставка> Подбросить монетку на ставку.
/слоты <ставка> Прокрутить слот-машину на ставку.
/дуэль <пользователь> <ставка> Дуэль на ставку с другим пользователем.
/ограбить <пользователь> Попробовать ограбить другого пользователя.
/эко-админ выдать <пользователь> <сумма> Выдать монеты пользователю.
/эко-админ забрать <пользователь> <сумма> Снять монеты у пользователя.
/эко-админ установить <пользователь> <сумма> Установить баланс кошелька пользователю.
/эко-админ сброс <пользователь> Сбросить экономику пользователя.
/эко-админ товар-добавить <название> <цена> <описание> Добавить товар в магазин.
/эко-админ товар-удалить <товар> Удалить товар из магазина.
Новичок 4 мин

Кланы

Кланы — это MMO внутри Discord. Игроки объединяются, копят казну, выполняют задания, строят цитадель, воюют с другими кланами и вместе ходят в рейды на боссов. Всё управляется одной командой /клан: она открывает интерактивную панель с разделами.

📊Инфоуровень клана, участники и казна
💰Казнавзносы и снятие (с перерывом), вместимость растёт с уровнем
📋Заданияобщие цели и экспедиции раз в 4 часа за жетоны и очки
🛒Магазинулучшения за жетоны: больше мест, доход, склад
👥Участникилидер, офицеры и участники, приглашения на 7 дней
🏰Цитадельпостройки, которые усиливают клан
📦Складобщие ресурсы клана
⚔️Войнаосады и войны с другими кланами
🐉Операциисовместные рейды на боссов
🤝Дипломатиясоюзы и отношения с другими кланами
🗳️Управлениеголосования и правила клана
💬Чатсообщения внутри клана
📈Прогрессуровень, опыт и достижения
🌐Публичноевитрина клана для остальных
⚙️Настройканазвание, описание, цвет, вступление
  • Создание клана стоит 100 000 монет экономики. В клане может быть до 30 участников, улучшения добавляют места.
  • Роли внутри клана: лидер (всё), офицеры (помощники лидера), участники.
  • Если включена публичная страница сервера, лучшие кланы видны и на сайте.
/клан Открыть интерактивную панель клана.

Пять модулей для жизни сообщества. Все, кроме розыгрышей, настраиваются во вкладке «Модули» и переживают перезапуск бота: кнопки продолжают работать.

Тикеты поддержки

В выбранном канале появляется панель с кнопкой «Создать обращение». Нажатие создаёт приватный канал, который видят только автор и роли поддержки. Когда вопрос решён, тикет закрывают, и расшифровка всей переписки уходит в канал логов. Можно ограничить число открытых тикетов на одного участника.

Тикеты поддержки Панель → Модули
  • Тикеты включены
  • Канал с кнопкой «Создать обращение»
  • Заголовок панели
  • Текст панели
  • Надпись на кнопке
  • Категория для каналов тикетов
  • Роли поддержки (видят все тикеты)
  • Первое сообщение в тикете
  • Канал для расшифровок закрытых тикетов
  • Открытых тикетов на участника

В текстах: {user} — автор обращения, {user.name}, {server}

Розыгрыши

Команда /розыгрыш создать публикует карточку с кнопкой участия. Длительность — от 1 минуты до 60 дней (30m, 2h, 3d), победителей — до 20. Участие можно ограничить ролью или минимальным уровнем. Итоги подводятся автоматически. Если победитель не отозвался, есть /розыгрыш перевыбрать. Нужно право «Управлять сервером».

/розыгрыш создать <приз> <длительность> [победителей] [роль] [уровень] Запустить розыгрыш в этом канале.
/розыгрыш завершить <номер> Подвести итоги досрочно.
/розыгрыш перевыбрать <номер> Выбрать новых победителей завершённого розыгрыша.

Роли по кнопкам

Меню из кнопок, по одной на каждую роль: «🎮 Геймер», «🎨 Художник»… Режим «можно несколько» переключает роли, режим «только одна» снимает остальные роли этого меню. На сервере может быть до 25 меню, в каждом до 25 ролей. Роли с правами модерации или администратора бот выдавать откажется, это защита от ошибок.

Приватные голосовые комнаты

Участник заходит в канал «➕ Создать комнату» и попадает в свою комнату. В её текстовом чате появляются кнопки 🔒 закрыть или открыть, 👥 лимит и ✏️ название. Пустая комната удаляется сама.

Приватные голосовые комнаты Панель → Модули
  • Комнаты включены
  • Голосовой канал «➕ Создать комнату»
  • Категория для комнат (пусто — как у канала-создателя)
  • Название комнаты
  • Лимит участников по умолчанию (0 — без лимита)

В текстах: {user.name} — имя владельца

Оповещения YouTube

Вставьте ссылку на канал или @handle и выберите канал Discord. Бот проверяет новые видео каждые 5 минут, API-ключ не нужен. Текст оповещения можно изменить, по умолчанию он такой:

Текст оповещения
📺 Новое видео на канале **{author}**: {title}
{url}

Музыка в голосовых каналах, мини-игры и полезные инструменты.

Музыка

Зайдите в голосовой канал и напишите /играть с названием трека или ссылкой, плейлисты тоже подходят. /поиск покажет несколько вариантов в меню. Очередью управляют кнопки плеера и команды ниже.

/музыка Показать статус музыкального модуля.
/играть <запрос> Добавить трек или плейлист в очередь.
/поиск <запрос> Найти треки и выбрать нужный в меню.
/очередь Показать текущую очередь треков.
/текущий Показать текущий трек.
/пауза Поставить воспроизведение на паузу.
/продолжить Продолжить воспроизведение.
/пропустить Пропустить текущий трек.
/стоп Остановить музыку и очистить очередь.
/выйти Отключить бота от голосового канала.
/очиститьочередь Очистить очередь, не трогая текущий трек.

Развлечения

/пейнт — общий холст, на котором рисует весь сервер. /бродилка — процедурный мир 12×12, по которому можно гулять.

/мем Получить случайный мем с Reddit.
/шар <вопрос> Спросить магический шар.
/орел_решка Подбросить монетку.
/кубик <бросок> Бросить кубики в формате NdM (например, 2d6).
/шутка Получить случайную шутку.
/цитата Получить мотивационную цитату.
/бродилка Открыть процедурно сгенерированный мир 12x12.
/пейнт Многопользовательская рисовалка на общем холсте.

Полезное

/напоминание <время> <сообщение> Создать напоминание.
/опрос <вопрос> <варианты> Создать опрос.
/перевод <текст> <язык> Перевести текст на другой язык.
/ascii <изображение> [ширина] Преобразовать изображение в ASCII-арт.
/погода <город> Показать погоду в городе.
/калькулятор <выражение> Вычислить арифметическое выражение.
/роль <роль> Выдать или снять себе роль.
/таймер <время> Запустить таймер.
/идея <текст> Опубликовать идею для сервера.

Информация

/хелп показывает команды по категориям на языке, выбранном в панели. Ваши собственные команды туда тоже попадают.

/хелп Показать команды по категориям.
/пользователь [участник] Показать информацию о пользователе.
/сервер Показать информацию о сервере.
/сервера Показать серверы, на которых находится бот.
/аватар [участник] Показать аватар пользователя.
/бот Показать информацию о боте.
/пинг Проверить задержку бота.

Возможности сайта помимо панели настроек.

Silent AI в редактореКнопка «Silent AI» в редакторе команды открывает чат. Опишите словами, что нужно («сделай рыбалку с продажей улова»), и ИИ напишет или исправит команду. Можно приложить скриншот ошибки или результат эмулятора. Прежде чем предложить команду, ИИ прогоняет её через движок.
/нейросеть в DiscordПомощник по боту: ответит, где что настраивается, как работает команда или функция движка, и даст ссылку на нужный раздел. Отвечает в чате для всех; чтобы продолжить разговор, ответьте на его сообщение. Писать и править свои команды он отправит в редактор на сайт, к Silent AI.
ГалереяКоманды, которыми поделились другие серверы. Откройте любую и импортируйте к себе. Ставьте лайки и сохраняйте закладки: в боковой панели галереи есть вкладки «Понравившиеся», «Сохранённые» и «Импортированные». Наборы ставят сразу несколько команд одной кнопкой. Свою команду можно опубликовать из редактора с описанием и категорией.
История версийКаждое сохранение команды — это версия. Если что-то сломалось, откройте историю в редакторе и откатитесь.
Поделиться и импортСсылка на команду открывает страницу импорта: получатель выбирает свой сервер. Через «Экспорт» и «Импорт» во вкладке команд можно переносить сразу все команды.
Публичная страница сервераОткрытая страница с описанием, приглашением, топом по уровням и кланами. Её удобно показывать тем, кто ещё не на сервере.
Межсерверный чатОбщий чат на сайте для участников серверов, где есть бот. Писать можно от имени своего сервера, картинки прикрепляются до 2 МБ.
СтатистикаПубличная страница со статистикой бота.
Публичная страница сервера Панель → Модули
  • Страница открыта для всех
  • Описание сервера
  • Ссылка-приглашение (discord.gg/...)
  • Показывать топ по уровням
  • Показывать кланы
  • Показывать имена участников (иначе — скрытые)
/нейросеть [вопрос] Silent AI: ответит на вопросы о боте, своих командах, сайте и панели управления.
Часть 3

Учебник: свои команды

Новичок 5 мин

Первая команда

Своя команда — это шаблон: текст, в котором некоторые места бот заполняет сам. Начнём с самого простого приветствия и к концу учебника дойдём до игр с сохранением прогресса.

  1. Откройте панель сервера → «Команды и триггеры» → «Создать команду».
  2. Придумайте имя, например привет. По нему команду будут вызывать: /привет или !привет.
  3. Напишите шаблон, посмотрите результат в эмуляторе справа и нажмите «Сохранить». Через пару секунд команда появится в меню / на сервере.

Главное правило: фигурные скобки

Всё, что вне фигурных скобок, бот отправит как есть. То, что внутри {…}, он вычислит и подставит результат. {user.mention} превратится в упоминание того, кто вызвал команду, а {guild.name} — в название сервера.

Приветствие в одну строку
Шаблон сообщения
Привет, {user.mention}! 👋
Добро пожаловать на **{guild.name}**.
Результат в Discord
T
Tester
/привет
SilentBotБОТ
Привет, @Tester! 👋
Добро пожаловать на Silent Server.

Сообщение и панель

В редакторе два поля, и у каждого своя роль:

Шаблон сообщенияОбычный текст над карточкой. Поддерживает Markdown Discord: **жирный**, *курсив*, `код`, > цитата, ||спойлер||. Может быть пустым.
Шаблон панелиКрасивая карточка (embed) с заголовком, цветом, полями и картинками, а также кнопки, формы и меню. Каждая строка начинается с директивы: title:, description:, color:…
То же приветствие, но карточкой
Шаблон панели
title: 👋 Привет, {user.display_name}!
description: Рады видеть тебя на **{guild.name}**.\nНас уже **{guild.member_count}**.
color: #5865F2
thumbnail: {user.avatar}
Результат в Discord
T
Tester
/привет
SilentBotБОТ
👋 Привет, Tester!
Рады видеть тебя на Silent Server.
Нас уже 142.
Попробуйте сами Команда /правила

Сделайте команду правила: карточку с заголовком «📜 Правила {guild.name}», тремя пунктами в описании через \n и цветом #e74c3c.

Шаблон панели
title: 📜 Правила {guild.name}
description: 1. Уважайте друг друга\n2. Без спама и рекламы\n3. Споры решаем в личке
color: #e74c3c
Результат в Discord
T
Tester
/правила
SilentBotБОТ
📜 Правила Silent Server
1. Уважайте друг друга
2. Без спама и рекламы
3. Споры решаем в личке

Переменная — это имя в фигурных скобках, вместо которого бот подставит значение: кто вызвал команду, на каком сервере и в каком канале. Путь пишется через точку: {объект.свойство}.

Тот, кто вызвал команду — user
{user.mention}
упоминание (синяя подсветка), то же, что просто {user}
{user.name}
имя аккаунта
{user.display_name}
ник на этом сервере
{user.id}
числовой ID
{user.avatar}
ссылка на аватарку (для thumbnail: и image:)
{user.top_role}
самая высокая роль
{user.color}
цвет ника, например #5865f2
{user.joined_at}
когда зашёл на сервер (Discord покажет в часовом поясе читателя)
{user.created_at}
когда создан аккаунт
{user.bot}
true, если это бот
Сервер — guild
{guild.name}
название
{guild.member_count}
сколько участников
{guild.icon}
ссылка на иконку
{guild.id}
ID сервера
{guild.boost_count}
число бустов, {guild.boost_level} — уровень
{guild.roles_count}
ролей, {guild.channels_count} — каналов
Канал — channel
{channel.mention}
ссылка на канал
{channel.name}
название
{channel.id}
ID, {channel.topic} — тема канала
Карточка профиля из одних переменных
Шаблон панели
title: 🪪 {user.display_name}
thumbnail: {user.avatar}
color: #82e0ff
field: Упоминание | {user.mention} | true
field: ID | {user.id} | true
field: Главная роль | {user.top_role} | true
field: Сервер | {guild.name} ({guild.member_count} участников) | false
field: Канал | {channel.mention} | false
footer: Команда вызвана в #{channel.name}
Результат в Discord
T
Tester
/профиль
SilentBotБОТ
🪪 Tester
Упоминание
@Tester
ID
7
Главная роль
@Member
Сервер
Silent Server (142 участников)
Канал
#канал
Попробуйте сами Карточка сервера

Соберите карточку «О сервере»: иконка сервера в thumbnail:, название в заголовке, в полях — число участников и бустов.

Шаблон панели
title: 🏠 {guild.name}
thumbnail: {guild.icon}
field: 👥 Участники | {guild.member_count} | true
field: 🚀 Бусты | {guild.boost_count} (уровень {guild.boost_level}) | true
color: #82e0ff
Результат в Discord
T
Tester
/сервер
SilentBotБОТ
🏠 Silent Server
👥 Участники
142
🚀 Бусты
(уровень )

Аргументы — это то, что игрок написал после названия команды. В !обнять @Маша аргумент — @Маша. Благодаря аргументам одна команда умеет разные вещи.

Слова по номерам

{args}
всё, что написали после команды
{args.1}, {args.2}…
первое, второе… слово (есть и короткая запись {arg1})
{args.2+}
всё, начиная со второго слова: удобно для причины или текста
{args.count}
сколько слов написали
Разбираем фразу на части
Шаблон сообщения
Всё целиком: **{args}**
Первое слово: **{args.1}**
Второе слово: **{args.2}**
Со второго до конца: **{args.2+}**
Сколько слов: **{args.count}**
Результат в Discord
T
Tester
/заказ меч огненный 3 штуки
SilentBotБОТ
Всё целиком: меч огненный 3 штуки
Первое слово: меч
Второе слово: огненный
Со второго до конца: огненный 3 штуки
Сколько слов: 4

Если аргумента нет

Если слова нет, переменная будет пустой. Функция {default(значение, 'запасное')} подставит запасной вариант. Упоминание @друг приходит как настоящее упоминание, так что его можно просто вывести.

Обнимашки с запасным вариантом
Шаблон сообщения
{user.mention} крепко обнимает {default({args.1}, 'всех вокруг')} 🤗
Результат в Discord
T
Tester
/обнять @friend
SilentBotБОТ
@Tester крепко обнимает @Friend 🤗

Слэш-опции: поля ввода в меню «/»

Вместо того чтобы помнить порядок слов, можно объявить опции. Discord покажет их как поля ввода с подсказками: выбор участника из списка, только числа и так далее. Опции задаются в редакторе, в разделе «Слэш-команда»:

  • Типы опций: text, integer (целое), number, bool, user, channel, role. Для текста и чисел можно задать варианты на выбор.
  • В шаблоне опция читается как {args.имя}. У участника, канала и роли есть ещё {args.имя.id} и {args.имя.name}.
  • В текстовом вызове (!подарить @друг 50) опции заполняются по порядку слов, так что одна команда работает обоими способами.
Подарок с двумя опциями
Слэш-опции: кому (user), сумма (integer)
Шаблон сообщения
🎁 {user.mention} дарит {args.кому} **{args.сумма}** монет!
Результат в Discord
T
Tester
/подарить @friend 50
SilentBotБОТ
🎁 @Tester дарит @Friend 50 монет!
Попробуйте сами Команда «кто лучший»

Команда !лучший отвечает «🏆 Лучший на сервере — …». Выводит того, кого указали, а если никого не указали — автора команды.

Шаблон сообщения
🏆 Лучший на сервере — {default({args.1}, {user.mention})}!
Результат в Discord
T
Tester
/лучший
SilentBotБОТ
🏆 Лучший на сервере — @Tester!
Новичок 6 мин

Функции и текст

Функция — это маленькая готовая программа. Вы отдаёте ей значения, а она возвращает результат: делает текст заглавным, считает длину, заменяет слова. Записывается так: {имя(аргумент1, аргумент2)}.

{replace({args}, 'мир', 'Discord')}
имя функциизначение из переменной — в своих фигурных скобкахтекст — в одинарных кавычках

Три правила аргументов

  1. Текст берётся в кавычки: 'мир'. Числа пишутся без кавычек: 5, 3.14.
  2. Переменные и другие функции оборачиваются в свои фигурные скобки: {upper({user.name})}. Так функции вкладываются друг в друга сколько угодно раз.
  3. Плюс внутри аргументов склеивает текст: {upper('привет, ' + {user.name})} даст «ПРИВЕТ, TESTER».
Функции для текста
Шаблон сообщения
Громко: {upper({args})}
С большой буквы: {capitalize({args})}
Задом наперёд: {reverse({args})}
Длина: {len({args})} символов
Звёзды: {repeat('⭐', 5)}
Замена: {replace({args}, 'мир', 'Discord')}
Результат в Discord
T
Tester
/текст привет мир
SilentBotБОТ
Громко: ПРИВЕТ МИР
С большой буквы: Привет мир
Задом наперёд: рим тевирп
Длина: 10 символов
Звёзды: ⭐⭐⭐⭐⭐
Замена: привет Discord
Функция внутри функции
Шаблон сообщения
Твой ник капсом: **{upper({user.display_name})}**
Первые 3 буквы: **{slice({user.display_name}, 0, 3)}**
Результат в Discord
T
Tester
/ник
SilentBotБОТ
Твой ник капсом: TESTER
Первые 3 буквы: Tes

Самые нужные функции для текста

upper / lowerЗАГЛАВНЫЕ / строчные
capitalizeС большой буквы
replaceзаменить часть
lenдлина
sliceкусок текста
truncateобрезать с «…»
repeatповторить N раз
containsесть ли внутри
pad_left7 → 007
escape_mdобезвредить Markdown
clean_mentionsобезвредить @everyone

Все функции с описанием собраны в справочнике.

Новичок 6 мин

Числа и математика

Если внутри фигурных скобок стоит арифметика, бот её посчитает. {2 + 2} превратится в 4. Считать можно и значения переменных и функций, например {{store_get('coins', 0)} * 2}.

+сложить
-вычесть
*умножить
/разделить
//разделить нацело
%остаток
**степень
( )скобки
Калькулятор и форматирование
Шаблон сообщения
2 + 2 × 2 = **{2 + 2 * 2}**
Деление: {10 / 4} · целочисленное: {10 // 4} · остаток: {10 % 4}
Округление: {round(3.14159, 2)}
Большое число: {format_number(1234567, 'ru')}
Процент: {percent(37, 120)}%
Прогресс: {progress_bar(7, 10, 10)}
Результат в Discord
T
Tester
/математика
SilentBotБОТ
2 + 2 × 2 = 6
Деление: 2.5 · целочисленное: 2 · остаток: 2
Округление: 3.14
Большое число: 1 234 567
Процент: 30.8%
Прогресс: ▰▰▰▰▰▰▰▱▱▱

Самое важное правило этого урока

Числа по-русски

plural_ru подбирает нужную форму слова к числу: 1 монета, 2 монеты, 5 монет, 21 монета.

Склонение
Шаблон сообщения
У тебя {args.1} {plural_ru({args.1}, 'монета', 'монеты', 'монет')}.
Результат в Discord
T
Tester
/монеты 21
SilentBotБОТ
У тебя 21 монета.
round(x, 2)округлить до 2 знаков
floor / ceilвниз / вверх
min / maxменьшее / большее
clamp(x, 0, 100)зажать в рамки
absмодуль
percent(a, b)процент a от b
progress_bar(7, 10)▰▰▰▰▰▰▰▱▱▱
format_number(n, 'ru')1 234 567
to_int(x, 0)число из текста или 0
even / oddчётное / нечётное
Попробуйте сами Калькулятор скидки

!скидка 2500 15 выводит цену со скидкой 15% и сумму экономии.

Шаблон сообщения
{set('price', {to_int({args.1}, 0)})}
{set('off', {calc({get('price')} * {to_int({args.2}, 0)} / 100)})}
Цена со скидкой: **{format_number({calc({get('price')} - {get('off')})}, 'ru')}** ₽
Экономия: {format_number({get('off')}, 'ru')} ₽
Результат в Discord
T
Tester
/скидка 2500 15
SilentBotБОТ
Цена со скидкой: 2 125 ₽
Экономия: 375 ₽
Новичок 4 мин

Случайность

Случайность оживляет команды: кубики, лут из сундуков, шанс критического удара, ответы магического шара.

random(1, 6)целое от 1 до 6 включительно
choice('а', 'б', 'в')один вариант из списка, все равновероятны
random(['обычный', 'редкий'], [90, 10])взвешенный выбор: шансы в процентах
list_random(список)случайный элемент списка
list_shuffle(список)перемешать список
Кубик, монетка и сундук с шансами
Шаблон сообщения
🎲 Выпало: **{random(1, 6)}**
🪙 Монетка: **{choice('Орёл', 'Решка')}**
🎁 Из сундука: **{random(['🪵 Палка', '⚔️ Меч', '💎 Алмаз'], [70, 25, 5])}**
Результат в Discord
T
Tester
/кубик
SilentBotБОТ
🎲 Выпало: 5
🪙 Монетка: Решка
🎁 Из сундука: 🪵 Палка
Магический шар
Шаблон панели
title: 🎱 Магический шар
description: **Вопрос:** {default({args}, 'а вопрос-то где?')}\n**Ответ:** {choice('Бесспорно', 'Скорее да', 'Спроси позже', 'Даже не думай', 'Звёзды молчат')}
color: #7d92e8
Результат в Discord
T
Tester
/шар я сегодня выиграю?
SilentBotБОТ
🎱 Магический шар
Вопрос: я сегодня выиграю?
Ответ: Скорее да
Уверенный 8 мин

Условия и логика

Условие позволяет команде вести себя по-разному: пускать только взрослых, отвечать модераторам иначе, чем обычным участникам, проверять, хватает ли монет.

Блок {#if}

{#if условие}
  то, что выполнится, если условие верно
{#elif другое условие}
  если первое неверно, а это верно (веток elif может быть сколько угодно)
{#else}
  если ничего не подошло
{/if}

Ветки {#elif} и {#else} необязательны. Внутри веток может быть всё что угодно: текст, директивы карточки, другие условия.

Проверка возраста с тремя ветками
Шаблон сообщения
{#if {to_int({args.1}, 0)} >= 18}
🔞 Доступ открыт, тебе {args.1}.
{#elif {to_int({args.1}, 0)} > 0}
🧒 Подрасти немного — тебе всего {args.1}.
{#else}
❓ Напиши возраст числом: `!возраст 20`
{/if}
Результат в Discord
T
Tester
/возраст 20
SilentBotБОТ
🔞 Доступ открыт, тебе 20.

Сравнения и связки

==равно
!=не равно
> <больше, меньше
>= <=больше или равно…
containsсодержит текст
startswithначинается с
endswithзаканчивается на
andи то, и другое
orхотя бы одно
notнаоборот
Текстовые проверки
Шаблон сообщения
{#if {args} contains 'кот'}🐱 Котики — лучшие!{#else}🤔 А где котики?{/if}
{#if {args.count} > 3 and not {args} contains 'собак'}Длинное сообщение без собак.{/if}
Результат в Discord
T
Tester
/чат я люблю котиков
SilentBotБОТ
🐱 Котики — лучшие!

Как бот читает условие

Условие вычисляется в два шага. Сначала бот подставляет всё, что стоит в фигурных скобках внутри условия, и только потом сравнивает то, что получилось:

  1. {#if {store_get('coins', 0)} >= 100 and {args.1} == 'меч'} — так написано.
  2. {#if 250 >= 100 and меч == 'меч'} — так выглядит после подстановки (у игрока 250 монет, он написал «меч»).
  3. верно и верно → верно, выполняется первая ветка.

Поэтому любое значение в условии — функция, переменная, аргумент — пишется в своих фигурных скобках, а сравниваемый текст — в кавычках.

Что считается «да», а что «нет»

Ложьпустое значение, 0, false. Например, {#if {args.1}} не сработает, если аргумент не передан.
Истинавсё остальное: любой текст, любое число, кроме нуля, true.

Числа, текст и регистр

  • Числа сравниваются как числа, даже если записаны в кавычках: '10' > '9' — верно.
  • Текст сравнивается буква в букву, с учётом регистра: 'Привет' == 'привет' — неверно. То же с contains, startswith и endswith.
  • Со списком (["меч","щит"]) эти три оператора смотрят на элементы, а не на буквы: contains 'щит' — такой элемент есть, startswith 'меч' — первый элемент «меч», endswith 'щит' — последний «щит».
  • Чтобы регистр не мешал, приведите значение к нижнему регистру: {lower({args.1})} == 'секрет'.
Сравнение с учётом регистра и без
Шаблон сообщения
Как написали: {#if {args.1} == 'секрет'}✅ совпало{#else}❌ не совпало{/if}
Без учёта регистра: {#if {lower({args.1})} == 'секрет'}✅ совпало{#else}❌ не совпало{/if}
Результат в Discord
T
Tester
/пароль СЕКРЕТ
SilentBotБОТ
Как написали: ❌ не совпало
Без учёта регистра: ✅ совпало

Несколько условий сразу

Связки объединяют проверки в одну:

  • A and B — верно, только если верны оба.
  • A or B — верно, если верно хотя бы одно.
  • not A — переворачивает результат.

Порядок важен: and связывает сильнее, чем or, как умножение сильнее сложения. Запись A or B and C читается как A or (B and C). Если нужно иначе, ставьте круглые скобки:

1 == 1 or 1 == 2 and 1 == 2верно: сначала and (ложь), потом or
(1 == 1 or 1 == 2) and 1 == 2неверно: скобки считаются первыми
not (1 == 2 or 2 == 2)неверно: внутри скобок верно, not переворачивает
{x} >= 3 and {x} <= 7число в диапазоне от 3 до 7

Пример ниже проверяет сразу несколько вещей: передан ли товар, есть ли он в магазине и хватает ли уровня или монет для меча. Попробуйте в редакторе поменять значения coins и lvl.

Магазин: and, or и скобки
Шаблон сообщения
{set('coins', {store_get('coins', 40)})}{set('lvl', {store_get('lvl', 3)})}
{#if {args.1} == ''}
🛒 Укажите товар: `!купить меч` или `!купить щит`.
{#elif {args.1} != 'меч' and {args.1} != 'щит'}
❓ Товара «{args.1}» нет. Есть только меч и щит.
{#elif {args.1} == 'меч' and ({get('lvl')} < 5 or {get('coins')} < 100)}
🔒 Меч продаётся с **5 уровня** за **100** монет.
У вас: уровень {get('lvl')}, монет {get('coins')}.
{#else}
✅ Покупка «{args.1}» оформлена!
{/if}
Результат в Discord
T
Tester
/купить меч
SilentBotБОТ
🔒 Меч продаётся с 5 уровня за 100 монет.
У вас: уровень 3, монет 40.

Порядок веток

Бот проверяет ветки сверху вниз и выполняет только первую подходящую, остальные пропускает. Поэтому самые строгие проверки ставьте выше общих. Если первой поставить {#if {coins} >= 10}, а под ней {#elif {coins} >= 1000}, вторая ветка не выполнится никогда: 1000 монет — это тоже «больше 10».

Условие внутри условия

Внутри любой ветки можно открыть ещё один {#if}. Это удобно, когда вторая проверка нужна только в одном случае. У каждого внутреннего блока должен быть свой {/if}.

Рыбалка: вложенные условия
Шаблон сообщения
{set('hour', 14)}{set('rod', 'bamboo')}
{#if {args.1} == 'река'}
🏞️ Вы на реке.
{#if {get('hour')} >= 5 and {get('hour')} < 10}🐟 Утренний клёв: поймали щуку!{#elif {get('rod')} == 'bamboo'}🎣 Бамбуковая удочка: поймали карася.{#else}🌊 Ничего не клюёт.{/if}
{#elif {args.1} == 'море'}
🌊 В море нужна лодка.
{#else}
🗺️ Куда пойдём? `!рыбалка река` или `!рыбалка море`
{/if}
Результат в Discord
T
Tester
/рыбалка река
SilentBotБОТ
🏞️ Вы на реке.
🎣 Бамбуковая удочка: поймали карася.

Роли и права

{has_role('VIP')} и {has_perm('manage_messages')} возвращают true или false. Роль можно указать названием, ID или упоминанием.

Разные ответы для модераторов, VIP и остальных
Шаблон сообщения
{#if {has_perm('manage_messages')} == true}
🛡️ Ты модератор — тебе можно всё.
{#elif {has_role('VIP')} == true}
👑 Привет, VIP!
{#else}
🙂 Обычный участник. Хочешь VIP? Спроси у админов.
{/if}
Результат в Discord
T
Tester
/доступ
SilentBotБОТ
🙂 Обычный участник. Хочешь VIP? Спроси у админов.

Условие в одну строку

Когда нужно выбрать одно из двух слов прямо в тексте, удобнее функция if(условие, 'да', 'нет'). Для нескольких вариантов подойдёт switch, а для запасного значения — default.

if и switch
Шаблон сообщения
Число {args.1} — {if({even({args.1})} == true, 'чётное', 'нечётное')}.
Оценка: {switch({args.1}, '5', 'отлично 🏆', '4', 'хорошо 👍', 'надо подтянуть 📚')}
Результат в Discord
T
Tester
/статус 5
SilentBotБОТ
Число 5 — нечётное.
Оценка: отлично 🏆
Попробуйте сами Приветствие по времени суток

Команда здоровается «Доброе утро», «Добрый день» или «Добрый вечер» в зависимости от часа по Москве. Подсказка: {date('%H', '+3h')} вернёт текущий час.

Шаблон сообщения
{set('h', {to_int({date('%H', '+3h')}, 0)})}
{#if {get('h')} < 6}🌙 Доброй ночи{#elif {get('h')} < 12}☀️ Доброе утро{#elif {get('h')} < 18}🌤️ Добрый день{#else}🌆 Добрый вечер{/if}, {user.mention}!
Результат в Discord
T
Tester
/привет
SilentBotБОТ
🌤️ Добрый день, @Tester!

Иногда значение нужно вычислить один раз, а использовать несколько: случайный бросок, итоговую цену, выбранный предмет. Для этого есть переменные команды.

{set('имя', значение)}запомнить (ничего не выводит)
{get('имя', 'если нет')}прочитать
{имя}короткая запись для чтения; в арифметике: {урон * 2}
Удар с шансом крита
Шаблон сообщения
{set('base', {to_int({args.1}, 10)})}
{set('roll', {random(1, 100)})}
{set('damage', {if({get('roll')} <= 25, {calc({get('base')} * 2)}, {get('base')})})}
⚔️ Удар на **{get('damage')}** урона{if({get('roll')} <= 25, ' — КРИТ! 💥', '')} (бросок {get('roll')})
Результат в Discord
T
Tester
/урон 15
SilentBotБОТ
⚔️ Удар на 30 урона — КРИТ! 💥 (бросок 11)

Переменные в условиях

Переменную удобно вычислить один раз и потом проверять в нескольких местах. В условии её пишут в фигурных скобках, как любое значение: {#if {урон} > 50} или {#if {get('урон')} > 50}. Подробно про условия — в уроке «Условия и логика».

set, store или servervar: что выбрать

Три вида «переменных» отличаются тем, как долго живёт значение и чьё оно:

set / getодно выполнение команды, потом забывается
store_*навсегда, у каждого игрока своё
servervar_*навсегда, одно на весь сервер

Пример: команду, которая прибавляет 1, по разу вызвали Вася и Петя. С store_inc('clicks') у Васи будет 1 и у Пети 1: у каждого свой счётчик. С servervar получится 2: счётчик общий. С set — 1 каждый раз, потому что значение не сохраняется между вызовами.

void: выполнить молча

Функции, которые что-то записывают (store_inc, store_set, list_push, lb_add…), возвращают результат, например новое значение счётчика, и он попадает в текст. Если он не нужен, оберните вызов в {void(…)}.

С void и без
Шаблон сообщения
Без void: {store_inc('visits', 1)} ← функция напечатала новое значение
{void(store_inc('visits', 1))}С void: ничего не напечатано, но счётчик вырос.
Сейчас посещений: **{store_get('visits', 0)}**
Результат в Discord
T
Tester
/посещения
SilentBotБОТ
Без void: 1 ← функция напечатала новое значение
С void: ничего не напечатано, но счётчик вырос.
Сейчас посещений: 2
Уверенный 6 мин

Карточка (embed)

Карточка (в Discord её называют embed) — это рамка с цветной полосой, заголовком, текстом, полями и картинками. Она описывается в шаблоне панели: каждая строка начинается с директивы.

title:заголовок
url:сделать заголовок ссылкой
description:основной текст, до 4096 символов
color:цвет полосы, #RRGGBB
field: Имя | Значение | trueполе; true — в ряд с соседними
thumbnail:маленькая картинка справа
image:большая картинка снизу
author: Имя | ссылка | иконкастрока над заголовком
footer: Текст | иконкаподпись внизу
timestamp: nowвремя отправки в подписи
Карточка со всеми директивами
Шаблон панели
author: {guild.name} | https://discord.com | {guild.icon}
title: 📰 Новости сервера
url: https://discord.com
description: **Большое обновление!**\nДобавили кланы, магазин и еженедельные турниры.
color: #f1c40f
thumbnail: {user.avatar}
field: 🏰 Кланы | Создай свой — `/клан` | true
field: 🛒 Магазин | `/магазин` | true
field: 🏆 Турнир | Каждую субботу в 18:00 | false
image: https://cdn.discordapp.com/embed/avatars/2.png
footer: Опубликовал {user.display_name} | {user.avatar}
timestamp: now
Результат в Discord
T
Tester
/карточка
SilentBotБОТ
Silent Server
Большое обновление!
Добавили кланы, магазин и еженедельные турниры.
🏰 Кланы
Создай свой — /клан
🛒 Магазин
/магазин
🏆 Турнир
Каждую субботу в 18:00

Мелочи, которые важно знать

  • Строка без директивы приклеивается к описанию. Поэтому текст, который выводит функция на отдельной строке, попадёт в карточку. Здесь снова помогает void.
  • После | в author: и footer: должна стоять настоящая ссылка https://… или ничего. {user.avatar} и {guild.icon} — это ссылки, а {user.name} — нет.
  • В значении поля нельзя использовать символ |: он разделяет части директивы.
  • Ограничения Discord: до 25 полей, значение поля до 1024 символов.
Попробуйте сами Карточка «Анонс»

Сделайте анонс турнира: жёлтая полоса, заголовок со ссылкой, три поля в ряд (дата, приз, взнос) и подпись с аватаркой автора.

Шаблон панели
title: 🏆 Турнир по шахматам
url: https://discord.com
description: Регистрация открыта! Жмите на заголовок.
color: #f1c40f
field: 📅 Дата | Суббота, 18:00 | true
field: 🎁 Приз | 5 000 💰 | true
field: 💳 Взнос | бесплатно | true
footer: Организатор: {user.display_name} | {user.avatar}
Результат в Discord
T
Tester
/анонс
SilentBotБОТ
Регистрация открыта! Жмите на заголовок.
📅 Дата
Суббота, 18:00
🎁 Приз
5 000 💰
💳 Взнос
бесплатно
Уверенный 9 мин

Кнопки и действия

Кнопки превращают карточку в маленькое приложение. У каждой кнопки есть ключ, и при нажатии выполняется блок {#action ключ} … {/action} с тем же ключом.

button: Надпись | ключ | стиль | режим | only_author | disabled
Стильprimary success danger secondary
Режимedit — обновить эту же карточку (по умолчанию), reply — новое сообщение, ephemeral — ответ видит только нажавший, modal — открыть форму
only_authorнажимать может только тот, кто вызвал команду. Обязательно для личных игр, иначе чужие нажмут за вас
only=@игроккнопка для одного участника, например only={args.1} — см. «Игры на несколько игроков»
Ссылкаесли вместо ключа указать https://… и стиль link, кнопка откроет сайт

Первая интерактивная команда

Счётчик нажатий: кнопка прибавляет единицу. Нажмите «➕ Нажать» в превью, это настоящий результат движка.

Счётчик
Шаблон панели
title: 🔢 Счётчик
description: {user.mention}, нажатий: **{store_get('clicks', 0)}**
color: #5865F2
button: ➕ Нажать | inc | primary | edit | only_author
button: ♻️ Сброс | reset | danger | edit | only_author

{#action inc}
{void(store_inc('clicks', 1))}
{/action}

{#action reset}
{void(store_set('clicks', 0))}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/счётчик
SilentBotБОТ
🔢 Счётчик
@Tester, нажатий: 0

Что показывает карточка после нажатия

Это главная тонкость кнопок. Результат зависит от того, что выводит блок действия:

1
Ничего не выводит (только void и set)Основная панель перерисуется со свежими данными. Так сделан счётчик выше: это самый простой и удобный вариант.
2
Выводит директивы (description:, field:…)Карточка заменится ими. Заголовок, цвет и кнопки, которых нет в блоке, возьмутся из основной панели, так что повторять их не нужно.
3
Выводит просто текстТекст появится над карточкой.
Режимы edit, reply и ephemeral и кнопка-ссылка
Шаблон панели
title: 🧪 Режимы кнопок
description: Нажмите каждую кнопку и сравните, куда приходит ответ.
color: #7d92e8
button: ✏️ edit | e | primary | edit
button: 💬 reply | r | success | reply
button: 🙈 ephemeral | h | secondary | ephemeral
newline:
button: 📖 Документация | https://discord.com | link

{#action e}
description: Карточка **изменилась**, а заголовок, цвет и кнопки остались — они берутся из основной панели.
{/action}

{#action r}
📨 Это новое сообщение в чате, его видят все.
{/action}

{#action h}
🤫 Это видишь только ты.
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/кнопки
SilentBotБОТ
🧪 Режимы кнопок
Нажмите каждую кнопку и сравните, куда приходит ответ.
📖 Документация

Экраны и кнопка «Назад»

Если в блоке действия объявить свои кнопки, набор кнопок сменится. Так делаются меню с разделами. Пустой блок home возвращает основную панель.

Меню с разделами
Шаблон панели
title: 🏠 Главное меню
description: Выберите раздел.
color: #2ecc71
button: 🛒 Магазин | shop | primary | edit | only_author
button: ℹ️ О сервере | about | secondary | edit | only_author

{#action shop}
title: 🛒 Магазин
description: 🗡️ Меч — 100 💰\n🛡️ Щит — 80 💰
button: ⬅️ Назад | home | secondary | edit | only_author
{/action}

{#action about}
title: ℹ️ {guild.name}
description: Участников: **{guild.member_count}**
button: ⬅️ Назад | home | secondary | edit | only_author
{/action}

{#action home}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/меню
SilentBotБОТ
🏠 Главное меню
Выберите раздел.
Попробуйте сами Камень, ножницы, бумага

Три кнопки. Бот выбирает случайный вариант и пишет, кто победил.

Шаблон панели
title: ✊ Камень, ножницы, бумага
description: Ваш ход!
color: #9b59b6
button: 🪨 Камень | rock | secondary | edit | only_author
button: ✂️ Ножницы | scissors | secondary | edit | only_author
button: 📄 Бумага | paper | secondary | edit | only_author

{#action rock}
{set('bot', {choice('rock', 'scissors', 'paper')})}
description: Вы: 🪨 · Бот: {switch({get('bot')}, 'rock', '🪨', 'scissors', '✂️', '📄')}\n**{switch({get('bot')}, 'scissors', '🎉 Победа!', 'rock', '🤝 Ничья', '😢 Поражение')}**
{/action}

{#action scissors}
{set('bot', {choice('rock', 'scissors', 'paper')})}
description: Вы: ✂️ · Бот: {switch({get('bot')}, 'rock', '🪨', 'scissors', '✂️', '📄')}\n**{switch({get('bot')}, 'paper', '🎉 Победа!', 'scissors', '🤝 Ничья', '😢 Поражение')}**
{/action}

{#action paper}
{set('bot', {choice('rock', 'scissors', 'paper')})}
description: Вы: 📄 · Бот: {switch({get('bot')}, 'rock', '🪨', 'scissors', '✂️', '📄')}\n**{switch({get('bot')}, 'rock', '🎉 Победа!', 'paper', '🤝 Ничья', '😢 Поражение')}**
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/кнб
SilentBotБОТ
✊ Камень, ножницы, бумага
Ваш ход!
Уверенный 5 мин

Формы и выпадающие меню

Если нужно, чтобы игрок что-то написал, нужна форма (модальное окно). Если нужно, чтобы он что-то выбрал из списка, — выпадающее меню.

Формы

button: Надпись | ключ | стиль | modal
modal: ключ | Заголовок формы | Поле*, Ещё поле, >Длинное поле | reply
  • Кнопка в режиме modal и строка modal: должны иметь один и тот же ключ.
  • До 5 полей. * в конце делает поле обязательным, > в начале — многострочным.
  • Последняя часть говорит, куда придёт ответ: edit, reply или ephemeral.
  • Ответы доступны в блоке действия как {modal_1}, {modal_2}… по порядку полей.
Предложка идей
Шаблон панели
title: 📝 Идеи для сервера
description: Последняя идея: {servervar_get('last_idea', 'пока нет')}
color: #9b59b6
button: 💡 Предложить | idea | primary | modal
modal: idea | Новая идея | Коротко*, >Подробнее | reply

{#action idea}
{set('text', {escape_md({clean_mentions({modal_1})})})}
{void(servervar_set('last_idea', {get('text')}))}
title: 💡 Идея от {user.display_name}
description: **{get('text')}**\n{escape_md({clean_mentions({modal_2})})}
color: #2ecc71
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/идея
SilentBotБОТ
📝 Идеи для сервера
Последняя идея: пока нет

Выпадающие меню

select: Подсказка | Вариант 1, Вариант 2, Вариант 3
select_action: Вариант 1 | что ответить на выбор
Меню выбора цвета
Шаблон панели
title: 🎨 Любимый цвет
description: Выберите вариант в меню ниже.
color: #e67e22
select: Выберите цвет | Красный, Синий, Зелёный
select_action: Красный | ❤️ Страсть и энергия!
select_action: Синий | 💙 Спокойствие и глубина.
select_action: Зелёный | 💚 Природа и гармония.
Результат в Discord
T
Tester
/цвет
SilentBotБОТ
🎨 Любимый цвет
Выберите вариант в меню ниже.
Выберите цвет
КрасныйСинийЗелёный

Всё, что мы делали до этого, забывалось сразу после ответа. Память хранит данные между вызовами и нажатиями: монеты, уровни, инвентарь, время последнего бонуса. Именно она превращает команды в игры.

У каждого игрока свояstore_*: монеты, здоровье, прогресс. Данные отдельно для каждого игрока и каждого сервера, делить их вручную не нужно.
Одна на серверservervar_*: общий счётчик, последняя идея, цель сервера. Видна и в панели, во вкладке «Переменные».
Рейтингиlb_*: общие таблицы лидеров. О них отдельный урок.

Что значит звёздочка и где лежит запись

Звёздочка — сокращение для семейства функций с одним началом: store_* — это store_get, store_set, store_inc и остальные. Все они работают с одним и тем же хранилищем.

Каждая запись в памяти — это значение с «адресом» из нескольких частей. Вы пишете в команде только ключ — имя записи, например 'coins'. Остальные части бот подставляет сам:

store_* серверID сервера, где вызвана команда + игрокID того, кто вызвал команду или нажал кнопку + ключ'coins' → 150
servervar_* серверID сервера + ключ'cookies' → 42
lb_* серверID сервера + доска'clicker' + игрокID игрока → 120 очков
  • Одинаковый ключ у разных игроков — разные записи. У Васи coins = 150, у Пети coins = 20, и они друг другу не мешают.
  • На другом сервере — своя память. Та же команда на другом сервере начнёт с нуля.
  • store_get читает только данные того, кто вызвал команду. Чужие записи видны через store_scan(…, 'all') и store_sort, а общие для всех данные удобнее хранить в servervar_* или таблице лидеров lb_*.
  • Ключ не зависит от регистра (Coins и coins — одно и то же), до 64 символов. У одного игрока на сервере — до 300 ключей, значение — до 1200 символов.

Чтение и запись

store_get('coins', 0)прочитать; 0 — что показать, если ещё не записано
store_set('coins', 100)записать значение
store_inc('coins', 5)прибавить (отсутствующий ключ считается нулём)
store_dec('coins', 5)отнять (может уйти в минус!)
store_mul('coins', 2)умножить
store_del('coins')удалить
store_type('coins')пусто, если ключа ещё нет
store_set('buff', 1, 3600)третий аргумент — срок жизни в секундах

Ежедневный бонус: память + время

Запоминаем, когда игрок брал бонус, и пускаем снова только через сутки. unix() — текущее время в секундах, 86400 — секунд в сутках.

Ежедневный бонус с кулдауном
Шаблон панели
{set('last', {store_get('daily_at', 0)})}
{#if {calc({unix()} - {get('last')})} < 86400}
title: ⏳ Рано!
description: Следующий бонус {discord_time({calc({get('last')} + 86400)}, 'R')}.\nБаланс: **{store_get('coins', 0)}** 💰
color: #e67e22
{#else}
{void(store_set('daily_at', {unix()}))}
{void(store_inc('coins', 100))}
title: 🎁 Ежедневный бонус
description: +100 💰 зачислено! Баланс: **{store_get('coins', 0)}** 💰
color: #2ecc71
{/if}
Результат в Discord
T
Tester
/бонус
SilentBotБОТ
🎁 Ежедневный бонус
+100 💰 зачислено! Баланс: 100 💰

Стартовые значения

Второй аргумент store_get('hp', 100) только показывается, в память он не записывается. Если игроку нужно начать со 100 здоровья, запишите это один раз, при первом запуске. Проверить, что ключа ещё нет, помогает store_type.

Персонаж с начальными значениями
Шаблон панели
{#if {store_type('hp')} == ''}{void(store_set('hp', 60))}{void(store_set('coins', 50))}{/if}
title: 🧙 Персонаж {user.display_name}
description: ❤️ Здоровье: **{store_get('hp', 0)}**/100\n💰 Монеты: **{store_get('coins', 0)}**
color: #e74c3c
button: 🍖 Поесть (−10 💰, +20 ❤️) | eat | success | edit | only_author

{#action eat}
{#if {store_get('coins', 0)} < 10}
description: ❌ Не хватает монет.
{#elif {store_get('hp', 0)} >= 100}
description: 😋 Вы и так сыты.
{#else}
{void(store_dec('coins', 10))}
{void(store_set('hp', {min(100, {calc({store_get('hp', 0)} + 20)})}))}
{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/старт
SilentBotБОТ
🧙 Персонаж Tester
❤️ Здоровье: 60/100
💰 Монеты: 50

Запись с несколькими значениями

Одна запись хранит одно значение. Когда у объекта несколько свойств (у героя — уровень, здоровье и золото) или нужно хранить много однотипных вещей, есть два способа.

Несколько ключей с общим началомhero_lvl, hero_hp, hero_gold. Каждое свойство — отдельное число: его легко прибавлять через store_inc и проверять в {#if}. Подходит для характеристик, счётчиков и «сколько у меня предметов каждого вида».
Список в одном ключеКлюч bag хранит ["Меч", "Щит", "Зелье"]. Подходит, когда важен порядок или элементы повторяются: инвентарь, история, очередь. Подробно — в уроке «Списки и циклы».

Первый способ в деле: три характеристики героя. Кнопка «Все записи героя» показывает их разом через store_scan('hero_') — он находит все ключи игрока, которые начинаются с hero_.

Герой с несколькими характеристиками
Шаблон панели
{#if {store_type('hero_lvl')} == ''}{void(store_set('hero_lvl', 1))}{void(store_set('hero_hp', 100))}{void(store_set('hero_gold', 25))}{/if}
title: 🧙 Герой {user.display_name}
color: #9b59b6
field: Уровень | {store_get('hero_lvl', 1)} | true
field: Здоровье | {store_get('hero_hp', 100)} ❤️ | true
field: Золото | {store_get('hero_gold', 0)} 🪙 | true
button: ⚔️ Тренировка (−20 ❤️, +1 ур.) | train | primary | edit | only_author
button: 📋 Все записи героя | all | secondary | ephemeral

{#action train}
{#if {store_get('hero_hp', 0)} < 20}
description: 😵 Слишком мало здоровья для тренировки.
{#else}
{void(store_dec('hero_hp', 20))}
{void(store_inc('hero_lvl'))}
{/if}
{/action}

{#action all}
{store_scan('hero_')}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/герой
SilentBotБОТ
🧙 Герой Tester
Уровень
1
Здоровье
100 ❤️
Золото
25 🪙

Общие данные сервера

Общий счётчик печенек
Шаблон сообщения
{void(servervar_set('cookies', {servervar_get('cookies', 0)} + 1))}
🍪 {user.mention} съел печеньку. Сервер съел уже **{servervar_get('cookies', 0)}**!
Результат в Discord
T
Tester
/печенька
SilentBotБОТ
🍪 @Tester съел печеньку. Сервер съел уже 1!

Переменную сервера можно прочитать и короче: {servervar.cookies}. Во вкладке «Переменные» панели их видно и можно менять вручную.

На одном сервере — до 500 переменных, имя — до 64 символов, значение — до 1200 символов. Менять существующие переменные можно без ограничений. Если лимит заполнен, новая переменная не сохранится, servervar_set ничего не выведет, а эмулятор покажет предупреждение. Все лимиты собраны в справочнике.

Данные игроков в панели

Вкладка «Данные игроков» в панели сервера показывает всё, что команды запомнили: поиск по ключу и ID игрока, правка значения (тип сохраняется: число останется числом), удаление у одного игрока или сразу у всех. Там же таблицы лидеров с кнопкой «Закрыть сезон» (как lb_reset) и экспорт всего в JSON-файл.

Уверенный 12 мин

Списки, наборы и циклы

Иногда одного значения мало: инвентарь, полка с книгами, коллекция значков. Для этого есть списки и наборы, а чтобы вывести их по пунктам — циклы. В этом уроке — как список создать, прочитать, изменить, проверить и сохранить.

Где живёт список

Сохранённый — в памяти игрокаХранится под ключом, как монеты: bag → ["Меч", "Щит"]. Переживает вызовы и нажатия. С ним работают функции list_* по ключу: list_push('bag', …).
Временный — значениеСписок, который существует только во время выполнения: list_create('a', 'b'), range(1, 6), строка 'a,b,c' или список из JSON внешнего API. Его можно вывести, отсортировать или записать в память.

Внутри список хранится как JSON-строка: ["📕 Дюна", "📗 Хоббит"]. Номера элементов начинаются с нуля: первый элемент — № 0, второй — № 1, а −1 означает последний.

Создать и добавить

list_push('bag', 'Меч')добавить в конец; ключа ещё нет — список создастся. Выводит новую длину
list_push('log', 'текст', 20)третий аргумент — максимальная длина: лишние старые элементы удаляются
store_set('bag', list_create('Меч', 'Щит'))записать сразу весь список (прежний заменится)
list_create('a', 'b', 'c')временный список из значений

Прочитать

store_get('bag', '[]')весь список; '[]' — пустой, если ещё ничего нет. Используйте его в циклах
list_len('bag')сколько элементов
list_get('bag', 0)элемент по номеру: 0 — первый, −1 — последний. Третий аргумент — что вернуть, если элемента нет
list_slice('bag', 0, 3, ', ')часть списка текстом: с № 0 до № 3 (не включая), через разделитель
list_first('bag')первый элемент
list_last('bag')последний элемент
list_random('bag')случайный элемент

Чтобы вывести все элементы по пунктам, используйте цикл {#for книга in {store_get('shelf', '[]')}}…{/for}: внутри {книга} — элемент, {index} — его номер с единицы. Если нужна одна строка, есть функция for(список, 'шаблон', 'разделитель').

Все способы прочитать список
Шаблон сообщения
{#if {store_type('shelf')} == ''}{void(store_set('shelf', list_create('📕 Дюна', '📗 Хоббит', '📘 Солярис', '📙 Пикник')))}{/if}
Весь список (так он хранится): `{store_get('shelf', '[]')}`
Сколько книг: **{list_len('shelf')}**
Первая: **{list_get('shelf', 0)}** · последняя: **{list_get('shelf', -1)}**
Первые две: {list_slice('shelf', 0, 2, ', ')}
Случайная: {list_random('shelf')}
По порядку:
{#for book in {store_get('shelf', '[]')}}{index}. {book}
{/for}
Результат в Discord
T
Tester
/полка
SilentBotБОТ
Весь список (так он хранится): ["📕 Дюна", "📗 Хоббит", "📘 Солярис", "📙 Пикник"]
Сколько книг: 4
Первая: 📕 Дюна · последняя: 📙 Пикник
Первые две: 📕 Дюна, 📗 Хоббит
Случайная: 📕 Дюна
По порядку:
1. 📕 Дюна
2. 📗 Хоббит
3. 📘 Солярис
4. 📙 Пикник

Изменить

list_set('bag', 0, 'Лук')заменить элемент по номеру (−1 — последний). Выводит новое значение
list_remove('bag', 'Зелье')убрать первый такой элемент; третий аргумент — сколько (0 — все). Выводит новую длину
list_pop('bag')снять последний элемент и вывести его; 'left' — первый
store_set('bag', list_sort('bag'))отсортировать и сохранить. Так же работают list_reverse, list_shuffle, list_unique
store_del('bag')удалить список целиком

list_sort и похожие функции возвращают новый список и ничего не сохраняют. Чтобы изменение осталось, запишите результат обратно через store_set.

Пример ниже собирает всё вместе. «Выпить зелье» убирает одно зелье из двух, «Улучшить меч» находит номер меча и заменяет его, «Сортировать» сохраняет отсортированный список. Флаг pack_ready нужен, чтобы стартовый набор выдавался один раз: без него после «Выбросить всё» пустой рюкзак снова выглядел бы как первый запуск.

Рюкзак: убрать, заменить, отсортировать
Шаблон панели
{#if {store_type('pack_ready')} == ''}{void(store_set('pack', list_create('🧪 Зелье', '🗡️ Меч', '🧪 Зелье', '🍞 Хлеб')))}{void(store_set('pack_ready', 1))}{/if}
title: 🎒 Рюкзак ({list_len('pack')}/8)
description: {#if {list_len('pack')} == 0}Пусто{#else}{for({store_get('pack', '[]')}, '{index}. {item}', '\n')}{/if}
color: #16a085
button: 🧪 Выпить зелье | use | success | edit | only_author
button: ✨ Улучшить меч | upgrade | primary | edit | only_author
button: 🔤 Сортировать | sort | secondary | edit | only_author
button: 🗑️ Выбросить всё | clear | danger | edit | only_author

{#action use}
{#if {list_contains('pack', '🧪 Зелье')} == true}
{void(list_remove('pack', '🧪 Зелье'))}
🧪 Вы выпили одно зелье
{#else}
❌ Зелий не осталось
{/if}
{/action}

{#action upgrade}
{set('pos', {list_index('pack', '🗡️ Меч')})}
{#if {get('pos')} >= 0}
{void(list_set('pack', {get('pos')}, '⚔️ Меч +1'))}
✨ Меч улучшен
{#else}
❌ Обычного меча нет
{/if}
{/action}

{#action sort}
{void(store_set('pack', list_sort('pack')))}
{/action}

{#action clear}
{void(store_del('pack'))}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/рюкзак
SilentBotБОТ
🎒 Рюкзак (4/8)
1. 🧪 Зелье
2. 🗡️ Меч
3. 🧪 Зелье
4. 🍞 Хлеб

Проверить

list_contains('bag', 'Меч')true, если элемент есть
list_index('bag', 'Меч')номер элемента (с нуля) или −1, если его нет
list_count('bag', 'Зелье')сколько раз элемент встречается
{#if {list_len('bag')} == 0}список пуст
Дверь, которую открывает ключ из списка
Шаблон сообщения
{#if {store_type('keys')} == ''}{void(store_set('keys', list_create('🗝️ Медный', '🗝️ Медный', '🔑 Серебряный')))}{/if}
В кармане: {list_slice('keys', 0, 10, ', ')}
{#if {list_contains('keys', '🔑 Серебряный')} == true}🚪 Серебряная дверь открыта! Ключ лежит на месте №{calc({list_index('keys', '🔑 Серебряный')} + 1)}.{#else}🔒 Нужен серебряный ключ.{/if}
Медных ключей: **{list_count('keys', '🗝️ Медный')}**{#if {list_count('keys', '🗝️ Медный')} >= 2} — хватит на сундук{/if}
Результат в Discord
T
Tester
/дверь
SilentBotБОТ
В кармане: 🗝️ Медный, 🗝️ Медный, 🔑 Серебряный
🚪 Серебряная дверь открыта! Ключ лежит на месте №3.
Медных ключей: 2 — хватит на сундук

Цикл {#for} по числам

Цикл умеет проходить не только по списку, но и по числам. range(1, 6) — это числа от 1 до 5 (последнее не включается).

Таблица умножения
Шаблон сообщения
Таблица умножения на {args.1}:
{#for i in {range(1, 6)}}{args.1} × {i} = **{{args.1} * {i}}**
{/for}
Результат в Discord
T
Tester
/таблица 7
SilentBotБОТ
Таблица умножения на 7:
7 × 1 = 7
7 × 2 = 14
7 × 3 = 21
7 × 4 = 28
7 × 5 = 35
Список в одну строку
Шаблон сообщения
{for({list_create('🍎 Яблоко', '🍌 Банан', '🍇 Виноград')}, '{index}. {item}', '\n')}
Результат в Discord
T
Tester
/список
SilentBotБОТ
1. 🍎 Яблоко
2. 🍌 Банан
3. 🍇 Виноград

Ещё один пример: сумка с лутом

Сумка с лутом
Шаблон панели
title: 🎒 Сумка ({list_len('bag')}/10)
description: {#if {list_len('bag')} == 0}Пусто{#else}{for({store_get('bag', '[]')}, '{index}. {item}', '\n')}{/if}
color: #1abc9c
button: 🎁 Открыть сундук | loot | primary | edit | only_author
button: 🗑️ Выбросить последний | drop | danger | edit | only_author

{#action loot}
{#if {list_len('bag')} >= 10}
description: ❌ Сумка полна!
{#else}
{void(list_push('bag', {random(['🗡️ Меч', '🛡️ Щит', '🧪 Зелье'], [30, 30, 40])}))}
{/if}
{/action}

{#action drop}
{void(list_pop('bag'))}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/сумка
SilentBotБОТ
🎒 Сумка (0/10)
Пусто

Наборы: без повторов

Набор (set_*) похож на список, но один и тот же элемент в нём хранится только раз. Он подходит для значков, открытых локаций и полученных достижений: повторное добавление ничего не меняет.

set_add('badges', 'Рыбак')добавить (если ещё нет); выводит размер
set_remove('badges', 'Рыбак')убрать; выводит размер
set_has('badges', 'Рыбак')true или false
set_values('badges', ' · ')все элементы текстом через разделитель
set_size('badges')сколько элементов
Коллекция значков
Шаблон сообщения
{void(set_add('badges', '🌟 Новичок'))}{void(set_add('badges', '🎣 Рыбак'))}{void(set_add('badges', '🌟 Новичок'))}
Значков: **{set_size('badges')}**: {set_values('badges', ' · ')}
Есть «Рыбак»? {if({set_has('badges', '🎣 Рыбак')} == true, 'да ✅', 'нет')}
Результат в Discord
T
Tester
/значки
SilentBotБОТ
Значков: 2: 🌟 Новичок · 🎣 Рыбак
Есть «Рыбак»? да ✅
Уверенный 10 мин

Циклы: for и while

Цикл повторяет кусок шаблона несколько раз: для каждого предмета в сумке, для чисел от 1 до 10 или пока выполняется условие. Есть два цикла: {#for} идёт по списку, а {#while} повторяет, пока условие верно.

{#for x in список} … {/for}по каждому элементу списка; {x} — текущий элемент
{#while условие} … {/while}пока условие верно
{index}номер шага, с единицы
{break}прервать цикл сразу
{continue}пропустить остаток шага и перейти к следующему

Цикл for: по каждому элементу

После for пишется имя для текущего элемента, затем in и список: {#for вещь in {get('bag')}}. Внутри цикла {вещь} — очередной элемент, {index} — его номер. Если имя не указать ({#for {список}}), элемент доступен как {item}.

Содержимое сумки
Шаблон панели
{set('bag', {list_create('🗡️ Меч', '🛡️ Щит', '🏹 Лук')})}
title: 🎒 Твоя сумка
description: {#for вещь in {get('bag')}}{index}. {вещь}\n{/for}
color: #8b5cf6
Результат в Discord
T
Tester
/сумка
SilentBotБОТ
🎒 Твоя сумка
1. 🗡️ Меч
2. 🛡️ Щит
3. 🏹 Лук

Список можно взять откуда угодно: list_create(…), store_get('bag', '[]'), split({args}, ','), file_list('#канал') или прямо написать [1, 2, 3].

Повторить N раз: range

Чтобы повторить действие несколько раз, пройдите по числам: range(1, 6) даёт 1, 2, 3, 4, 5 (последнее число не входит). range(5) — от 0 до 4, range(10, 0, -2) — 10, 8, 6, 4, 2.

Таблица умножения
Шаблон сообщения
Таблица умножения на {args.1}:
{#for i in {range(1, 6)}}{args.1} × {i} = **{{args.1} * {i}}**
{/for}
Результат в Discord
T
Tester
/таблица 7
SilentBotБОТ
Таблица умножения на 7:
7 × 1 = 7
7 × 2 = 14
7 × 3 = 21
7 × 4 = 28
7 × 5 = 35

Счётчики и суммы внутри цикла

Переменные, записанные через {set} внутри цикла, сохраняются и после него. Так удобно считать сумму, количество или собирать текст по кусочкам. А имя элемента (цена в примере ниже) и index после цикла возвращают прежние значения.

Чек с суммой
Шаблон панели
{set('prices', {list_create(120, 45, 300, 80)})}
{set('total', 0)}
{set('lines', '')}
{#for цена in {get('prices')}}{set('total', {math({get('total')} + {цена})})}{set('lines', {get('lines')} + 'Товар ' + {index} + ': ' + {цена} + ' 🪙\n')}{/for}
title: 🧾 Чек
description: {get('lines')}**Итого: {get('total')} 🪙**
color: #22c55e
Результат в Discord
T
Tester
/чек
SilentBotБОТ
🧾 Чек
Товар 1: 120 🪙
Товар 2: 45 🪙
Товар 3: 300 🪙
Товар 4: 80 🪙
Итого: 545 🪙

Цикл while: пока условие верно

{#while условие} проверяет условие перед каждым шагом. Если оно верно — выполняет тело и проверяет снова, если нет — цикл заканчивается. Условие пишется так же, как в {#if}: можно сравнивать, использовать and, or, not.

Сколько раз делится пополам
Шаблон сообщения
{set('n', {default({args.1}, 100)})}
{set('steps', 0)}
{#while {get('n')} >= 2}
{set('n', {math({get('n')} // 2)})}
{set('steps', {math({get('steps')} + 1)})}
{/while}
Число {args.1} можно разделить пополам **{get('steps')}** раз, пока не останется меньше 2.
Результат в Discord
T
Tester
/пополам 100
SilentBotБОТ
Число 100 можно разделить пополам 6 раз, пока не останется меньше 2.

break и continue

{break} сразу выходит из цикла: оставшиеся шаги не выполняются. {continue} пропускает остаток текущего шага и переходит к следующему (у while — к новой проверке условия). Обычно их ставят внутрь {#if}:

Поиск с пропуском чётных
Шаблон сообщения
{set('found', '')}
{#for n in {range(1, 20)}}{#if {math({n} % 2)} == 0}{continue}{/if}{#if {math({n} * {n})} > {math({args.1} * 10)}}{set('found', {n})}{break}{/if}Проверил {n}: {math({n} * {n})} — мало
{/for}
Первое нечётное число, квадрат которого больше {math({args.1} * 10)}: **{get('found')}**
Результат в Discord
T
Tester
/найти 3
SilentBotБОТ
Проверил 1: 1 — мало
Проверил 3: 9 — мало
Проверил 5: 25 — мало

Первое нечётное число, квадрат которого больше 30: 7

Оба действуют на ближайший цикл, в котором стоят: break во внутреннем цикле не прерывает внешний. Вне цикла они ничего не делают, редактор подсветит это предупреждением.

Цикл в цикле

Циклы можно вкладывать друг в друга — например, чтобы пройти по строкам и столбцам таблицы. Внешний цикл делает шаг, и на каждом его шаге внутренний проходит целиком.

Таблица 3 × 3
Шаблон сообщения
{#for a in {range(1, 4)}}{#for b in {range(1, 4)}}{a}×{b}={math({a} * {b})}   {/for}
{/for}
Результат в Discord
T
Tester
/таблица
SilentBotБОТ
1×1=1 1×2=2 1×3=3
2×1=2 2×2=4 2×3=6
3×1=3 3×2=6 3×3=9

Ограничения

Циклы выполняются на сервере бота, поэтому у них есть пределы: одна неудачная команда не должна тормозить бота на всех серверах.

50элементов проходит один for; остальные пропускаются
100повторов у одного while; потом цикл останавливается
3000шагов всех циклов за один запуск команды вместе
3цикла можно вложить друг в друга: цикл, в нём второй, в нём третий. Четвёртый не выполнится

Когда предел достигнут, цикл просто останавливается, а команда продолжает работу. Эмулятор в редакторе покажет, какой предел сработал, а редактор заранее подсветит слишком глубокую вложенность.

Уверенный 4 мин

Таблицы лидеров

Таблица лидеров — общий рейтинг сервера по любым очкам: кликам, пойманной рыбе, победам. У каждой таблицы есть имя, и в команде их может быть сколько угодно.

lb_add('clicker', 1)прибавить очки себе
lb_set('clicker', 50)установить счёт
lb_get('clicker', '', 0)свой счёт (0 — если ещё нет)
lb_top('clicker', 10)топ: «1. @игрок — 120»
lb_rank('clicker')своё место (0 — не в таблице)
lb_near('clicker', '', 2)соседи сверху и снизу
lb_reset('clicker', 'Сезон 1')закрыть сезон: сохранить в архив и обнулить
lb_season_top('clicker', 'Сезон 1')топ прошлого сезона
Кликер-турнир
Шаблон панели
title: 🏆 Кликер-турнир
description: {default({lb_top('clicker', 5)}, 'Пока никто не играл — стань первым!')}
color: #f1c40f
button: 👆 +1 очко | tap | success | edit
button: 📊 Моё место | me | secondary | ephemeral

{#action tap}
{void(lb_add('clicker', 1))}
{/action}

{#action me}
Ваш счёт: **{lb_get('clicker', '', 0)}**, место: **#{lb_rank('clicker')}**
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/турнир
SilentBotБОТ
🏆 Кликер-турнир
Пока никто не играл — стань первым!

Кнопка «Моё место» работает в режиме ephemeral: личный результат видит только нажавший, а общая карточка остаётся нетронутой.

Голосование, дуэль, общий босс, игра на двоих: панелью пользуется несколько человек сразу. Для этого есть две вещи: данные панели, свои у каждого отправленного сообщения, и кнопки для конкретного игрока.

Данные панели

store_* хранит данные игрока, servervar_* — данные всего сервера. msg_* хранит данные одной панели: если открыть два голосования одной командой, у каждого будет свой счёт.

msg_get('votes', 0)значение этой панели (0 — если ещё нет)
msg_set('turn', {user.mention})записать; печатает значение, поэтому оборачивайте в void
msg_inc('votes')прибавить 1 (или сколько указано)
msg_del('turn')удалить
Голосование: у каждой панели свой счёт, один голос на человека
Шаблон панели
title: 📊 {default({args}, 'Голосование')}
description: 👍 За: **{msg_get('yes', 0)}**  ·  👎 Против: **{msg_get('no', 0)}**\nГолосов: {calc({msg_get('yes', 0)} + {msg_get('no', 0)})}
color: #5865F2
button: 👍 За | yes | success
button: 👎 Против | no | danger

{#action yes}
{#if {contains({msg_get('voters')}, {user.id})} == true}
Вы уже голосовали.
{#else}
{void(msg_set('voters', {msg_get('voters')},{user.id}))}{void(msg_inc('yes'))}
{/if}
{/action}

{#action no}
{#if {contains({msg_get('voters')}, {user.id})} == true}
Вы уже голосовали.
{#else}
{void(msg_set('voters', {msg_get('voters')},{user.id}))}{void(msg_inc('no'))}
{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/опрос Пицца или суши?
SilentBotБОТ
📊 Пицца или суши?
👍 За: 0 · 👎 Против: 0
Голосов: 0

При первом запуске панель ещё не отправлена, поэтому msg_set в основной панели запоминает значения и сохраняет их за сообщением сразу после отправки. У панели до 50 ключей, данные живут 30 дней.

Кнопка для одного игрока

button: Принять | accept | success | edit | only={args.1}

only= — упоминание или ID участника: остальным кнопка ответит «Эта кнопка только для …». Если игрок не указан (only= пустое), кнопку не нажмёт никто. А only_author по-прежнему оставляет кнопку тому, кто открыл панель.

В действиях кнопок {user} — тот, кто нажал, а {owner} и {owner_id} — тот, кто открыл панель.

Дуэль: принять вызов может только тот, кого вызвали
Шаблон панели
title: ⚔️ Дуэль
description: {owner} вызывает {args.1}!\n{#if {msg_get('win')} == ''}Ждём, примет ли вызов…{#else}🎲 {owner} — **{msg_get('a')}**, {args.1} — **{msg_get('b')}**. Победа: {msg_get('win')}{/if}
color: #e74c3c
button: ✅ Принять вызов | accept | success | edit | only={args.1} | disabled={#if {msg_get('win')} == ''}false{#else}true{/if}

{#action accept}
{void(msg_set('a', {random(1, 6)}))}{void(msg_set('b', {random(1, 6)}))}
{#if {msg_get('a')} > {msg_get('b')}}{void(msg_set('win', {owner}))}{#else}{#if {msg_get('a')} < {msg_get('b')}}{void(msg_set('win', {args.1}))}{#else}{void(msg_set('win', 'ничья'))}{/if}{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/дуэль @friend
SilentBotБОТ
⚔️ Дуэль
@Tester вызывает @Friend!
Ждём, примет ли вызов…

Команды могут работать с тем же, что и встроенные модули бота: монетами из /баланс, уровнями из /ранг и кланами из /клан. Магазин, где покупают за настоящие монеты, или квест, который даёт опыт, — пара строк.

eco_balance()монеты в кошельке ('bank' — в банке, 'total' — всего)
eco_add(50)дать монеты, минус — забрать (не ниже 0); печатает баланс
eco_take(100)списать, только если хватает: true / false
eco_pay(@игрок, 20)перевести свои монеты другому: true / false
level()уровень на сервере; 'xp', 'need', 'total', 'rank'
xp_add(25)дать опыт; печатает новый уровень
clan_name()клан игрока (пусто — не в клане), ещё clan_tag(), clan_role()

Все функции принимают игрока последним аргументом: eco_balance('total', {args.1}), level('rank', @игрок). Без него — тот, кто вызвал команду.

Профиль игрока

Монеты, уровень и клан в одной карточке
Шаблон панели
title: 🪪 {user.display_name}
thumbnail: {user.avatar}
color: #D4A017
field: 💰 Кошелёк | {format_number({eco_balance()})} | true
field: 🏦 Всего | {format_number({eco_balance('total')})} | true
field: ⭐ Уровень | {level()} ({level('xp')}/{level('need')} XP), место #{level('rank')} | false
field: 🛡️ Клан | {default({clan_name()}, 'без клана')} | false
Результат в Discord
T
Tester
/профиль
SilentBotБОТ
🪪 Tester
💰 Кошелёк
500
🏦 Всего
500
⭐ Уровень
1 (0/100 XP), место #1
🛡️ Клан
без клана

Магазин за монеты бота

eco_take сначала проверяет, хватает ли денег, и только тогда списывает — купить «в минус» нельзя, даже если два игрока жмут кнопку одновременно. Забрать монеты у другого игрока (eco_take(100, @кто)) можно, только если он участник этого сервера, и не больше 10 000 в день с одного игрока: кошелёк общий для всего бота, и команды чужого сервера не должны его опустошать.

Покупка опыта за монеты
Шаблон панели
title: 🛒 Лавка
description: Зелье опыта — **100** монет, даёт **+100 XP**.\nУ вас: **{eco_balance()}** монет.
color: #2ecc71
button: 🧪 Купить зелье | buy | success | ephemeral

{#action buy}
{#if {eco_take(100)} == true}
{set('lvl', {xp_add(100)})}
Куплено! Теперь у вас **{eco_balance()}** монет и **{get('lvl')}** уровень.
{#else}
Не хватает монет: нужно 100, у вас {eco_balance()}.
{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/лавка
SilentBotБОТ
🛒 Лавка
Зелье опыта — 100 монет, даёт +100 XP.
У вас: 500 монет.

Перевод другому игроку

Подарок монетами
Шаблон сообщения
{#if {eco_pay({args.1}, {args.2})} == true}
🎁 {user.mention} подарил {args.1} **{args.2}** монет!
{#else}
Не получилось: проверьте сумму и хватает ли монет.
{/if}
Результат в Discord
T
Tester
/подарить @friend 25
SilentBotБОТ
🎁 @Tester подарил @Friend 25 монет!

Бот считает время в UTC. Для таймеров и кулдаунов удобнее всего UNIX-время: число секунд с 1970 года. Его легко складывать, вычитать и хранить в памяти.

unix()сейчас в секундах; unix('+1h') — через час
discord_time(t, 'R')«через 5 минут» / «2 часа назад», Discord покажет у каждого в его часовом поясе
now()дата и время меткой Discord
date('%d.%m %H:%M', '+3h')дата по формату со сдвигом (+3h — Москва)
time_ago(t)«5 мин. назад»
time_diff(a, b, 'hours')разница в часах, минутах, днях
day_of_week()«Понедельник»…

Сдвиги записываются как +30m, -2h, +1d, +1w.

Все способы показать время
Шаблон сообщения
Сейчас: {now()}
Через час: {discord_time({unix('+1h')}, 'R')}
Дата по Москве: {date('%d.%m.%Y %H:%M', '+3h')}
Сегодня: {day_of_week()}
Неделю назад это было: {time_ago({unix('-7d')})}
Результат в Discord
T
Tester
/время
SilentBotБОТ
Сейчас: 28 сентября 2026 г. 13:32
Через час: 4 ч. назад
Дата по Москве: 28.09.2026 16:32
Сегодня: Понедельник
Неделю назад это было: 7 дн. назад

Кулдаун своими руками

Схема всегда одна и та же. Запоминаем unix() в момент действия, а в следующий раз сравниваем разницу с нужной паузой. Готовый пример с ежедневным бонусом — в уроке про память, энергия, которая восстанавливается со временем, — в проекте «Шахта».

Мастер 5 мин

Действия в Discord

Команда может не только отвечать, но и действовать: выдавать роли, писать в другие каналы и в личку, ставить реакции, менять ник. Действия выполняются после того, как ответ собран, и сами ничего не выводят.

role_add('Роль')выдать роль (название, ID или упоминание)
role_remove('Роль')снять роль
role_toggle('Роль')выдать, если нет, иначе снять
send_to('#канал', 'текст')сообщение в другой канал
dm('текст')личное сообщение вызвавшему
react('✅')реакция на сообщение с командой или на панель
nick_set('ник')сменить ник; пустой — сбросить
Выбор ролей кнопками
Шаблон панели
title: 🎭 Выбери роли
description: Нажми кнопку, чтобы получить роль или снять её.
color: #5865F2
button: 🎮 Геймер | gamer | primary | ephemeral
button: 🎨 Художник | artist | secondary | ephemeral

{#action gamer}
{role_toggle('Геймер')}
{#if {has_role('Геймер')} == true}Роль «Геймер» снята.{#else}Роль «Геймер» выдана!{/if}
{/action}

{#action artist}
{role_toggle('Художник')}
{#if {has_role('Художник')} == true}Роль «Художник» снята.{#else}Роль «Художник» выдана!{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/роли
SilentBotБОТ
🎭 Выбери роли
Нажми кнопку, чтобы получить роль или снять её.

Обратите внимание: has_role в примере проверяется до того, как действие выполнится, поэтому показывает прошлое состояние. Отсюда и «снята» в первой ветке.

Жалоба в канал модераторов
Шаблон сообщения
{#if {args.count} < 2}
Напиши так: `!жалоба @участник причина`
{#else}
{send_to('#модерация', '🚨 Жалоба от ' + {user.mention} + ' на ' + {args.1} + ': ' + {clean_mentions({args.2+})})}
{react('✅')}
Спасибо! Жалоба отправлена модераторам.
{/if}
Результат в Discord
T
Tester
/жалоба @friend спамит в чате
SilentBotБОТ
Спасибо! Жалоба отправлена модераторам.
Мастер 5 мин

Файлы в каналах

Команда может вести файлы прямо в каналах сервера: заметки, журналы, таблицы результатов. Бот выкладывает файл сообщением с вложением, а при каждой записи обновляет это сообщение. Файл можно скачать и открыть в блокноте или Excel.

file_create('#канал', 'имя.txt', текст)создать новый файл; если он уже есть — ошибка
file_save('#канал', 'имя.txt', текст)перезаписать файл, а если его нет — создать
file_append('#канал', 'имя.txt', строка)дописать строку в конец (создаёт файл, если нет)
file_load('#канал', 'имя.txt')текст файла
file_delete('#канал', 'имя.txt')удалить файл и его сообщение
file_copy('#откуда', 'имя.txt', '#куда')копия в другом канале
file_move('#откуда', 'имя.txt', '#куда')перенести в другой канал
file_exists('#канал', 'имя.txt')true или false
file_list('#канал')список имён файлов канала

Канал и имя файла

Канал пишут как #название, упоминанием <#ID> или числом ID. Слово здесь означает канал, где вызвали команду; file_list() без аргументов тоже смотрит в текущий канал.

Имя — буквы, цифры, _ и - (до 60 символов, без пробелов) и расширение:

.txtобычный текст
.csvтаблица: строка — это ряд, ячейки через ; или ,
.xlsxтаблица Excel; пишется так же, как csv. .xls тоже можно указать — файл сохранится как .xlsx

Регистр в имени не важен: Notes.TXT и notes.txt — один файл. В разных каналах могут лежать файлы с одинаковыми именами, это разные файлы.

Заметки в файле
Шаблон сообщения
{#if {args.count} == 0}
{#if {file_exists('#заметки', 'notes.txt')} == true}📄 Заметки:
{file_load('#заметки', 'notes.txt')}{#else}Заметок пока нет — напиши `!заметка текст`.{/if}
{#else}
{file_append('#заметки', 'notes.txt', {user.name} + ': ' + {args})}
✅ Записал в **notes.txt** (канал #заметки). Теперь там:
{file_load('#заметки', 'notes.txt')}
{/if}
Результат в Discord
T
Tester
/заметка купить печенье
SilentBotБОТ
✅ Записал в notes.txt (канал #заметки). Теперь там:
Tester: купить печенье

Функции, которые пишут (file_create, file_save, file_append, file_delete, file_copy, file_move), сами ничего не выводят. void для них не нужен.

Таблицы: csv и xlsx

Таблицу удобно пополнять через file_append: каждая строка — новый ряд, ячейки разделены ;. file_load возвращает текст таблицы в том же виде, так что его можно разобрать через split и {#for}. У xlsx числа становятся числами, остальное — текстом.

Журнал продаж в csv
Шаблон панели
{file_append('#отчёты', 'продажи.csv', {user.name} + ';' + {args.1} + ';' + {args.2})}
title: 🧾 Продажа записана
description: Таблица **продажи.csv** в канале #отчёты:
```
{file_load('#отчёты', 'продажи.csv')}
```
color: #2ecc71
footer: Файл можно скачать из канала и открыть в Excel
Результат в Discord
T
Tester
/продажа Меч 250
SilentBotБОТ
🧾 Продажа записана
Таблица продажи.csv в канале #отчёты:

Tester;Меч;250

Копирование и перенос

file_copy оставляет файл на месте и кладёт копию в другой канал, file_move убирает его из старого канала. Имя файла сохраняется.

Перенос из черновиков и ошибка
Шаблон сообщения
{file_save('#черновики', 'план.txt', 'Шаг 1: придумать')}
{file_append('#черновики', 'план.txt', 'Шаг 2: сделать')}
{file_move('#черновики', 'план.txt', '#готово')}
Файлы в #готово: {join({file_list('#готово')}, ', ')}
Файлы в #черновики: {join({file_list('#черновики')}, ', ')}
{file_move('#готово', 'план.txt', '#готово')}
Результат в Discord
T
Tester
/готово
SilentBotБОТ
Файлы в #готово: план.txt
Файлы в #черновики:
⚠️ file_move: канал-источник и канал назначения совпадают (#готово)

Строки текстового файла

Файл можно читать и менять по строкам. Строки и символы считаются с 1, границы включительно. -1 (или last) — последняя строка, -2 — предпоследняя. Номер можно сдвинуть прямо в аргументе: 3+1, {get('n')}-1.

file_line('#канал', 'имя.txt', N)строка N целиком
file_line('#канал', 'имя.txt', N, a, b)символы строки N с a по b
file_line('#канал', 'имя.txt', N, a)строка N с символа a до конца
file_line('#канал', 'имя.txt', N, '', b)строка N от начала до символа b
file_lines('#канал', 'имя.txt')сколько строк в файле
file_line_set('#канал', 'имя.txt', N, текст)заменить строку N; N на 1 больше числа строк — дописать в конец
file_line_insert('#канал', 'имя.txt', N, текст)вставить новую строку перед строкой N
file_line_delete('#канал', 'имя.txt', N)удалить строку N
file_replace('#канал', 'имя.txt', что, на_что)заменить все вхождения (с учётом регистра); выводит число замен
file_count('#канал', 'имя.txt', что)сколько раз встречается, без учёта регистра и в том числе внутри слов

У file_count и file_replace есть режим 'word': тогда ищутся только целые слова с учётом регистра. file_count('#чат', 'log.txt', 'кот', 'word') не посчитает «котик» и «Кот». Чтобы искать в одной строке, укажите её номер: file_count('#чат', 'log.txt', 'кот', 3).

Список дел по строкам
Шаблон сообщения
{file_save('#задачи', 'todo.txt', 'Купить хлеб')}
{file_append('#задачи', 'todo.txt', 'Позвонить маме')}
{file_append('#задачи', 'todo.txt', 'Сделать уроки')}
{file_line_set('#задачи', 'todo.txt', 2, '✅ Позвонить маме')}
{file_line_insert('#задачи', 'todo.txt', 1, '📋 Список дел')}
{set('n', {math({file_lines('#задачи', 'todo.txt')} + 1)})}
{#for i in {range(1, {get('n')})}}{i}. {file_line('#задачи', 'todo.txt', {i})}
{/for}
Первые 6 букв последней строки: «{file_line('#задачи', 'todo.txt', -1, 1, 6)}»
Результат в Discord
T
Tester
/дела
SilentBotБОТ
1. 📋 Список дел
2. Купить хлеб
3. ✅ Позвонить маме
4. Сделать уроки

Первые 6 букв последней строки: «Сделат»

Ячейки таблицы csv и xlsx

Первая строка таблицы — заголовки столбцов. Ячейку можно указать двумя способами:

  • по заголовку и номеру строки: table_get('#канал', 'очки.csv', 'Очки', 2) — столбец «Очки», вторая строка под заголовками. Вместо заголовка можно написать букву столбца (B) или его номер;
  • по адресу, как в Excel: table_get('#канал', 'очки.csv', 'B3'). Здесь строки считаются вместе с заголовками: B1 — заголовок второго столбца.
table_get(…, 'Столбец', N)значение ячейки
table_set(…, 'Столбец', N, значение)записать в ячейку; N на 1 больше числа строк — новая строка
table_add_row(…, значение, значение, …)добавить строку в конец; выводит её номер (оберните в void)
table_add_column(…, 'Заголовок', позиция)новый столбец; без позиции — в конец
table_delete_row(…, N)удалить строку
table_delete_column(…, 'Столбец')удалить столбец со всеми значениями
table_find(…, 'Столбец', значение)номер первой строки с таким значением, 0 — не найдено
table_rows(…) · table_columns(…)число строк под заголовками · список заголовков
table_column(…, 'Столбец')все значения столбца списком — удобно для {#for}
table_range(…, 'A2:C5')значения диапазона: список строк, каждая — список ячеек
table_fill(…, 'A2:C5', значение)записать значение во все ячейки диапазона; пустое '' — очистить
Таблица очков
Шаблон сообщения
{file_save('#рейтинг', 'очки.csv', 'Игрок;Очки')}
{void(table_add_row('#рейтинг', 'очки.csv', 'Аня', 10))}
{void(table_add_row('#рейтинг', 'очки.csv', 'Борис', 7))}
{set('row', {table_find('#рейтинг', 'очки.csv', 'Игрок', 'Борис')})}
{table_set('#рейтинг', 'очки.csv', 'Очки', {get('row')}, {math({table_get('#рейтинг', 'очки.csv', 'Очки', {get('row')})} + 5)})}
Борис — строка {get('row')}, теперь у него {table_get('#рейтинг', 'очки.csv', 'Очки', {get('row')})} очков.
Ячейка B2 (как в Excel): {table_get('#рейтинг', 'очки.csv', 'B2')}
Все игроки: {join({table_column('#рейтинг', 'очки.csv', 'Игрок')}, ', ')}
Результат в Discord
T
Tester
/очки
SilentBotБОТ
Борис — строка 2, теперь у него 12 очков.
Ячейка B2 (как в Excel): 10
Все игроки: Аня, Борис

Ошибки

Если что-то не так, вместо вызова в ответе появляется строка с ⚠️ — её видно и в эмуляторе, и в Discord. Ошибка будет, если:

  • канала нет на сервере или это не текстовый канал;
  • файла нет (file_load, file_delete, file_copy, file_move);
  • file_create вызван для файла, который уже есть (перезаписывает file_save);
  • при копировании или переносе канал-источник совпадает с каналом назначения, или там уже есть файл с таким именем;
  • у бота в канале нет прав видеть его, писать сообщения или прикреплять файлы;
  • расширение не txt, csv или xlsx, или в имени есть пробелы и точки.
Мастер 6 мин

Интернет, API и JSON

Через бесплатные API команда может брать данные из интернета: погоду, курсы, факты, картинки. Ответ API приходит в формате JSON, и из него нужно достать нужное поле.

http_get_json('https://адрес', 'путь.к.полю', 'если ошибка', ttl)
  • Путь — где лежит значение: current_weather.temperature, у массивов номер в скобках: data.items[0].name.
  • Запасное значение обязательно: сайты бывают недоступны. Для картинок запасной вариант должен быть рабочей ссылкой на другом сайте.
  • ttl — сколько секунд хранить ответ, по умолчанию 180. Для API со случайным ответом (факты, картинки) ставьте 0, иначе три минуты будет приходить одно и то же.
Погода (open-meteo, без ключа)
Шаблон панели
{set('t', {http_get_json('https://api.open-meteo.com/v1/forecast?latitude=55.75&longitude=37.62&current_weather=true', 'current_weather.temperature', '?', 300)})}
title: 🌤️ Погода в Москве
description: Температура: **{get('t')}°C**\nВетер: {http_get_json('https://api.open-meteo.com/v1/forecast?latitude=55.75&longitude=37.62&current_weather=true', 'current_weather.windspeed', '?', 300)} км/ч
color: #3498db
footer: Данные: open-meteo.com
Результат в Discord
T
Tester
/погода
SilentBotБОТ
🌤️ Погода в Москве
Температура: 21.4°C
Ветер: 3.2 км/ч
Случайный факт с кнопкой «ещё»
Шаблон панели
title: 🐱 Факт о кошках
description: {http_get_json('https://catfact.ninja/fact', 'fact', 'Сервис фактов сейчас недоступен.', 0)}
color: #f39c12
button: 🔄 Ещё факт | more | primary | edit

{#action more}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/факт
SilentBotБОТ
🐱 Факт о кошках
Кошки спят 12–16 часов в сутки.

Отправка данных: POST

http_post_json(адрес, данные, путь, запасное) отправляет JSON на сервер (форму заявки, бота-посредника, таблицу) и возвращает поле ответа по пути. Данные удобно собирать через json_stringify. Ответ POST никогда не кэшируется, а при ошибке придёт запасное значение — по нему удобно показать сообщение об ошибке.

Отправка идеи во внешний сервис
Шаблон сообщения
{set('ok', {http_post_json('https://httpbin.org/post', {json_stringify({args})}, 'json', 'нет')})}
{#if {get('ok')} == 'нет'}
⚠️ Сервис не ответил, попробуйте позже.
{#else}
✅ Идея отправлена: **{clean_mentions({args})}**
{/if}

JSON без интернета

json_get и url_encode
Шаблон сообщения
Имя: {json_get('{"name": "Артур", "stats": {"hp": 120, "items": ["меч", "щит"]}}', 'name')}
Здоровье: {json_get('{"name": "Артур", "stats": {"hp": 120, "items": ["меч", "щит"]}}', 'stats.hp')}
Первый предмет: {json_get('{"name": "Артур", "stats": {"hp": 120, "items": ["меч", "щит"]}}', 'stats.items[0]')}
Поиск: https://duckduckgo.com/?q={url_encode('котики и собаки')}
Результат в Discord
T
Tester
/json
SilentBotБОТ
Имя: Артур
Здоровье: 120
Первый предмет: меч
Поиск: https://duckduckgo.com/?q=%D0%BA%D0%BE%D1%82%D0%B8%D0%BA%D0%B8%20%D0%B8%20%D1%81%D0%BE%D0%B1%D0%B0%D0%BA%D0%B8

Параметры, которые вставляются в адрес (город, поисковый запрос), пропускайте через url_encode, иначе пробелы и кириллица сломают ссылку. Для отправки данных есть http_post_json(адрес, данные, путь, запасное): его ответ никогда не кэшируется.

Мастер 5 мин

Картинки: холст

Холст рисует настоящую картинку: карточки профиля, баннеры, прогресс-бары. Картинка описывается командами рисования внутри блока {#canvas('имя')} и показывается через image: {canvas('имя')}.

Карточка игрока с аватаркой и прогрессом
Шаблон панели
{#canvas('card')}
set(size=600x200, background="#0e1728")
rect(x=10, y=10, w=580, h=180, fill="#162034", outline="#82e0ff", radius=18)
avatar(url="{user.avatar}", x=30, y=40, size=120, circle=true)
text("{user.display_name}", x=175, y=50, size=34, color="#eff4f8", font="bold")
text("Уровень 7 · 1 240 опыта", x=175, y=100, size=20, color="#a1acbb")
progress(x=175, y=140, w=380, h=18, current=62, max=100, fill="#82e0ff", bg="#243047", radius=9)
{/canvas}
title: 🪪 Карточка игрока
image: {canvas('card')}
color: #82e0ff
Результат в Discord
T
Tester
/карта
SilentBotБОТ
🪪 Карточка игрока

Команды рисования

set(size=600x200, background="#hex")размер и фон (первой строкой)
rect(x, y, w, h, fill, outline, radius)прямоугольник, можно со скруглением
circ(x, y, r, fill)круг
line(x1, y1, x2, y2, color, width)линия
trian(x1, y1, x2, y2, x3, y3, fill)треугольник
text("…", x, y, size, color, font="bold")текст
avatar(url, x, y, size, circle=true)аватарка
img(url, x, y, w, h)картинка по ссылке
progress(x, y, w, h, current, max, fill, bg, radius)полоса прогресса

Внутри холста работают переменные и функции: {user.display_name}, {store_get('xp', 0)}. Так картинка у каждого игрока своя.

Три способа запустить шаблон без того, чтобы кто-то вызывал команду: по времени, по событию на сервере и по сигналу извне.

Расписание

schedule_in('30m', 'текст')отправить сообщение через 30 минут
schedule_cron('0 9 * * 1', 'текст')повторять по расписанию cron (UTC), здесь — по понедельникам в 9:00
schedule_cancel(3)отменить задачу №3
schedule_list()список задач сервера
Команда-напоминалка: !напомни 30m полить цветы
Шаблон сообщения
{schedule_in({args.1}, '⏰ ' + {user.mention} + ', напоминание: ' + {clean_mentions({args.2+})})}
Результат в Discord
T
Tester
/напомни 30m полить цветы
SilentBotБОТ
⏳ Задача #1 запланирована (через 30 мин)

Необязательный третий аргумент — ID канала, куда отправить сообщение. Все задачи видны и отменяются во вкладке «Задачи». Они не теряются при перезапуске бота. Повторяющиеся задачи запускаются не чаще раза в 5 минут.

Триггеры событий

Вкладка «Команды и триггеры» → «Триггеры». Триггер срабатывает на событие и выполняет свой шаблон или одну из ваших команд. Фильтр сужает срабатывание: по слову в сообщении (или /регулярке/), по эмодзи, по роли. Можно указать канал.

on_member_joinУчастник зашёлФильтр: —Данные: {user.*} — новичок
on_member_removeУчастник вышелФильтр: —Данные: {user.*} — ушедший
on_messageСообщение в чатеФильтр: слово или /регулярка/Данные: {user.*} — автор, {message.content}
on_message_editСообщение изменилиФильтр: слово или /регулярка/Данные: {old_content}, {new_content}, {jump_url}
on_message_deleteСообщение удалилиФильтр: слово или /регулярка/Данные: {content}, {channel_name}
on_reaction_addПоставили реакциюФильтр: эмодзиДанные: {user.*} — кто поставил
on_reaction_removeСняли реакциюФильтр: эмодзиДанные: {user.*} — кто снял
on_member_role_addВыдали рольФильтр: название или ID ролиДанные: {role}, {role_id}, {role_mention}
on_member_role_removeСняли рольФильтр: название или ID ролиДанные: {role}, {role_id}, {role_mention}
on_member_boostБуст сервераФильтр: —Данные: {boost_count}, {boost_level}
on_nick_changeСмена никаФильтр: —Данные: {old_nick}, {new_nick}
on_voice_state_updateВход/выход в голосовойФильтр: —Данные: {user.*}
Шаблон триггера on_member_join
Шаблон панели
title: 👋 Новичок на сервере!
description: {user.mention}, добро пожаловать на **{guild.name}**!\nТы наш **{guild.member_count}**-й участник.
thumbnail: {user.avatar}
color: #2ecc71
{void(store_set('joined_at', {unix()}))}
on_message_edit: лог правок
Шаблон панели
title: ✏️ Сообщение изменено
description: **Было:** {old_content}\n**Стало:** {new_content}\n[Перейти к сообщению]({jump_url})
color: #f1c40f
on_message: автоответчик
Шаблон сообщения
{#if {message.content} contains 'помогите'}
🆘 {user.mention}, модераторы уже спешат на помощь! Опиши проблему подробнее.
{/if}

Входящие вебхуки

Создайте вебхук в той же вкладке, и вы получите секретный адрес вида https://сайт/api/webhook/<сервер>/<токен>. Любой сервис (GitHub, донаты, ваш скрипт) шлёт на него POST-запрос с JSON, а бот публикует шаблон в выбранный канал. Поля запроса доступны как {webhook.путь.к.полю}.

Уведомление о коммите GitHub
Шаблон панели
title: 🚀 Новый коммит
description: **{webhook.head_commit.message}**\nАвтор: {webhook.sender.login}
url: {webhook.head_commit.url}
color: #2ea44f

Когда команд много, одни и те же куски начинают повторяться: шапка профиля, расчёт уровня, формат цены. Макросы позволяют написать такой кусок один раз.

Макросы

  1. Панель → вкладка «Переменные» → «Макросы шаблонов» → создайте макрос, например шапка.
  2. В его шаблоне переданные значения доступны как {arg1}, {arg2}…: **{arg1}** · класс: {arg2}.
  3. Вызывайте его из любой команды через macro('имя', значение1, значение2).
Вызов макроса
Шаблон сообщения
{macro('шапка', {user.display_name}, 'Воин')}

Исправили макрос — исправились все команды, которые его вызывают. Внутри макроса работает весь язык: память, условия, функции.

Переменные сервера в панели

В той же вкладке видны все servervar_* сервера. Их можно создать и поменять руками: удобно для настроек, которые меняет админ, а читают команды (цена в магазине, текст объявления, ID канала для логов).

{servervar_get('shop_price', 100)}   или короче   {servervar.shop_price}

Не хотите писать код? В редакторе есть режим «Блоки»: команда собирается из цветных блоков, как в Scratch. Блоки и код — одна и та же команда в двух видах: переключайтесь когда угодно, ничего не потеряется.

Как это устроено

СобытияКоманда разбита на «шапки»: «Когда вызвали команду», «Когда нажали кнопку …», «Когда отправили форму …». Под каждой шапкой стопка блоков.
ПалитраСлева блоки по категориям: сообщение и карточка, условия и повторы, переменные, данные игрока, данные сервера, данные этой панели, файлы в каналах, таблицы лидеров, экономика, уровни, кланы, действия в discord, текст, числа, случайность, логика, списки, время, интернет и json, картинки и таймеры. Всего 179 блоков, они покрывают каждую функцию движка.
ПеретаскиваниеПрямоугольные блоки — это действия и строки карточки, они складываются в стопку. Круглые — значения, их кладут в поля других блоков. Блоки с пунктирной рамкой — условия «да/нет».
На телефонеНажмите на блок, чтобы увидеть его кнопки (копировать, удалить). Меню открываются снизу, поля ввода крупные.

Полезные кнопки на панели блоков

  • 📦 Данные команды — все ключи памяти, которые использует команда. Здесь же их можно переименовать сразу во всей команде.
  • 📚 Рецепты — готовые команды (счётчик, рыбалка, роли, инвентарь…), которые можно открыть и переделать.
  • 👁 Эмулятор — выдвижная панель с результатом, как в коде.
  • ✨ Собрать с ИИ — описать задачу словами.
  • Переменные — в категории «Переменные» кнопка «Создать переменную» заводит переменную команды (set/get).
  • Отменить / Повторить — или Ctrl+Z / Ctrl+Y.
Уверенный 6 мин

Проверка и отладка

Что делать, если команда работает не так, как задумано.

Эмулятор

Справа от кода эмулятор показывает, что увидит игрок. В поле «Аргументы» можно написать то, что игрок напишет после команды. Есть готовые пресеты: упоминание, число, несколько слов. Кнопки в эмуляторе нажимаются по-настоящему. Память команды (store_*, таблицы лидеров) эмулятор ведёт от вашего имени на этом сервере, так что счётчики копятся между запусками; почистить их можно во вкладке «Данные игроков». Настоящие монеты и опыт бота (eco_*, xp_add) эмулятор не меняет.

Отладка по шагам

Кнопка Отладка в заголовке эмулятора включает журнал движка. После каждого запуска и нажатия кнопки под предпросмотром видно по порядку:

  • какие переменные и функции посчитались и что они вернули (get('n') → 5);
  • какие условия выполнились: «да → выполняется» или «нет → пропуск»;
  • сколько элементов обошёл каждый цикл;
  • итоговые значения переменных set().

Вложенные блоки показаны с отступом, а шаги разбиты по частям: действие кнопки, текст сообщения, панель. Так проще всего найти, почему условие не сработало: обычно рядом видно, что функция вернула пустую строку или текст вместо числа.

Подсказки об ошибках

Редактор подчёркивает ошибки прямо в коде: незакрытую скобку или {#if} без {/if}, неизвестную функцию («может, вы имели в виду…»), кнопку без блока действия. В режиме «Блоки» такой блок подсвечивается красным. При сохранении команда проверяется ещё раз.

Автоподстановка скобок

Если в конце строки осталась незакрытая скобка или кавычка, после курсора появляется серая подсказка с недостающими символами, например )}. Нажмите Enter — они подставятся. Выражение {…} всегда пишется в одну строку, поэтому Enter посреди незакрытой скобки почти всегда означает «закрой». После строки с {#if …}, {#for …} или {#action …} без закрывающего тега подсказка предложит {/if} и оставит пустую строку для кода внутри. Esc скрывает подсказку, Tab по-прежнему выбирает вариант из списка автодополнения.

Горячие клавиши

На Mac вместо Ctrl — ⌘. Список всегда под рукой: Ctrl + / или кнопка с клавиатурой рядом с «Сохранить».

Ctrl + Sсохранить команду; в панели сервера — настройки открытой вкладки
Ctrl + Enterзапустить команду в эмуляторе
Enterподставить серую подсказку
Escскрыть подсказку или список
Ctrl + Spaceоткрыть автодополнение
Tabвыбрать вариант автодополнения
Ctrl + B / I / Uжирный / курсив / подчёркивание
Ctrl + Dдублировать строку
Alt + ↑ / ↓переместить строку
Ctrl + /окно со всеми горячими клавишами

На странице фидбека Ctrl + Enter отправляет анкету или комментарий.

Команда работает в эмуляторе, но молчит в Discord

  1. Текстовые команды выключены во вкладке «Основное», или вы пишете не тот префикс.
  2. Ограничения команды: кулдаун, «только для ролей», «только в каналах», «активные часы» (они в UTC!).
  3. У бота нет прав писать или встраивать ссылки в этом канале, выдавать роль или его роль ниже выдаваемой.
  4. API не ответил вовремя: включите «Отложенный ответ».
  5. Посмотрите вкладку «Задачи» → «Журнал выполнений»: там записана каждая ошибка с текстом.

Вкладка «Аналитика»

Сколько раз вызывали каждую команду, сколько раз она сработала без ошибок, топ ошибок и самые активные игроки. Помогает понять, что людям нравится, а что ломается.

Часть 4

Проекты

Соберём настоящую игру за шесть шагов. Каждый шаг добавляет одну механику и опирается на уроки учебника. На каждом шаге можно остановиться: команда уже работает. Нажимайте кнопки в превью!

1

Одна кнопка и счётчик

Основа любой кликер-игры: кнопка прибавляет камень в память игрока, а пустое действие перерисовывает панель. Уроки: кнопки, память.

Шаблон панели
title: ⛏️ Шахта
description: Камней в сумке: **{store_get('stone', 0)}**
color: #7f8c8d
button: ⛏️ Копать | dig | primary | edit | only_author

{#action dig}
{void(store_inc('stone', 1))}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/шахта
SilentBotБОТ
⛏️ Шахта
Камней в сумке: 0
2

Разная руда и шансы

Взвешенный random выбирает руду: 70% камень, 25% железо, 5% золото. Имя руды и есть ключ памяти, поэтому одна строка store_inc({get('ore')}, 1) работает для всех видов. Урок: случайность.

Шаблон панели
title: ⛏️ Шахта
description: 🪨 Камень: **{store_get('stone', 0)}** · ⚙️ Железо: **{store_get('iron', 0)}** · 🥇 Золото: **{store_get('gold', 0)}**
color: #7f8c8d
button: ⛏️ Копать | dig | primary | edit | only_author

{#action dig}
{set('ore', {random(['stone', 'iron', 'gold'], [70, 25, 5])})}
{void(store_inc({get('ore')}, 1))}
description: Вы нашли: **{switch({get('ore')}, 'stone', '🪨 камень', 'iron', '⚙️ железо', '🥇 ЗОЛОТО!')}**\n🪨 {store_get('stone', 0)} · ⚙️ {store_get('iron', 0)} · 🥇 {store_get('gold', 0)}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/шахта
SilentBotБОТ
⛏️ Шахта
🪨 Камень: 0 · ⚙️ Железо: 0 · 🥇 Золото: 0
3

Продажа: руда → монеты

Считаем стоимость через calc (помните правило про арифметику в аргументах?). Действие «Продать» пересчитывает её само, потому что set основной панели ему не виден. И проверяем, есть ли что продавать.

Шаблон панели
{set('worth', {calc({store_get('stone', 0)} + {store_get('iron', 0)} * 5 + {store_get('gold', 0)} * 25)})}
title: ⛏️ Шахта
description: 🪨 {store_get('stone', 0)} · ⚙️ {store_get('iron', 0)} · 🥇 {store_get('gold', 0)}\n💰 Монеты: **{store_get('coins', 0)}** · руда стоит **{get('worth')}**
color: #7f8c8d
button: ⛏️ Копать | dig | primary | edit | only_author
button: 💰 Продать всё | sell | success | edit | only_author

{#action dig}
{void(store_inc({random(['stone', 'iron', 'gold'], [70, 25, 5])}, 1))}
{/action}

{#action sell}
{set('worth', {calc({store_get('stone', 0)} + {store_get('iron', 0)} * 5 + {store_get('gold', 0)} * 25)})}
{#if {get('worth')} == 0}
description: 🎒 Продавать нечего — сначала покопайте.
{#else}
{void(store_inc('coins', {get('worth')}))}
{void(store_set('stone', 0))}{void(store_set('iron', 0))}{void(store_set('gold', 0))}
description: 💰 Продано на **{get('worth')}** монет! Баланс: **{store_get('coins', 0)}**
{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/шахта
SilentBotБОТ
⛏️ Шахта
🪨 0 · ⚙️ 0 · 🥇 0
💰 Монеты: 0 · руда стоит 0
4

Прокачка кирки

Уровень кирки — это множитель добычи. Цена растёт квадратично: 50, 200, 450… Сначала проверка денег, потом списание. Урок: условия.

Шаблон панели
{set('pick', {store_get('pickaxe', 1)})}
{set('price', {calc({get('pick')} * {get('pick')} * 50)})}
title: ⛏️ Шахта · кирка ур. {get('pick')}
description: 🪨 {store_get('stone', 0)} · ⚙️ {store_get('iron', 0)} · 🥇 {store_get('gold', 0)} · 💰 **{store_get('coins', 0)}**\nЗа удар добываете **{get('pick')}** руды. Улучшение: **{get('price')}** 💰
color: #7f8c8d
button: ⛏️ Копать | dig | primary | edit | only_author
button: 💰 Продать | sell | success | edit | only_author
button: ⬆️ Улучшить кирку | up | secondary | edit | only_author

{#action dig}
{void(store_inc({random(['stone', 'iron', 'gold'], [70, 25, 5])}, {store_get('pickaxe', 1)}))}
{/action}

{#action sell}
{void(store_inc('coins', {calc({store_get('stone', 0)} + {store_get('iron', 0)} * 5 + {store_get('gold', 0)} * 25)}))}
{void(store_set('stone', 0))}{void(store_set('iron', 0))}{void(store_set('gold', 0))}
{/action}

{#action up}
{set('price', {calc({store_get('pickaxe', 1)} * {store_get('pickaxe', 1)} * 50)})}
{#if {store_get('coins', 0)} < {get('price')}}
description: ❌ Нужно **{get('price')}** 💰, у вас **{store_get('coins', 0)}**.
{#else}
{void(store_dec('coins', {get('price')}))}
{void(store_set('pickaxe', {calc({store_get('pickaxe', 1)} + 1)}))}
description: ⬆️ Кирка улучшена до **{store_get('pickaxe', 1)}** уровня!
{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/шахта
SilentBotБОТ
⛏️ Шахта · кирка ур. 1
🪨 0 · ⚙️ 0 · 🥇 0 · 💰 0
За удар добываете 1 руды. Улучшение: 50 💰
5

Энергия, которая восстанавливается

Каждый удар тратит энергию, а она копится сама: +1 в минуту, максимум 10. Храним не энергию «сейчас», а сколько её было и когда, и досчитываем разницу при каждом показе. Никаких фоновых таймеров не нужно. Урок: время.

Шаблон панели
{set('now', {unix()})}
{set('energy', {min(10, {calc({store_get('energy', 10)} + ({get('now')} - {store_get('energy_at', {get('now')})}) // 60)})})}
title: ⛏️ Шахта
description: ⚡ Энергия: {progress_bar({get('energy')}, 10, 10)} **{get('energy')}/10**\n🪨 Камней: **{store_get('stone', 0)}** · +1 энергии в минуту
color: #7f8c8d
button: ⛏️ Копать (−1 ⚡) | dig | primary | edit | only_author

{#action dig}
{set('now', {unix()})}
{set('energy', {min(10, {calc({store_get('energy', 10)} + ({get('now')} - {store_get('energy_at', {get('now')})}) // 60)})})}
{#if {get('energy')} < 1}
description: 😮‍💨 Нет сил! Энергия восстановится {discord_time({calc({get('now')} + 60)}, 'R')}.
{#else}
{void(store_set('energy', {calc({get('energy')} - 1)}))}
{void(store_set('energy_at', {get('now')}))}
{void(store_inc('stone', 1))}
{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/шахта
SilentBotБОТ
⛏️ Шахта
⚡ Энергия: ▰▰▰▰▰▰▰▰▰▰ 10/10
🪨 Камней: 0 · +1 энергии в минуту
6

Соревнование

Каждый удар добавляет очко в общую таблицу сервера, а кнопка «Моё место» показывает соседей по рейтингу только нажавшему. Урок: таблицы лидеров.

Шаблон панели
title: ⛏️ Шахта
description: 🪨 Камней: **{store_get('stone', 0)}** · 📈 Ваш рекорд за сезон: **{lb_get('miners', '', 0)}**
field: 🏆 Лучшие шахтёры | {default({lb_top('miners', 5)}, 'Пока пусто — будьте первым!')} | false
color: #7f8c8d
button: ⛏️ Копать | dig | primary | edit | only_author
button: 📊 Моё место | rank | secondary | ephemeral

{#action dig}
{void(store_inc('stone', 1))}
{void(lb_add('miners', 1))}
{/action}

{#action rank}
Вы на **#{lb_rank('miners')}** месте. Ближайшие соперники:
{lb_near('miners', '', 1)}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/шахта
SilentBotБОТ
⛏️ Шахта
🪨 Камней: 0 · 📈 Ваш рекорд за сезон: 0
🏆 Лучшие шахтёры
Пока пусто — будьте первым!

Магазин, в котором за монеты покупают роль на сервере и предметы в инвентарь. В нём сходятся память, условия, наборы и действия в Discord.

Магазин с ролью и инвентарём
Шаблон панели
{#if {store_type('coins')} == ''}{void(store_set('coins', 300))}{/if}
title: 🛒 Магазин сервера
description: Ваш баланс: **{format_number({store_get('coins', 0)}, 'ru')}** 💰
field: 🎨 Цветной ник | 250 💰 — роль «Цветной» | true
field: 🧪 Зелье удачи | 100 💰 — в инвентарь | true
field: 🎒 Инвентарь | {default({set_values('items', ', ')}, 'пусто')} | false
color: #e67e22
button: 🎨 Купить ник | nick | primary | edit | only_author
button: 🧪 Купить зелье | potion | success | edit | only_author

{#action nick}
{#if {has_role('Цветной')} == true}
description: ✨ У вас уже есть цветной ник.
{#elif {store_get('coins', 0)} < 250}
description: ❌ Не хватает **{250 - {store_get('coins', 0)}}** 💰.
{#else}
{void(store_dec('coins', 250))}
{role_add('Цветной')}
description: 🎨 Готово! Роль «Цветной» выдана. Баланс: **{store_get('coins', 0)}** 💰
{/if}
{/action}

{#action potion}
{#if {store_get('coins', 0)} < 100}
description: ❌ Не хватает монет.
{#else}
{void(store_dec('coins', 100))}
{void(set_add('items', '🧪 Зелье удачи'))}
{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/магазин
SilentBotБОТ
🛒 Магазин сервера
Ваш баланс: 300 💰
🎨 Цветной ник
250 💰 — роль «Цветной»
🧪 Зелье удачи
100 💰 — в инвентарь
🎒 Инвентарь
пусто

Разбор

  1. Стартовый капитал выдаётся один раз: проверка store_type в первой строке.
  2. Три ветки покупки ника: роль уже есть → денег не хватает (показываем, сколько именно, через {250 - {…}}) → покупка.
  3. Роль выдаётся через role_add после списания. Роль «Цветной» должна существовать, а роль бота должна стоять выше неё.
  4. Инвентарь — это набор, так что одинаковые зелья не копятся дублями. Если нужны количества, заведите для зелий обычный счётчик: store_inc('potions', 1).
  5. Действие зелья без директив ничего не выводит, и панель просто перерисовывается с новым балансом и инвентарём.
Попробуйте сами Добавьте продажу

Кнопка «Продать зелье» возвращает 60 💰 и убирает зелье из инвентаря. Подсказка: set_has и set_remove.

Шаблон панели
{#if {store_type('coins')} == ''}{void(store_set('coins', 300))}{/if}
title: 🧪 Лавка зелий
description: 💰 **{store_get('coins', 0)}** · Инвентарь: {default({set_values('items', ', ')}, 'пусто')}
color: #1abc9c
button: 🧪 Купить (100 💰) | buy | success | edit | only_author
button: 💱 Продать (60 💰) | sell | secondary | edit | only_author

{#action buy}
{#if {store_get('coins', 0)} >= 100}{void(store_dec('coins', 100))}{void(set_add('items', '🧪 Зелье удачи'))}{/if}
{/action}

{#action sell}
{#if {set_has('items', '🧪 Зелье удачи')} == true}
{void(set_remove('items', '🧪 Зелье удачи'))}
{void(store_inc('coins', 60))}
{#else}
description: ❌ Продавать нечего.
{/if}
{/action}
Результат в Discordкнопки нажимаются
T
Tester
/лавка
SilentBotБОТ
🧪 Лавка зелий
💰 300 · Инвентарь: пусто
Часть 5

Справочник

Все 190 функций движка. Список собирается прямо из движка: здесь есть каждая функция, которая реально работает, и нет несуществующих.

Вывод и переменные рендера

void(выражение, ...)
вычисляет аргументы и НИЧЕГО не печатает. Главный способ записать без вывода: {void(store_inc('coins', 5))}
silent(...)
синоним void
pass(...)
синоним void
set('имя', значение)
локальная переменная на ОДНО выполнение (не между кликами!). Ничего не печатает. Имя без учёта регистра
get('имя', 'дефолт')
значение переменной из set; в математике можно писать имя без get: {a + b}
if(условие, 'да', 'нет')
однострочное условие: {if({store_get('vip', 0)} == 1, '👑', '')}

Случайность

random(мин, макс) | random(N) | random(['a','b'], [70, 30])
целое в диапазоне ВКЛЮЧИТЕЛЬНО; 0..N; взвешенный выбор из списка (веса — проценты/доли). Равновероятный выбор из вариантов — choice
choice('a', 'b', ...)
равновероятный выбор
pick('a', 'b', ...)
синоним choice
random_choice('a', 'b', ...)
синоним choice
range(стоп) | range(старт, стоп[, шаг])
JSON-список чисел (стоп не включается, максимум 500) для {#for}

Аргументы команды

arg(N)
N-й аргумент (N БЕЗ кавычек: arg(2)); проще писать переменную {args.2}
args()
все аргументы строкой (то же, что {args})
join(список, 'разделитель')
склеивает JSON-список в текст; join() — аргументы через пробел

Время (UTC)

now() | now('%H:%M', '+3h')
без аргументов — Discord-метка <t:…:f> (показывается в часовом поясе читателя); с форматом — strftime в UTC со сдвигом
time(...)
синоним now
date('%d.%m.%Y', '+1d')
дата/время UTC по формату, сдвиг: +30m, -2h, +1d, +1w
unix('+1h')
UNIX-время в секундах (со сдвигом) — храни его в store для таймеров
timestamp(...)
синоним unix
time_diff(t1, t2, 'seconds|minutes|hours|days')
модуль разницы двух UNIX-времён в единицах
time_ago(unix)
«только что», «5 мин. назад», «3 ч. назад», «2 дн. назад»
discord_time(unix, 'R')
Discord-метка; стили: R (через/назад), f, F, d, D, t, T
day_of_week(unix?)
день недели по-русски («Понедельник»…)
format_date('+0', 'ru', '%d.%m')
дата со сдвигом, локаль ru/en или strftime-формат

Числа и математика

math(выражение)
+ - * / // % **, скобки, внутри только abs/round/min/max/int/float. Голый островок {a * 2 + 1} работает так же
calc(выражение)
синоним math
abs(x)
модуль
even(x)
true/false — чётное
odd(x)
true/false — нечётное
round(x, знаков=0)
округление
floor(x)
вниз
ceil(x)
вверх
min(a, b, ...)
минимум
max(a, b, ...)
максимум
clamp(x, мин, макс)
ограничить диапазоном
is_numeric(x)
true/false
percent(часть, всего, знаков=1)
процент: percent(1, 3) → 33.3
progress_bar(тек, макс=100, длина=10, '▰', '▱')
текстовая полоска прогресса
format(число, ',')
ТОЛЬКО для чисел: 1,234,567; шаблоны {0} внутри format НЕ работают
format_number(число, 'ru')
1 234 567 (ru) / 1,234,567 (en)
to_int(x, дефолт)
целое или дефолт, если x не число
to_float(x, дефолт)
дробное или дефолт
to_bool(x)
true/false (понимает yes/on/1/да)

Логика

default(x, 'запасное')
запасное, если x пустой ИЛИ равен 0 (для чисел используй store_get(key, дефолт))
or(x, 'запасное')
синоним default
is_empty(x)
true для '', [], {}, null
switch(значение, 'в1', 'р1', 'в2', 'р2', 'иначе')
таблица соответствий
coalesce(a, b, c)
первое непустое

Строки

split('a,b,c', ',', индекс?)
JSON-список или элемент по индексу (отрицательный — с конца)
replace(текст, 'что', 'на что', сколько?)
замена
contains(текст, 'часть')
true/false (в {#if} можно x contains 'y')
startswith(текст, 'нач')
true/false
endswith(текст, 'кон')
true/false
lower(текст)
нижний регистр
upper(текст)
верхний регистр
capitalize(текст)
Первая заглавная
title(текст)
Каждое Слово С Заглавной
trim(текст, 'символы'?)
обрезать пробелы/символы по краям
strip(...)
синоним trim
len(текст|JSON-список)
длина строки или число элементов
length(...)
синоним len
slice(текст, от, до)
подстрока [от, до) — строка-поле 9x9 читается так
substr(текст, от, длина)
подстрока по длине
pad_left('7', 3, '0')
007
pad_right('hi', 5, '.')
hi...
repeat('🟩', n)
повтор (до 1000)
reverse(текст)
задом наперёд
truncate(текст, 50, '…')
обрезка с суффиксом
escape_md(текст)
экранирует Markdown в пользовательском вводе
plural_ru(n, 'монета', 'монеты', 'монет')
русское склонение по числу
clean_mentions(текст)
обезвреживает @everyone/@here во вводе игрока
url_encode(текст)
для параметров в URL запросов
hash_md5(текст)
md5-хеш
hash_sha256(текст)
sha256-хеш
base64_encode(текст)
base64
base64_decode(текст)
из base64

Регулярные выражения (флаги: i, m, s)

regex_match(текст, 'шаблон', 'i')
true/false
regex_find(текст, 'шаблон', группа=0, 'i')
найденный фрагмент или группа
regex_replace(текст, 'шаблон', 'замена', 'i')
замена по шаблону

JSON и HTTP

json_parse(строка)
нормализованный JSON или пусто
json_get(json, 'a.b[0].c', 'дефолт')
значение по пути
json_stringify(x)
JSON-строка
http_get_json('https://…', 'путь[0].ключ', 'fallback', ttl=180)
GET → значение по пути; при сбое — fallback. ttl — кэш в секундах: для API со СЛУЧАЙНЫМ ответом ставь 0 (иначе 3 минуты одна и та же картинка)
http_post_json('https://…', '{"k":"v"}', 'путь', 'fallback')
POST с JSON-телом, никогда не кэшируется

Списки-значения (JSON-список, ключ store со списком или 'a,b,c')

list_create('a', 'b', 'c')
JSON-список
list_contains(список, 'x')
true/false
list_index(список, 'x')
индекс или -1
list_count(список, 'x')
сколько раз встречается
list_random(список)
случайный элемент
list_shuffle(список)
перемешанный JSON-список
list_sort(список, 'asc|desc')
сортировка (числа — как числа)
list_reverse(список)
обратный порядок
list_unique(список)
без повторов
list_first(список)
первый элемент
list_last(список)
последний элемент

Циклы

for(список, '{index}. {item}', ' | ')
шаблон для каждого элемента ({item}, {index} с 1), склеенный разделителем
each(...)
синоним for

Личное хранилище игрока (изолировано по серверу И пользователю)

store_get('ключ', дефолт)
значение или дефолт. Дефолт ТОЛЬКО показывается — в хранилище он не пишется
store_set('ключ', значение, ttl=0)
записывает и ПЕЧАТАЕТ значение → пиши {void(store_set(...))}; ttl — срок жизни в секундах
store_inc('ключ', n=1)
прибавляет (отсутствующий ключ = 0) и ПЕЧАТАЕТ новое значение
store_dec('ключ', n=1)
вычитает, печатает новое значение (может уйти в минус — проверяй в {#if} ДО списания)
store_mul('ключ', k)
умножает, печатает новое значение
store_del('ключ')
удаляет, печатает ok
store_type('ключ')
'' если ключа нет — так проверяют «первый запуск»
store_ttl('ключ')
секунд до истечения
store_expire('ключ', сек)
задать срок жизни, печатает ok
store_persist('ключ')
снять срок жизни, печатает ok
store_scan('префикс', 10, 0, 'self|all', '\n')
строки ключ = значение (all — всех игроков сервера)
store_sort('префикс', 10, 0, true, 'global|self', '\n')
рейтинг числовых значений по ключам с префиксом
store_audit('префикс', 10, 0, 'self|all', '\n')
журнал изменений
list_push('ключ', значение, макс_длина=0)
добавляет в список-ключ, печатает новую длину
list_pop('ключ', 'right|left')
снимает элемент и печатает его
list_remove('ключ', значение, сколько=1)
удаляет первые N таких элементов (0 — все), печатает новую длину
list_set('ключ', индекс, значение)
заменяет элемент (индекс с 0, -1 — последний), печатает значение; вне списка — пусто
list_get('ключ', индекс, 'дефолт')
элемент (индекс с 0, -1 — последний)
list_slice('ключ', от, до, ', ')
часть списка ТЕКСТОМ через разделитель
list_len('ключ')
длина списка
set_add('ключ', значение)
добавляет в множество, печатает размер
set_remove('ключ', значение)
удаляет, печатает размер
set_has('ключ', значение)
true/false
set_values('ключ', ', ')
элементы текстом
set_size('ключ')
размер

Таблицы лидеров (общие для сервера)

lb_add('доска', очки=1, @игрок?)
прибавить очки (по умолчанию себе), печатает новый счёт
lb_set('доска', очки, @игрок?)
установить счёт, печатает его
lb_get('доска', @игрок?, 'дефолт')
счёт игрока
lb_top('доска', 10, 0, '\n')
строки 1. <@id> — 120
lb_rank('доска', @игрок?)
место (0 — нет в таблице)
lb_near('доска', @игрок?, 2, '\n')
соседи по таблице
lb_reset('доска', 'Сезон 1')
архивирует сезон и обнуляет, печатает число записей
lb_seasons('доска', 5)
список архивных сезонов
lb_season_top('доска', 'id|метка', 10)
топ архивного сезона

Общие данные сервера и макросы

servervar_get('ключ', 'дефолт')
общая переменная сервера (одна на всех)
servervar_set('ключ', значение)
записывает и ПЕЧАТАЕТ значение → в void
servervar_del('ключ')
удаляет, печатает ok
macro('имя', a1, a2)
выполняет сохранённый макрос сервера; внутри доступны {arg1}, {arg2}

Данные одной панели (у каждого отправленного сообщения-панели свои; для игр на двоих, голосований)

msg_get('ключ', 'дефолт')
значение этой панели
msg_set('ключ', значение)
записывает и ПЕЧАТАЕТ → в void; при первом запуске сохраняется за отправленной панелью
msg_inc('ключ', 1)
прибавляет число, печатает новое
msg_del('ключ')
удаляет, печатает ok

Файлы в каналах (txt, csv, xlsx; бот держит файл сообщением с вложением в канале и обновляет его после команды)

file_create(#канал, 'имя.txt', текст?)
создаёт файл; если он уже есть — ошибка ⚠️. Печатает пусто
file_save(#канал, 'имя.txt', текст)
перезаписывает (или создаёт) файл. Печатает пусто
file_append(#канал, 'log.csv', строка)
дописывает строку в конец (создаёт, если нет) — для журналов и таблиц
file_load(#канал, 'имя.txt')
текст файла; у csv/xlsx — строки CSV
file_delete(#канал, 'имя.txt')
удаляет файл и его сообщение
file_copy(#откуда, 'имя.txt', #куда)
копия в другом канале; тот же канал или занятое имя — ошибка
file_move(#откуда, 'имя.txt', #куда)
перенос в другой канал
file_exists(#канал, 'имя.txt')
true/false — проверить до file_load/file_create
file_list(#канал)
список имён файлов канала (для {#for})

Строки текстового файла (номера с 1; -1 — последняя; можно 3+1)

file_line(#канал, 'имя.txt', N, начало?, конец?)
строка N целиком или её символы с…по (включительно)
file_lines(#канал, 'имя.txt')
сколько строк в файле
file_line_set(#канал, 'имя.txt', N, текст)
заменить строку N (N = строк+1 — дописать в конец)
file_line_insert(#канал, 'имя.txt', N, текст)
вставить новую строку перед строкой N
file_line_delete(#канал, 'имя.txt', N)
удалить строку N
file_replace(#канал, 'имя.txt', что, замена, 'word'?)
заменить все вхождения, печатает число замен; 'word' — только целые слова с учётом регистра
file_count(#канал, 'имя.txt', что, N?, 'word'?)
сколько раз встречается (без учёта регистра); N — только в строке N; 'word' — целые слова с учётом регистра

Ячейки таблицы csv/xlsx (первая строка — заголовки; строки данных с 1)

table_get(#канал, 't.csv', 'Столбец', N) | table_get(#канал, 't.csv', 'B3')
значение ячейки: по заголовку и номеру строки или по адресу как в Excel
table_set(#канал, 't.csv', 'Столбец', N, значение) | table_set(#канал, 't.csv', 'B3', значение)
записать в ячейку; строка = строк+1 — новая строка
table_add_row(#канал, 't.csv', значение, значение, …)
добавить строку в конец, печатает её номер
table_add_column(#канал, 't.csv', 'Заголовок', позиция?)
новый столбец (в конец или на место N)
table_delete_row(#канал, 't.csv', N)
удалить строку данных
table_delete_column(#канал, 't.csv', 'Столбец')
удалить столбец
table_find(#канал, 't.csv', 'Столбец', значение)
номер первой строки с таким значением, 0 — нет
table_rows(#канал, 't.csv')
сколько строк данных
table_columns(#канал, 't.csv')
список заголовков
table_column(#канал, 't.csv', 'Столбец')
все значения столбца списком (для {#for})
table_range(#канал, 't.csv', 'A2:C5')
значения диапазона: список строк, каждая — список ячеек
table_fill(#канал, 't.csv', 'A2:C5', значение)
записать значение во все ячейки диапазона; пусто — очистить

Экономика, уровни и кланы бота (общие с /баланс, /ранг, /клан)

eco_balance('wallet' | 'bank' | 'total', @игрок?)
монеты игрока в экономике бота (кошелёк один на все серверы)
eco_add(монеты, @игрок?)
дать монеты (минус — забрать, не ниже 0), печатает новый баланс → в void. Лимит 10 000 монет от команд на игрока в день
eco_take(монеты, @игрок?)
списать, только если хватает: true/false — для магазинов {#if {eco_take(100)} == true}
eco_pay(@кому, монеты)
перевод от вызвавшего другому игроку: true/false
level('level' | 'xp' | 'need' | 'total' | 'rank', @игрок?)
уровень на сервере (как /ранг), опыт, сколько нужно, весь опыт, место
xp_add(опыт, @игрок?)
дать опыт, печатает новый уровень → в void; повышение выдаёт роли-награды. Лимит 5 000 XP в день
clan_name(@игрок?)
название клана игрока (пусто — не в клане)
clan_tag(@игрок?)
тег клана
clan_role(@игрок?)
роль в клане: leader / officer / member

Права

has_role('Название' | ID | '<@&ID>')
true/false
has_perm('administrator' | 'manage_messages' | …)
true/false

Действия в Discord (выполняются после рендера, ничего не печатают; до 5 за запуск)

role_add('Роль' | ID | '<@&ID>', @участник?)
выдать роль (по умолчанию автору). Другим — только если у вызвавшего есть «Управлять ролями». Роли с правами модерации/админа запрещены
role_remove('Роль', @участник?)
снять роль
role_toggle('Роль', @участник?)
выдать, если нет, иначе снять — идеально для кнопок выбора ролей
send_to('#канал' | ID, 'текст')
сообщение в другой канал сервера (без пингов @everyone и ролей)
dm('текст', @участник?)
личное сообщение автору (другим — только модераторам)
react('✅')
реакция на сообщение с текстовой командой / на панель при нажатии кнопки
nick_set('ник', @участник?)
сменить ник (пусто — сбросить)

Холст и расписание

canvas('имя')
ссылка на картинку из блока {#canvas('имя')} → image: {canvas('имя')}
schedule_in('10m', 'текст', ID_канала?)
отложенное сообщение, печатает «⏳ Задача #N запланирована…»
schedule_cron('0 12 * * *', 'текст', ID_канала?)
повторяющееся по cron (UTC), печатает статус
schedule_cancel(N)
отменить задачу, печатает статус
schedule_list()
список задач сервера

Директивы панели

title: текстзаголовок карточки
url: https://…ссылка на заголовке
description: текстописание, перенос — \n
color: #5865F2цвет полосы
field: Имя | Значение | true/falseполе, true — в ряд
thumbnail: URLкартинка справа
image: URLбольшая картинка
author: Имя | URL | иконкаавтор над заголовком
footer: Текст | иконкаподпись
timestamp: nowвремя в подписи
button: Текст | ключ | стиль | режим | only_author | only=@игрок | disabledкнопка
newline:следующие кнопки — с новой строки
modal: ключ | Заголовок | Поле*, >Длинное | режимформа для modal-кнопки
select: Подсказка | А, Б, Ввыпадающее меню
select_action: А | ответответ на выбор пункта

Блоки

{#if усл} … {#elif усл} … {#else} … {/if}условие
{#for x in {список}} … {x} … {/for}цикл
{#action ключ} … {/action}что делает кнопка / форма
{#canvas('имя')} … {/canvas}рисунок, показ — image: {canvas('имя')}
{# комментарий #}заметка в коде, в ответ не попадает

Переменные

{user} {user.mention} {user.name} {user.display_name} {user.id} {user.avatar}вызвавший команду (member.* — то же)
{user.tag} {user.nick} {user.color} {user.top_role} {user.created_at} {user.joined_at} {user.bot}ещё о нём
{guild.name} {guild.id} {guild.icon} {guild.member_count} {guild.owner_id}сервер (server.* — то же)
{guild.roles_count} {guild.channels_count} {guild.boost_count} {guild.boost_level} {guild.description}ещё о сервере
{channel.mention} {channel.name} {channel.id} {channel.topic}канал
{args} {args.1} {args.2+} {args.count} {arg1}аргументы
{args.опция} {args.опция.id} {args.опция.name}слэш-опции
{modal_1} … {modal_5}ответы формы
{item} {index}внутри for()
{servervar.ключ}переменная сервера
{owner} {owner_id}кто открыл панель (в действиях кнопок {user} — кто нажал)
{webhook.путь}данные входящего вебхука
{message.content}текст сообщения (текстовая команда, on_message)

События для триггеров

on_member_joinУчастник зашёлФильтр: —Данные: {user.*} — новичок
on_member_removeУчастник вышелФильтр: —Данные: {user.*} — ушедший
on_messageСообщение в чатеФильтр: слово или /регулярка/Данные: {user.*} — автор, {message.content}
on_message_editСообщение изменилиФильтр: слово или /регулярка/Данные: {old_content}, {new_content}, {jump_url}
on_message_deleteСообщение удалилиФильтр: слово или /регулярка/Данные: {content}, {channel_name}
on_reaction_addПоставили реакциюФильтр: эмодзиДанные: {user.*} — кто поставил
on_reaction_removeСняли реакциюФильтр: эмодзиДанные: {user.*} — кто снял
on_member_role_addВыдали рольФильтр: название или ID ролиДанные: {role}, {role_id}, {role_mention}
on_member_role_removeСняли рольФильтр: название или ID ролиДанные: {role}, {role_id}, {role_mention}
on_member_boostБуст сервераФильтр: —Данные: {boost_count}, {boost_level}
on_nick_changeСмена никаФильтр: —Данные: {old_nick}, {new_nick}
on_voice_state_updateВход/выход в голосовойФильтр: —Данные: {user.*}

Ограничения защищают бота: одна тяжёлая или ошибочная команда не должна тормозить его на всех серверах. Когда команда упирается в предел, она не ломается — лишнее просто не выполняется, а эмулятор в редакторе показывает, что сработало.

Команда и сообщение

20 000символов в шаблоне
100слэш-команд на сервер (ограничение Discord)
2 000символов в тексте сообщения (Discord)
4096символов в описании карточки
25 × 1024полей в карточке × символов в поле
5 × 5рядов кнопок × кнопок в ряду
5полей в одной форме
2 смежду запусками команды одним участником
0.35 смежду нажатиями кнопок одним участником

Выполнение

3 спроцессорного времени на один запуск
20 сна весь запуск вместе с запросами в интернет
400 000символов может вывести один запуск (с циклами)
200 000символов в результате одной функции
12уровней вложенности блоков {#if}, {#for}…
2команды сервера выполняются одновременно, остальные ждут
300 / 5 000символов в регулярном выражении / в тексте для него

Циклы

50элементов проходит один for
100повторов одного while
3 000шагов всех циклов за один запуск
3цикла можно вложить друг в друга

Интернет и API

6запросов за один запуск
4 с / 8 сожидание одного ответа / всех запросов запуска
256 КБразмер ответа
64 КБтело POST-запроса
120 / 30запросов в минуту на сервер / на участника
3 минответ хранится в кэше (если не указать ttl)

Действия в Discord

5действий за один запуск
60действий в минуту на сервер
2 000символов в send_to и dm
10 000монет от команд одному игроку в день
5 000опыта от команд одному игроку в день

Память

500переменных сервера (servervar_*)
300ключей у одного игрока (store_*)
50 000записей данных игроков на сервер
50 000записей во всех таблицах лидеров сервера
64символа в ключе или имени переменной
1 200символов в одном значении (список — как JSON)
50 × 2 000ключей у одной панели (msg_*) × символов в значении

Файлы в каналах

100файлов на сервер
50 000символов в одном файле
10 / 100разных файлов / записей за один запуск
2 000 × 50строк × столбцов в таблице
5 000строк в текстовом файле

Картинки

3холста за один запуск
2048 × 2048пикселей — наибольший холст

Автоматизация

50задач по расписанию на сервер
5 минсамый частый повтор задачи по расписанию
50триггеров на сервер
5 / 15срабатываний одного триггера / всех триггеров сервера за 10 с
25вебхуков на сервер
100макросов на сервер

Нейросеть (Silent AI и /нейросеть)

25вопросов в день на сервер
15вопросов в день на участника
5вопросов за 10 минут на участника
200вопросов в день на весь бот; сброс в полночь по тихоокеанскому времени (10:00–11:00 по Москве)

Изменить уже существующую запись можно всегда. Лимит памяти мешает только создать новую: она не сохранится, а эмулятор в редакторе покажет предупреждение.

Частые вопросы

Почему в карточке появилась лишняя цифра?

Функция записи (store_inc, list_push, lb_add…) напечатала свой результат. Оберните её в {void(…)}.

Почему {set} не работает в кнопке?

Действие кнопки выполняется отдельно и не видит set() основной панели. Вычислите значение внутри {#action}, а то, что нужно помнить между нажатиями, храните в памяти (store_*).

Почему условие всегда ложное или всегда истинное?

Если в условии есть арифметика, оберните её в calc: {#if {calc({a} - {b})} > 10}. Значения функций и переменных пишутся в своих фигурных скобках.

Почему у игрока 0, хотя я написал store_get('hp', 100)?

Второй аргумент store_get только показывается и не записывается. Стартовые значения записывайте один раз, при первом запуске (см. урок «Память»).

Как сделать команду только для админов?

В настройках команды выберите «Только для ролей» или проверяйте внутри: {#if {has_perm('administrator')} == true}.

Кнопку нажимают чужие люди

Добавьте only_author в строку кнопки, а если кнопка для конкретного игрока — only={args.1}.

Бот не выдаёт роль

Роль бота должна быть выше выдаваемой роли. Роли с правами модерации выдавать через команды нельзя.

Картинка из API всё время одна и та же

Поставьте ttl = 0 четвёртым аргументом http_get_json: ответ кэшируется на 3 минуты.

Как перенести команды на другой сервер?

«Экспорт» и «Импорт» во вкладке команд, ссылка «Поделиться» в редакторе или галерея.

Сломал команду. Как вернуть?

Кнопка истории версий в редакторе: каждое сохранение можно восстановить.

Вы дочитали до конца

Осталось открыть редактор и собрать что-то своё. Если что-то не получается, в редакторе есть Silent AI: опишите, что хотите сделать.

К моим серверам